diff --git a/README.rst b/README.rst index 3a2277d..0ae2d16 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ wxpy: 用 Python 玩微信 ============================== -微信机器人 / 优雅的微信个人号API,基于 `itchat `_,全面优化接口,更有 Python 范儿。 +微信机器人 / 优雅的微信个人号API,基于 itchat,全面优化接口,更有 Python 范儿。 用来干啥 @@ -65,8 +65,8 @@ wxpy: 用 Python 玩微信 def reply_my_friend(msg): return 'received: {} ({})'.format(msg.text, msg.type) - # 开始监听和自动处理消息 - bot.start() + # 堵塞线程,并进入 Python 命令行 + embed() 模块特色 diff --git a/docs/console.rst b/docs/console.rst index c5aaf11..9bf9bf2 100644 --- a/docs/console.rst +++ b/docs/console.rst @@ -19,14 +19,17 @@ from wxpy import * bot = Bot() - embed() # 开始探索调试 + embed() # 进入 Python 命令行 + # 输入对象名称并回车 >>> bot # Out[1]: >>> bot.friends() # Out[2]: [, , ] + .. autofunction:: embed + :noindex: 使用 `wxpy` 命令 @@ -99,3 +102,4 @@ 在此基础上,指定使用 bpython:: wxpy bot1 bot2 -c -s bpython + diff --git a/docs/faq.rst b/docs/faq.rst new file mode 100644 index 0000000..9e5c1f5 --- /dev/null +++ b/docs/faq.rst @@ -0,0 +1,65 @@ +常见问题解答 (FAQ) +============================== + +.. module:: wxpy + +.. hint:: + + 这里罗列了一些常见的问题,在提出新的问题前,请先看完本文。 + + +每次登陆都要扫二维码? +-------------------------------- + +可以启用登陆状态缓存功能,具体请见 :any:`Bot` 中的 `cache_path` 参数说明。 + + +能在纯命令行环境中使用吗? +-------------------------------- + +wxpy 不依赖于图形界面,因此完全兼容各种纯命令行的服务器。 + +只有一点需要注意,在纯命令行环境中,登陆时必须使用"终端二维码"。 + +具体请见 :any:`Bot` 中的 `console_qr` 参数说明。 + + +能不能抢红包,看朋友圈…? +-------------------------------- + +wxpy 使用了 Web 微信的通讯协议,因此仅能覆盖 Web 微信的所具备的功能。 + +所以以下功能 **目前均不在支持范围内** + +* 支付相关 - 红包、转账、收款 等都不行 +* 在群聊中@他人 - 没错,Web 微信中被@后也不会被提醒 +* 发送名片 - 但可以通过 :any:`send_raw_msg()` 转发 +* 发送分享链接 - 并且无法转发 +* 发送语音消息 +* 朋友圈相关 + + +Python 2.x 还是 3.x? +-------------------------------- + +因为投入精力所限,现阶段会聚焦在 Python 3.x 版本的开发上,短期内不考虑兼容 2.x。 + +欢迎其他热心同学参与贡献来支持 2.x。 + + +会不会被封号? +-------------------------------- + +目前来看并不会因为使用 wxpy 等类似的工具/模块导致封号。 + +但是,如果你使用微信来骚扰他人、破坏环境,甚至违法犯法的话,那么不管用什么方式使用微信都会导致封号。 + +wxpy 的初衷是帮助人们利用微信来使生活和工作更轻松。 + +.. note:: + + 请每位使用者: + + * 维护良好的交流环境 + * 永远不骚扰他人 + * 绝不触犯法律 diff --git a/docs/index.rst b/docs/index.rst index 004bbdf..85bea73 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,11 +7,7 @@ wxpy: 用 Python 玩微信 ============================== -微信机器人 / 优雅的微信个人号API,基于 |itchat|,全面优化接口,更有 Python 范儿。 - -.. |itchat| raw:: html - - itchat +微信机器人 / 优雅的微信个人号API,基于 itchat,全面优化接口,更有 Python 范儿。 用来干啥 @@ -75,6 +71,7 @@ wxpy: 用 Python 玩微信 console utils itchat + faq 项目主页 diff --git a/docs/logging_with_wechat.rst b/docs/logging_with_wechat.rst index aae7b1e..2b22369 100644 --- a/docs/logging_with_wechat.rst +++ b/docs/logging_with_wechat.rst @@ -3,9 +3,7 @@ .. module:: wxpy -你是否曾守着电脑不肯走开,生怕错过一条重要的警告日志?或者当你外出时,心里挂念着电脑上的程序运行得如何? - -现在,我们可以利用微信强大的通知能力,在微信上接收程序的日志。 +通过利用微信强大的通知能力,我们可以把程序中的警告/日志发到自己的微信上。 wxpy 提供以下两种方式来实现这个需求。 diff --git a/docs/messages.rst b/docs/messages.rst index b6cd4dc..0002fc7 100644 --- a/docs/messages.rst +++ b/docs/messages.rst @@ -5,8 +5,8 @@ 每当机器人接收到消息时,会自动执行以下两个步骤 -1. 将消息保存到 `Bot.messages` 中 -2. 查找消息预先注册的函数,并执行(若有注册) +1. 将消息保存到 :class:`Bot.messages ` 中 +2. 查找消息预先注册的函数,并执行(若有匹配的函数) 消息对象 ---------------- @@ -63,9 +63,9 @@ .. method:: get_file(save_path=None) - :param save_path: 文件的保存路径,可与 :any:`file_name` 配合使用。若为 `None`,将直接返回字节数据 + :param save_path: 文件的保存路径,若为 `None`,将直接返回字节数据 - 下载文件(包括图片、视频等)。 + 下载文件(包括图片、视频等)。可与 :any:`file_name` 配合使用。 .. attribute:: file_name @@ -73,27 +73,27 @@ .. method:: reply(...) - 等同于 :meth:`Message.sender.send(...) ` + 等同于 :meth:`Message.chat.send(...) ` .. method:: reply_image(...) - 等同于 :meth:`Message.sender.send_image(...) ` + 等同于 :meth:`Message.chat.send_image(...) ` .. method:: reply_file(...) - 等同于 :meth:`Message.sender.send_file(...) ` + 等同于 :meth:`Message.chat.send_file(...) ` .. method:: reply_video(...) - 等同于 :meth:`Message.sender.send_video(...) ` + 等同于 :meth:`Message.chat.send_video(...) ` .. method:: reply_msg(...) - 等同于 :meth:`Message.sender.send_msg(...) ` + 等同于 :meth:`Message.chat.send_msg(...) ` .. method:: reply_raw_msg(...) - 等同于 :meth:`Message.sender.send_raw_msg(...) ` + 等同于 :meth:`Message.chat.send_raw_msg(...) ` .. attribute:: img_height @@ -133,18 +133,32 @@ 可通过 **预先注册** 的方式,实现消息的自动处理。 -.. hint:: **预先注册**: 预先将来自特定聊天对象的特定类型的消息,注册到相应的处理函数。 -消息注册 +"预先注册" 是指 + 预先将特定聊天对象的特定类型消息,注册到对应的处理函数,以实现自动回复等功能。 + + +注册消息 ^^^^^^^^^^^^^^ -将 :meth:`以下方法 ` 作为函数的装饰器,即可完成注册。 +.. hint:: -当接收到符合条件的消息时,会自动执行被注册的函数,并以参数的形式传入 :class:`消息对象 `。 + | 当接收到符合条件的消息时,会自动执行被注册的函数。 + | 同时 :class:`消息对象 ` 将作为唯一参数传入该函数。 -.. automethod:: Bot.register +将 :meth:`Bot.register` 作为函数的装饰器,即可注册生效。 + +:: + + # 打印所有群聊对象中的文本消息 + @bot.register(Group, TEXT) + def print_group_msg(msg): + print(msg) -.. note:: 每条消息仅匹配一个预先注册函数,且优先匹配后注册的函数! + +.. important:: 每条消息仅匹配一个预先注册函数,且优先匹配后注册的函数! + +.. automethod:: Bot.register .. tip:: @@ -152,12 +166,33 @@ 2. `chats` 参数既可以是聊天对象实例,也可以是对象类。当为类时,表示匹配该类型的所有聊天对象。 3. 在被注册函数中,可以直接通过 `return <回复内容>` 的方式来回复消息,等同于调用 `msg.reply(<回复内容>)`。 -开始监听 + +开始运行 ^^^^^^^^^^^^^^ -.. note:: 在完成消息注册后,务必通过以下方法开始监听和处理消息。 +.. note:: + + | 根据 Python 的特性,在完成以上注册操作后,若没有其他操作,程序会因主线程结束而退出。 + **因此必须堵塞线程以保持监听状态。** + | wxpy 的 :any:`embed()` 可在堵塞线程的同时,进入 Python 命令行,方便调试,一举两得。 + + +:: + + from wxpy import * + + bot = Bot() + + @bot.register() + def print_messages(msg): + print(msg) + + # 堵塞线程,并进入 Python 命令行 + embed() + + +.. autofunction:: embed -.. automethod:: Bot.start 示例代码 ^^^^^^^^^^^^^ @@ -187,7 +222,7 @@ @bot.register([my_friend, Group], TEXT) def auto_reply(msg): # 如果是群聊,但没有被 @,则不回复 - if not (isinstance(msg.sender, Group) and not msg.is_at): + if not (isinstance(msg.chat, Group) and not msg.is_at): # 回复消息内容和类型 return '收到消息: {} ({})'.format(msg.text, msg.type) @@ -199,9 +234,9 @@ return -开始监听和自动处理:: +堵塞线程,并进入 Python 命令行:: - bot.start() + embed() 动态开关注册配置 @@ -212,22 +247,22 @@ 查看当前的注册配置情况:: - bot.message_configs + bot.registered # [, # , # ] 关闭所有注册配置:: - bot.message_configs.disable() + bot.registered.disable() 重新开启 `just_print` 函数:: - bot.message_configs.enable(just_print) + bot.registered.enable(just_print) 查看当前开启的注册配置:: - bot.message_configs.enabled + bot.registered.enabled # [] @@ -235,7 +270,7 @@ 消息记录 ---------------- -可通过访问 `bot.messages` 来查看自 `bot.start()` 后收到的消息列表。 +可通过访问 `bot.messages` 来查看历史消息列表。 消息列表为 :class:`Messages` 对象,具有搜索功能。 diff --git a/requirements.txt b/requirements.txt index 588b761..ac2dcd1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -itchat>=1.2.30 +itchat>=1.2.32 requests diff --git a/setup.py b/setup.py index 4435c4a..e2abe13 100755 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ ] }, install_requires=[ - 'itchat>=1.2.30', + 'itchat>=1.2.32', 'requests', ], url='https://github.com/youfou/wxpy', diff --git a/wxpy/__init__.py b/wxpy/__init__.py index 069c0e1..5f8f349 100644 --- a/wxpy/__init__.py +++ b/wxpy/__init__.py @@ -35,8 +35,8 @@ def print_others(msg): def reply_my_friend(msg): return 'received: {} ({})'.format(msg.text, msg.type) - # 开始监听和自动处理消息 - bot.start() + # 堵塞线程,并进入 Python 命令行 + embed() """ @@ -52,7 +52,7 @@ def reply_my_friend(msg): from .utils import dont_raise_response_error, embed, ensure_one, mutual_friends __title__ = 'wxpy' -__version__ = '0.2.6' +__version__ = '0.3.0' __author__ = 'Youfou' __license__ = 'MIT' __copyright__ = '2017, Youfou' diff --git a/wxpy/api/bot.py b/wxpy/api/bot.py index bf1abb0..867b26a 100644 --- a/wxpy/api/bot.py +++ b/wxpy/api/bot.py @@ -1,13 +1,14 @@ +import atexit import logging +import queue from pprint import pformat from threading import Thread import itchat from wxpy.api.chats import Chat, Chats, Friend, Group, MP, User -from wxpy.api.messages import Message, MessageConfig, MessageConfigs, Messages +from wxpy.api.messages import Message, MessageConfig, Messages, Registered from wxpy.api.messages import SYSTEM -from wxpy.exceptions import ResponseError from wxpy.utils import ensure_list, get_user_name, handle_response, wrap_user_name logger = logging.getLogger(__name__) @@ -44,6 +45,8 @@ def __init__( if cache_path is True: cache_path = 'wxpy.pkl' + self.cache_path = cache_path + if console_qr is True: console_qr = 2 @@ -53,15 +56,17 @@ def __init__( loginCallback=login_callback, exitCallback=logout_callback ) - self.message_configs = MessageConfigs(self) - self.messages = Messages(bot=self) - + self.self = Friend(self.core.loginInfo['User'], self) self.file_helper = Chat(wrap_user_name('filehelper'), self) - self.self = Chat(self.core.loginInfo['User'], self) - self.self.bot = self + self.messages = Messages(bot=self) + self.registered = Registered(self) - self.cache_path = cache_path + self.is_listening = False + self.listening_thread = None + self.start() + + atexit.register(self._cleanup) def __repr__(self): return '<{}: {}>'.format(self.__class__.__name__, self.self.name) @@ -257,18 +262,19 @@ def create_group(self, users, topic=None): @handle_response() def request(): return self.core.create_chatroom( - memberList=ensure_list(wrap_user_name(users)), + memberList=dict_list, topic=topic or '' ) + dict_list = wrap_user_name(self.except_self(ensure_list(users))) ret = request() user_name = ret.get('ChatRoomName') if user_name: return Group(self.core.update_chatroom(userName=user_name), self) else: - raise ResponseError('Failed to create group:\n{}'.format(pformat(ret))) + raise Exception('Failed to create group:\n{}'.format(pformat(ret))) - # messages + # messages / register def _process_message(self, msg): """ @@ -278,7 +284,7 @@ def _process_message(self, msg): if not self.alive: return - config = self.message_configs.get_config(msg) + config = self.registered.get_config(msg) if not config: return @@ -288,64 +294,81 @@ def process(): try: ret = config.func(msg) if ret is not None: - self.core.send(msg=str(ret), toUserName=msg.sender.user_name) + msg.reply(ret) except: logger.exception('\nAn error occurred in {}.'.format(config.func)) if config.run_async: - Thread(target=process).start() + Thread(target=process, daemon=True).start() else: process() def register( - self, senders=None, msg_types=None, + self, chats=None, msg_types=None, except_self=True, run_async=True, enabled=True ): """ 装饰器:用于注册消息配置 - :param senders: 单个或列表形式的多个聊天对象或聊天类型,为空时匹配所有聊天对象 - :param msg_types: 单个或列表形式的多个消息类型,为空时匹配所有消息类型 (SYSTEM 类消息除外) - :param except_self: 排除自己在手机上发送的消息 - :param run_async: 异步执行配置的函数,可提高响应速度 + :param chats: 消息所在的聊天对象:单个或列表形式的多个聊天对象或聊天类型,为空时匹配所有聊天对象 + :param msg_types: 消息的类型:单个或列表形式的多个消息类型,为空时匹配所有消息类型 (SYSTEM 类消息除外) + :param except_self: 排除由自己发送的消息 + :param run_async: 是否异步执行所配置的函数:可提高响应速度 :param enabled: 当前配置的默认开启状态,可事后动态开启或关闭 """ - def register(func): - self.message_configs.append(MessageConfig( - bot=self, func=func, senders=senders, msg_types=msg_types, + def decorator(func): + self.registered.append(MessageConfig( + bot=self, func=func, chats=chats, msg_types=msg_types, except_self=except_self, run_async=run_async, enabled=enabled )) return func - return register + return decorator - def start(self, block=True): - """ - 开始监听和处理消息 + def _listen(self): + try: + logger.info('{} started.'.format(self)) + self.is_listening = True + while self.alive and self.is_listening: + try: + msg = Message(self.core.msgList.get(timeout=0.5), self) + except queue.Empty: + continue + if msg.type is not SYSTEM: + self.messages.append(msg) + self._process_message(msg) + finally: + self.is_listening = False + logger.info('{} stopped.'.format(self)) - :param block: 是否堵塞线程,为 False 时将在新的线程中运行 + def start(self): + """ + 开始消息监听和处理 (登陆后会自动开始) """ - def listen(): + if not self.alive: + logger.warning('{} has been logged out!'.format(self)) + elif self.is_listening: + logger.warning('{} is already running, no need to start again.'.format(self)) + else: + self.listening_thread = Thread(target=self._listen, daemon=True) + self.listening_thread.start() - logger.info('{} Auto-reply started.'.format(self)) - try: - while self.alive: - msg = Message(self.core.msgList.get(), self) - if msg.type is not SYSTEM: - self.messages.append(msg) - self._process_message(msg) - except KeyboardInterrupt: - logger.info('KeyboardInterrupt received, ending...') - self.alive = False - if self.core.useHotReload: - self.dump_login_status() - logger.info('Bye.') - - if block: - listen() + def stop(self): + """ + 停止消息监听和处理 (登出后会自动停止) + """ + + if self.is_listening: + self.is_listening = False + self.listening_thread.join() else: - t = Thread(target=listen, daemon=True) - t.start() + logger.warning('{} is not running.'.format(self)) + + def _cleanup(self): + if self.is_listening: + self.stop() + if self.alive and self.core.useHotReload: + self.dump_login_status() diff --git a/wxpy/api/chats/chat.py b/wxpy/api/chats/chat.py index 58e6750..e80063f 100644 --- a/wxpy/api/chats/chat.py +++ b/wxpy/api/chats/chat.py @@ -119,7 +119,7 @@ def send_raw_msg(self, msg_type, content): bot = Bot() @bot.register(msg_types=CARD) def reply_text(msg): - msg.sender.send_raw_msg(msg['MsgType'], msg['Content']) + msg.chat.send_raw_msg(msg['MsgType'], msg['Content']) """ return self.bot.core.send_raw_msg(msgType=msg_type, content=content, toUserName=self.user_name) @@ -138,6 +138,31 @@ def unpin(self): """ return self.bot.core.set_pinned(userName=self.user_name, isPinned=False) + @handle_response() + def get_avatar(self, save_path=None): + """ + 获取头像 + + :param save_path: 保存路径(后缀通常为.jpg),若为 `None` 则返回字节数据 + """ + + from .friend import Friend + from .group import Group + from .member import Member + + if isinstance(self, Friend): + kwargs = dict(userName=self.user_name, chatroomUserName=None) + elif isinstance(self, Group): + kwargs = dict(userName=None, chatroomUserName=self.user_name) + elif isinstance(self, Member): + kwargs = dict(userName=self.user_name, chatroomUserName=self.group.user_name) + else: + raise TypeError('expected `Friend`, `Group` or `Member`, got`{}`'.format(type(self))) + + kwargs.update(picDir=save_path) + + return self.bot.core.get_head_img(**kwargs) + def __repr__(self): return '<{}: {}>'.format(self.__class__.__name__, self.name) diff --git a/wxpy/api/chats/member.py b/wxpy/api/chats/member.py index a16f834..b6ab974 100644 --- a/wxpy/api/chats/member.py +++ b/wxpy/api/chats/member.py @@ -1,4 +1,5 @@ from .user import User +from wxpy.utils import handle_response class Member(User): @@ -17,6 +18,12 @@ def display_name(self): """ return self.raw.get('DisplayName') + def remove(self): + """ + 从群聊中移除该成员 + """ + return self.group.remove_members(self) + # Todo: 如何在获取以下信息时自动更新所在的群的详细数据?(下面注释的实现有误) # def _auto_update_group_for_details(self, attr): diff --git a/wxpy/api/chats/user.py b/wxpy/api/chats/user.py index d4eca80..f791920 100644 --- a/wxpy/api/chats/user.py +++ b/wxpy/api/chats/user.py @@ -47,6 +47,16 @@ def signature(self): """ return self.raw.get('Signature') + @property + def is_friend(self): + """ + 判断当前用户是否为好友关系 + + :return: 若为好友关系则为 True,否则为 False + """ + if self.bot: + return self in self.bot.friends() + def add(self, verify_content=''): """ 把当前用户加为好友 @@ -63,13 +73,3 @@ def accept(self, verify_content=''): :return: 新的好友对象 """ return self.bot.accept_friend(user=self, verify_content=verify_content) - - @property - def is_friend(self): - """ - 判断当前用户是否为好友关系 - - :return: 若为好友关系则为 True,否则为 False - """ - if self.bot: - return self in self.bot.friends() diff --git a/wxpy/api/messages/__init__.py b/wxpy/api/messages/__init__.py index 217478c..b56723a 100644 --- a/wxpy/api/messages/__init__.py +++ b/wxpy/api/messages/__init__.py @@ -1,5 +1,5 @@ from .message import ATTACHMENT, CARD, FRIENDS, MAP, NOTE, PICTURE, RECORDING, SHARING, SYSTEM, TEXT, VIDEO from .message import Message from .message_config import MessageConfig -from .message_configs import MessageConfigs +from .registered import Registered from .messages import Messages diff --git a/wxpy/api/messages/message.py b/wxpy/api/messages/message.py index e7b625a..bf29971 100644 --- a/wxpy/api/messages/message.py +++ b/wxpy/api/messages/message.py @@ -83,9 +83,9 @@ def __init__(self, raw, bot): self.card = User(self.raw.get('RecommendInfo'), self.bot) self.text = self.card.raw.get('Content') - # 将 msg.sender.send* 方法绑定到 msg.reply*,例如 msg.sender.send_img => msg.reply_img + # 将 msg.chat.send* 方法绑定到 msg.reply*,例如 msg.chat.send_img => msg.reply_img for method in '', '_image', '_file', '_video', '_msg', '_raw_msg': - setattr(self, 'reply' + method, getattr(self.sender, 'send' + method)) + setattr(self, 'reply' + method, getattr(self.chat, 'send' + method)) def __hash__(self): return hash((Message, self.id)) @@ -101,31 +101,19 @@ def __repr__(self): ret += '({0.type})' return ret.format(self, text) - def _get_chat_by_user_name(self, user_name): + @property + def chat(self): """ - 通过 user_name 找到对应的聊天对象 + 消息所在的聊天会话,即: - :param user_name: user_name - :return: 找到的对应聊天对象 + 对于自己发送的消息,为消息的接收者; + 对于别人发送的消息,为消息的发送者。 """ - def match_in_chats(_chats): - for c in _chats: - if c.user_name == user_name: - return c - _chat = None - - if user_name.startswith('@@'): - _chat = match_in_chats(self.bot.groups()) - elif user_name: - _chat = match_in_chats(self.bot.friends()) - if _chat is None: - _chat = match_in_chats(self.bot.mps()) - - if _chat is None: - _chat = Chat(wrap_user_name(user_name), self.bot) - - return _chat + if self.raw.get('FromUserName') == self.bot.self.user_name: + return self.receiver + else: + return self.sender @property def sender(self): @@ -146,25 +134,39 @@ def receiver(self): @property def member(self): """ - 若消息来自群聊,则此属性为实际发送消息的群成员 + 若消息来自群聊,则此属性为消息的实际发送人(具体的群成员) """ - if isinstance(self.sender, Group): + if isinstance(self.chat, Group): actual_user_name = self.raw.get('ActualUserName') - for _member in self.sender: + for _member in self.chat: if _member.user_name == actual_user_name: return _member - return Member(dict(UserName=actual_user_name, NickName=self.raw.get('ActualNickName')), self.sender) + return Member(dict(UserName=actual_user_name, NickName=self.raw.get('ActualNickName')), self.chat) - @property - def chat(self): + def _get_chat_by_user_name(self, user_name): """ - 消息所在的聊天会话,即: - 对于自己发送的消息,为消息的接收者; - 对于别人发送的消息,为消息的发送者。 + 通过 user_name 找到对应的聊天对象 + + :param user_name: user_name + :return: 找到的对应聊天对象 """ - if self.sender == self.bot.self: - return self.receiver - else: - return self.sender + def match_in_chats(_chats): + for c in _chats: + if c.user_name == user_name: + return c + + _chat = None + + if user_name.startswith('@@'): + _chat = match_in_chats(self.bot.groups()) + elif user_name: + _chat = match_in_chats(self.bot.friends()) + if _chat is None: + _chat = match_in_chats(self.bot.mps()) + + if _chat is None: + _chat = Chat(wrap_user_name(user_name), self.bot) + + return _chat diff --git a/wxpy/api/messages/message_config.py b/wxpy/api/messages/message_config.py index 5745fa8..4eacc19 100644 --- a/wxpy/api/messages/message_config.py +++ b/wxpy/api/messages/message_config.py @@ -11,17 +11,18 @@ class MessageConfig(object): """ def __init__( - self, bot, func, senders, msg_types, - except_self, run_async, enabled + self, bot, func, + chats, msg_types, except_self, + run_async, enabled ): self.bot = bot self.func = func - self.senders = ensure_list(senders) + self.chats = ensure_list(chats) self.msg_types = ensure_list(msg_types) self.except_self = except_self - self.run_async = run_async + self.run_async = run_async self._enabled = None self.enabled = enabled @@ -45,6 +46,6 @@ def __repr__(self): self.__class__.__name__, self.bot.self.name, self.func.__name__, - 'Async, ' if self.run_async else '', 'Enabled' if self.enabled else 'Disabled', + ', Async' if self.run_async else '', ) diff --git a/wxpy/api/messages/message_configs.py b/wxpy/api/messages/registered.py similarity index 71% rename from wxpy/api/messages/message_configs.py rename to wxpy/api/messages/registered.py index 2d7882d..330b918 100644 --- a/wxpy/api/messages/message_configs.py +++ b/wxpy/api/messages/registered.py @@ -1,9 +1,9 @@ from .message import SYSTEM -class MessageConfigs(list): +class Registered(list): """ - 一个机器人(Bot)的所有消息注册配置 + 一个机器人(Bot)的所有已注册消息配置 """ def __init__(self, bot): @@ -12,12 +12,12 @@ def __init__(self, bot): :param bot: 这些配置所属的机器人 """ - super(MessageConfigs, self).__init__() + super(Registered, self).__init__() self.bot = bot def get_config(self, msg): """ - 获取给定消息的回复配置。每条消息仅匹配一个回复配置,后注册的配置具有更高的匹配优先级。 + 获取给定消息的注册配置。每条消息仅匹配一个注册配置,后注册的配置具有更高的匹配优先级。 :param msg: 给定的消息 :return: 匹配的回复配置 @@ -33,16 +33,28 @@ def get_config(self, msg): elif conf.msg_types is None and msg.type == SYSTEM: continue - if conf.senders is None: + if conf.chats is None: return conf - for sender in conf.senders: - if (isinstance(sender, type) and isinstance(msg.sender, sender)) or sender == msg.sender: + for chat in conf.chats: + if (isinstance(chat, type) and isinstance(msg.chat, chat)) or chat == msg.chat: return conf + def get_config_by_func(self, func): + """ + 通过给定的函数找到对应的注册配置 + + :param func: 给定的函数 + :return: 对应的注册配置 + """ + + for conf in self: + if conf.func is func: + return conf + def _change_status(self, func, enabled): if func: - self.get_config(func).enabled = enabled + self.get_config_by_func(func).enabled = enabled else: for conf in self: conf.enabled = enabled diff --git a/wxpy/ext/tuling.py b/wxpy/ext/tuling.py index cfeb875..5b01995 100644 --- a/wxpy/ext/tuling.py +++ b/wxpy/ext/tuling.py @@ -42,29 +42,29 @@ def _change_words(self): )) def is_last_member(self, msg): - if msg.member == self.last_member.get(msg.sender): + if msg.member == self.last_member.get(msg.chat): return True else: - self.last_member[msg.sender] = msg.member + self.last_member[msg.chat] = msg.member - def do_reply(self, msg, to_member=True): + def do_reply(self, msg, at_member=True): """ 回复消息,并返回答复文本 :param msg: Message 对象 - :param to_member: 若消息来自群聊,回复 @发消息的群成员 + :param at_member: 若消息来自群聊,回复时 @发消息的群成员 :return: 答复文本 """ - ret = self.reply_text(msg, to_member) + ret = self.reply_text(msg, at_member) msg.reply(ret) return ret - def reply_text(self, msg, to_member=True): + def reply_text(self, msg, at_member=True): """ - 返回消息的答复文本 + 仅返回消息的答复文本 :param msg: Message 对象 - :param to_member: 若消息来自群聊,回复 @发消息的群成员 + :param at_member: 若消息来自群聊,回复时 @发消息的群成员 :return: 答复文本 """ @@ -73,9 +73,9 @@ def process_answer(): logger.debug('Tuling answer:\n' + pprint.pformat(answer)) ret = str() - if to_member: - if len(msg.sender) > 2 and msg.member.name and not self.is_last_member(msg): - ret += '@{} '.format(msg.member.name) + if at_member: + if len(msg.chat) > 2 and msg.member.name and not self.is_last_member(msg): + ret += '@{} '.format(msg.member.display_name or msg.member.nick_name) code = -1 if answer: @@ -119,13 +119,13 @@ def get_location(_chat): return from wxpy.api.chats import Group - if to_member and isinstance(msg.sender, Group) and msg.member: + if at_member and isinstance(msg.chat, Group) and msg.member: user_id = msg.member.user_name location = get_location(msg.member) else: - to_member = False - user_id = msg.sender.user_name - location = get_location(msg.sender) + at_member = False + user_id = msg.chat.user_name + location = get_location(msg.chat) user_id = re.sub(r'[^a-zA-Z\d]', '', user_id) user_id = user_id[-32:]