From 65d0fd9ffc3d0bae281b03ea083d82cc6386343f Mon Sep 17 00:00:00 2001 From: Azide <rukuy@qq.com> Date: Sat, 5 Mar 2022 21:01:48 +0800 Subject: [PATCH 01/42] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E8=AE=A2?= =?UTF-8?q?=E9=98=85=E8=BF=87=E7=A8=8B=E4=B8=AD=E6=AD=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/nonebot_bison/config_manager.py | 16 +++++-- src/plugins/nonebot_bison/plugin_config.py | 1 + src/plugins/nonebot_bison/send.py.bak | 48 +++++++++++++++++++++ 3 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 src/plugins/nonebot_bison/send.py.bak diff --git a/src/plugins/nonebot_bison/config_manager.py b/src/plugins/nonebot_bison/config_manager.py index 7836121..c4845a1 100644 --- a/src/plugins/nonebot_bison/config_manager.py +++ b/src/plugins/nonebot_bison/config_manager.py @@ -57,7 +57,7 @@ def do_add_sub(add_sub: Type[Matcher]): for platform_name in common_platform ] ) - + "要查看全部平台请输入:“全部”" + + "要查看全部平台请输入:“全部”\n中止订阅过程请输入:“取消”" ) async def parse_platform(event: AbstractEvent, state: T_State) -> None: @@ -72,6 +72,8 @@ def do_add_sub(add_sub: Type[Matcher]): ] ) await add_sub.reject(message) + elif platform == "取消": + await add_sub.finish("已中止订阅") elif platform in platform_manager: state["platform"] = platform else: @@ -96,12 +98,16 @@ def do_add_sub(add_sub: Type[Matcher]): return target = str(event.get_message()).strip() try: + if target == "取消": + raise KeyboardInterrupt name = await check_sub_target(state["platform"], target) if not name: raise ValueError state["id"] = target state["name"] = name - except: + except(KeyboardInterrupt): + await add_sub.finish("已中止订阅") + except(ValueError): await add_sub.reject("id输入错误") @add_sub.got("id", _gen_prompt_template("{_prompt}"), [Depends(parse_id)]) @@ -118,7 +124,9 @@ def do_add_sub(add_sub: Type[Matcher]): return res = [] for cat in str(event.get_message()).strip().split(): - if cat not in platform_manager[state["platform"]].reverse_category: + if cat == "取消": + await add_sub.finish("已中止订阅") + elif cat not in platform_manager[state["platform"]].reverse_category: await add_sub.reject("不支持 {}".format(cat)) res.append(platform_manager[state["platform"]].reverse_category[cat]) state["cats"] = res @@ -133,6 +141,8 @@ def do_add_sub(add_sub: Type[Matcher]): async def parser_tags(event: AbstractEvent, state: T_State): if not isinstance(state["tags"], Message): return + if str(event.get_message()).strip() == "取消":#一般不会有叫 取消 的tag吧 + await add_sub.finish("已中止订阅") if str(event.get_message()).strip() == "全部标签": state["tags"] = [] else: diff --git a/src/plugins/nonebot_bison/plugin_config.py b/src/plugins/nonebot_bison/plugin_config.py index 927dfa4..c16071b 100644 --- a/src/plugins/nonebot_bison/plugin_config.py +++ b/src/plugins/nonebot_bison/plugin_config.py @@ -12,6 +12,7 @@ class PlugConfig(BaseSettings): bison_filter_log: bool = False bison_to_me: bool = True bison_skip_browser_check: bool = False + bison_use_pic_merge: bool = True#多图片时启用图片合并转发 class Config: extra = "ignore" diff --git a/src/plugins/nonebot_bison/send.py.bak b/src/plugins/nonebot_bison/send.py.bak new file mode 100644 index 0000000..1518636 --- /dev/null +++ b/src/plugins/nonebot_bison/send.py.bak @@ -0,0 +1,48 @@ +import time +from typing import Literal, Union + +from nonebot.adapters import Message, MessageSegment +from nonebot.adapters.onebot.v11.bot import Bot +from nonebot.log import logger + +from .plugin_config import plugin_config + +QUEUE = [] +LAST_SEND_TIME = time.time() + + +async def _do_send( + bot: "Bot", user: str, user_type: str, msg: Union[str, Message, MessageSegment] +): + if user_type == "group": + await bot.call_api("send_group_msg", group_id=user, message=msg) + elif user_type == "private": + await bot.call_api("send_private_msg", user_id=user, message=msg) + + +async def do_send_msgs(): + global LAST_SEND_TIME + if time.time() - LAST_SEND_TIME < 1.5: + return + if QUEUE: + bot, user, user_type, msg, retry_time = QUEUE.pop(0) + try: + await _do_send(bot, user, user_type, msg) + except Exception as e: + if retry_time > 0: + QUEUE.insert(0, (bot, user, user_type, msg, retry_time - 1)) + else: + msg_str = str(msg) + if len(msg_str) > 50: + msg_str = msg_str[:50] + "..." + logger.warning(f"send msg err {e} {msg_str}") + LAST_SEND_TIME = time.time() + + +async def send_msgs(bot: Bot, user, user_type: Literal["private", "group"], msgs: list): + if plugin_config.bison_use_queue: + for msg in msgs: + QUEUE.append((bot, user, user_type, msg, 2)) + else: + for msg in msgs: + await _do_send(bot, user, user_type, msg) From 8f8658d6045c1b6e19a521112cab6ca3334f5167 Mon Sep 17 00:00:00 2001 From: Azide <rukuy@qq.com> Date: Sat, 5 Mar 2022 23:05:03 +0800 Subject: [PATCH 02/42] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E5=A4=9A?= =?UTF-8?q?=E5=9B=BE=E7=89=87=E6=97=B6=E5=90=88=E5=B9=B6=E8=BD=AC=E5=8F=91?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=92=8C=E7=9B=B8=E5=85=B3=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=EF=BC=88=E9=BB=98=E8=AE=A4=E5=BC=80=E5=90=AF=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/nonebot_bison/plugin_config.py | 2 +- src/plugins/nonebot_bison/send.py | 48 ++++++++++++++++++++-- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/plugins/nonebot_bison/plugin_config.py b/src/plugins/nonebot_bison/plugin_config.py index c16071b..4637280 100644 --- a/src/plugins/nonebot_bison/plugin_config.py +++ b/src/plugins/nonebot_bison/plugin_config.py @@ -12,7 +12,7 @@ class PlugConfig(BaseSettings): bison_filter_log: bool = False bison_to_me: bool = True bison_skip_browser_check: bool = False - bison_use_pic_merge: bool = True#多图片时启用图片合并转发 + bison_use_pic_merge: bool = True#多图片时启用图片合并转发(仅限群),当bison_use_queue为False时该配置不会生效, class Config: extra = "ignore" diff --git a/src/plugins/nonebot_bison/send.py b/src/plugins/nonebot_bison/send.py index 1518636..c1060e0 100644 --- a/src/plugins/nonebot_bison/send.py +++ b/src/plugins/nonebot_bison/send.py @@ -1,3 +1,4 @@ +from email import message import time from typing import Literal, Union @@ -7,9 +8,25 @@ from nonebot.log import logger from .plugin_config import plugin_config -QUEUE = [] +QUEUE = []#不开启图片合并转发时使用 LAST_SEND_TIME = time.time() +def generate_forward_msg( + msgs:list, self_id:str, nickname:str + ): + group_msg=[] + for msg in msgs: + sub_msg={ + "type":"node", + "data": { + "name": f"{nickname}", + "uin": f"{self_id}", + "content": f"{msg}" + } + } + group_msg.append(sub_msg) + + return group_msg async def _do_send( bot: "Bot", user: str, user_type: str, msg: Union[str, Message, MessageSegment] @@ -19,6 +36,26 @@ async def _do_send( elif user_type == "private": await bot.call_api("send_private_msg", user_id=user, message=msg) +async def _do_merge_send( + bot: Bot, user, user_type: Literal["private", "group"], msgs: list + ): + try: + await _do_send(bot, user, user_type, msgs.pop(0))#弹出第一条消息,剩下的消息合并 + except Exception as e: + logger.error("向群{}发送消息序列首消息失败:{}".format(user,repr(e))) + else: + logger.info("成功向群{}发送消息序列中的首条消息".format(user)) + try: + group_bot_info = await bot.get_group_member_info(group_id=user,user_id=bot.self_id,no_cache=True)#调用api获取群内bot的相关参数 + forward_msg = generate_forward_msg(msgs = msgs, + self_id = group_bot_info["user_id"], + nickname = group_bot_info["card"] + )#生成合并转发内容 + await bot.send_group_forward_msg(group_id=user,messages=forward_msg) + except: + logger.error("向群{}发送合并图片消息失败:{}".format(user,repr(e))) + else: + logger.info("成功向群{}发送合并图片转发消息".format(user)) async def do_send_msgs(): global LAST_SEND_TIME @@ -39,10 +76,15 @@ async def do_send_msgs(): LAST_SEND_TIME = time.time() + async def send_msgs(bot: Bot, user, user_type: Literal["private", "group"], msgs: list): if plugin_config.bison_use_queue: - for msg in msgs: - QUEUE.append((bot, user, user_type, msg, 2)) + if plugin_config.bison_use_pic_merge and user_type == "group": + await _do_merge_send(bot, user, user_type, msgs) + else: + for msg in msgs: + QUEUE.append((bot, user, user_type, msg, 2)) + else: for msg in msgs: await _do_send(bot, user, user_type, msg) From 6920698e291af8a69b6869a6ccdff21a1757079d Mon Sep 17 00:00:00 2001 From: Azide <rukuy@qq.com> Date: Sat, 5 Mar 2022 23:54:08 +0800 Subject: [PATCH 03/42] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BA=86=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E7=9A=84try...except=E8=AF=AD=E5=8F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/nonebot_bison/send.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/nonebot_bison/send.py b/src/plugins/nonebot_bison/send.py index c1060e0..7535805 100644 --- a/src/plugins/nonebot_bison/send.py +++ b/src/plugins/nonebot_bison/send.py @@ -41,8 +41,8 @@ async def _do_merge_send( ): try: await _do_send(bot, user, user_type, msgs.pop(0))#弹出第一条消息,剩下的消息合并 - except Exception as e: - logger.error("向群{}发送消息序列首消息失败:{}".format(user,repr(e))) + except Exception as e_f:#first_msg_exception + logger.error("向群{}发送消息序列首消息失败:{}".format(user,repr(e_f))) else: logger.info("成功向群{}发送消息序列中的首条消息".format(user)) try: @@ -52,8 +52,8 @@ async def _do_merge_send( nickname = group_bot_info["card"] )#生成合并转发内容 await bot.send_group_forward_msg(group_id=user,messages=forward_msg) - except: - logger.error("向群{}发送合并图片消息失败:{}".format(user,repr(e))) + except Exception as e_b:#behind_msg_exception + logger.error("向群{}发送合并图片消息失败:{}".format(user,repr(e_b))) else: logger.info("成功向群{}发送合并图片转发消息".format(user)) From 2617acf46a261191b763c388d63d4fc726706076 Mon Sep 17 00:00:00 2001 From: Azide <rukuy@qq.com> Date: Sun, 6 Mar 2022 00:33:45 +0800 Subject: [PATCH 04/42] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BA=86=E5=8F=91?= =?UTF-8?q?=E9=80=81=E5=90=88=E5=B9=B6=E6=B6=88=E6=81=AF=E6=97=B6=E7=9A=84?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E7=BA=A7=E5=88=AB:error->warning(=E6=8A=9B?= =?UTF-8?q?=E5=87=BAapi=20call=20timeout=E5=BC=82=E5=B8=B8=E5=90=8E?= =?UTF-8?q?=E4=BB=8D=E7=84=B6=E8=83=BD=E5=8F=91=E5=87=BA=E6=B6=88=E6=81=AF?= =?UTF-8?q?),=E4=BF=AE=E5=A4=8D=E4=BA=86=E5=8F=91=E9=80=81=E7=BA=AF?= =?UTF-8?q?=E6=96=87=E5=AD=97=E6=88=96=E8=BD=AC=E5=8F=91=E6=97=B6=E6=9C=BA?= =?UTF-8?q?=E5=99=A8=E4=BA=BA=E8=BD=AC=E5=8F=91=E7=A9=BA=E7=9A=84=E5=90=88?= =?UTF-8?q?=E5=B9=B6=E8=BD=AC=E5=8F=91=E6=B6=88=E6=81=AF=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/nonebot_bison/send.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/plugins/nonebot_bison/send.py b/src/plugins/nonebot_bison/send.py index 7535805..7f196ac 100644 --- a/src/plugins/nonebot_bison/send.py +++ b/src/plugins/nonebot_bison/send.py @@ -46,14 +46,15 @@ async def _do_merge_send( else: logger.info("成功向群{}发送消息序列中的首条消息".format(user)) try: - group_bot_info = await bot.get_group_member_info(group_id=user,user_id=bot.self_id,no_cache=True)#调用api获取群内bot的相关参数 - forward_msg = generate_forward_msg(msgs = msgs, - self_id = group_bot_info["user_id"], - nickname = group_bot_info["card"] - )#生成合并转发内容 - await bot.send_group_forward_msg(group_id=user,messages=forward_msg) + if msgs: + group_bot_info = await bot.get_group_member_info(group_id=user,user_id=bot.self_id,no_cache=True)#调用api获取群内bot的相关参数 + forward_msg = generate_forward_msg(msgs = msgs, + self_id = group_bot_info["user_id"], + nickname = group_bot_info["card"] + )#生成合并转发内容 + await bot.send_group_forward_msg(group_id=user,messages=forward_msg) except Exception as e_b:#behind_msg_exception - logger.error("向群{}发送合并图片消息失败:{}".format(user,repr(e_b))) + logger.warning("向群{}发送合并图片消息超时或者可能失败:{}".format(user,repr(e_b))) else: logger.info("成功向群{}发送合并图片转发消息".format(user)) From 8ab7328611c943840306dc4bb36cd95c6952083e Mon Sep 17 00:00:00 2001 From: Azide <rukuy@qq.com> Date: Sun, 6 Mar 2022 00:35:51 +0800 Subject: [PATCH 05/42] =?UTF-8?q?=E8=80=83=E8=99=91=E5=88=B0=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E5=A4=9A=E5=9B=BE=E7=89=87=E6=97=B6=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E8=BD=AC=E5=8F=91=E6=B6=88=E6=81=AF=E6=97=B6=EF=BC=8C=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E5=A4=AA=E5=A4=A7=E5=A4=AA=E5=A4=9A=E5=8F=AF=E8=83=BD?= =?UTF-8?q?=E9=80=A0=E6=88=90=E5=90=88=E5=B9=B6=E8=BD=AC=E5=8F=91=E7=9A=84?= =?UTF-8?q?=E5=BB=B6=E8=BF=9F=E8=BF=87=E5=A4=A7=EF=BC=8C=E5=B0=86=E8=AF=A5?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E8=B0=83=E6=95=B4=E4=B8=BA=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E5=85=B3=E9=97=AD=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/nonebot_bison/plugin_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/nonebot_bison/plugin_config.py b/src/plugins/nonebot_bison/plugin_config.py index 4637280..4a50175 100644 --- a/src/plugins/nonebot_bison/plugin_config.py +++ b/src/plugins/nonebot_bison/plugin_config.py @@ -12,7 +12,7 @@ class PlugConfig(BaseSettings): bison_filter_log: bool = False bison_to_me: bool = True bison_skip_browser_check: bool = False - bison_use_pic_merge: bool = True#多图片时启用图片合并转发(仅限群),当bison_use_queue为False时该配置不会生效, + bison_use_pic_merge: bool = False#多图片时启用图片合并转发(仅限群),当bison_use_queue为False时该配置不会生效, class Config: extra = "ignore" From 48210675f761d01afd1fe58d26bef9b86ba0474e Mon Sep 17 00:00:00 2001 From: Azide <rukuy@qq.com> Date: Sun, 6 Mar 2022 01:19:49 +0800 Subject: [PATCH 06/42] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86bison=5Fuse=5F?= =?UTF-8?q?pic=5Fmerge=E9=85=8D=E7=BD=AE=E9=A1=B9=E7=9A=84=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E6=95=B0=E9=87=8F=EF=BC=8C=E7=8E=B0=E5=9C=A8=E5=8F=AF?= =?UTF-8?q?=E4=BB=A5=E9=80=89=E6=8B=A9=E6=98=AF=E5=90=A6=E5=85=A8=E9=83=A8?= =?UTF-8?q?=E5=90=88=E5=B9=B6=E8=BD=AC=E5=8F=91=EF=BC=88=E6=8E=A9=E7=9B=96?= =?UTF-8?q?=E9=A6=96=E6=9D=A1=E6=B6=88=E6=81=AF=E5=92=8C=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E5=88=86=E5=BC=80=E8=BD=AC=E5=8F=91=E6=97=B6?= =?UTF-8?q?=E7=9A=84=E5=BB=B6=E8=BF=9F=E9=97=AE=E9=A2=98=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/nonebot_bison/plugin_config.py | 4 +- src/plugins/nonebot_bison/send.py | 30 ++++++++------ src/plugins/nonebot_bison/send.py.bak | 48 ---------------------- 3 files changed, 19 insertions(+), 63 deletions(-) delete mode 100644 src/plugins/nonebot_bison/send.py.bak diff --git a/src/plugins/nonebot_bison/plugin_config.py b/src/plugins/nonebot_bison/plugin_config.py index 4a50175..35e2b36 100644 --- a/src/plugins/nonebot_bison/plugin_config.py +++ b/src/plugins/nonebot_bison/plugin_config.py @@ -12,8 +12,8 @@ class PlugConfig(BaseSettings): bison_filter_log: bool = False bison_to_me: bool = True bison_skip_browser_check: bool = False - bison_use_pic_merge: bool = False#多图片时启用图片合并转发(仅限群),当bison_use_queue为False时该配置不会生效, - + bison_use_pic_merge: int = 0#多图片时启用图片合并转发(仅限群),当bison_use_queue为False时该配置不会生效 + #0:不启用;1:首条消息单独发送,剩余照片合并转发;3:所有消息全部合并转发 class Config: extra = "ignore" diff --git a/src/plugins/nonebot_bison/send.py b/src/plugins/nonebot_bison/send.py index 7f196ac..2283adc 100644 --- a/src/plugins/nonebot_bison/send.py +++ b/src/plugins/nonebot_bison/send.py @@ -39,22 +39,26 @@ async def _do_send( async def _do_merge_send( bot: Bot, user, user_type: Literal["private", "group"], msgs: list ): - try: - await _do_send(bot, user, user_type, msgs.pop(0))#弹出第一条消息,剩下的消息合并 - except Exception as e_f:#first_msg_exception - logger.error("向群{}发送消息序列首消息失败:{}".format(user,repr(e_f))) - else: - logger.info("成功向群{}发送消息序列中的首条消息".format(user)) + if plugin_config.bison_use_pic_merge == 1: + try: + await _do_send(bot, user, user_type, msgs.pop(0))#弹出第一条消息,剩下的消息合并 + except Exception as e_f:#first_msg_exception + logger.error("向群{}发送消息序列首消息失败:{}".format(user,repr(e_f))) + else: + logger.info("成功向群{}发送消息序列中的首条消息".format(user)) try: if msgs: - group_bot_info = await bot.get_group_member_info(group_id=user,user_id=bot.self_id,no_cache=True)#调用api获取群内bot的相关参数 - forward_msg = generate_forward_msg(msgs = msgs, - self_id = group_bot_info["user_id"], - nickname = group_bot_info["card"] - )#生成合并转发内容 - await bot.send_group_forward_msg(group_id=user,messages=forward_msg) + if len(msgs) == 1:#只有一条消息序列就不合并转发 + await _do_send(bot, user, user_type, msgs.pop(0)) + else: + group_bot_info = await bot.get_group_member_info(group_id=user,user_id=bot.self_id,no_cache=True)#调用api获取群内bot的相关参数 + forward_msg = generate_forward_msg(msgs = msgs, + self_id = group_bot_info["user_id"], + nickname = group_bot_info["card"] + )#生成合并转发内容 + await bot.send_group_forward_msg(group_id=user,messages=forward_msg) except Exception as e_b:#behind_msg_exception - logger.warning("向群{}发送合并图片消息超时或者可能失败:{}".format(user,repr(e_b))) + logger.warning("向群{}发送合并图片消息超时或者可能失败:{}\n可能是因为图片太大或者太多".format(user,repr(e_b))) else: logger.info("成功向群{}发送合并图片转发消息".format(user)) diff --git a/src/plugins/nonebot_bison/send.py.bak b/src/plugins/nonebot_bison/send.py.bak deleted file mode 100644 index 1518636..0000000 --- a/src/plugins/nonebot_bison/send.py.bak +++ /dev/null @@ -1,48 +0,0 @@ -import time -from typing import Literal, Union - -from nonebot.adapters import Message, MessageSegment -from nonebot.adapters.onebot.v11.bot import Bot -from nonebot.log import logger - -from .plugin_config import plugin_config - -QUEUE = [] -LAST_SEND_TIME = time.time() - - -async def _do_send( - bot: "Bot", user: str, user_type: str, msg: Union[str, Message, MessageSegment] -): - if user_type == "group": - await bot.call_api("send_group_msg", group_id=user, message=msg) - elif user_type == "private": - await bot.call_api("send_private_msg", user_id=user, message=msg) - - -async def do_send_msgs(): - global LAST_SEND_TIME - if time.time() - LAST_SEND_TIME < 1.5: - return - if QUEUE: - bot, user, user_type, msg, retry_time = QUEUE.pop(0) - try: - await _do_send(bot, user, user_type, msg) - except Exception as e: - if retry_time > 0: - QUEUE.insert(0, (bot, user, user_type, msg, retry_time - 1)) - else: - msg_str = str(msg) - if len(msg_str) > 50: - msg_str = msg_str[:50] + "..." - logger.warning(f"send msg err {e} {msg_str}") - LAST_SEND_TIME = time.time() - - -async def send_msgs(bot: Bot, user, user_type: Literal["private", "group"], msgs: list): - if plugin_config.bison_use_queue: - for msg in msgs: - QUEUE.append((bot, user, user_type, msg, 2)) - else: - for msg in msgs: - await _do_send(bot, user, user_type, msg) From d3a782cb18eb02728a72c69931ed95fa70dfa0c2 Mon Sep 17 00:00:00 2001 From: Azide <rukuy@qq.com> Date: Sun, 6 Mar 2022 01:39:33 +0800 Subject: [PATCH 07/42] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=E9=94=99=E8=AF=AF...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/nonebot_bison/plugin_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/nonebot_bison/plugin_config.py b/src/plugins/nonebot_bison/plugin_config.py index 35e2b36..ecdbb40 100644 --- a/src/plugins/nonebot_bison/plugin_config.py +++ b/src/plugins/nonebot_bison/plugin_config.py @@ -13,7 +13,7 @@ class PlugConfig(BaseSettings): bison_to_me: bool = True bison_skip_browser_check: bool = False bison_use_pic_merge: int = 0#多图片时启用图片合并转发(仅限群),当bison_use_queue为False时该配置不会生效 - #0:不启用;1:首条消息单独发送,剩余照片合并转发;3:所有消息全部合并转发 + #0:不启用;1:首条消息单独发送,剩余照片合并转发;2以及以上:所有消息全部合并转发 class Config: extra = "ignore" From cb0df0528e127c90acc000f9e8783d83e45bed54 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 5 Mar 2022 18:20:12 +0000 Subject: [PATCH 08/42] auto fix by pre-commit hooks --- src/plugins/nonebot_bison/config_manager.py | 6 +-- src/plugins/nonebot_bison/plugin_config.py | 4 +- src/plugins/nonebot_bison/send.py | 56 ++++++++++----------- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/plugins/nonebot_bison/config_manager.py b/src/plugins/nonebot_bison/config_manager.py index c4845a1..e0837f7 100644 --- a/src/plugins/nonebot_bison/config_manager.py +++ b/src/plugins/nonebot_bison/config_manager.py @@ -105,9 +105,9 @@ def do_add_sub(add_sub: Type[Matcher]): raise ValueError state["id"] = target state["name"] = name - except(KeyboardInterrupt): + except (KeyboardInterrupt): await add_sub.finish("已中止订阅") - except(ValueError): + except (ValueError): await add_sub.reject("id输入错误") @add_sub.got("id", _gen_prompt_template("{_prompt}"), [Depends(parse_id)]) @@ -141,7 +141,7 @@ def do_add_sub(add_sub: Type[Matcher]): async def parser_tags(event: AbstractEvent, state: T_State): if not isinstance(state["tags"], Message): return - if str(event.get_message()).strip() == "取消":#一般不会有叫 取消 的tag吧 + if str(event.get_message()).strip() == "取消": # 一般不会有叫 取消 的tag吧 await add_sub.finish("已中止订阅") if str(event.get_message()).strip() == "全部标签": state["tags"] = [] diff --git a/src/plugins/nonebot_bison/plugin_config.py b/src/plugins/nonebot_bison/plugin_config.py index ecdbb40..e6067d8 100644 --- a/src/plugins/nonebot_bison/plugin_config.py +++ b/src/plugins/nonebot_bison/plugin_config.py @@ -12,8 +12,8 @@ class PlugConfig(BaseSettings): bison_filter_log: bool = False bison_to_me: bool = True bison_skip_browser_check: bool = False - bison_use_pic_merge: int = 0#多图片时启用图片合并转发(仅限群),当bison_use_queue为False时该配置不会生效 - #0:不启用;1:首条消息单独发送,剩余照片合并转发;2以及以上:所有消息全部合并转发 + bison_use_pic_merge: int = 0 # 多图片时启用图片合并转发(仅限群),当bison_use_queue为False时该配置不会生效 + # 0:不启用;1:首条消息单独发送,剩余照片合并转发;2以及以上:所有消息全部合并转发 class Config: extra = "ignore" diff --git a/src/plugins/nonebot_bison/send.py b/src/plugins/nonebot_bison/send.py index 2283adc..ce720b7 100644 --- a/src/plugins/nonebot_bison/send.py +++ b/src/plugins/nonebot_bison/send.py @@ -1,5 +1,5 @@ -from email import message import time +from email import message from typing import Literal, Union from nonebot.adapters import Message, MessageSegment @@ -8,26 +8,22 @@ from nonebot.log import logger from .plugin_config import plugin_config -QUEUE = []#不开启图片合并转发时使用 +QUEUE = [] # 不开启图片合并转发时使用 LAST_SEND_TIME = time.time() -def generate_forward_msg( - msgs:list, self_id:str, nickname:str - ): - group_msg=[] + +def generate_forward_msg(msgs: list, self_id: str, nickname: str): + group_msg = [] for msg in msgs: - sub_msg={ - "type":"node", - "data": { - "name": f"{nickname}", - "uin": f"{self_id}", - "content": f"{msg}" - } + sub_msg = { + "type": "node", + "data": {"name": f"{nickname}", "uin": f"{self_id}", "content": f"{msg}"}, } group_msg.append(sub_msg) - + return group_msg + async def _do_send( bot: "Bot", user: str, user_type: str, msg: Union[str, Message, MessageSegment] ): @@ -36,32 +32,37 @@ async def _do_send( elif user_type == "private": await bot.call_api("send_private_msg", user_id=user, message=msg) + async def _do_merge_send( bot: Bot, user, user_type: Literal["private", "group"], msgs: list - ): +): if plugin_config.bison_use_pic_merge == 1: try: - await _do_send(bot, user, user_type, msgs.pop(0))#弹出第一条消息,剩下的消息合并 - except Exception as e_f:#first_msg_exception - logger.error("向群{}发送消息序列首消息失败:{}".format(user,repr(e_f))) + await _do_send(bot, user, user_type, msgs.pop(0)) # 弹出第一条消息,剩下的消息合并 + except Exception as e_f: # first_msg_exception + logger.error("向群{}发送消息序列首消息失败:{}".format(user, repr(e_f))) else: logger.info("成功向群{}发送消息序列中的首条消息".format(user)) try: if msgs: - if len(msgs) == 1:#只有一条消息序列就不合并转发 + if len(msgs) == 1: # 只有一条消息序列就不合并转发 await _do_send(bot, user, user_type, msgs.pop(0)) else: - group_bot_info = await bot.get_group_member_info(group_id=user,user_id=bot.self_id,no_cache=True)#调用api获取群内bot的相关参数 - forward_msg = generate_forward_msg(msgs = msgs, - self_id = group_bot_info["user_id"], - nickname = group_bot_info["card"] - )#生成合并转发内容 - await bot.send_group_forward_msg(group_id=user,messages=forward_msg) - except Exception as e_b:#behind_msg_exception - logger.warning("向群{}发送合并图片消息超时或者可能失败:{}\n可能是因为图片太大或者太多".format(user,repr(e_b))) + group_bot_info = await bot.get_group_member_info( + group_id=user, user_id=bot.self_id, no_cache=True + ) # 调用api获取群内bot的相关参数 + forward_msg = generate_forward_msg( + msgs=msgs, + self_id=group_bot_info["user_id"], + nickname=group_bot_info["card"], + ) # 生成合并转发内容 + await bot.send_group_forward_msg(group_id=user, messages=forward_msg) + except Exception as e_b: # behind_msg_exception + logger.warning("向群{}发送合并图片消息超时或者可能失败:{}\n可能是因为图片太大或者太多".format(user, repr(e_b))) else: logger.info("成功向群{}发送合并图片转发消息".format(user)) + async def do_send_msgs(): global LAST_SEND_TIME if time.time() - LAST_SEND_TIME < 1.5: @@ -81,7 +82,6 @@ async def do_send_msgs(): LAST_SEND_TIME = time.time() - async def send_msgs(bot: Bot, user, user_type: Literal["private", "group"], msgs: list): if plugin_config.bison_use_queue: if plugin_config.bison_use_pic_merge and user_type == "group": From 1bbbb7925d9d3f3e935b8ae10dd41512287d708c Mon Sep 17 00:00:00 2001 From: Azide <rukuy@qq.com> Date: Sun, 6 Mar 2022 18:37:24 +0800 Subject: [PATCH 09/42] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86test=5Fconfig?= =?UTF-8?q?=5Fmaneger.py=E6=96=87=E4=BB=B6=EF=BC=8C=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E4=BA=86=E4=B8=AD=E6=AD=A2=E8=AE=A2=E9=98=85=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E7=9A=84=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_config_manager.py | 10 +- tests/test_config_manager_abort.py | 314 +++++++++++++++++++++++++++++ 2 files changed, 319 insertions(+), 5 deletions(-) create mode 100644 tests/test_config_manager_abort.py diff --git a/tests/test_config_manager.py b/tests/test_config_manager.py index 160563d..ce2764b 100644 --- a/tests/test_config_manager.py +++ b/tests/test_config_manager.py @@ -59,7 +59,7 @@ async def test_configurable_at_me_false(app: App): for platform_name in common_platform ] ) - + "要查看全部平台请输入:“全部”" + + "要查看全部平台请输入:“全部”\n中止订阅过程请输入:“取消”" ), True, ) @@ -113,7 +113,7 @@ async def test_add_with_target(app: App): for platform_name in common_platform ] ) - + "要查看全部平台请输入:“全部”" + + "要查看全部平台请输入:“全部”\n中止订阅过程请输入:“取消”" ), True, ) @@ -230,7 +230,7 @@ async def test_add_with_target_no_cat(app: App): for platform_name in common_platform ] ) - + "要查看全部平台请输入:“全部”" + + "要查看全部平台请输入:“全部”\n中止订阅过程请输入:“取消”" ), True, ) @@ -294,7 +294,7 @@ async def test_add_no_target(app: App): for platform_name in common_platform ] ) - + "要查看全部平台请输入:“全部”" + + "要查看全部平台请输入:“全部”\n中止订阅过程请输入:“取消”" ), True, ) @@ -358,7 +358,7 @@ async def test_platform_name_err(app: App): for platform_name in common_platform ] ) - + "要查看全部平台请输入:“全部”" + + "要查看全部平台请输入:“全部”\n中止订阅过程请输入:“取消”" ), True, ) diff --git a/tests/test_config_manager_abort.py b/tests/test_config_manager_abort.py new file mode 100644 index 0000000..8e3e196 --- /dev/null +++ b/tests/test_config_manager_abort.py @@ -0,0 +1,314 @@ +import pytest +import respx +from httpx import Response +from nonebug.app import App +from .platforms.utils import get_json +from .utils import fake_admin_user, fake_group_message_event + +#选择platform阶段中止 +@pytest.mark.asyncio +@respx.mock +async def test_abort_add_on_platform(app: App): + from nonebot.adapters.onebot.v11.event import Sender + from nonebot.adapters.onebot.v11.message import Message + from nonebot_bison.config import Config + from nonebot_bison.config_manager import add_sub_matcher, common_platform + from nonebot_bison.platform import platform_manager + + config = Config() + config.user_target.truncate() + + ak_list_router = respx.get( + "https://m.weibo.cn/api/container/getIndex?containerid=1005056279793937" + ) + ak_list_router.mock( + return_value=Response(200, json=get_json("weibo_ak_profile.json")) + ) + ak_list_bad_router = respx.get( + "https://m.weibo.cn/api/container/getIndex?containerid=100505000" + ) + ak_list_bad_router.mock( + return_value=Response(200, json=get_json("weibo_err_profile.json")) + ) + async with app.test_matcher(add_sub_matcher) as ctx: + bot = ctx.create_bot() + event_1 = fake_group_message_event( + message=Message("添加订阅"), + sender=Sender(card="", nickname="test", role="admin"), + to_me=True, + ) + ctx.receive_event(bot, event_1) + ctx.should_pass_rule() + ctx.should_call_send( + event_1, + Message( + "请输入想要订阅的平台,目前支持,请输入冒号左边的名称:\n" + + "".join( + [ + "{}:{}\n".format( + platform_name, platform_manager[platform_name].name + ) + for platform_name in common_platform + ] + ) + + "要查看全部平台请输入:“全部”\n中止订阅过程请输入:“取消”" + ), + True, + ) + event_abort = fake_group_message_event( + message=Message("取消"), sender=Sender(card="", nickname="test", role="admin") + ) + ctx.receive_event(bot, event_abort) + ctx.should_call_send( + event_abort, + "已中止订阅", + True, + ) + ctx.should_finished() +#输入id阶段中止 +@pytest.mark.asyncio +@respx.mock +async def test_abort_add_on_id(app: App): + from nonebot.adapters.onebot.v11.event import Sender + from nonebot.adapters.onebot.v11.message import Message + from nonebot_bison.config import Config + from nonebot_bison.config_manager import add_sub_matcher, common_platform + from nonebot_bison.platform import platform_manager + + config = Config() + config.user_target.truncate() + + ak_list_router = respx.get( + "https://m.weibo.cn/api/container/getIndex?containerid=1005056279793937" + ) + ak_list_router.mock( + return_value=Response(200, json=get_json("weibo_ak_profile.json")) + ) + ak_list_bad_router = respx.get( + "https://m.weibo.cn/api/container/getIndex?containerid=100505000" + ) + ak_list_bad_router.mock( + return_value=Response(200, json=get_json("weibo_err_profile.json")) + ) + async with app.test_matcher(add_sub_matcher) as ctx: + bot = ctx.create_bot() + event_1 = fake_group_message_event( + message=Message("添加订阅"), + sender=Sender(card="", nickname="test", role="admin"), + to_me=True, + ) + ctx.receive_event(bot, event_1) + ctx.should_pass_rule() + ctx.should_call_send( + event_1, + Message( + "请输入想要订阅的平台,目前支持,请输入冒号左边的名称:\n" + + "".join( + [ + "{}:{}\n".format( + platform_name, platform_manager[platform_name].name + ) + for platform_name in common_platform + ] + ) + + "要查看全部平台请输入:“全部”\n中止订阅过程请输入:“取消”" + ), + True, + ) + event_2 = fake_group_message_event( + message=Message("weibo"), sender=fake_admin_user + ) + ctx.receive_event(bot, event_2) + ctx.should_call_send( + event_2, + Message( + "请输入订阅用户的id,详情查阅https://nonebot-bison.vercel.app/usage/#%E6%89%80%E6%94%AF%E6%8C%81%E5%B9%B3%E5%8F%B0%E7%9A%84uid" + ), + True, + ) + event_abort = fake_group_message_event( + message=Message("取消"), sender=Sender(card="", nickname="test", role="admin") + ) + ctx.receive_event(bot, event_abort) + ctx.should_call_send( + event_abort, + "已中止订阅", + True, + ) + ctx.should_finished() + +#输入订阅类别阶段中止 +@pytest.mark.asyncio +@respx.mock +async def test_abort_add_on_cats(app: App): + from nonebot.adapters.onebot.v11.event import Sender + from nonebot.adapters.onebot.v11.message import Message + from nonebot_bison.config import Config + from nonebot_bison.config_manager import add_sub_matcher, common_platform + from nonebot_bison.platform import platform_manager + + config = Config() + config.user_target.truncate() + + ak_list_router = respx.get( + "https://m.weibo.cn/api/container/getIndex?containerid=1005056279793937" + ) + ak_list_router.mock( + return_value=Response(200, json=get_json("weibo_ak_profile.json")) + ) + ak_list_bad_router = respx.get( + "https://m.weibo.cn/api/container/getIndex?containerid=100505000" + ) + ak_list_bad_router.mock( + return_value=Response(200, json=get_json("weibo_err_profile.json")) + ) + async with app.test_matcher(add_sub_matcher) as ctx: + bot = ctx.create_bot() + event_1 = fake_group_message_event( + message=Message("添加订阅"), + sender=Sender(card="", nickname="test", role="admin"), + to_me=True, + ) + ctx.receive_event(bot, event_1) + ctx.should_pass_rule() + ctx.should_call_send( + event_1, + Message( + "请输入想要订阅的平台,目前支持,请输入冒号左边的名称:\n" + + "".join( + [ + "{}:{}\n".format( + platform_name, platform_manager[platform_name].name + ) + for platform_name in common_platform + ] + ) + + "要查看全部平台请输入:“全部”\n中止订阅过程请输入:“取消”" + ), + True, + ) + event_2 = fake_group_message_event( + message=Message("weibo"), sender=fake_admin_user + ) + ctx.receive_event(bot, event_2) + ctx.should_call_send( + event_2, + Message( + "请输入订阅用户的id,详情查阅https://nonebot-bison.vercel.app/usage/#%E6%89%80%E6%94%AF%E6%8C%81%E5%B9%B3%E5%8F%B0%E7%9A%84uid" + ), + True, + ) + event_3 = fake_group_message_event( + message=Message("6279793937"), sender=fake_admin_user + ) + ctx.receive_event(bot, event_3) + ctx.should_call_send( + event_3, + Message( + "请输入要订阅的类别,以空格分隔,支持的类别有:{}".format( + " ".join(list(platform_manager["weibo"].categories.values())) + ) + ), + True, + ) + event_abort = fake_group_message_event( + message=Message("取消"), sender=Sender(card="", nickname="test", role="admin") + ) + ctx.receive_event(bot, event_abort) + ctx.should_call_send( + event_abort, + "已中止订阅", + True, + ) + ctx.should_finished() + + +#输入标签阶段中止 +@pytest.mark.asyncio +@respx.mock +async def test_abort_add_on_tag(app: App): + from nonebot.adapters.onebot.v11.event import Sender + from nonebot.adapters.onebot.v11.message import Message + from nonebot_bison.config import Config + from nonebot_bison.config_manager import add_sub_matcher, common_platform + from nonebot_bison.platform import platform_manager + + config = Config() + config.user_target.truncate() + + ak_list_router = respx.get( + "https://m.weibo.cn/api/container/getIndex?containerid=1005056279793937" + ) + ak_list_router.mock( + return_value=Response(200, json=get_json("weibo_ak_profile.json")) + ) + ak_list_bad_router = respx.get( + "https://m.weibo.cn/api/container/getIndex?containerid=100505000" + ) + ak_list_bad_router.mock( + return_value=Response(200, json=get_json("weibo_err_profile.json")) + ) + async with app.test_matcher(add_sub_matcher) as ctx: + bot = ctx.create_bot() + event_1 = fake_group_message_event( + message=Message("添加订阅"), + sender=Sender(card="", nickname="test", role="admin"), + to_me=True, + ) + ctx.receive_event(bot, event_1) + ctx.should_pass_rule() + ctx.should_call_send( + event_1, + Message( + "请输入想要订阅的平台,目前支持,请输入冒号左边的名称:\n" + + "".join( + [ + "{}:{}\n".format( + platform_name, platform_manager[platform_name].name + ) + for platform_name in common_platform + ] + ) + + "要查看全部平台请输入:“全部”\n中止订阅过程请输入:“取消”" + ), + True, + ) + event_2 = fake_group_message_event( + message=Message("weibo"), sender=fake_admin_user + ) + ctx.receive_event(bot, event_2) + ctx.should_call_send( + event_2, + Message( + "请输入订阅用户的id,详情查阅https://nonebot-bison.vercel.app/usage/#%E6%89%80%E6%94%AF%E6%8C%81%E5%B9%B3%E5%8F%B0%E7%9A%84uid" + ), + True, + ) + event_3 = fake_group_message_event( + message=Message("6279793937"), sender=fake_admin_user + ) + ctx.receive_event(bot, event_3) + ctx.should_call_send( + event_3, + Message( + "请输入要订阅的类别,以空格分隔,支持的类别有:{}".format( + " ".join(list(platform_manager["weibo"].categories.values())) + ) + ), + True, + ) + event_4 = fake_group_message_event( + message=Message("图文 文字"), sender=fake_admin_user + ) + ctx.receive_event(bot, event_4) + ctx.should_call_send(event_4, Message('请输入要订阅的tag,订阅所有tag输入"全部标签"'), True) + event_abort = fake_group_message_event( + message=Message("取消"), sender=Sender(card="", nickname="test", role="admin") + ) + ctx.receive_event(bot, event_abort) + ctx.should_call_send( + event_abort, + "已中止订阅", + True, + ) + ctx.should_finished() From 29a82b80c8db14341d55569d191f324378717ba7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 6 Mar 2022 10:42:15 +0000 Subject: [PATCH 10/42] auto fix by pre-commit hooks --- tests/test_config_manager_abort.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/test_config_manager_abort.py b/tests/test_config_manager_abort.py index 8e3e196..5e9e1c5 100644 --- a/tests/test_config_manager_abort.py +++ b/tests/test_config_manager_abort.py @@ -2,10 +2,12 @@ import pytest import respx from httpx import Response from nonebug.app import App + from .platforms.utils import get_json from .utils import fake_admin_user, fake_group_message_event -#选择platform阶段中止 + +# 选择platform阶段中止 @pytest.mark.asyncio @respx.mock async def test_abort_add_on_platform(app: App): @@ -65,7 +67,9 @@ async def test_abort_add_on_platform(app: App): True, ) ctx.should_finished() -#输入id阶段中止 + + +# 输入id阶段中止 @pytest.mark.asyncio @respx.mock async def test_abort_add_on_id(app: App): @@ -137,7 +141,8 @@ async def test_abort_add_on_id(app: App): ) ctx.should_finished() -#输入订阅类别阶段中止 + +# 输入订阅类别阶段中止 @pytest.mark.asyncio @respx.mock async def test_abort_add_on_cats(app: App): @@ -223,7 +228,7 @@ async def test_abort_add_on_cats(app: App): ctx.should_finished() -#输入标签阶段中止 +# 输入标签阶段中止 @pytest.mark.asyncio @respx.mock async def test_abort_add_on_tag(app: App): From 483e1722afdd05b88ed4c3dd1d3ca2556a50d125 Mon Sep 17 00:00:00 2001 From: Azide <rukuy@qq.com> Date: Mon, 7 Mar 2022 20:00:48 +0800 Subject: [PATCH 11/42] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E7=BE=A4?= =?UTF-8?q?=E5=90=8D=E7=89=87=E4=B8=BA=E7=A9=BA=E6=97=B6=E7=9A=84=E5=90=88?= =?UTF-8?q?=E5=B9=B6=E6=B6=88=E6=81=AF=E6=98=B5=E7=A7=B0=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/nonebot_bison/send.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/nonebot_bison/send.py b/src/plugins/nonebot_bison/send.py index ce720b7..283abb5 100644 --- a/src/plugins/nonebot_bison/send.py +++ b/src/plugins/nonebot_bison/send.py @@ -54,7 +54,7 @@ async def _do_merge_send( forward_msg = generate_forward_msg( msgs=msgs, self_id=group_bot_info["user_id"], - nickname=group_bot_info["card"], + nickname=group_bot_info["card"] if group_bot_info["card"] else group_bot_info["nickname"], ) # 生成合并转发内容 await bot.send_group_forward_msg(group_id=user, messages=forward_msg) except Exception as e_b: # behind_msg_exception From 931dc6f9396863b0f8864a4f98c92968d2170c30 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 7 Mar 2022 12:12:06 +0000 Subject: [PATCH 12/42] auto fix by pre-commit hooks --- src/plugins/nonebot_bison/send.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/plugins/nonebot_bison/send.py b/src/plugins/nonebot_bison/send.py index 283abb5..420e4ac 100644 --- a/src/plugins/nonebot_bison/send.py +++ b/src/plugins/nonebot_bison/send.py @@ -54,7 +54,9 @@ async def _do_merge_send( forward_msg = generate_forward_msg( msgs=msgs, self_id=group_bot_info["user_id"], - nickname=group_bot_info["card"] if group_bot_info["card"] else group_bot_info["nickname"], + nickname=group_bot_info["card"] + if group_bot_info["card"] + else group_bot_info["nickname"], ) # 生成合并转发内容 await bot.send_group_forward_msg(group_id=user, messages=forward_msg) except Exception as e_b: # behind_msg_exception From eddc226a4f80c1489667ae37d436c90bd396ebc3 Mon Sep 17 00:00:00 2001 From: felinae98 <731499577@qq.com> Date: Tue, 8 Mar 2022 22:28:22 +0800 Subject: [PATCH 13/42] adjust code & add test --- src/plugins/nonebot_bison/send.py | 110 +++++++++++----------- tests/test_send.py | 147 +++++++++++++++++++++++++++++- 2 files changed, 198 insertions(+), 59 deletions(-) diff --git a/src/plugins/nonebot_bison/send.py b/src/plugins/nonebot_bison/send.py index 420e4ac..f78472f 100644 --- a/src/plugins/nonebot_bison/send.py +++ b/src/plugins/nonebot_bison/send.py @@ -1,68 +1,36 @@ import time -from email import message from typing import Literal, Union -from nonebot.adapters import Message, MessageSegment from nonebot.adapters.onebot.v11.bot import Bot +from nonebot.adapters.onebot.v11.message import Message, MessageSegment from nonebot.log import logger from .plugin_config import plugin_config -QUEUE = [] # 不开启图片合并转发时使用 +QUEUE: list[ + tuple[ + Bot, + int, + Literal["private", "group", "group-forward"], + Union[str, Message], + int, + ] +] = [] LAST_SEND_TIME = time.time() -def generate_forward_msg(msgs: list, self_id: str, nickname: str): - group_msg = [] - for msg in msgs: - sub_msg = { - "type": "node", - "data": {"name": f"{nickname}", "uin": f"{self_id}", "content": f"{msg}"}, - } - group_msg.append(sub_msg) - - return group_msg - - async def _do_send( - bot: "Bot", user: str, user_type: str, msg: Union[str, Message, MessageSegment] + bot: "Bot", + user: int, + user_type: Literal["group", "private", "group-forward"], + msg: Union[str, Message], ): if user_type == "group": - await bot.call_api("send_group_msg", group_id=user, message=msg) + await bot.send_group_msg(group_id=user, message=msg) elif user_type == "private": - await bot.call_api("send_private_msg", user_id=user, message=msg) - - -async def _do_merge_send( - bot: Bot, user, user_type: Literal["private", "group"], msgs: list -): - if plugin_config.bison_use_pic_merge == 1: - try: - await _do_send(bot, user, user_type, msgs.pop(0)) # 弹出第一条消息,剩下的消息合并 - except Exception as e_f: # first_msg_exception - logger.error("向群{}发送消息序列首消息失败:{}".format(user, repr(e_f))) - else: - logger.info("成功向群{}发送消息序列中的首条消息".format(user)) - try: - if msgs: - if len(msgs) == 1: # 只有一条消息序列就不合并转发 - await _do_send(bot, user, user_type, msgs.pop(0)) - else: - group_bot_info = await bot.get_group_member_info( - group_id=user, user_id=bot.self_id, no_cache=True - ) # 调用api获取群内bot的相关参数 - forward_msg = generate_forward_msg( - msgs=msgs, - self_id=group_bot_info["user_id"], - nickname=group_bot_info["card"] - if group_bot_info["card"] - else group_bot_info["nickname"], - ) # 生成合并转发内容 - await bot.send_group_forward_msg(group_id=user, messages=forward_msg) - except Exception as e_b: # behind_msg_exception - logger.warning("向群{}发送合并图片消息超时或者可能失败:{}\n可能是因为图片太大或者太多".format(user, repr(e_b))) - else: - logger.info("成功向群{}发送合并图片转发消息".format(user)) + await bot.send_private_msg(user_id=user, message=msg) + elif user_type == "group-forward": + await bot.send_group_forward_msg(group_id=user, messages=msg) async def do_send_msgs(): @@ -84,14 +52,40 @@ async def do_send_msgs(): LAST_SEND_TIME = time.time() -async def send_msgs(bot: Bot, user, user_type: Literal["private", "group"], msgs: list): +async def _send_msgs_dispatch( + bot: Bot, + user, + user_type: Literal["private", "group", "group-forward"], + msg: Union[str, Message], +): if plugin_config.bison_use_queue: - if plugin_config.bison_use_pic_merge and user_type == "group": - await _do_merge_send(bot, user, user_type, msgs) - else: - for msg in msgs: - QUEUE.append((bot, user, user_type, msg, 2)) - + QUEUE.append((bot, user, user_type, msg, 2)) else: + await _do_send(bot, user, user_type, msg) + + +async def send_msgs(bot: Bot, user, user_type: Literal["private", "group"], msgs: list): + if not plugin_config.bison_use_pic_merge or user_type == "private": for msg in msgs: - await _do_send(bot, user, user_type, msg) + await _send_msgs_dispatch(bot, user, user_type, msg) + return + if plugin_config.bison_use_pic_merge == 1: + await _send_msgs_dispatch(bot, user, "group", msgs.pop(0)) + if msgs: + if len(msgs) == 1: # 只有一条消息序列就不合并转发 + await _send_msgs_dispatch(bot, user, "group", msgs.pop(0)) + else: + group_bot_info = await bot.get_group_member_info( + group_id=user, user_id=int(bot.self_id), no_cache=True + ) # 调用api获取群内bot的相关参数 + forward_msg = Message( + [ + MessageSegment.node_custom( + group_bot_info["user_id"], + nickname=group_bot_info["card"] or group_bot_info["nickname"], + content=msg, + ) + for msg in msgs + ] + ) + await _send_msgs_dispatch(bot, user, "group-forward", forward_msg) diff --git a/tests/test_send.py b/tests/test_send.py index 288e77f..7e0ff37 100644 --- a/tests/test_send.py +++ b/tests/test_send.py @@ -1,13 +1,13 @@ import asyncio import pytest -from nonebot.adapters.onebot.v11.bot import Bot from nonebug import App @pytest.mark.asyncio async def test_send_no_queue(app: App): import nonebot + from nonebot.adapters.onebot.v11.bot import Bot from nonebot_bison.plugin_config import plugin_config from nonebot_bison.send import send_msgs @@ -32,6 +32,7 @@ async def test_send_no_queue(app: App): @pytest.mark.asyncio async def test_send_queue(app: App): import nonebot + from nonebot.adapters.onebot.v11.bot import Bot from nonebot_bison import send from nonebot_bison.plugin_config import plugin_config from nonebot_bison.send import LAST_SEND_TIME, do_send_msgs, send_msgs @@ -56,3 +57,147 @@ async def test_send_queue(app: App): app.monkeypatch.setattr(send, "LAST_SEND_TIME", 0, True) await do_send_msgs() assert ctx.wait_list.empty() + + +@pytest.mark.asyncio +async def test_send_merge_no_queue(app: App): + from nonebot.adapters.onebot.v11.bot import Bot + from nonebot.adapters.onebot.v11.message import Message, MessageSegment + from nonebot_bison.plugin_config import plugin_config + from nonebot_bison.send import send_msgs + + plugin_config.bison_use_pic_merge = 1 + plugin_config.bison_use_queue = False + + async with app.test_api() as ctx: + bot = ctx.create_bot(base=Bot, self_id="8888") + assert isinstance(bot, Bot) + message = [ + Message(MessageSegment.text("test msg")), + Message(MessageSegment.image("https://picsum.photos/200/300")), + Message(MessageSegment.image("https://picsum.photos/200/300")), + ] + ctx.should_call_api( + "send_group_msg", + {"group_id": 633, "message": Message(MessageSegment.text("test msg"))}, + None, + ) + ctx.should_call_api( + "get_group_member_info", + {"group_id": 633, "user_id": 8888, "no_cache": True}, + {"user_id": 8888, "card": "admin", "nickname": "adminuser"}, + ) + merged_message = Message( + [ + MessageSegment.node_custom( + user_id=8888, nickname="admin", content=message[1] + ), + MessageSegment.node_custom( + user_id=8888, nickname="admin", content=message[2] + ), + ] + ) + ctx.should_call_api( + "send_group_forward_msg", + {"group_id": 633, "messages": merged_message}, + None, + ) + await send_msgs(bot, 633, "group", message) + + message = [ + Message(MessageSegment.text("test msg")), + Message(MessageSegment.image("https://picsum.photos/200/300")), + Message(MessageSegment.image("https://picsum.photos/200/300")), + Message(MessageSegment.image("https://picsum.photos/200/300")), + ] + ctx.should_call_api( + "send_group_msg", + {"group_id": 633, "message": Message(MessageSegment.text("test msg"))}, + None, + ) + ctx.should_call_api( + "get_group_member_info", + {"group_id": 633, "user_id": 8888, "no_cache": True}, + {"user_id": 8888, "card": None, "nickname": "adminuser"}, + ) + merged_message = Message( + [ + MessageSegment.node_custom( + user_id=8888, nickname="adminuser", content=message[1] + ), + MessageSegment.node_custom( + user_id=8888, nickname="adminuser", content=message[2] + ), + MessageSegment.node_custom( + user_id=8888, nickname="adminuser", content=message[3] + ), + ] + ) + ctx.should_call_api( + "send_group_forward_msg", + {"group_id": 633, "messages": merged_message}, + None, + ) + await send_msgs(bot, 633, "group", message) + + # private user should not send in forward + message = [ + Message(MessageSegment.text("test msg")), + Message(MessageSegment.image("https://picsum.photos/200/300")), + Message(MessageSegment.image("https://picsum.photos/200/300")), + ] + ctx.should_call_api( + "send_private_msg", + {"user_id": 633, "message": Message(MessageSegment.text("test msg"))}, + None, + ) + ctx.should_call_api( + "send_private_msg", {"user_id": 633, "message": message[1]}, None + ) + ctx.should_call_api( + "send_private_msg", {"user_id": 633, "message": message[2]}, None + ) + await send_msgs(bot, 633, "private", message) + + +async def test_send_merge2_no_queue(app: App): + from nonebot.adapters.onebot.v11.bot import Bot + from nonebot.adapters.onebot.v11.message import Message, MessageSegment + from nonebot_bison.plugin_config import plugin_config + from nonebot_bison.send import send_msgs + + plugin_config.bison_use_pic_merge = 2 + plugin_config.bison_use_queue = False + + async with app.test_api() as ctx: + bot = ctx.create_bot(base=Bot, self_id="8888") + assert isinstance(bot, Bot) + message = [ + Message(MessageSegment.text("test msg")), + Message(MessageSegment.image("https://picsum.photos/200/300")), + Message(MessageSegment.image("https://picsum.photos/200/300")), + ] + ctx.should_call_api( + "get_group_member_info", + {"group_id": 633, "user_id": 8888, "no_cache": True}, + {"user_id": 8888, "card": "admin", "nickname": "adminuser"}, + ) + merged_message = Message( + [ + MessageSegment.node_custom( + user_id=8888, nickname="admin", content=message[0] + ), + MessageSegment.node_custom( + user_id=8888, nickname="admin", content=message[1] + ), + MessageSegment.node_custom( + user_id=8888, nickname="admin", content=message[2] + ), + ] + ) + ctx.should_call_api( + "send_group_forward_msg", + {"group_id": 633, "messages": merged_message}, + None, + ) + await send_msgs(bot, 633, "group", message) From 24bf9de208e8cfee173193e07556456414d57279 Mon Sep 17 00:00:00 2001 From: felinae98 <731499577@qq.com> Date: Tue, 8 Mar 2022 22:34:19 +0800 Subject: [PATCH 14/42] add more test --- tests/test_send.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_send.py b/tests/test_send.py index 7e0ff37..dd2c089 100644 --- a/tests/test_send.py +++ b/tests/test_send.py @@ -72,6 +72,22 @@ async def test_send_merge_no_queue(app: App): async with app.test_api() as ctx: bot = ctx.create_bot(base=Bot, self_id="8888") assert isinstance(bot, Bot) + message = [ + Message(MessageSegment.text("test msg")), + Message(MessageSegment.image("https://picsum.photos/200/300")), + ] + ctx.should_call_api( + "send_group_msg", + {"group_id": 633, "message": Message(MessageSegment.text("test msg"))}, + None, + ) + ctx.should_call_api( + "send_group_msg", + {"group_id": 633, "message": message[1]}, + None, + ) + await send_msgs(bot, 633, "group", message) + message = [ Message(MessageSegment.text("test msg")), Message(MessageSegment.image("https://picsum.photos/200/300")), From e27c1bc3532e21263c6c03ae671b112ac9995bc1 Mon Sep 17 00:00:00 2001 From: Azide <rukuy@qq.com> Date: Thu, 10 Mar 2022 23:42:01 +0800 Subject: [PATCH 15/42] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E5=85=B3?= =?UTF-8?q?=E4=BA=8EBISON=5FUSE=5FPIC=5FMERGE=E9=85=8D=E7=BD=AE=E9=A1=B9?= =?UTF-8?q?=E7=9A=84=E7=9B=B8=E5=85=B3=E6=96=87=E6=A1=A3=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/usage/README.md | 10 ++++++++++ docs/usage/pic/forward-msg-simple1.png | Bin 0 -> 36821 bytes docs/usage/pic/forward-msg-simple2.png | Bin 0 -> 11817 bytes 3 files changed, 10 insertions(+) create mode 100644 docs/usage/pic/forward-msg-simple1.png create mode 100644 docs/usage/pic/forward-msg-simple2.png diff --git a/docs/usage/README.md b/docs/usage/README.md index ed88c54..b88299f 100644 --- a/docs/usage/README.md +++ b/docs/usage/README.md @@ -115,6 +115,16 @@ sidebar: auto 开启,默认关 - `BISON_USE_QUEUE`: 是否用队列的方式发送消息,降低发送频率,默认开 - `BISON_RESEND_TIMES`: 最大重发次数,默认 0 +- `BISON_USE_PIC_MERGE`: 是否启用多图片时合并转发(仅限群) + - `0`: 不启用(默认) + - `1`: 首条消息单独发送,剩余图片合并转发 + - `2`: 所有消息全部合并转发 + ::: details 配置项示例 + - 当`BISON_USE_PIC_MERGE=1`时: +  + - 当`BISON_USE_PIC_MERGE=2`时: +  + ::: ## 使用 diff --git a/docs/usage/pic/forward-msg-simple1.png b/docs/usage/pic/forward-msg-simple1.png new file mode 100644 index 0000000000000000000000000000000000000000..c10e60fa3a96384da580e673f063495a3e984e05 GIT binary patch literal 36821 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 Z=U*Uq<BP(QccKhH;OXk;vd$@?2>_Zq#fks` literal 0 HcmV?d00001 diff --git a/docs/usage/pic/forward-msg-simple2.png b/docs/usage/pic/forward-msg-simple2.png new file mode 100644 index 0000000000000000000000000000000000000000..a3f807b031a61f8fe7f5f9e554e77eaf06d3d96e GIT binary patch literal 11817 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 PALx;~j#`<DRp@^KvuX?| literal 0 HcmV?d00001 From 196b2389f6dd64d53fe81fb003fe9ba7fbd0e693 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 10 Mar 2022 15:47:26 +0000 Subject: [PATCH 16/42] auto fix by pre-commit hooks --- docs/usage/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/usage/README.md b/docs/usage/README.md index b88299f..0ac89ff 100644 --- a/docs/usage/README.md +++ b/docs/usage/README.md @@ -119,12 +119,12 @@ sidebar: auto - `0`: 不启用(默认) - `1`: 首条消息单独发送,剩余图片合并转发 - `2`: 所有消息全部合并转发 - ::: details 配置项示例 + ::: details 配置项示例 - 当`BISON_USE_PIC_MERGE=1`时: -  +  - 当`BISON_USE_PIC_MERGE=2`时: -  - ::: +  + ::: ## 使用 From 897e8f3f2cf727b7eaf34331a63c36a2e57d42d5 Mon Sep 17 00:00:00 2001 From: Azide <rukuy@qq.com> Date: Fri, 11 Mar 2022 00:18:50 +0800 Subject: [PATCH 17/42] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E7=BC=A9=E8=BF=9B=E6=96=B9=E5=BC=8F=EF=BC=8C=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?bison-use-pic-merge=E9=85=8D=E7=BD=AE=E9=A1=B9=E8=AD=A6?= =?UTF-8?q?=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../.temp/internal/clientAppEnhances.js | 11 ++ .../.temp/internal/clientAppRootComponents.js | 5 + .../.temp/internal/clientAppSetups.js | 9 + .../.temp/internal/layoutComponents.js | 6 + .../.temp/internal/pagesComponents.js | 12 ++ docs/.vuepress/.temp/internal/pagesData.js | 10 ++ docs/.vuepress/.temp/internal/pagesRoutes.js | 33 ++++ docs/.vuepress/.temp/internal/siteData.js | 21 +++ docs/.vuepress/.temp/internal/themeData.js | 62 +++++++ docs/.vuepress/.temp/pages/404.html.js | 26 +++ docs/.vuepress/.temp/pages/404.html.vue | 1 + docs/.vuepress/.temp/pages/dev/index.html.js | 65 +++++++ docs/.vuepress/.temp/pages/dev/index.html.vue | 78 +++++++++ docs/.vuepress/.temp/pages/index.html.js | 59 +++++++ docs/.vuepress/.temp/pages/index.html.vue | 1 + .../.vuepress/.temp/pages/usage/index.html.js | 101 +++++++++++ .../.temp/pages/usage/index.html.vue | 162 ++++++++++++++++++ docs/.vuepress/.temp/styles/index.scss | 0 docs/.vuepress/.temp/styles/palette.scss | 0 docs/.vuepress/.temp/vite-root/index.html | 13 ++ docs/usage/README.md | 7 +- 21 files changed, 680 insertions(+), 2 deletions(-) create mode 100644 docs/.vuepress/.temp/internal/clientAppEnhances.js create mode 100644 docs/.vuepress/.temp/internal/clientAppRootComponents.js create mode 100644 docs/.vuepress/.temp/internal/clientAppSetups.js create mode 100644 docs/.vuepress/.temp/internal/layoutComponents.js create mode 100644 docs/.vuepress/.temp/internal/pagesComponents.js create mode 100644 docs/.vuepress/.temp/internal/pagesData.js create mode 100644 docs/.vuepress/.temp/internal/pagesRoutes.js create mode 100644 docs/.vuepress/.temp/internal/siteData.js create mode 100644 docs/.vuepress/.temp/internal/themeData.js create mode 100644 docs/.vuepress/.temp/pages/404.html.js create mode 100644 docs/.vuepress/.temp/pages/404.html.vue create mode 100644 docs/.vuepress/.temp/pages/dev/index.html.js create mode 100644 docs/.vuepress/.temp/pages/dev/index.html.vue create mode 100644 docs/.vuepress/.temp/pages/index.html.js create mode 100644 docs/.vuepress/.temp/pages/index.html.vue create mode 100644 docs/.vuepress/.temp/pages/usage/index.html.js create mode 100644 docs/.vuepress/.temp/pages/usage/index.html.vue create mode 100644 docs/.vuepress/.temp/styles/index.scss create mode 100644 docs/.vuepress/.temp/styles/palette.scss create mode 100644 docs/.vuepress/.temp/vite-root/index.html diff --git a/docs/.vuepress/.temp/internal/clientAppEnhances.js b/docs/.vuepress/.temp/internal/clientAppEnhances.js new file mode 100644 index 0000000..9ce5486 --- /dev/null +++ b/docs/.vuepress/.temp/internal/clientAppEnhances.js @@ -0,0 +1,11 @@ +import clientAppEnhance0 from 'E:/Azide/nonebot/nonebot-bison/node_modules/@vuepress/plugin-external-link-icon/lib/client/clientAppEnhance.js' +import clientAppEnhance1 from 'E:/Azide/nonebot/nonebot-bison/node_modules/@vuepress/plugin-medium-zoom/lib/client/clientAppEnhance.js' +import clientAppEnhance2 from 'E:/Azide/nonebot/nonebot-bison/node_modules/@vuepress/plugin-theme-data/lib/client/clientAppEnhance.js' +import clientAppEnhance3 from 'E:/Azide/nonebot/nonebot-bison/node_modules/@vuepress/theme-default/lib/client/clientAppEnhance.js' + +export const clientAppEnhances = [ + clientAppEnhance0, + clientAppEnhance1, + clientAppEnhance2, + clientAppEnhance3, +] diff --git a/docs/.vuepress/.temp/internal/clientAppRootComponents.js b/docs/.vuepress/.temp/internal/clientAppRootComponents.js new file mode 100644 index 0000000..5853e99 --- /dev/null +++ b/docs/.vuepress/.temp/internal/clientAppRootComponents.js @@ -0,0 +1,5 @@ +import clientAppRootComponent0 from 'E:/Azide/nonebot/nonebot-bison/node_modules/@vuepress/plugin-back-to-top/lib/client/components/BackToTop.js' + +export const clientAppRootComponents = [ + clientAppRootComponent0, +] diff --git a/docs/.vuepress/.temp/internal/clientAppSetups.js b/docs/.vuepress/.temp/internal/clientAppSetups.js new file mode 100644 index 0000000..45f3b50 --- /dev/null +++ b/docs/.vuepress/.temp/internal/clientAppSetups.js @@ -0,0 +1,9 @@ +import clientAppSetup0 from 'E:/Azide/nonebot/nonebot-bison/node_modules/@vuepress/plugin-active-header-links/lib/client/clientAppSetup.js' +import clientAppSetup1 from 'E:/Azide/nonebot/nonebot-bison/node_modules/@vuepress/plugin-nprogress/lib/client/clientAppSetup.js' +import clientAppSetup2 from 'E:/Azide/nonebot/nonebot-bison/node_modules/@vuepress/theme-default/lib/client/clientAppSetup.js' + +export const clientAppSetups = [ + clientAppSetup0, + clientAppSetup1, + clientAppSetup2, +] diff --git a/docs/.vuepress/.temp/internal/layoutComponents.js b/docs/.vuepress/.temp/internal/layoutComponents.js new file mode 100644 index 0000000..2abe096 --- /dev/null +++ b/docs/.vuepress/.temp/internal/layoutComponents.js @@ -0,0 +1,6 @@ +import { defineAsyncComponent } from 'vue' + +export const layoutComponents = { + "404": defineAsyncComponent(() => import("E:/Azide/nonebot/nonebot-bison/node_modules/@vuepress/theme-default/lib/client/layouts/404.vue")), + "Layout": defineAsyncComponent(() => import("E:/Azide/nonebot/nonebot-bison/node_modules/@vuepress/theme-default/lib/client/layouts/Layout.vue")), +} diff --git a/docs/.vuepress/.temp/internal/pagesComponents.js b/docs/.vuepress/.temp/internal/pagesComponents.js new file mode 100644 index 0000000..e16bc2d --- /dev/null +++ b/docs/.vuepress/.temp/internal/pagesComponents.js @@ -0,0 +1,12 @@ +import { defineAsyncComponent } from 'vue' + +export const pagesComponents = { + // path: / + "v-8daa1a0e": defineAsyncComponent(() => import(/* webpackChunkName: "v-8daa1a0e" */"E:/Azide/nonebot/nonebot-bison/docs/.vuepress/.temp/pages/index.html.vue")), + // path: /dev/ + "v-7445cd33": defineAsyncComponent(() => import(/* webpackChunkName: "v-7445cd33" */"E:/Azide/nonebot/nonebot-bison/docs/.vuepress/.temp/pages/dev/index.html.vue")), + // path: /usage/ + "v-d0736a32": defineAsyncComponent(() => import(/* webpackChunkName: "v-d0736a32" */"E:/Azide/nonebot/nonebot-bison/docs/.vuepress/.temp/pages/usage/index.html.vue")), + // path: /404.html + "v-3706649a": defineAsyncComponent(() => import(/* webpackChunkName: "v-3706649a" */"E:/Azide/nonebot/nonebot-bison/docs/.vuepress/.temp/pages/404.html.vue")), +} diff --git a/docs/.vuepress/.temp/internal/pagesData.js b/docs/.vuepress/.temp/internal/pagesData.js new file mode 100644 index 0000000..1c59595 --- /dev/null +++ b/docs/.vuepress/.temp/internal/pagesData.js @@ -0,0 +1,10 @@ +export const pagesData = { + // path: / + "v-8daa1a0e": () => import(/* webpackChunkName: "v-8daa1a0e" */"E:/Azide/nonebot/nonebot-bison/docs/.vuepress/.temp/pages/index.html.js").then(({ data }) => data), + // path: /dev/ + "v-7445cd33": () => import(/* webpackChunkName: "v-7445cd33" */"E:/Azide/nonebot/nonebot-bison/docs/.vuepress/.temp/pages/dev/index.html.js").then(({ data }) => data), + // path: /usage/ + "v-d0736a32": () => import(/* webpackChunkName: "v-d0736a32" */"E:/Azide/nonebot/nonebot-bison/docs/.vuepress/.temp/pages/usage/index.html.js").then(({ data }) => data), + // path: /404.html + "v-3706649a": () => import(/* webpackChunkName: "v-3706649a" */"E:/Azide/nonebot/nonebot-bison/docs/.vuepress/.temp/pages/404.html.js").then(({ data }) => data), +} diff --git a/docs/.vuepress/.temp/internal/pagesRoutes.js b/docs/.vuepress/.temp/internal/pagesRoutes.js new file mode 100644 index 0000000..9fb3e2b --- /dev/null +++ b/docs/.vuepress/.temp/internal/pagesRoutes.js @@ -0,0 +1,33 @@ +import { Vuepress } from '@vuepress/client/lib/components/Vuepress' + +const routeItems = [ + ["v-8daa1a0e","/",{"title":""},["/index.html","/README.md"]], + ["v-7445cd33","/dev/",{"title":"开发指南"},["/dev/index.html","/dev/README.md"]], + ["v-d0736a32","/usage/",{"title":"部署和使用"},["/usage/index.html","/usage/README.md"]], + ["v-3706649a","/404.html",{"title":""},["/404"]], +] + +export const pagesRoutes = routeItems.reduce( + (result, [name, path, meta, redirects]) => { + result.push( + { + name, + path, + component: Vuepress, + meta, + }, + ...redirects.map((item) => ({ + path: item, + redirect: path, + })) + ) + return result + }, + [ + { + name: "404", + path: "/:catchAll(.*)", + component: Vuepress, + } + ] +) diff --git a/docs/.vuepress/.temp/internal/siteData.js b/docs/.vuepress/.temp/internal/siteData.js new file mode 100644 index 0000000..538a815 --- /dev/null +++ b/docs/.vuepress/.temp/internal/siteData.js @@ -0,0 +1,21 @@ +export const siteData = { + "base": "/", + "lang": "en-US", + "title": "Nonebot Bison", + "description": "Docs for Nonebot Bison", + "head": [], + "locales": {} +} + +if (import.meta.webpackHot) { + import.meta.webpackHot.accept() + if (__VUE_HMR_RUNTIME__.updateSiteData) { + __VUE_HMR_RUNTIME__.updateSiteData(siteData) + } +} + +if (import.meta.hot) { + import.meta.hot.accept(({ siteData }) => { + __VUE_HMR_RUNTIME__.updateSiteData(siteData) + }) +} diff --git a/docs/.vuepress/.temp/internal/themeData.js b/docs/.vuepress/.temp/internal/themeData.js new file mode 100644 index 0000000..7c257d1 --- /dev/null +++ b/docs/.vuepress/.temp/internal/themeData.js @@ -0,0 +1,62 @@ +export const themeData = { + "nav": [ + { + "text": "主页", + "link": "/" + }, + { + "text": "部署与使用", + "link": "/usage/" + }, + { + "text": "开发", + "link": "/dev/" + }, + { + "text": "Github", + "link": "https://github.com/felinae98/nonebot-bison" + } + ], + "locales": { + "/": { + "selectLanguageName": "English" + } + }, + "navbar": [], + "logo": null, + "darkMode": true, + "repo": null, + "selectLanguageText": "Languages", + "selectLanguageAriaLabel": "Select language", + "sidebar": "auto", + "sidebarDepth": 2, + "editLink": true, + "editLinkText": "Edit this page", + "lastUpdated": true, + "lastUpdatedText": "Last Updated", + "contributors": true, + "contributorsText": "Contributors", + "notFound": [ + "There's nothing here.", + "How did we get here?", + "That's a Four-Oh-Four.", + "Looks like we've got some broken links." + ], + "backToHome": "Take me home", + "openInNewWindow": "open in new window", + "toggleDarkMode": "toggle dark mode", + "toggleSidebar": "toggle sidebar" +} + +if (import.meta.webpackHot) { + import.meta.webpackHot.accept() + if (__VUE_HMR_RUNTIME__.updateThemeData) { + __VUE_HMR_RUNTIME__.updateThemeData(themeData) + } +} + +if (import.meta.hot) { + import.meta.hot.accept(({ themeData }) => { + __VUE_HMR_RUNTIME__.updateThemeData(themeData) + }) +} diff --git a/docs/.vuepress/.temp/pages/404.html.js b/docs/.vuepress/.temp/pages/404.html.js new file mode 100644 index 0000000..09dc53f --- /dev/null +++ b/docs/.vuepress/.temp/pages/404.html.js @@ -0,0 +1,26 @@ +export const data = { + "key": "v-3706649a", + "path": "/404.html", + "title": "", + "lang": "en-US", + "frontmatter": { + "layout": "404" + }, + "excerpt": "", + "headers": [], + "git": {}, + "filePathRelative": null +} + +if (import.meta.webpackHot) { + import.meta.webpackHot.accept() + if (__VUE_HMR_RUNTIME__.updatePageData) { + __VUE_HMR_RUNTIME__.updatePageData(data) + } +} + +if (import.meta.hot) { + import.meta.hot.accept(({ data }) => { + __VUE_HMR_RUNTIME__.updatePageData(data) + }) +} diff --git a/docs/.vuepress/.temp/pages/404.html.vue b/docs/.vuepress/.temp/pages/404.html.vue new file mode 100644 index 0000000..cc340bc --- /dev/null +++ b/docs/.vuepress/.temp/pages/404.html.vue @@ -0,0 +1 @@ +<template></template> diff --git a/docs/.vuepress/.temp/pages/dev/index.html.js b/docs/.vuepress/.temp/pages/dev/index.html.js new file mode 100644 index 0000000..4281b8b --- /dev/null +++ b/docs/.vuepress/.temp/pages/dev/index.html.js @@ -0,0 +1,65 @@ +export const data = { + "key": "v-7445cd33", + "path": "/dev/", + "title": "开发指南", + "lang": "en-US", + "frontmatter": { + "sidebar": "auto" + }, + "excerpt": "", + "headers": [ + { + "level": 2, + "title": "基本概念", + "slug": "基本概念", + "children": [] + }, + { + "level": 2, + "title": "快速上手", + "slug": "快速上手", + "children": [] + }, + { + "level": 2, + "title": "类的方法与成员变量", + "slug": "类的方法与成员变量", + "children": [] + }, + { + "level": 2, + "title": "方法与变量的定义", + "slug": "方法与变量的定义", + "children": [] + } + ], + "git": { + "updatedTime": 1645018969000, + "contributors": [ + { + "name": "felinae98", + "email": "731499577@qq.com", + "commits": 4 + }, + { + "name": "hemengyang", + "email": "hmy0119@gmail.com", + "commits": 1 + } + ] + }, + "filePathRelative": "dev/README.md" +} + +if (import.meta.webpackHot) { + import.meta.webpackHot.accept() + if (__VUE_HMR_RUNTIME__.updatePageData) { + __VUE_HMR_RUNTIME__.updatePageData(data) + } +} + +if (import.meta.hot) { + import.meta.hot.accept(({ data }) => { + __VUE_HMR_RUNTIME__.updatePageData(data) + }) +} diff --git a/docs/.vuepress/.temp/pages/dev/index.html.vue b/docs/.vuepress/.temp/pages/dev/index.html.vue new file mode 100644 index 0000000..8d6f9e2 --- /dev/null +++ b/docs/.vuepress/.temp/pages/dev/index.html.vue @@ -0,0 +1,78 @@ +<template><h1 id="开发指南" tabindex="-1"><a class="header-anchor" href="#开发指南" aria-hidden="true">#</a> 开发指南</h1> +<p>本插件需要你的帮助!只需要会写简单的爬虫,就能给本插件适配新的网站。</p> +<h2 id="基本概念" tabindex="-1"><a class="header-anchor" href="#基本概念" aria-hidden="true">#</a> 基本概念</h2> +<ul> +<li><code>nonebot_bison.post.Post</code>: 可以理解为推送内容,其中包含需要发送的文字,图片,链接,平台信息等</li> +<li><code>nonebot_bison.types.RawPost</code>: 从站点/平台中爬到的单条信息</li> +<li><code>nonebot_bison.types.Target</code>: 目标账号,Bilibili,微博等社交媒体中的账号</li> +<li><code>nonebot_bison.types.Category</code>: 信息分类,例如视频,动态,图文,文章等</li> +<li><code>nonebot_bison.types.Tag</code>: 信息标签,例如微博中的超话或者 hashtag</li> +</ul> +<h2 id="快速上手" tabindex="-1"><a class="header-anchor" href="#快速上手" aria-hidden="true">#</a> 快速上手</h2> +<p>上车!我们走</p> +<p>先明确需要适配的站点类型,先明确两个问题:</p> +<h4 id="我要发送什么样的推送" tabindex="-1"><a class="header-anchor" href="#我要发送什么样的推送" aria-hidden="true">#</a> 我要发送什么样的推送</h4> +<ul> +<li><code>nonebot_bison.platform.platform.NewMessage</code> 最常见的类型,每次爬虫向特定接口爬取一个消息列表, +与之前爬取的信息对比,过滤出新的消息,再根据用户自定义的分类和标签进行过滤,最后处理消息,把 +处理过后的消息发送给用户<br> +例如:微博,Bilibili</li> +<li><code>nonebot_bison.platform.platform.StatusChange</code> 每次爬虫获取一个状态,在状态改变时发布推送<br> +例如:游戏开服提醒,主播上播提醒</li> +<li><code>nonebot_bison.platform.platform.SimplePost</code> 与<code>NewMessage</code>相似,但是不过滤新的消息 +,每次发送全部消息<br> +例如:每日榜单定时发送</li> +</ul> +<h4 id="这个平台是否有账号的概念" tabindex="-1"><a class="header-anchor" href="#这个平台是否有账号的概念" aria-hidden="true">#</a> 这个平台是否有账号的概念</h4> +<ul> +<li>有账号的概念<br> +例如:B 站用户动态,微博用户动态,网易云电台更新</li> +<li>没有账号的概念<br> +例如:游戏公告,教务处公告</li> +</ul> +<p>现在你需要在<code>src/plugins/nonebot_bison/platform</code>下新建一个 py 文件, +在里面新建一个类,继承推送类型的基类,重载一些关键的函数,然后……就完成了,不需要修改别的东西了。</p> +<p>任何一种订阅类型需要实现的方法/字段如下:</p> +<ul> +<li><code>schedule_type</code>, <code>schedule_kw</code> 调度的参数,本质是使用 apscheduler 的<a href="https://apscheduler.readthedocs.io/en/3.x/userguide.html?highlight=trigger#choosing-the-right-scheduler-job-store-s-executor-s-and-trigger-s" target="_blank" rel="noopener noreferrer">trigger 参数<ExternalLinkIcon/></a>,<code>schedule_type</code>可以是<code>date</code>,<code>interval</code>和<code>cron</code>, +<code>schedule_kw</code>是对应的参数,一个常见的配置是<code>schedule_type=interval</code>, <code>schedule_kw={'seconds':30}</code></li> +<li><code>is_common</code> 是否常用,如果被标记为常用,那么和机器人交互式对话添加订阅时,会直接出现在选择列表中,否则 +需要输入<code>全部</code>才会出现。</li> +<li><code>enabled</code> 是否启用</li> +<li><code>name</code> 平台的正式名称,例如<code>微博</code></li> +<li><code>has_target</code> 平台是否有“帐号”</li> +<li><code>category</code> 平台的发布内容分类,例如 B 站包括专栏,视频,图文动态,普通动态等,如果不包含分类功能则设为<code>{}</code></li> +<li><code>enable_tag</code> 平台发布内容是否带 Tag,例如微博</li> +<li><code>platform_name</code> 唯一的,英文的识别标识,比如<code>weibo</code></li> +<li><code>async get_target_name(Target) -> Optional[str]</code> 通常用于获取帐号的名称,如果平台没有帐号概念,可以直接返回平台的<code>name</code></li> +<li><code>async parse(RawPost) -> Post</code>将获取到的 RawPost 处理成 Post</li> +<li><code>get_tags(RawPost) -> Optional[Collection[Tag]]</code> (可选) 从 RawPost 中提取 Tag</li> +<li><code>get_category(RawPos) -> Optional[Category]</code> (可选)从 RawPost 中提取 Category</li> +</ul> +<p>例如要适配微博,我希望 bot 搬运新的消息,所以微博的类应该这样定义:</p> +<div class="language-python ext-py line-numbers-mode"><pre v-pre class="language-python"><code><span class="token keyword">class</span> <span class="token class-name">Weibo</span><span class="token punctuation">(</span>NewMessage<span class="token punctuation">)</span><span class="token punctuation">:</span> + + categories <span class="token operator">=</span> <span class="token punctuation">{</span> + <span class="token number">1</span><span class="token punctuation">:</span> <span class="token string">"转发"</span><span class="token punctuation">,</span> + <span class="token number">2</span><span class="token punctuation">:</span> <span class="token string">"视频"</span><span class="token punctuation">,</span> + <span class="token number">3</span><span class="token punctuation">:</span> <span class="token string">"图文"</span><span class="token punctuation">,</span> + <span class="token number">4</span><span class="token punctuation">:</span> <span class="token string">"文字"</span><span class="token punctuation">,</span> + <span class="token punctuation">}</span> + enable_tag <span class="token operator">=</span> <span class="token boolean">True</span> + platform_name <span class="token operator">=</span> <span class="token string">"weibo"</span> + name <span class="token operator">=</span> <span class="token string">"新浪微博"</span> + enabled <span class="token operator">=</span> <span class="token boolean">True</span> + is_common <span class="token operator">=</span> <span class="token boolean">True</span> + schedule_type <span class="token operator">=</span> <span class="token string">"interval"</span> + schedule_kw <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token string">"seconds"</span><span class="token punctuation">:</span> <span class="token number">3</span><span class="token punctuation">}</span> + has_target <span class="token operator">=</span> <span class="token boolean">True</span> +</code></pre><div class="line-numbers" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br></div></div><p>当然我们非常希望你对自己适配的平台写一些单元测试,你可以模仿<code>tests/platforms/test_*.py</code>中的内容写 +一些单元测试。为保证多次运行测试的一致性,可以 mock http 的响应,测试的内容包括获取 RawPost,处理成 Post +,测试分类以及提取 tag 等,当然最好和 rsshub 做一个交叉验证。</p> +<div class="custom-container danger"><p class="custom-container-title">DANGER</p> +<p>Nonebot 项目使用了全异步的处理方式,所以你需要对异步,Python asyncio 的机制有一定了解,当然, +依葫芦画瓢也是足够的</p> +</div> +<h2 id="类的方法与成员变量" tabindex="-1"><a class="header-anchor" href="#类的方法与成员变量" aria-hidden="true">#</a> 类的方法与成员变量</h2> +<h2 id="方法与变量的定义" tabindex="-1"><a class="header-anchor" href="#方法与变量的定义" aria-hidden="true">#</a> 方法与变量的定义</h2> +</template> diff --git a/docs/.vuepress/.temp/pages/index.html.js b/docs/.vuepress/.temp/pages/index.html.js new file mode 100644 index 0000000..badb334 --- /dev/null +++ b/docs/.vuepress/.temp/pages/index.html.js @@ -0,0 +1,59 @@ +export const data = { + "key": "v-8daa1a0e", + "path": "/", + "title": "", + "lang": "en-US", + "frontmatter": { + "home": true, + "heroText": "Nonebot Bison", + "tagline": "本bot励志做全泰拉骑车最快的信使", + "actionText": "快速部署", + "actionLink": "/usage/", + "features": [ + { + "title": "拓展性强", + "details": "没有自己想要的网站?只要简单的爬虫知识就可以给它适配一个新的网站" + }, + { + "title": "通用,强大", + "details": "社交媒体?网站更新?游戏开服?只要能爬就都能推,还支持自定义过滤" + }, + { + "title": "后台管理", + "details": "提供后台管理页面,简单快捷修改配置" + } + ], + "footer": "MIT Licensed" + }, + "excerpt": "", + "headers": [], + "git": { + "updatedTime": 1644411914000, + "contributors": [ + { + "name": "felinae98", + "email": "731499577@qq.com", + "commits": 7 + }, + { + "name": "hemengyang", + "email": "hmy0119@gmail.com", + "commits": 1 + } + ] + }, + "filePathRelative": "README.md" +} + +if (import.meta.webpackHot) { + import.meta.webpackHot.accept() + if (__VUE_HMR_RUNTIME__.updatePageData) { + __VUE_HMR_RUNTIME__.updatePageData(data) + } +} + +if (import.meta.hot) { + import.meta.hot.accept(({ data }) => { + __VUE_HMR_RUNTIME__.updatePageData(data) + }) +} diff --git a/docs/.vuepress/.temp/pages/index.html.vue b/docs/.vuepress/.temp/pages/index.html.vue new file mode 100644 index 0000000..cc340bc --- /dev/null +++ b/docs/.vuepress/.temp/pages/index.html.vue @@ -0,0 +1 @@ +<template></template> diff --git a/docs/.vuepress/.temp/pages/usage/index.html.js b/docs/.vuepress/.temp/pages/usage/index.html.js new file mode 100644 index 0000000..2850f11 --- /dev/null +++ b/docs/.vuepress/.temp/pages/usage/index.html.js @@ -0,0 +1,101 @@ +export const data = { + "key": "v-d0736a32", + "path": "/usage/", + "title": "部署和使用", + "lang": "en-US", + "frontmatter": { + "sidebar": "auto" + }, + "excerpt": "", + "headers": [ + { + "level": 2, + "title": "部署", + "slug": "部署", + "children": [ + { + "level": 3, + "title": "作为 Bot 使用", + "slug": "作为-bot-使用", + "children": [] + }, + { + "level": 3, + "title": "作为插件使用", + "slug": "作为插件使用", + "children": [] + }, + { + "level": 3, + "title": "自动安装", + "slug": "自动安装", + "children": [] + } + ] + }, + { + "level": 2, + "title": "配置", + "slug": "配置", + "children": [] + }, + { + "level": 2, + "title": "使用", + "slug": "使用", + "children": [ + { + "level": 3, + "title": "命令", + "slug": "命令", + "children": [] + }, + { + "level": 3, + "title": "所支持平台的 uid", + "slug": "所支持平台的-uid", + "children": [] + } + ] + } + ], + "git": { + "updatedTime": 1646927246000, + "contributors": [ + { + "name": "felinae98", + "email": "731499577@qq.com", + "commits": 10 + }, + { + "name": "Azide", + "email": "rukuy@qq.com", + "commits": 1 + }, + { + "name": "hemengyang", + "email": "hmy0119@gmail.com", + "commits": 1 + }, + { + "name": "pre-commit-ci[bot]", + "email": "66853113+pre-commit-ci[bot]@users.noreply.github.com", + "commits": 1 + } + ] + }, + "filePathRelative": "usage/README.md" +} + +if (import.meta.webpackHot) { + import.meta.webpackHot.accept() + if (__VUE_HMR_RUNTIME__.updatePageData) { + __VUE_HMR_RUNTIME__.updatePageData(data) + } +} + +if (import.meta.hot) { + import.meta.hot.accept(({ data }) => { + __VUE_HMR_RUNTIME__.updatePageData(data) + }) +} diff --git a/docs/.vuepress/.temp/pages/usage/index.html.vue b/docs/.vuepress/.temp/pages/usage/index.html.vue new file mode 100644 index 0000000..8892514 --- /dev/null +++ b/docs/.vuepress/.temp/pages/usage/index.html.vue @@ -0,0 +1,162 @@ +<template><h1 id="部署和使用" tabindex="-1"><a class="header-anchor" href="#部署和使用" aria-hidden="true">#</a> 部署和使用</h1> +<p>本节将教你快速部署和使用一个 nonebot-bison,如果你不知道要选择哪种部署方式,推荐使用<a href="#docker-compose%E9%83%A8%E7%BD%B2-%E6%8E%A8%E8%8D%90">docker-compose</a></p> +<h2 id="部署" tabindex="-1"><a class="header-anchor" href="#部署" aria-hidden="true">#</a> 部署</h2> +<p>本项目可以作为单独的 Bot 使用,可以作为 nonebot2 的插件使用</p> +<h3 id="作为-bot-使用" tabindex="-1"><a class="header-anchor" href="#作为-bot-使用" aria-hidden="true">#</a> 作为 Bot 使用</h3> +<p>额外提供自动同意超级用户的好友申请和同意超级用户的加群邀请的功能</p> +<h4 id="docker-compose-部署-推荐" tabindex="-1"><a class="header-anchor" href="#docker-compose-部署-推荐" aria-hidden="true">#</a> docker-compose 部署(推荐)</h4> +<ol> +<li> +<p>在一个新的目录中下载<a href="https://raw.githubusercontent.com/felinae98/nonebot-bison/main/docker-compose.yml" target="_blank" rel="noopener noreferrer">docker-compose.yml<ExternalLinkIcon/></a><br> +将其中的<code><your QQ></code>改成自己的 QQ 号</p> +<div class="language-bash ext-sh line-numbers-mode"><pre v-pre class="language-bash"><code><span class="token function">wget</span> https://raw.githubusercontent.com/felinae98/nonebot-bison/main/docker-compose.yml +</code></pre><div class="line-numbers" aria-hidden="true"><span class="line-number">1</span><br></div></div></li> +<li> +<p>运行配置 go-cqhttp</p> +<div class="language-bash ext-sh line-numbers-mode"><pre v-pre class="language-bash"><code><span class="token function">docker-compose</span> run go-cqhttp +</code></pre><div class="line-numbers" aria-hidden="true"><span class="line-number">1</span><br></div></div><p>通信方式选择:<code>3: 反向 Websocket 通信</code><br> +编辑<code>bot-data/config.yml</code>,更改下面字段:</p> +<div class="language-text ext-text line-numbers-mode"><pre v-pre class="language-text"><code>account: # 账号相关 + uin: <QQ号> # QQ账号 + password: "<QQ密码>" # 密码为空时使用扫码登录 + +message: + post-format: array + +............ + +servers: + - ws-reverse: + universal: ws://nonebot:8080/onebot/v11/ws/ # 将这个字段写为这个值 +</code></pre><div class="line-numbers" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br></div></div></li> +<li> +<p>登录 go-cqhttp +再次</p> +<div class="language-bash ext-sh line-numbers-mode"><pre v-pre class="language-bash"><code><span class="token function">docker-compose</span> run go-cqhttp +</code></pre><div class="line-numbers" aria-hidden="true"><span class="line-number">1</span><br></div></div><p>参考<a href="https://docs.go-cqhttp.org/faq/slider.html#%E6%96%B9%E6%A1%88a-%E8%87%AA%E8%A1%8C%E6%8A%93%E5%8C%85" target="_blank" rel="noopener noreferrer">go-cqhttp 文档<ExternalLinkIcon/></a> +完成登录</p> +</li> +<li> +<p>确定完成登录后,启动 bot:</p> +<div class="language-bash ext-sh line-numbers-mode"><pre v-pre class="language-bash"><code><span class="token function">docker-compose</span> up -d +</code></pre><div class="line-numbers" aria-hidden="true"><span class="line-number">1</span><br></div></div></li> +</ol> +<h4 id="docker-部署" tabindex="-1"><a class="header-anchor" href="#docker-部署" aria-hidden="true">#</a> docker 部署</h4> +<p>本项目的 docker 镜像为<code>felinae98/nonebot-bison</code>,可以直接 pull 后 run 进行使用, +相关配置参数可以使用<code>-e</code>作为环境变量传入</p> +<h4 id="直接运行-不推荐" tabindex="-1"><a class="header-anchor" href="#直接运行-不推荐" aria-hidden="true">#</a> 直接运行(不推荐)</h4> +<p>可以参考<a href="https://v2.nonebot.dev/guide/getting-started.html" target="_blank" rel="noopener noreferrer">nonebot 的运行方法<ExternalLinkIcon/></a></p> +<div class="custom-container danger"><p class="custom-container-title">DANGER</p> +<p>直接克隆源代码需要自行编译前端,否则会出现无法使用管理后台等情况。</p> +</div> +<div class="custom-container danger"><p class="custom-container-title">DANGER</p> +<p>本项目中使用了 Python 3.9 的语法,如果出现问题,请检查 Python 版本</p> +</div> +<ol> +<li>首先安装 poetry:<a href="https://python-poetry.org/docs/#installation" target="_blank" rel="noopener noreferrer">安装方法<ExternalLinkIcon/></a></li> +<li>clone 本项目,在项目中<code>poetry install</code>安装依赖</li> +<li>安装 yarn,配置 yarn 源(推荐)</li> +<li>在<code>admin-fronted</code>中运行<code>yarn && yarn build</code>编译前端</li> +<li>编辑<code>.env.prod</code>配置各种环境变量,见<a href="https://v2.nonebot.dev/guide/basic-configuration.html" target="_blank" rel="noopener noreferrer">Nonebot2 配置<ExternalLinkIcon/></a></li> +<li>运行<code>poetry run python bot.py</code>启动机器人</li> +</ol> +<h3 id="作为插件使用" tabindex="-1"><a class="header-anchor" href="#作为插件使用" aria-hidden="true">#</a> 作为插件使用</h3> +<p>本部分假设大家会部署 nonebot2</p> +<h4 id="手动安装" tabindex="-1"><a class="header-anchor" href="#手动安装" aria-hidden="true">#</a> 手动安装</h4> +<ol> +<li>安装 pip 包<code>nonebot-bison</code></li> +<li>在<code>bot.py</code>中导入插件<code>nonebot_bison</code></li> +</ol> +<h3 id="自动安装" tabindex="-1"><a class="header-anchor" href="#自动安装" aria-hidden="true">#</a> 自动安装</h3> +<p>使用<code>nb-cli</code>执行:<code>nb plugin install nonebot_bison</code></p> +<h2 id="配置" tabindex="-1"><a class="header-anchor" href="#配置" aria-hidden="true">#</a> 配置</h2> +<p>可参考<a href="https://github.com/felinae98/nonebot-bison/blob/main/src/plugins/nonebot_bison/plugin_config.py" target="_blank" rel="noopener noreferrer">源文件<ExternalLinkIcon/></a></p> +<ul> +<li><code>BISON_CONFIG_PATH</code>: 插件存放配置文件的位置,如果不设定默认为项目目录下的<code>data</code>目录</li> +<li><code>BISON_USE_PIC</code>: 将文字渲染成图片后进行发送,多用于规避风控</li> +<li><code>BISON_BROWSER</code>: 本插件使用 Chrome 来渲染图片 +<ul> +<li>使用 browserless 提供的 Chrome 管理服务,设置为<code>ws://xxxxxxxx</code>,值为 Chrome Endpoint(推荐)</li> +<li>使用 cdp 连接相关服务,设置为<code>wsc://xxxxxxxxx</code></li> +<li>使用本地安装的 Chrome,设置为<code>local:<chrome path></code>,例如<code>local:/usr/bin/google-chrome-stable</code></li> +<li>如果不进行配置,那么会在启动时候自动进行安装,在官方的 docker 镜像中已经安装了浏览器<div class="custom-container warning"><p class="custom-container-title">WARNING</p> +<p>截止发布时,本项目尚不能完全与 browserless 兼容,目前建议使用镜像内自带的浏览器,即 +不要配置这个变量</p> +</div> +</li> +</ul> +</li> +<li><code>BISON_SKIP_BROWSER_CHECK</code>: 是否在启动时自动下载浏览器,如果选择<code>False</code>会在用到浏览器时自动下载, +默认<code>True</code></li> +<li><code>BISON_OUTER_URL</code>: 从外部访问服务器的地址,默认为<code>http://localhost:8080/bison</code>,如果你的插件部署 +在服务器上,建议配置为<code>http://<你的服务器ip>:8080/bison</code></li> +<li><code>BISON_FILTER_LOG</code>: 是否过滤来自<code>nonebot</code>的 warning 级以下的 log,如果你的 bot 只运行了这个插件可以考虑 +开启,默认关</li> +<li><code>BISON_USE_QUEUE</code>: 是否用队列的方式发送消息,降低发送频率,默认开</li> +<li><code>BISON_RESEND_TIMES</code>: 最大重发次数,默认 0</li> +<li><code>BISON_USE_PIC_MERGE</code>: 是否启用多图片时合并转发(仅限群) +<ul> +<li><code>0</code>: 不启用(默认)</li> +<li><code>1</code>: 首条消息单独发送,剩余图片合并转发</li> +<li><code>2</code>: 所有消息全部合并转发<details class="custom-container details"><summary>配置项示例</summary> +<ul> +<li>当<code>BISON_USE_PIC_MERGE=1</code>时: +<img src="@source/usage/pic/forward-msg-simple1.png" alt="simple1"></li> +<li>当<code>BISON_USE_PIC_MERGE=2</code>时: +<img src="@source/usage/pic/forward-msg-simple2.png" alt="simple1"></li> +</ul> +</details> +<div class="custom-container warning"><p class="custom-container-title">WARNING</p> +<p>启用此功能时,可能会因为待推送图片过大/过多而导致文字消息与合并转发图片消息推送间隔过大(选择模式<code>1</code>时),请谨慎考虑开启。或者选择模式<code>2</code>,使图文消息一同合并转发(可能会使消息推送延迟过长)</p> +</div> +</li> +</ul> +</li> +</ul> +<h2 id="使用" tabindex="-1"><a class="header-anchor" href="#使用" aria-hidden="true">#</a> 使用</h2> +<div class="custom-container warning"><p class="custom-container-title">WARNING</p> +<p>本节假设<code>COMMAND_START</code>设置中包含<code>''</code>,如果出现 bot 不响应的问题,请先 +排查这个设置</p> +</div> +<h3 id="命令" tabindex="-1"><a class="header-anchor" href="#命令" aria-hidden="true">#</a> 命令</h3> +<h4 id="在本群中进行配置" tabindex="-1"><a class="header-anchor" href="#在本群中进行配置" aria-hidden="true">#</a> 在本群中进行配置</h4> +<p>所有命令都需要@bot 触发</p> +<ul> +<li>添加订阅(仅管理员和群主和 SUPERUSER):<code>添加订阅</code></li> +<li>查询订阅:<code>查询订阅</code></li> +<li>删除订阅(仅管理员和群主和 SUPERUSER):<code>删除订阅</code></li> +</ul> +<h4 id="私聊机器人获取后台地址" tabindex="-1"><a class="header-anchor" href="#私聊机器人获取后台地址" aria-hidden="true">#</a> 私聊机器人获取后台地址</h4> +<p><code>后台管理</code>,之后点击返回的链接<br> +如果你是 superuser,那么你可以管理所有群的订阅;如果你是 bot 所在的群的其中部分群的管理, +你可以管理你管理的群里的订阅;如果你不是任意一个群的管理,那么 bot 将会报错。</p> +<div class="custom-container tip"><p class="custom-container-title">TIP</p> +<p>可以和 bot 通过临时聊天触发</p> +</div> +<div class="custom-container warning"><p class="custom-container-title">WARNING</p> +<p>网页的身份鉴别机制全部由 bot 返回的链接确定,所以这个链接并不能透露给别人。 +并且链接会过期,所以一段时间后需要重新私聊 bot 获取新的链接。</p> +</div> +<h4 id="私聊机器人进行配置-需要-superuer-权限" tabindex="-1"><a class="header-anchor" href="#私聊机器人进行配置-需要-superuer-权限" aria-hidden="true">#</a> 私聊机器人进行配置(需要 SUPERUER 权限)</h4> +<ul> +<li>添加订阅:<code>管理-添加订阅</code></li> +<li>查询订阅:<code>管理-查询订阅</code></li> +<li>删除订阅:<code>管理-删除订阅</code></li> +</ul> +<h3 id="所支持平台的-uid" tabindex="-1"><a class="header-anchor" href="#所支持平台的-uid" aria-hidden="true">#</a> 所支持平台的 uid</h3> +<h4 id="weibo" tabindex="-1"><a class="header-anchor" href="#weibo" aria-hidden="true">#</a> Weibo</h4> +<ul> +<li>对于一般用户主页<code>https://weibo.com/u/6441489862?xxxxxxxxxxxxxxx</code>,<code>/u/</code>后面的数字即为 uid</li> +<li>对于有个性域名的用户如:<code>https://weibo.com/arknights</code>,需要点击左侧信息标签下“更多”,链接为<code>https://weibo.com/6279793937/about</code>,其中中间数字即为 uid</li> +</ul> +<h4 id="bilibili" tabindex="-1"><a class="header-anchor" href="#bilibili" aria-hidden="true">#</a> Bilibili</h4> +<p>主页链接一般为<code>https://space.bilibili.com/161775300?xxxxxxxxxx</code>,数字即为 uid</p> +<h4 id="rss" tabindex="-1"><a class="header-anchor" href="#rss" aria-hidden="true">#</a> RSS</h4> +<p>RSS 链接即为 uid</p> +<h4 id="网易云音乐-歌手" tabindex="-1"><a class="header-anchor" href="#网易云音乐-歌手" aria-hidden="true">#</a> 网易云音乐-歌手</h4> +<p>在网易云网页上歌手的链接一般为<code>https://music.163.com/#/artist?id=32540734</code>,<code>id=</code> +后面的数字即为 uid</p> +<h4 id="网易云音乐-电台" tabindex="-1"><a class="header-anchor" href="#网易云音乐-电台" aria-hidden="true">#</a> 网易云音乐-电台</h4> +<p>在网易云网页上电台的链接一般为<code>https://music.163.com/#/djradio?id=793745436</code>,<code>id=</code> +后面的数字即为 uid</p> +</template> diff --git a/docs/.vuepress/.temp/styles/index.scss b/docs/.vuepress/.temp/styles/index.scss new file mode 100644 index 0000000..e69de29 diff --git a/docs/.vuepress/.temp/styles/palette.scss b/docs/.vuepress/.temp/styles/palette.scss new file mode 100644 index 0000000..e69de29 diff --git a/docs/.vuepress/.temp/vite-root/index.html b/docs/.vuepress/.temp/vite-root/index.html new file mode 100644 index 0000000..459371d --- /dev/null +++ b/docs/.vuepress/.temp/vite-root/index.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width,initial-scale=1"> + </head> + <body> + <div id="app"></div> + <script type="module"> +import '@vuepress/client/lib/app.js' +</script> +</body> +</html> diff --git a/docs/usage/README.md b/docs/usage/README.md index 0ac89ff..989a14c 100644 --- a/docs/usage/README.md +++ b/docs/usage/README.md @@ -120,11 +120,14 @@ sidebar: auto - `1`: 首条消息单独发送,剩余图片合并转发 - `2`: 所有消息全部合并转发 ::: details 配置项示例 - - 当`BISON_USE_PIC_MERGE=1`时: + - 当`BISON_USE_PIC_MERGE=1`时:  - - 当`BISON_USE_PIC_MERGE=2`时: + - 当`BISON_USE_PIC_MERGE=2`时:  ::: + ::: warning + 启用此功能时,可能会因为待推送图片过大/过多而导致文字消息与合并转发图片消息推送间隔过大(选择模式`1`时),请谨慎考虑开启。或者选择模式`2`,使图文消息一同合并转发(可能会使消息推送延迟过长) + ::: ## 使用 From d02d206fd537110849cf4f8a4f532d0e2faea653 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 10 Mar 2022 16:19:39 +0000 Subject: [PATCH 18/42] auto fix by pre-commit hooks --- docs/usage/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/usage/README.md b/docs/usage/README.md index 989a14c..82a0719 100644 --- a/docs/usage/README.md +++ b/docs/usage/README.md @@ -121,13 +121,13 @@ sidebar: auto - `2`: 所有消息全部合并转发 ::: details 配置项示例 - 当`BISON_USE_PIC_MERGE=1`时: -  +  - 当`BISON_USE_PIC_MERGE=2`时: -  - ::: - ::: warning - 启用此功能时,可能会因为待推送图片过大/过多而导致文字消息与合并转发图片消息推送间隔过大(选择模式`1`时),请谨慎考虑开启。或者选择模式`2`,使图文消息一同合并转发(可能会使消息推送延迟过长) - ::: +  + ::: + ::: warning + 启用此功能时,可能会因为待推送图片过大/过多而导致文字消息与合并转发图片消息推送间隔过大(选择模式`1`时),请谨慎考虑开启。或者选择模式`2`,使图文消息一同合并转发(可能会使消息推送延迟过长) + ::: ## 使用 From 550dc9f74c313854675d938b1e50f43b45a7dd81 Mon Sep 17 00:00:00 2001 From: felinae98 <731499577@qq.com> Date: Fri, 11 Mar 2022 00:22:39 +0800 Subject: [PATCH 19/42] update doc --- .gitignore | 1 + docs/.vuepress/config.js | 2 +- docs/usage/README.md | 8 ++++++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 8b8f3e5..d6029a3 100644 --- a/.gitignore +++ b/.gitignore @@ -274,6 +274,7 @@ dist # vuepress build output .vuepress/dist +docs/.vuepress/.temp/ # Serverless directories .serverless/ diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 6f6c566..c002680 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -2,7 +2,7 @@ module.exports = { title: 'Nonebot Bison', description: 'Docs for Nonebot Bison', themeConfig: { - nav: [ + navbar: [ { text: '主页', link: '/' }, { text: '部署与使用', link: '/usage/' }, { text: '开发', link: '/dev/' }, diff --git a/docs/usage/README.md b/docs/usage/README.md index 0ac89ff..dee0122 100644 --- a/docs/usage/README.md +++ b/docs/usage/README.md @@ -116,15 +116,19 @@ sidebar: auto - `BISON_USE_QUEUE`: 是否用队列的方式发送消息,降低发送频率,默认开 - `BISON_RESEND_TIMES`: 最大重发次数,默认 0 - `BISON_USE_PIC_MERGE`: 是否启用多图片时合并转发(仅限群) + - `0`: 不启用(默认) - `1`: 首条消息单独发送,剩余图片合并转发 - `2`: 所有消息全部合并转发 - ::: details 配置项示例 + + ::: details 配置项示例 + - 当`BISON_USE_PIC_MERGE=1`时:  - 当`BISON_USE_PIC_MERGE=2`时:  - ::: + + ::: ## 使用 From 026390e6f2cc562c1d081650de185c47b3c47daa Mon Sep 17 00:00:00 2001 From: Azide <rukuy@qq.com> Date: Fri, 11 Mar 2022 23:57:06 +0800 Subject: [PATCH 20/42] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=AE=A2=E9=98=85?= =?UTF-8?q?=E8=BF=87=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/nonebot_bison/config_manager.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/plugins/nonebot_bison/config_manager.py b/src/plugins/nonebot_bison/config_manager.py index e2cd220..3d330a6 100644 --- a/src/plugins/nonebot_bison/config_manager.py +++ b/src/plugins/nonebot_bison/config_manager.py @@ -112,7 +112,7 @@ def do_add_sub(add_sub: Type[Matcher]): if platform_manager[state["platform"]].has_target: state[ "_prompt" - ] = "请输入订阅用户的id,详情查阅https://nonebot-bison.vercel.app/usage/#%E6%89%80%E6%94%AF%E6%8C%81%E5%B9%B3%E5%8F%B0%E7%9A%84uid" + ] = "请输入订阅用户的id:\n查询id获取方法请回复:“查询”" else: state["id"] = "default" state["name"] = await platform_manager[state["platform"]].get_target_name( @@ -124,6 +124,8 @@ def do_add_sub(add_sub: Type[Matcher]): return target = str(event.get_message()).strip() try: + if target == "查询": + raise LookupError if target == "取消": raise KeyboardInterrupt name = await check_sub_target(state["platform"], target) @@ -131,10 +133,19 @@ def do_add_sub(add_sub: Type[Matcher]): raise ValueError state["id"] = target state["name"] = name + except (LookupError): + url="https://nonebot-bison.vercel.app/usage/#%E6%89%80%E6%94%AF%E6%8C%81%E5%B9%B3%E5%8F%B0%E7%9A%84uid" + title="Bison所支持的平台UID" + content="查询相关平台的uid格式或获取方式" + image="https://s3.bmp.ovh/imgs/2022/03/ab3cc45d83bd3dd3.jpg" + getId_share=f"[CQ:share,url={url},title={title},content={content},image={image}"#缩短字符串格式长度,以及方便后续修改为消息段格式 + await add_sub.reject(Message(getId_share)) except (KeyboardInterrupt): await add_sub.finish("已中止订阅") except (ValueError): await add_sub.reject("id输入错误") + else: + await add_sub.send("即将订阅的用户为:{} {} {}\n如有错误请输入“取消”重新订阅".format(state["platform"],state["name"],state["id"])) @add_sub.got("id", _gen_prompt_template("{_prompt}"), [Depends(parse_id)]) async def init_cat(state: T_State): From 62feeb84897dabb484e36307fab9e2fe37805e0c Mon Sep 17 00:00:00 2001 From: Azide <rukuy@qq.com> Date: Sat, 12 Mar 2022 00:30:36 +0800 Subject: [PATCH 21/42] =?UTF-8?q?=E4=BF=AE=E6=94=B9test=5Fconfig=5Fmanager?= =?UTF-8?q?=E5=86=85=E6=B7=BB=E5=8A=A0=E8=AE=A2=E9=98=85test=E7=9A=84?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_config_manager.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/tests/test_config_manager.py b/tests/test_config_manager.py index 1119223..bacd128 100644 --- a/tests/test_config_manager.py +++ b/tests/test_config_manager.py @@ -142,7 +142,7 @@ async def test_add_with_target(app: App): ctx.should_call_send( event_3, Message( - "请输入订阅用户的id,详情查阅https://nonebot-bison.vercel.app/usage/#%E6%89%80%E6%94%AF%E6%8C%81%E5%B9%B3%E5%8F%B0%E7%9A%84uid" + "请输入订阅用户的id:\n查询id获取方法请回复:“查询”" ), True, ) @@ -156,6 +156,13 @@ async def test_add_with_target(app: App): message=Message("6279793937"), sender=fake_admin_user ) ctx.receive_event(bot, event_4_ok) + ctx.should_call_send( + event_4_ok, + Message( + "即将订阅的用户为:weibo 明日方舟Arknights 6279793937\n如有错误请输入“取消”重新订阅" + ), + True + ) ctx.should_call_send( event_4_ok, Message( @@ -241,7 +248,7 @@ async def test_add_with_target_no_cat(app: App): ctx.should_call_send( event_3, Message( - "请输入订阅用户的id,详情查阅https://nonebot-bison.vercel.app/usage/#%E6%89%80%E6%94%AF%E6%8C%81%E5%B9%B3%E5%8F%B0%E7%9A%84uid" + "请输入订阅用户的id:\n查询id获取方法请回复:“查询”" ), True, ) @@ -249,6 +256,13 @@ async def test_add_with_target_no_cat(app: App): message=Message("32540734"), sender=fake_admin_user ) ctx.receive_event(bot, event_4_ok) + ctx.should_call_send( + event_4_ok, + Message( + "即将订阅的用户为:ncm-artist 塞壬唱片-MSR 32540734\n如有错误请输入“取消”重新订阅" + ), + True + ) ctx.should_call_send(event_4_ok, ("添加 塞壬唱片-MSR 成功"), True) ctx.should_finished() subs = config.list_subscribe(10000, "group") @@ -302,6 +316,13 @@ async def test_add_no_target(app: App): message=Message("arknights"), sender=fake_admin_user ) ctx.receive_event(bot, event_3) + ctx.should_call_send( + event_3, + Message( + "即将订阅的用户为:arknights 明日方舟游戏信息 default\n如有错误请输入“取消”重新订阅" + ), + True + ) ctx.should_call_send( event_3, Message( From 15f605b719c55b8c78f5e2d297d7edd2e1fd2589 Mon Sep 17 00:00:00 2001 From: Azide <rukuy@qq.com> Date: Sat, 12 Mar 2022 12:59:45 +0800 Subject: [PATCH 22/42] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86test=5Fconfig?= =?UTF-8?q?=5Fmanager=5F*.py=E6=96=87=E4=BB=B6=E7=9A=84=E7=BB=93=E6=9E=84?= =?UTF-8?q?=EF=BC=8C=E5=B0=9D=E8=AF=95=E6=8A=BD=E5=8F=96=E5=87=BA=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E9=83=A8=E5=88=86(=E6=9C=AA=E9=AA=8C=E8=AF=81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_config_manager_query_del.py | 101 ++++++++ ..._manager.py => test_config_manages_add.py} | 224 ++---------------- tests/utils.py | 49 ++++ 3 files changed, 173 insertions(+), 201 deletions(-) create mode 100644 tests/test_config_manager_query_del.py rename tests/{test_config_manager.py => test_config_manages_add.py} (54%) diff --git a/tests/test_config_manager_query_del.py b/tests/test_config_manager_query_del.py new file mode 100644 index 0000000..0ae3f2d --- /dev/null +++ b/tests/test_config_manager_query_del.py @@ -0,0 +1,101 @@ +import pytest +import respx +from httpx import Response +from nonebug.app import App + +from .platforms.utils import get_json +from .utils import fake_admin_user, fake_group_message_event + + +@pytest.mark.asyncio +async def test_query_sub(app: App): + from nonebot.adapters.onebot.v11.message import Message + from nonebot_bison.config import Config + from nonebot_bison.config_manager import query_sub_matcher + from nonebot_bison.platform import platform_manager + + config = Config() + config.user_target.truncate() + config.add_subscribe( + 10000, + "group", + "6279793937", + "明日方舟Arknights", + "weibo", + [platform_manager["weibo"].reverse_category["图文"]], + ["明日方舟"], + ) + async with app.test_matcher(query_sub_matcher) as ctx: + bot = ctx.create_bot() + event = fake_group_message_event(message=Message("查询订阅"), to_me=True) + ctx.receive_event(bot, event) + ctx.should_pass_rule() + ctx.should_pass_permission() + ctx.should_call_send( + event, Message("订阅的帐号为:\nweibo 明日方舟Arknights 6279793937 [图文] 明日方舟\n"), True + ) + + +@pytest.mark.asyncio +async def test_del_sub(app: App): + from nonebot.adapters.onebot.v11.bot import Bot + from nonebot.adapters.onebot.v11.message import Message + from nonebot_bison.config import Config + from nonebot_bison.config_manager import del_sub_matcher + from nonebot_bison.platform import platform_manager + + config = Config() + config.user_target.truncate() + config.add_subscribe( + 10000, + "group", + "6279793937", + "明日方舟Arknights", + "weibo", + [platform_manager["weibo"].reverse_category["图文"]], + ["明日方舟"], + ) + async with app.test_matcher(del_sub_matcher) as ctx: + bot = ctx.create_bot(base=Bot) + assert isinstance(bot, Bot) + event = fake_group_message_event( + message=Message("删除订阅"), to_me=True, sender=fake_admin_user + ) + ctx.receive_event(bot, event) + ctx.should_pass_rule() + ctx.should_pass_permission() + ctx.should_call_send( + event, + Message( + "订阅的帐号为:\n1 weibo 明日方舟Arknights 6279793937\n [图文] 明日方舟\n请输入要删除的订阅的序号" + ), + True, + ) + event_1_err = fake_group_message_event( + message=Message("2"), sender=fake_admin_user + ) + ctx.receive_event(bot, event_1_err) + ctx.should_call_send(event_1_err, "删除错误", True) + ctx.should_rejected() + event_1_ok = fake_group_message_event( + message=Message("1"), sender=fake_admin_user + ) + ctx.receive_event(bot, event_1_ok) + ctx.should_call_send(event_1_ok, "删除成功", True) + ctx.should_finished() + subs = config.list_subscribe(10000, "group") + assert len(subs) == 0 + + +async def test_test(app: App): + from nonebot.adapters.onebot.v11.bot import Bot + from nonebot.adapters.onebot.v11.message import Message + from nonebot_bison.config_manager import test_matcher + + async with app.test_matcher(test_matcher) as ctx: + bot = ctx.create_bot(base=Bot) + event = fake_group_message_event(message=Message("testtt")) + ctx.receive_event(bot, event) + ctx.should_pass_permission() + ctx.should_pass_rule() + ctx.should_call_send(event, "666", True) diff --git a/tests/test_config_manager.py b/tests/test_config_manages_add.py similarity index 54% rename from tests/test_config_manager.py rename to tests/test_config_manages_add.py index bacd128..34ca2a8 100644 --- a/tests/test_config_manager.py +++ b/tests/test_config_manages_add.py @@ -4,8 +4,9 @@ from httpx import Response from nonebug.app import App from .platforms.utils import get_json -from .utils import fake_admin_user, fake_group_message_event +from .utils import fake_admin_user, fake_group_message_event, BotReply +bot_reply=BotReply() @pytest.mark.asyncio async def test_configurable_at_me_true_failed(app: App): @@ -31,7 +32,6 @@ async def test_configurable_at_me_true_failed(app: App): ctx.should_pass_rule() ctx.should_not_pass_permission() - @pytest.mark.asyncio async def test_configurable_at_me_false(app: App): from nonebot.adapters.onebot.v11.bot import Bot @@ -49,24 +49,12 @@ async def test_configurable_at_me_false(app: App): ctx.receive_event(bot, event) ctx.should_call_send( event, - Message( - "请输入想要订阅的平台,目前支持,请输入冒号左边的名称:\n" - + "".join( - [ - "{}:{}\n".format( - platform_name, platform_manager[platform_name].name - ) - for platform_name in common_platform - ] - ) - + "要查看全部平台请输入:“全部”\n中止订阅过程请输入:“取消”" - ), + bot_reply.add_reply_on_platform(platform_manager,common_platform), True, ) ctx.should_pass_rule() ctx.should_pass_permission() - @pytest.mark.asyncio @respx.mock async def test_add_with_target(app: App): @@ -103,18 +91,7 @@ async def test_add_with_target(app: App): ctx.should_pass_rule() ctx.should_call_send( event_1, - Message( - "请输入想要订阅的平台,目前支持,请输入冒号左边的名称:\n" - + "".join( - [ - "{}:{}\n".format( - platform_name, platform_manager[platform_name].name - ) - for platform_name in common_platform - ] - ) - + "要查看全部平台请输入:“全部”\n中止订阅过程请输入:“取消”" - ), + bot_reply.add_reply_on_platform(platform_manager,common_platform), True, ) event_2 = fake_group_message_event( @@ -124,15 +101,7 @@ async def test_add_with_target(app: App): ctx.should_rejected() ctx.should_call_send( event_2, - ( - "全部平台\n" - + "\n".join( - [ - "{}:{}".format(platform_name, platform.name) - for platform_name, platform in platform_manager.items() - ] - ) - ), + bot_reply.add_reply_on_platform_input_allplatform(platform_manager), True, ) event_3 = fake_group_message_event( @@ -141,16 +110,14 @@ async def test_add_with_target(app: App): ctx.receive_event(bot, event_3) ctx.should_call_send( event_3, - Message( - "请输入订阅用户的id:\n查询id获取方法请回复:“查询”" - ), + bot_reply.add_reply_on_id, True, ) event_4_err = fake_group_message_event( message=Message("000"), sender=fake_admin_user ) ctx.receive_event(bot, event_4_err) - ctx.should_call_send(event_4_err, "id输入错误", True) + ctx.should_call_send(event_4_err, bot_reply.add_reply_on_id_input_error, True) ctx.should_rejected() event_4_ok = fake_group_message_event( message=Message("6279793937"), sender=fake_admin_user @@ -158,36 +125,30 @@ async def test_add_with_target(app: App): ctx.receive_event(bot, event_4_ok) ctx.should_call_send( event_4_ok, - Message( - "即将订阅的用户为:weibo 明日方舟Arknights 6279793937\n如有错误请输入“取消”重新订阅" - ), + bot_reply.add_reply_on_target_confirm("weibo","明日方舟Arknights","6279793937"), True ) ctx.should_call_send( event_4_ok, - Message( - "请输入要订阅的类别,以空格分隔,支持的类别有:{}".format( - " ".join(list(platform_manager["weibo"].categories.values())) - ) - ), + bot_reply.add_reply_on_cats(platform_manager,"weibo"), True, ) event_5_err = fake_group_message_event( message=Message("图文 文字 err"), sender=fake_admin_user ) ctx.receive_event(bot, event_5_err) - ctx.should_call_send(event_5_err, "不支持 err", True) + ctx.should_call_send(event_5_err, bot_reply.add_reply_on_cats_input_error("err"), True) ctx.should_rejected() event_5_ok = fake_group_message_event( message=Message("图文 文字"), sender=fake_admin_user ) ctx.receive_event(bot, event_5_ok) - ctx.should_call_send(event_5_ok, Message('请输入要订阅的tag,订阅所有tag输入"全部标签"'), True) + ctx.should_call_send(event_5_ok, bot_reply.add_reply_on_tags, True) event_6 = fake_group_message_event( message=Message("全部标签"), sender=fake_admin_user ) ctx.receive_event(bot, event_6) - ctx.should_call_send(event_6, ("添加 明日方舟Arknights 成功"), True) + ctx.should_call_send(event_6, bot_reply.add_reply_subscribe_success("明日方舟Arknights"), True) ctx.should_finished() subs = config.list_subscribe(10000, "group") assert len(subs) == 1 @@ -200,7 +161,6 @@ async def test_add_with_target(app: App): assert sub["target_type"] == "weibo" assert sub["target_name"] == "明日方舟Arknights" - @pytest.mark.asyncio @respx.mock async def test_add_with_target_no_cat(app: App): @@ -227,18 +187,7 @@ async def test_add_with_target_no_cat(app: App): ctx.should_pass_rule() ctx.should_call_send( event_1, - Message( - "请输入想要订阅的平台,目前支持,请输入冒号左边的名称:\n" - + "".join( - [ - "{}:{}\n".format( - platform_name, platform_manager[platform_name].name - ) - for platform_name in common_platform - ] - ) - + "要查看全部平台请输入:“全部”\n中止订阅过程请输入:“取消”" - ), + bot_reply.add_reply_on_platform(platform_manager,common_platform), True, ) event_3 = fake_group_message_event( @@ -247,9 +196,7 @@ async def test_add_with_target_no_cat(app: App): ctx.receive_event(bot, event_3) ctx.should_call_send( event_3, - Message( - "请输入订阅用户的id:\n查询id获取方法请回复:“查询”" - ), + bot_reply.add_reply_on_id, True, ) event_4_ok = fake_group_message_event( @@ -258,12 +205,10 @@ async def test_add_with_target_no_cat(app: App): ctx.receive_event(bot, event_4_ok) ctx.should_call_send( event_4_ok, - Message( - "即将订阅的用户为:ncm-artist 塞壬唱片-MSR 32540734\n如有错误请输入“取消”重新订阅" - ), + bot_reply.add_reply_on_target_confirm("ncm-artist","塞壬唱片-MSR","32540734"), True ) - ctx.should_call_send(event_4_ok, ("添加 塞壬唱片-MSR 成功"), True) + ctx.should_call_send(event_4_ok, bot_reply.add_reply_subscribe_success("塞壬唱片-MSR"), True) ctx.should_finished() subs = config.list_subscribe(10000, "group") assert len(subs) == 1 @@ -274,7 +219,6 @@ async def test_add_with_target_no_cat(app: App): assert sub["target_type"] == "ncm-artist" assert sub["target_name"] == "塞壬唱片-MSR" - @pytest.mark.asyncio @respx.mock async def test_add_no_target(app: App): @@ -298,18 +242,7 @@ async def test_add_no_target(app: App): ctx.should_pass_rule() ctx.should_call_send( event_1, - Message( - "请输入想要订阅的平台,目前支持,请输入冒号左边的名称:\n" - + "".join( - [ - "{}:{}\n".format( - platform_name, platform_manager[platform_name].name - ) - for platform_name in common_platform - ] - ) - + "要查看全部平台请输入:“全部”\n中止订阅过程请输入:“取消”" - ), + bot_reply.add_reply_on_platform(platform_manager,common_platform), True, ) event_3 = fake_group_message_event( @@ -318,25 +251,20 @@ async def test_add_no_target(app: App): ctx.receive_event(bot, event_3) ctx.should_call_send( event_3, - Message( - "即将订阅的用户为:arknights 明日方舟游戏信息 default\n如有错误请输入“取消”重新订阅" - ), + bot_reply.add_reply_on_target_confirm("arknights","明日方舟游戏信息","default") + , True ) ctx.should_call_send( event_3, - Message( - "请输入要订阅的类别,以空格分隔,支持的类别有:{}".format( - " ".join(list(platform_manager["arknights"].categories.values())) - ) - ), + bot_reply.add_reply_on_cats(platform_manager,"arknights"), True, ) event_4 = fake_group_message_event( message=Message("游戏公告"), sender=fake_admin_user ) ctx.receive_event(bot, event_4) - ctx.should_call_send(event_4, ("添加 明日方舟游戏信息 成功"), True) + ctx.should_call_send(event_4, bot_reply.add_reply_subscribe_success("明日方舟游戏信息"), True) ctx.should_finished() subs = config.list_subscribe(10000, "group") assert len(subs) == 1 @@ -347,7 +275,6 @@ async def test_add_no_target(app: App): assert sub["target_type"] == "arknights" assert sub["target_name"] == "明日方舟游戏信息" - @pytest.mark.asyncio async def test_platform_name_err(app: App): from nonebot.adapters.onebot.v11.event import Sender @@ -369,18 +296,7 @@ async def test_platform_name_err(app: App): ctx.should_pass_rule() ctx.should_call_send( event_1, - Message( - "请输入想要订阅的平台,目前支持,请输入冒号左边的名称:\n" - + "".join( - [ - "{}:{}\n".format( - platform_name, platform_manager[platform_name].name - ) - for platform_name in common_platform - ] - ) - + "要查看全部平台请输入:“全部”\n中止订阅过程请输入:“取消”" - ), + bot_reply.add_reply_on_platform(platform_manager,common_platform), True, ) event_2 = fake_group_message_event( @@ -391,100 +307,6 @@ async def test_platform_name_err(app: App): ctx.should_rejected() ctx.should_call_send( event_2, - "平台输入错误", + bot_reply.add_reply_on_platform_input_error, True, ) - - -@pytest.mark.asyncio -async def test_query_sub(app: App): - from nonebot.adapters.onebot.v11.message import Message - from nonebot_bison.config import Config - from nonebot_bison.config_manager import query_sub_matcher - from nonebot_bison.platform import platform_manager - - config = Config() - config.user_target.truncate() - config.add_subscribe( - 10000, - "group", - "6279793937", - "明日方舟Arknights", - "weibo", - [platform_manager["weibo"].reverse_category["图文"]], - ["明日方舟"], - ) - async with app.test_matcher(query_sub_matcher) as ctx: - bot = ctx.create_bot() - event = fake_group_message_event(message=Message("查询订阅"), to_me=True) - ctx.receive_event(bot, event) - ctx.should_pass_rule() - ctx.should_pass_permission() - ctx.should_call_send( - event, Message("订阅的帐号为:\nweibo 明日方舟Arknights 6279793937 [图文] 明日方舟\n"), True - ) - - -@pytest.mark.asyncio -async def test_del_sub(app: App): - from nonebot.adapters.onebot.v11.bot import Bot - from nonebot.adapters.onebot.v11.message import Message - from nonebot_bison.config import Config - from nonebot_bison.config_manager import del_sub_matcher - from nonebot_bison.platform import platform_manager - - config = Config() - config.user_target.truncate() - config.add_subscribe( - 10000, - "group", - "6279793937", - "明日方舟Arknights", - "weibo", - [platform_manager["weibo"].reverse_category["图文"]], - ["明日方舟"], - ) - async with app.test_matcher(del_sub_matcher) as ctx: - bot = ctx.create_bot(base=Bot) - assert isinstance(bot, Bot) - event = fake_group_message_event( - message=Message("删除订阅"), to_me=True, sender=fake_admin_user - ) - ctx.receive_event(bot, event) - ctx.should_pass_rule() - ctx.should_pass_permission() - ctx.should_call_send( - event, - Message( - "订阅的帐号为:\n1 weibo 明日方舟Arknights 6279793937\n [图文] 明日方舟\n请输入要删除的订阅的序号" - ), - True, - ) - event_1_err = fake_group_message_event( - message=Message("2"), sender=fake_admin_user - ) - ctx.receive_event(bot, event_1_err) - ctx.should_call_send(event_1_err, "删除错误", True) - ctx.should_rejected() - event_1_ok = fake_group_message_event( - message=Message("1"), sender=fake_admin_user - ) - ctx.receive_event(bot, event_1_ok) - ctx.should_call_send(event_1_ok, "删除成功", True) - ctx.should_finished() - subs = config.list_subscribe(10000, "group") - assert len(subs) == 0 - - -async def test_test(app: App): - from nonebot.adapters.onebot.v11.bot import Bot - from nonebot.adapters.onebot.v11.message import Message - from nonebot_bison.config_manager import test_matcher - - async with app.test_matcher(test_matcher) as ctx: - bot = ctx.create_bot(base=Bot) - event = fake_group_message_event(message=Message("testtt")) - ctx.receive_event(bot, event) - ctx.should_pass_permission() - ctx.should_pass_rule() - ctx.should_call_send(event, "666", True) diff --git a/tests/utils.py b/tests/utils.py index a31b67e..17f009f 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -69,3 +69,52 @@ from nonebot.adapters.onebot.v11.event import Sender fake_admin_user = Sender(nickname="test", role="admin") fake_superuser = Sender(user_id=10001, nickname="superuser") + +from nonebot.adapters.onebot.v11.message import Message +class BotReply: + def add_reply_on_platform(platform_manager,common_platform): + return Message( + "请输入想要订阅的平台,目前支持,请输入冒号左边的名称:\n" + + "".join( + [ + "{}:{}\n".format( + platform_name, platform_manager[platform_name].name + ) + for platform_name in common_platform + ] + ) + + "要查看全部平台请输入:“全部”\n中止订阅过程请输入:“取消”" + ) + + def add_reply_on_platform_input_allplatform(platform_manager): + return "全部平台\n" + "\n".join( + [ + "{}:{}".format(platform_name, platform.name) + for platform_name, platform in platform_manager.items() + ]) + + def add_reply_on_id_input_search(): + search_url="https://nonebot-bison.vercel.app/usage/#%E6%89%80%E6%94%AF%E6%8C%81%E5%B9%B3%E5%8F%B0%E7%9A%84uid" + search_title="Bison所支持的平台UID" + search_content="查询相关平台的uid格式或获取方式" + search_image="https://s3.bmp.ovh/imgs/2022/03/ab3cc45d83bd3dd3.jpg" + return Message(f"[CQ:share,url={search_url},title={search_title},content={search_content},image={search_image}") + + def add_reply_on_target_confirm(platform,name,id): + return Message(f"即将订阅的用户为:{platform} {name} {id}\n如有错误请输入“取消”重新订阅") + + def add_reply_on_cats(platform_manager,platform:str): + return Message("请输入要订阅的类别,以空格分隔,支持的类别有:{}".format( + " ".join(list(platform_manager[platform].categories.values())))) + + def add_reply_on_cats_input_error(cat:str): + return Message("不支持 {}".format(cat)) + + def add_reply_subscribe_success(name): + return Message("添加 {} 成功".format(name)) + + add_reply_on_id_input_error="id输入错误" + add_reply_on_platform_input_error="平台输入错误" + add_reply_on_id="请输入订阅用户的id:\n查询id获取方法请回复:“查询”" + add_reply_on_tags='请输入要订阅的tag,订阅所有tag输入"全部标签"' + add_reply_abort="已中止订阅" \ No newline at end of file From 29f4abd9f4f4e166c505fe9e3bb4c6c6d906d1db Mon Sep 17 00:00:00 2001 From: Azide <rukuy@qq.com> Date: Sun, 13 Mar 2022 01:37:15 +0800 Subject: [PATCH 23/42] =?UTF-8?q?=E6=88=90=E5=8A=9F=E5=BE=AE=E8=B0=83test?= =?UTF-8?q?=5Fconfig=5Fmanager=5Fadd=E4=BD=BF=E4=B9=8Bpass?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/nonebot_bison/config_manager.py | 4 +- ...ages_add.py => test_config_manager_add.py} | 45 ++++++++----------- tests/utils.py | 12 ++--- 3 files changed, 27 insertions(+), 34 deletions(-) rename tests/{test_config_manages_add.py => test_config_manager_add.py} (85%) diff --git a/src/plugins/nonebot_bison/config_manager.py b/src/plugins/nonebot_bison/config_manager.py index 3d330a6..5e00414 100644 --- a/src/plugins/nonebot_bison/config_manager.py +++ b/src/plugins/nonebot_bison/config_manager.py @@ -134,11 +134,11 @@ def do_add_sub(add_sub: Type[Matcher]): state["id"] = target state["name"] = name except (LookupError): - url="https://nonebot-bison.vercel.app/usage/#%E6%89%80%E6%94%AF%E6%8C%81%E5%B9%B3%E5%8F%B0%E7%9A%84uid" + url="https://nonebot-bison.vercel.app/usage/#%E6%89%80%E6%94%AF%E6%8C%81%E5%B9%B3%E5%8F%B0%E7%9A%84-uid" title="Bison所支持的平台UID" content="查询相关平台的uid格式或获取方式" image="https://s3.bmp.ovh/imgs/2022/03/ab3cc45d83bd3dd3.jpg" - getId_share=f"[CQ:share,url={url},title={title},content={content},image={image}"#缩短字符串格式长度,以及方便后续修改为消息段格式 + getId_share=f"[CQ:share,url={url},title={title},content={content},image={image}]"#缩短字符串格式长度,以及方便后续修改为消息段格式 await add_sub.reject(Message(getId_share)) except (KeyboardInterrupt): await add_sub.finish("已中止订阅") diff --git a/tests/test_config_manages_add.py b/tests/test_config_manager_add.py similarity index 85% rename from tests/test_config_manages_add.py rename to tests/test_config_manager_add.py index 34ca2a8..5e80e3e 100644 --- a/tests/test_config_manages_add.py +++ b/tests/test_config_manager_add.py @@ -6,7 +6,6 @@ from nonebug.app import App from .platforms.utils import get_json from .utils import fake_admin_user, fake_group_message_event, BotReply -bot_reply=BotReply() @pytest.mark.asyncio async def test_configurable_at_me_true_failed(app: App): @@ -49,7 +48,7 @@ async def test_configurable_at_me_false(app: App): ctx.receive_event(bot, event) ctx.should_call_send( event, - bot_reply.add_reply_on_platform(platform_manager,common_platform), + Message(BotReply.add_reply_on_platform(platform_manager,common_platform)), True, ) ctx.should_pass_rule() @@ -91,7 +90,7 @@ async def test_add_with_target(app: App): ctx.should_pass_rule() ctx.should_call_send( event_1, - bot_reply.add_reply_on_platform(platform_manager,common_platform), + Message(BotReply.add_reply_on_platform(platform_manager=platform_manager,common_platform=common_platform)), True, ) event_2 = fake_group_message_event( @@ -101,7 +100,7 @@ async def test_add_with_target(app: App): ctx.should_rejected() ctx.should_call_send( event_2, - bot_reply.add_reply_on_platform_input_allplatform(platform_manager), + BotReply.add_reply_on_platform_input_allplatform(platform_manager), True, ) event_3 = fake_group_message_event( @@ -110,14 +109,14 @@ async def test_add_with_target(app: App): ctx.receive_event(bot, event_3) ctx.should_call_send( event_3, - bot_reply.add_reply_on_id, + Message(BotReply.add_reply_on_id), True, ) event_4_err = fake_group_message_event( message=Message("000"), sender=fake_admin_user ) ctx.receive_event(bot, event_4_err) - ctx.should_call_send(event_4_err, bot_reply.add_reply_on_id_input_error, True) + ctx.should_call_send(event_4_err, BotReply.add_reply_on_id_input_error, True) ctx.should_rejected() event_4_ok = fake_group_message_event( message=Message("6279793937"), sender=fake_admin_user @@ -125,30 +124,30 @@ async def test_add_with_target(app: App): ctx.receive_event(bot, event_4_ok) ctx.should_call_send( event_4_ok, - bot_reply.add_reply_on_target_confirm("weibo","明日方舟Arknights","6279793937"), + BotReply.add_reply_on_target_confirm("weibo","明日方舟Arknights","6279793937"), True ) ctx.should_call_send( event_4_ok, - bot_reply.add_reply_on_cats(platform_manager,"weibo"), + Message(BotReply.add_reply_on_cats(platform_manager,"weibo")), True, ) event_5_err = fake_group_message_event( message=Message("图文 文字 err"), sender=fake_admin_user ) ctx.receive_event(bot, event_5_err) - ctx.should_call_send(event_5_err, bot_reply.add_reply_on_cats_input_error("err"), True) + ctx.should_call_send(event_5_err, BotReply.add_reply_on_cats_input_error("err"), True) ctx.should_rejected() event_5_ok = fake_group_message_event( message=Message("图文 文字"), sender=fake_admin_user ) ctx.receive_event(bot, event_5_ok) - ctx.should_call_send(event_5_ok, bot_reply.add_reply_on_tags, True) + ctx.should_call_send(event_5_ok, Message(BotReply.add_reply_on_tags), True) event_6 = fake_group_message_event( message=Message("全部标签"), sender=fake_admin_user ) ctx.receive_event(bot, event_6) - ctx.should_call_send(event_6, bot_reply.add_reply_subscribe_success("明日方舟Arknights"), True) + ctx.should_call_send(event_6, BotReply.add_reply_subscribe_success("明日方舟Arknights"), True) ctx.should_finished() subs = config.list_subscribe(10000, "group") assert len(subs) == 1 @@ -187,7 +186,7 @@ async def test_add_with_target_no_cat(app: App): ctx.should_pass_rule() ctx.should_call_send( event_1, - bot_reply.add_reply_on_platform(platform_manager,common_platform), + Message(BotReply.add_reply_on_platform(platform_manager,common_platform)), True, ) event_3 = fake_group_message_event( @@ -196,7 +195,7 @@ async def test_add_with_target_no_cat(app: App): ctx.receive_event(bot, event_3) ctx.should_call_send( event_3, - bot_reply.add_reply_on_id, + Message(BotReply.add_reply_on_id), True, ) event_4_ok = fake_group_message_event( @@ -205,10 +204,10 @@ async def test_add_with_target_no_cat(app: App): ctx.receive_event(bot, event_4_ok) ctx.should_call_send( event_4_ok, - bot_reply.add_reply_on_target_confirm("ncm-artist","塞壬唱片-MSR","32540734"), + BotReply.add_reply_on_target_confirm("ncm-artist","塞壬唱片-MSR","32540734"), True ) - ctx.should_call_send(event_4_ok, bot_reply.add_reply_subscribe_success("塞壬唱片-MSR"), True) + ctx.should_call_send(event_4_ok, BotReply.add_reply_subscribe_success("塞壬唱片-MSR"), True) ctx.should_finished() subs = config.list_subscribe(10000, "group") assert len(subs) == 1 @@ -242,7 +241,7 @@ async def test_add_no_target(app: App): ctx.should_pass_rule() ctx.should_call_send( event_1, - bot_reply.add_reply_on_platform(platform_manager,common_platform), + Message(BotReply.add_reply_on_platform(platform_manager,common_platform)), True, ) event_3 = fake_group_message_event( @@ -251,20 +250,14 @@ async def test_add_no_target(app: App): ctx.receive_event(bot, event_3) ctx.should_call_send( event_3, - bot_reply.add_reply_on_target_confirm("arknights","明日方舟游戏信息","default") - , - True - ) - ctx.should_call_send( - event_3, - bot_reply.add_reply_on_cats(platform_manager,"arknights"), + Message(BotReply.add_reply_on_cats(platform_manager,"arknights")), True, ) event_4 = fake_group_message_event( message=Message("游戏公告"), sender=fake_admin_user ) ctx.receive_event(bot, event_4) - ctx.should_call_send(event_4, bot_reply.add_reply_subscribe_success("明日方舟游戏信息"), True) + ctx.should_call_send(event_4, BotReply.add_reply_subscribe_success("明日方舟游戏信息"), True) ctx.should_finished() subs = config.list_subscribe(10000, "group") assert len(subs) == 1 @@ -296,7 +289,7 @@ async def test_platform_name_err(app: App): ctx.should_pass_rule() ctx.should_call_send( event_1, - bot_reply.add_reply_on_platform(platform_manager,common_platform), + Message(BotReply.add_reply_on_platform(platform_manager,common_platform)), True, ) event_2 = fake_group_message_event( @@ -307,6 +300,6 @@ async def test_platform_name_err(app: App): ctx.should_rejected() ctx.should_call_send( event_2, - bot_reply.add_reply_on_platform_input_error, + BotReply.add_reply_on_platform_input_error, True, ) diff --git a/tests/utils.py b/tests/utils.py index 17f009f..6911ea9 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -73,7 +73,7 @@ fake_superuser = Sender(user_id=10001, nickname="superuser") from nonebot.adapters.onebot.v11.message import Message class BotReply: def add_reply_on_platform(platform_manager,common_platform): - return Message( + return ( "请输入想要订阅的平台,目前支持,请输入冒号左边的名称:\n" + "".join( [ @@ -98,20 +98,20 @@ class BotReply: search_title="Bison所支持的平台UID" search_content="查询相关平台的uid格式或获取方式" search_image="https://s3.bmp.ovh/imgs/2022/03/ab3cc45d83bd3dd3.jpg" - return Message(f"[CQ:share,url={search_url},title={search_title},content={search_content},image={search_image}") + return (f"[CQ:share,url={search_url},title={search_title},content={search_content},image={search_image}") def add_reply_on_target_confirm(platform,name,id): - return Message(f"即将订阅的用户为:{platform} {name} {id}\n如有错误请输入“取消”重新订阅") + return (f"即将订阅的用户为:{platform} {name} {id}\n如有错误请输入“取消”重新订阅") def add_reply_on_cats(platform_manager,platform:str): - return Message("请输入要订阅的类别,以空格分隔,支持的类别有:{}".format( + return ("请输入要订阅的类别,以空格分隔,支持的类别有:{}".format( " ".join(list(platform_manager[platform].categories.values())))) def add_reply_on_cats_input_error(cat:str): - return Message("不支持 {}".format(cat)) + return ("不支持 {}".format(cat)) def add_reply_subscribe_success(name): - return Message("添加 {} 成功".format(name)) + return ("添加 {} 成功".format(name)) add_reply_on_id_input_error="id输入错误" add_reply_on_platform_input_error="平台输入错误" From 48bca0447045892d5b226de818c353c53ef445af Mon Sep 17 00:00:00 2001 From: Azide <rukuy@qq.com> Date: Sun, 13 Mar 2022 01:50:49 +0800 Subject: [PATCH 24/42] =?UTF-8?q?=E6=88=90=E5=8A=9F=E5=BE=AE=E8=B0=83test?= =?UTF-8?q?=5Fconfig=5Fmanager=5Fabort=E4=BD=BF=E4=B9=8Bpass?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_config_manager_abort.py | 84 +++++++++--------------------- 1 file changed, 25 insertions(+), 59 deletions(-) diff --git a/tests/test_config_manager_abort.py b/tests/test_config_manager_abort.py index 5e9e1c5..bcc5c95 100644 --- a/tests/test_config_manager_abort.py +++ b/tests/test_config_manager_abort.py @@ -4,7 +4,7 @@ from httpx import Response from nonebug.app import App from .platforms.utils import get_json -from .utils import fake_admin_user, fake_group_message_event +from .utils import fake_admin_user, fake_group_message_event,BotReply # 选择platform阶段中止 @@ -44,16 +44,7 @@ async def test_abort_add_on_platform(app: App): ctx.should_call_send( event_1, Message( - "请输入想要订阅的平台,目前支持,请输入冒号左边的名称:\n" - + "".join( - [ - "{}:{}\n".format( - platform_name, platform_manager[platform_name].name - ) - for platform_name in common_platform - ] - ) - + "要查看全部平台请输入:“全部”\n中止订阅过程请输入:“取消”" + BotReply.add_reply_on_platform(platform_manager,common_platform) ), True, ) @@ -63,7 +54,7 @@ async def test_abort_add_on_platform(app: App): ctx.receive_event(bot, event_abort) ctx.should_call_send( event_abort, - "已中止订阅", + BotReply.add_reply_abort, True, ) ctx.should_finished() @@ -106,16 +97,7 @@ async def test_abort_add_on_id(app: App): ctx.should_call_send( event_1, Message( - "请输入想要订阅的平台,目前支持,请输入冒号左边的名称:\n" - + "".join( - [ - "{}:{}\n".format( - platform_name, platform_manager[platform_name].name - ) - for platform_name in common_platform - ] - ) - + "要查看全部平台请输入:“全部”\n中止订阅过程请输入:“取消”" + BotReply.add_reply_on_platform(platform_manager,common_platform) ), True, ) @@ -126,7 +108,7 @@ async def test_abort_add_on_id(app: App): ctx.should_call_send( event_2, Message( - "请输入订阅用户的id,详情查阅https://nonebot-bison.vercel.app/usage/#%E6%89%80%E6%94%AF%E6%8C%81%E5%B9%B3%E5%8F%B0%E7%9A%84uid" + BotReply.add_reply_on_id ), True, ) @@ -136,7 +118,7 @@ async def test_abort_add_on_id(app: App): ctx.receive_event(bot, event_abort) ctx.should_call_send( event_abort, - "已中止订阅", + BotReply.add_reply_abort, True, ) ctx.should_finished() @@ -179,16 +161,7 @@ async def test_abort_add_on_cats(app: App): ctx.should_call_send( event_1, Message( - "请输入想要订阅的平台,目前支持,请输入冒号左边的名称:\n" - + "".join( - [ - "{}:{}\n".format( - platform_name, platform_manager[platform_name].name - ) - for platform_name in common_platform - ] - ) - + "要查看全部平台请输入:“全部”\n中止订阅过程请输入:“取消”" + BotReply.add_reply_on_platform(platform_manager=platform_manager,common_platform=common_platform) ), True, ) @@ -199,7 +172,7 @@ async def test_abort_add_on_cats(app: App): ctx.should_call_send( event_2, Message( - "请输入订阅用户的id,详情查阅https://nonebot-bison.vercel.app/usage/#%E6%89%80%E6%94%AF%E6%8C%81%E5%B9%B3%E5%8F%B0%E7%9A%84uid" + BotReply.add_reply_on_id ), True, ) @@ -209,11 +182,12 @@ async def test_abort_add_on_cats(app: App): ctx.receive_event(bot, event_3) ctx.should_call_send( event_3, - Message( - "请输入要订阅的类别,以空格分隔,支持的类别有:{}".format( - " ".join(list(platform_manager["weibo"].categories.values())) - ) - ), + BotReply.add_reply_on_target_confirm("weibo","明日方舟Arknights","6279793937"), + True + ) + ctx.should_call_send( + event_3, + Message(BotReply.add_reply_on_cats(platform_manager,"weibo")), True, ) event_abort = fake_group_message_event( @@ -222,7 +196,7 @@ async def test_abort_add_on_cats(app: App): ctx.receive_event(bot, event_abort) ctx.should_call_send( event_abort, - "已中止订阅", + BotReply.add_reply_abort, True, ) ctx.should_finished() @@ -265,16 +239,7 @@ async def test_abort_add_on_tag(app: App): ctx.should_call_send( event_1, Message( - "请输入想要订阅的平台,目前支持,请输入冒号左边的名称:\n" - + "".join( - [ - "{}:{}\n".format( - platform_name, platform_manager[platform_name].name - ) - for platform_name in common_platform - ] - ) - + "要查看全部平台请输入:“全部”\n中止订阅过程请输入:“取消”" + BotReply.add_reply_on_platform(platform_manager=platform_manager,common_platform=common_platform) ), True, ) @@ -285,7 +250,7 @@ async def test_abort_add_on_tag(app: App): ctx.should_call_send( event_2, Message( - "请输入订阅用户的id,详情查阅https://nonebot-bison.vercel.app/usage/#%E6%89%80%E6%94%AF%E6%8C%81%E5%B9%B3%E5%8F%B0%E7%9A%84uid" + BotReply.add_reply_on_id ), True, ) @@ -295,25 +260,26 @@ async def test_abort_add_on_tag(app: App): ctx.receive_event(bot, event_3) ctx.should_call_send( event_3, - Message( - "请输入要订阅的类别,以空格分隔,支持的类别有:{}".format( - " ".join(list(platform_manager["weibo"].categories.values())) - ) - ), + BotReply.add_reply_on_target_confirm("weibo","明日方舟Arknights","6279793937"), + True + ) + ctx.should_call_send( + event_3, + Message(BotReply.add_reply_on_cats(platform_manager,"weibo")), True, ) event_4 = fake_group_message_event( message=Message("图文 文字"), sender=fake_admin_user ) ctx.receive_event(bot, event_4) - ctx.should_call_send(event_4, Message('请输入要订阅的tag,订阅所有tag输入"全部标签"'), True) + ctx.should_call_send(event_4, Message(BotReply.add_reply_on_tags), True) event_abort = fake_group_message_event( message=Message("取消"), sender=Sender(card="", nickname="test", role="admin") ) ctx.receive_event(bot, event_abort) ctx.should_call_send( event_abort, - "已中止订阅", + BotReply.add_reply_abort, True, ) ctx.should_finished() From c9bea2a780fe70608005c6bed53a0b07ee160564 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 12 Mar 2022 17:54:25 +0000 Subject: [PATCH 25/42] auto fix by pre-commit hooks --- src/plugins/nonebot_bison/config_manager.py | 20 +++--- tests/test_config_manager_abort.py | 46 ++++++------- tests/test_config_manager_add.py | 51 +++++++++----- tests/utils.py | 74 +++++++++++---------- 4 files changed, 107 insertions(+), 84 deletions(-) diff --git a/src/plugins/nonebot_bison/config_manager.py b/src/plugins/nonebot_bison/config_manager.py index 5e00414..de0f3bd 100644 --- a/src/plugins/nonebot_bison/config_manager.py +++ b/src/plugins/nonebot_bison/config_manager.py @@ -110,9 +110,7 @@ def do_add_sub(add_sub: Type[Matcher]): ) async def init_id(state: T_State): if platform_manager[state["platform"]].has_target: - state[ - "_prompt" - ] = "请输入订阅用户的id:\n查询id获取方法请回复:“查询”" + state["_prompt"] = "请输入订阅用户的id:\n查询id获取方法请回复:“查询”" else: state["id"] = "default" state["name"] = await platform_manager[state["platform"]].get_target_name( @@ -134,18 +132,22 @@ def do_add_sub(add_sub: Type[Matcher]): state["id"] = target state["name"] = name except (LookupError): - url="https://nonebot-bison.vercel.app/usage/#%E6%89%80%E6%94%AF%E6%8C%81%E5%B9%B3%E5%8F%B0%E7%9A%84-uid" - title="Bison所支持的平台UID" - content="查询相关平台的uid格式或获取方式" - image="https://s3.bmp.ovh/imgs/2022/03/ab3cc45d83bd3dd3.jpg" - getId_share=f"[CQ:share,url={url},title={title},content={content},image={image}]"#缩短字符串格式长度,以及方便后续修改为消息段格式 + url = "https://nonebot-bison.vercel.app/usage/#%E6%89%80%E6%94%AF%E6%8C%81%E5%B9%B3%E5%8F%B0%E7%9A%84-uid" + title = "Bison所支持的平台UID" + content = "查询相关平台的uid格式或获取方式" + image = "https://s3.bmp.ovh/imgs/2022/03/ab3cc45d83bd3dd3.jpg" + getId_share = f"[CQ:share,url={url},title={title},content={content},image={image}]" # 缩短字符串格式长度,以及方便后续修改为消息段格式 await add_sub.reject(Message(getId_share)) except (KeyboardInterrupt): await add_sub.finish("已中止订阅") except (ValueError): await add_sub.reject("id输入错误") else: - await add_sub.send("即将订阅的用户为:{} {} {}\n如有错误请输入“取消”重新订阅".format(state["platform"],state["name"],state["id"])) + await add_sub.send( + "即将订阅的用户为:{} {} {}\n如有错误请输入“取消”重新订阅".format( + state["platform"], state["name"], state["id"] + ) + ) @add_sub.got("id", _gen_prompt_template("{_prompt}"), [Depends(parse_id)]) async def init_cat(state: T_State): diff --git a/tests/test_config_manager_abort.py b/tests/test_config_manager_abort.py index bcc5c95..da307f4 100644 --- a/tests/test_config_manager_abort.py +++ b/tests/test_config_manager_abort.py @@ -4,7 +4,7 @@ from httpx import Response from nonebug.app import App from .platforms.utils import get_json -from .utils import fake_admin_user, fake_group_message_event,BotReply +from .utils import BotReply, fake_admin_user, fake_group_message_event # 选择platform阶段中止 @@ -43,9 +43,7 @@ async def test_abort_add_on_platform(app: App): ctx.should_pass_rule() ctx.should_call_send( event_1, - Message( - BotReply.add_reply_on_platform(platform_manager,common_platform) - ), + Message(BotReply.add_reply_on_platform(platform_manager, common_platform)), True, ) event_abort = fake_group_message_event( @@ -96,9 +94,7 @@ async def test_abort_add_on_id(app: App): ctx.should_pass_rule() ctx.should_call_send( event_1, - Message( - BotReply.add_reply_on_platform(platform_manager,common_platform) - ), + Message(BotReply.add_reply_on_platform(platform_manager, common_platform)), True, ) event_2 = fake_group_message_event( @@ -107,9 +103,7 @@ async def test_abort_add_on_id(app: App): ctx.receive_event(bot, event_2) ctx.should_call_send( event_2, - Message( - BotReply.add_reply_on_id - ), + Message(BotReply.add_reply_on_id), True, ) event_abort = fake_group_message_event( @@ -161,7 +155,9 @@ async def test_abort_add_on_cats(app: App): ctx.should_call_send( event_1, Message( - BotReply.add_reply_on_platform(platform_manager=platform_manager,common_platform=common_platform) + BotReply.add_reply_on_platform( + platform_manager=platform_manager, common_platform=common_platform + ) ), True, ) @@ -171,9 +167,7 @@ async def test_abort_add_on_cats(app: App): ctx.receive_event(bot, event_2) ctx.should_call_send( event_2, - Message( - BotReply.add_reply_on_id - ), + Message(BotReply.add_reply_on_id), True, ) event_3 = fake_group_message_event( @@ -182,12 +176,14 @@ async def test_abort_add_on_cats(app: App): ctx.receive_event(bot, event_3) ctx.should_call_send( event_3, - BotReply.add_reply_on_target_confirm("weibo","明日方舟Arknights","6279793937"), - True + BotReply.add_reply_on_target_confirm( + "weibo", "明日方舟Arknights", "6279793937" + ), + True, ) ctx.should_call_send( event_3, - Message(BotReply.add_reply_on_cats(platform_manager,"weibo")), + Message(BotReply.add_reply_on_cats(platform_manager, "weibo")), True, ) event_abort = fake_group_message_event( @@ -239,7 +235,9 @@ async def test_abort_add_on_tag(app: App): ctx.should_call_send( event_1, Message( - BotReply.add_reply_on_platform(platform_manager=platform_manager,common_platform=common_platform) + BotReply.add_reply_on_platform( + platform_manager=platform_manager, common_platform=common_platform + ) ), True, ) @@ -249,9 +247,7 @@ async def test_abort_add_on_tag(app: App): ctx.receive_event(bot, event_2) ctx.should_call_send( event_2, - Message( - BotReply.add_reply_on_id - ), + Message(BotReply.add_reply_on_id), True, ) event_3 = fake_group_message_event( @@ -260,12 +256,14 @@ async def test_abort_add_on_tag(app: App): ctx.receive_event(bot, event_3) ctx.should_call_send( event_3, - BotReply.add_reply_on_target_confirm("weibo","明日方舟Arknights","6279793937"), - True + BotReply.add_reply_on_target_confirm( + "weibo", "明日方舟Arknights", "6279793937" + ), + True, ) ctx.should_call_send( event_3, - Message(BotReply.add_reply_on_cats(platform_manager,"weibo")), + Message(BotReply.add_reply_on_cats(platform_manager, "weibo")), True, ) event_4 = fake_group_message_event( diff --git a/tests/test_config_manager_add.py b/tests/test_config_manager_add.py index 5e80e3e..497b135 100644 --- a/tests/test_config_manager_add.py +++ b/tests/test_config_manager_add.py @@ -4,7 +4,7 @@ from httpx import Response from nonebug.app import App from .platforms.utils import get_json -from .utils import fake_admin_user, fake_group_message_event, BotReply +from .utils import BotReply, fake_admin_user, fake_group_message_event @pytest.mark.asyncio @@ -31,6 +31,7 @@ async def test_configurable_at_me_true_failed(app: App): ctx.should_pass_rule() ctx.should_not_pass_permission() + @pytest.mark.asyncio async def test_configurable_at_me_false(app: App): from nonebot.adapters.onebot.v11.bot import Bot @@ -48,12 +49,13 @@ async def test_configurable_at_me_false(app: App): ctx.receive_event(bot, event) ctx.should_call_send( event, - Message(BotReply.add_reply_on_platform(platform_manager,common_platform)), + Message(BotReply.add_reply_on_platform(platform_manager, common_platform)), True, ) ctx.should_pass_rule() ctx.should_pass_permission() + @pytest.mark.asyncio @respx.mock async def test_add_with_target(app: App): @@ -90,7 +92,11 @@ async def test_add_with_target(app: App): ctx.should_pass_rule() ctx.should_call_send( event_1, - Message(BotReply.add_reply_on_platform(platform_manager=platform_manager,common_platform=common_platform)), + Message( + BotReply.add_reply_on_platform( + platform_manager=platform_manager, common_platform=common_platform + ) + ), True, ) event_2 = fake_group_message_event( @@ -124,19 +130,23 @@ async def test_add_with_target(app: App): ctx.receive_event(bot, event_4_ok) ctx.should_call_send( event_4_ok, - BotReply.add_reply_on_target_confirm("weibo","明日方舟Arknights","6279793937"), - True + BotReply.add_reply_on_target_confirm( + "weibo", "明日方舟Arknights", "6279793937" + ), + True, ) ctx.should_call_send( event_4_ok, - Message(BotReply.add_reply_on_cats(platform_manager,"weibo")), + Message(BotReply.add_reply_on_cats(platform_manager, "weibo")), True, ) event_5_err = fake_group_message_event( message=Message("图文 文字 err"), sender=fake_admin_user ) ctx.receive_event(bot, event_5_err) - ctx.should_call_send(event_5_err, BotReply.add_reply_on_cats_input_error("err"), True) + ctx.should_call_send( + event_5_err, BotReply.add_reply_on_cats_input_error("err"), True + ) ctx.should_rejected() event_5_ok = fake_group_message_event( message=Message("图文 文字"), sender=fake_admin_user @@ -147,7 +157,9 @@ async def test_add_with_target(app: App): message=Message("全部标签"), sender=fake_admin_user ) ctx.receive_event(bot, event_6) - ctx.should_call_send(event_6, BotReply.add_reply_subscribe_success("明日方舟Arknights"), True) + ctx.should_call_send( + event_6, BotReply.add_reply_subscribe_success("明日方舟Arknights"), True + ) ctx.should_finished() subs = config.list_subscribe(10000, "group") assert len(subs) == 1 @@ -160,6 +172,7 @@ async def test_add_with_target(app: App): assert sub["target_type"] == "weibo" assert sub["target_name"] == "明日方舟Arknights" + @pytest.mark.asyncio @respx.mock async def test_add_with_target_no_cat(app: App): @@ -186,7 +199,7 @@ async def test_add_with_target_no_cat(app: App): ctx.should_pass_rule() ctx.should_call_send( event_1, - Message(BotReply.add_reply_on_platform(platform_manager,common_platform)), + Message(BotReply.add_reply_on_platform(platform_manager, common_platform)), True, ) event_3 = fake_group_message_event( @@ -204,10 +217,12 @@ async def test_add_with_target_no_cat(app: App): ctx.receive_event(bot, event_4_ok) ctx.should_call_send( event_4_ok, - BotReply.add_reply_on_target_confirm("ncm-artist","塞壬唱片-MSR","32540734"), - True + BotReply.add_reply_on_target_confirm("ncm-artist", "塞壬唱片-MSR", "32540734"), + True, + ) + ctx.should_call_send( + event_4_ok, BotReply.add_reply_subscribe_success("塞壬唱片-MSR"), True ) - ctx.should_call_send(event_4_ok, BotReply.add_reply_subscribe_success("塞壬唱片-MSR"), True) ctx.should_finished() subs = config.list_subscribe(10000, "group") assert len(subs) == 1 @@ -218,6 +233,7 @@ async def test_add_with_target_no_cat(app: App): assert sub["target_type"] == "ncm-artist" assert sub["target_name"] == "塞壬唱片-MSR" + @pytest.mark.asyncio @respx.mock async def test_add_no_target(app: App): @@ -241,7 +257,7 @@ async def test_add_no_target(app: App): ctx.should_pass_rule() ctx.should_call_send( event_1, - Message(BotReply.add_reply_on_platform(platform_manager,common_platform)), + Message(BotReply.add_reply_on_platform(platform_manager, common_platform)), True, ) event_3 = fake_group_message_event( @@ -250,14 +266,16 @@ async def test_add_no_target(app: App): ctx.receive_event(bot, event_3) ctx.should_call_send( event_3, - Message(BotReply.add_reply_on_cats(platform_manager,"arknights")), + Message(BotReply.add_reply_on_cats(platform_manager, "arknights")), True, ) event_4 = fake_group_message_event( message=Message("游戏公告"), sender=fake_admin_user ) ctx.receive_event(bot, event_4) - ctx.should_call_send(event_4, BotReply.add_reply_subscribe_success("明日方舟游戏信息"), True) + ctx.should_call_send( + event_4, BotReply.add_reply_subscribe_success("明日方舟游戏信息"), True + ) ctx.should_finished() subs = config.list_subscribe(10000, "group") assert len(subs) == 1 @@ -268,6 +286,7 @@ async def test_add_no_target(app: App): assert sub["target_type"] == "arknights" assert sub["target_name"] == "明日方舟游戏信息" + @pytest.mark.asyncio async def test_platform_name_err(app: App): from nonebot.adapters.onebot.v11.event import Sender @@ -289,7 +308,7 @@ async def test_platform_name_err(app: App): ctx.should_pass_rule() ctx.should_call_send( event_1, - Message(BotReply.add_reply_on_platform(platform_manager,common_platform)), + Message(BotReply.add_reply_on_platform(platform_manager, common_platform)), True, ) event_2 = fake_group_message_event( diff --git a/tests/utils.py b/tests/utils.py index 6911ea9..aee9e2f 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -71,50 +71,54 @@ fake_admin_user = Sender(nickname="test", role="admin") fake_superuser = Sender(user_id=10001, nickname="superuser") from nonebot.adapters.onebot.v11.message import Message + + class BotReply: - def add_reply_on_platform(platform_manager,common_platform): + def add_reply_on_platform(platform_manager, common_platform): return ( - "请输入想要订阅的平台,目前支持,请输入冒号左边的名称:\n" - + "".join( - [ - "{}:{}\n".format( - platform_name, platform_manager[platform_name].name - ) - for platform_name in common_platform - ] - ) - + "要查看全部平台请输入:“全部”\n中止订阅过程请输入:“取消”" + "请输入想要订阅的平台,目前支持,请输入冒号左边的名称:\n" + + "".join( + [ + "{}:{}\n".format( + platform_name, platform_manager[platform_name].name + ) + for platform_name in common_platform + ] ) + + "要查看全部平台请输入:“全部”\n中止订阅过程请输入:“取消”" + ) def add_reply_on_platform_input_allplatform(platform_manager): return "全部平台\n" + "\n".join( - [ - "{}:{}".format(platform_name, platform.name) - for platform_name, platform in platform_manager.items() - ]) + [ + "{}:{}".format(platform_name, platform.name) + for platform_name, platform in platform_manager.items() + ] + ) def add_reply_on_id_input_search(): - search_url="https://nonebot-bison.vercel.app/usage/#%E6%89%80%E6%94%AF%E6%8C%81%E5%B9%B3%E5%8F%B0%E7%9A%84uid" - search_title="Bison所支持的平台UID" - search_content="查询相关平台的uid格式或获取方式" - search_image="https://s3.bmp.ovh/imgs/2022/03/ab3cc45d83bd3dd3.jpg" - return (f"[CQ:share,url={search_url},title={search_title},content={search_content},image={search_image}") - - def add_reply_on_target_confirm(platform,name,id): - return (f"即将订阅的用户为:{platform} {name} {id}\n如有错误请输入“取消”重新订阅") - - def add_reply_on_cats(platform_manager,platform:str): - return ("请输入要订阅的类别,以空格分隔,支持的类别有:{}".format( - " ".join(list(platform_manager[platform].categories.values())))) + search_url = "https://nonebot-bison.vercel.app/usage/#%E6%89%80%E6%94%AF%E6%8C%81%E5%B9%B3%E5%8F%B0%E7%9A%84uid" + search_title = "Bison所支持的平台UID" + search_content = "查询相关平台的uid格式或获取方式" + search_image = "https://s3.bmp.ovh/imgs/2022/03/ab3cc45d83bd3dd3.jpg" + return f"[CQ:share,url={search_url},title={search_title},content={search_content},image={search_image}" - def add_reply_on_cats_input_error(cat:str): - return ("不支持 {}".format(cat)) + def add_reply_on_target_confirm(platform, name, id): + return f"即将订阅的用户为:{platform} {name} {id}\n如有错误请输入“取消”重新订阅" + + def add_reply_on_cats(platform_manager, platform: str): + return "请输入要订阅的类别,以空格分隔,支持的类别有:{}".format( + " ".join(list(platform_manager[platform].categories.values())) + ) + + def add_reply_on_cats_input_error(cat: str): + return "不支持 {}".format(cat) def add_reply_subscribe_success(name): - return ("添加 {} 成功".format(name)) + return "添加 {} 成功".format(name) - add_reply_on_id_input_error="id输入错误" - add_reply_on_platform_input_error="平台输入错误" - add_reply_on_id="请输入订阅用户的id:\n查询id获取方法请回复:“查询”" - add_reply_on_tags='请输入要订阅的tag,订阅所有tag输入"全部标签"' - add_reply_abort="已中止订阅" \ No newline at end of file + add_reply_on_id_input_error = "id输入错误" + add_reply_on_platform_input_error = "平台输入错误" + add_reply_on_id = "请输入订阅用户的id:\n查询id获取方法请回复:“查询”" + add_reply_on_tags = '请输入要订阅的tag,订阅所有tag输入"全部标签"' + add_reply_abort = "已中止订阅" From 098aa959cc9bd3e97d167984d1e3dd82442336b0 Mon Sep 17 00:00:00 2001 From: Azide <rukuy@qq.com> Date: Sun, 13 Mar 2022 14:56:07 +0800 Subject: [PATCH 26/42] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E5=9C=A8?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=AE=A2=E9=98=85=E8=BF=87=E7=A8=8B=E4=B8=AD?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0id=E6=AD=A5=E9=AA=A4=E8=BE=93=E5=85=A5=20?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=20=E6=97=B6bot=E5=BA=94=E5=9B=9E=E5=A4=8D?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E7=9A=84test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_config_manager_add.py | 76 ++++++++++++++++++++++++++++++++ tests/utils.py | 16 +++++-- 2 files changed, 88 insertions(+), 4 deletions(-) diff --git a/tests/test_config_manager_add.py b/tests/test_config_manager_add.py index 5e80e3e..afd4e3d 100644 --- a/tests/test_config_manager_add.py +++ b/tests/test_config_manager_add.py @@ -303,3 +303,79 @@ async def test_platform_name_err(app: App): BotReply.add_reply_on_platform_input_error, True, ) + +@pytest.mark.asyncio +@respx.mock +async def test_add_with_get_id(app: App): + from nonebot.adapters.onebot.v11.event import Sender + from nonebot.adapters.onebot.v11.message import Message,MessageSegment + from nonebot_bison.config import Config + from nonebot_bison.config_manager import add_sub_matcher, common_platform + from nonebot_bison.platform import platform_manager + + config = Config() + config.user_target.truncate() + + ak_list_router = respx.get( + "https://m.weibo.cn/api/container/getIndex?containerid=1005056279793937" + ) + ak_list_router.mock( + return_value=Response(200, json=get_json("weibo_ak_profile.json")) + ) + ak_list_bad_router = respx.get( + "https://m.weibo.cn/api/container/getIndex?containerid=100505000" + ) + ak_list_bad_router.mock( + return_value=Response(200, json=get_json("weibo_err_profile.json")) + ) + + async with app.test_matcher(add_sub_matcher) as ctx: + bot = ctx.create_bot() + event_1 = fake_group_message_event( + message=Message("添加订阅"), + sender=Sender(card="", nickname="test", role="admin"), + to_me=True, + ) + ctx.receive_event(bot, event_1) + ctx.should_pass_rule() + ctx.should_call_send( + event_1, + Message(BotReply.add_reply_on_platform(platform_manager=platform_manager,common_platform=common_platform)), + True, + ) + event_3 = fake_group_message_event( + message=Message("weibo"), sender=fake_admin_user + ) + ctx.receive_event(bot, event_3) + ctx.should_call_send( + event_3, + Message(BotReply.add_reply_on_id), + True, + ) + event_4_query = fake_group_message_event( + message=Message("查询"), sender=fake_admin_user + ) + ctx.receive_event(bot, event_4_query) + ctx.should_rejected() + ctx.should_call_send( + event_4_query, + [MessageSegment(*BotReply.add_reply_on_id_input_search())], + True + ) + ''' + line 362: + 鬼知道为什么要在这里这样写, + 没有[]的话assert不了(should_call_send使用[MessageSegment(...)]的格式进行比较) + 不在这里MessageSegment()的话也assert不了(指不能让add_reply_on_id_input_search直接返回一个MessageSegment对象) + amen + ''' + event_abort = fake_group_message_event( + message=Message("取消"), sender=Sender(card="", nickname="test", role="admin") + ) + ctx.receive_event(bot, event_abort) + ctx.should_call_send( + event_abort, + BotReply.add_reply_abort, + True, + ) + ctx.should_finished() \ No newline at end of file diff --git a/tests/utils.py b/tests/utils.py index 6911ea9..796bd61 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -70,7 +70,7 @@ from nonebot.adapters.onebot.v11.event import Sender fake_admin_user = Sender(nickname="test", role="admin") fake_superuser = Sender(user_id=10001, nickname="superuser") -from nonebot.adapters.onebot.v11.message import Message +from nonebot.adapters.onebot.v11.message import Message,MessageSegment class BotReply: def add_reply_on_platform(platform_manager,common_platform): return ( @@ -94,11 +94,19 @@ class BotReply: ]) def add_reply_on_id_input_search(): - search_url="https://nonebot-bison.vercel.app/usage/#%E6%89%80%E6%94%AF%E6%8C%81%E5%B9%B3%E5%8F%B0%E7%9A%84uid" + search_url="https://nonebot-bison.vercel.app/usage/#%E6%89%80%E6%94%AF%E6%8C%81%E5%B9%B3%E5%8F%B0%E7%9A%84-uid" search_title="Bison所支持的平台UID" search_content="查询相关平台的uid格式或获取方式" - search_image="https://s3.bmp.ovh/imgs/2022/03/ab3cc45d83bd3dd3.jpg" - return (f"[CQ:share,url={search_url},title={search_title},content={search_content},image={search_image}") + search_image="https://s3.bmp.ovh/imgs/2022/03/ab3cc45d83bd3dd3.jpg" + type='share' + data={ + 'url':search_url, + 'title':search_title, + 'content':search_content, + 'image':search_image + } + msg=[type,data] + return msg def add_reply_on_target_confirm(platform,name,id): return (f"即将订阅的用户为:{platform} {name} {id}\n如有错误请输入“取消”重新订阅") From 036ab31f850675c77b9b110ecbd859a542173a37 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 13 Mar 2022 07:01:33 +0000 Subject: [PATCH 27/42] auto fix by pre-commit hooks --- tests/test_config_manager_add.py | 17 ++++++++---- tests/utils.py | 47 +++++++++++++++++--------------- 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/tests/test_config_manager_add.py b/tests/test_config_manager_add.py index a4f681a..65447ed 100644 --- a/tests/test_config_manager_add.py +++ b/tests/test_config_manager_add.py @@ -323,11 +323,12 @@ async def test_platform_name_err(app: App): True, ) + @pytest.mark.asyncio @respx.mock async def test_add_with_get_id(app: App): from nonebot.adapters.onebot.v11.event import Sender - from nonebot.adapters.onebot.v11.message import Message,MessageSegment + from nonebot.adapters.onebot.v11.message import Message, MessageSegment from nonebot_bison.config import Config from nonebot_bison.config_manager import add_sub_matcher, common_platform from nonebot_bison.platform import platform_manager @@ -359,7 +360,11 @@ async def test_add_with_get_id(app: App): ctx.should_pass_rule() ctx.should_call_send( event_1, - Message(BotReply.add_reply_on_platform(platform_manager=platform_manager,common_platform=common_platform)), + Message( + BotReply.add_reply_on_platform( + platform_manager=platform_manager, common_platform=common_platform + ) + ), True, ) event_3 = fake_group_message_event( @@ -379,15 +384,15 @@ async def test_add_with_get_id(app: App): ctx.should_call_send( event_4_query, [MessageSegment(*BotReply.add_reply_on_id_input_search())], - True + True, ) - ''' + """ line 362: 鬼知道为什么要在这里这样写, 没有[]的话assert不了(should_call_send使用[MessageSegment(...)]的格式进行比较) 不在这里MessageSegment()的话也assert不了(指不能让add_reply_on_id_input_search直接返回一个MessageSegment对象) amen - ''' + """ event_abort = fake_group_message_event( message=Message("取消"), sender=Sender(card="", nickname="test", role="admin") ) @@ -397,4 +402,4 @@ async def test_add_with_get_id(app: App): BotReply.add_reply_abort, True, ) - ctx.should_finished() \ No newline at end of file + ctx.should_finished() diff --git a/tests/utils.py b/tests/utils.py index 3005f10..4d957f4 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -70,7 +70,9 @@ from nonebot.adapters.onebot.v11.event import Sender fake_admin_user = Sender(nickname="test", role="admin") fake_superuser = Sender(user_id=10001, nickname="superuser") -from nonebot.adapters.onebot.v11.message import Message,MessageSegment +from nonebot.adapters.onebot.v11.message import Message, MessageSegment + + class BotReply: def add_reply_on_platform(platform_manager, common_platform): return ( @@ -95,29 +97,30 @@ class BotReply: ) def add_reply_on_id_input_search(): - search_url="https://nonebot-bison.vercel.app/usage/#%E6%89%80%E6%94%AF%E6%8C%81%E5%B9%B3%E5%8F%B0%E7%9A%84-uid" - search_title="Bison所支持的平台UID" - search_content="查询相关平台的uid格式或获取方式" - search_image="https://s3.bmp.ovh/imgs/2022/03/ab3cc45d83bd3dd3.jpg" - type='share' - data={ - 'url':search_url, - 'title':search_title, - 'content':search_content, - 'image':search_image - } - msg=[type,data] + search_url = "https://nonebot-bison.vercel.app/usage/#%E6%89%80%E6%94%AF%E6%8C%81%E5%B9%B3%E5%8F%B0%E7%9A%84-uid" + search_title = "Bison所支持的平台UID" + search_content = "查询相关平台的uid格式或获取方式" + search_image = "https://s3.bmp.ovh/imgs/2022/03/ab3cc45d83bd3dd3.jpg" + type = "share" + data = { + "url": search_url, + "title": search_title, + "content": search_content, + "image": search_image, + } + msg = [type, data] return msg - - def add_reply_on_target_confirm(platform,name,id): - return (f"即将订阅的用户为:{platform} {name} {id}\n如有错误请输入“取消”重新订阅") - - def add_reply_on_cats(platform_manager,platform:str): - return ("请输入要订阅的类别,以空格分隔,支持的类别有:{}".format( - " ".join(list(platform_manager[platform].categories.values())))) - def add_reply_on_cats_input_error(cat:str): - return ("不支持 {}".format(cat)) + def add_reply_on_target_confirm(platform, name, id): + return f"即将订阅的用户为:{platform} {name} {id}\n如有错误请输入“取消”重新订阅" + + def add_reply_on_cats(platform_manager, platform: str): + return "请输入要订阅的类别,以空格分隔,支持的类别有:{}".format( + " ".join(list(platform_manager[platform].categories.values())) + ) + + def add_reply_on_cats_input_error(cat: str): + return "不支持 {}".format(cat) def add_reply_subscribe_success(name): return "添加 {} 成功".format(name) From 8226c526662527c212940714bcbb68cefa0fae75 Mon Sep 17 00:00:00 2001 From: Azide <rukuy@qq.com> Date: Sun, 13 Mar 2022 15:59:41 +0800 Subject: [PATCH 28/42] =?UTF-8?q?=E8=A1=A5=E5=85=85README.MD=E7=9A=84?= =?UTF-8?q?=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ docs/usage/README.md | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d3c6348..916d729 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,8 @@ yarn && yarn build 本项目使用了 Python 3.9 的语法,请将 Python 版本升级到 3.9 及以上,推荐使用 docker 部署 2. bot 不理我 请确认自己是群主或者管理员,并且检查`COMMAND_START`环境变量是否设为`[""]` + 或者按照`COMMAND_START`中的设置添加命令前缀,例: + `COMMAND_START=["/"]`则应发送`/添加订阅` 3. 微博漏订阅了 微博更新了新的风控措施,某些含有某些关键词的微博会获取不到。 diff --git a/docs/usage/README.md b/docs/usage/README.md index 530b908..099fb7c 100644 --- a/docs/usage/README.md +++ b/docs/usage/README.md @@ -136,8 +136,9 @@ sidebar: auto ## 使用 ::: warning -本节假设`COMMAND_START`设置中包含`''`,如果出现 bot 不响应的问题,请先 -排查这个设置 +本节假设`COMMAND_START`设置中包含`''` +- 如果出现 bot 不响应的问题,请先排查这个设置 +- 尝试在命令前添加设置的命令前缀,如`COMMAND_START=['/']`,则尝试使用`/添加订阅` ::: ### 命令 From 8d6d5c9e8b7bbe5187ed04b6d75877deccf8ea87 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 13 Mar 2022 08:20:02 +0000 Subject: [PATCH 29/42] auto fix by pre-commit hooks --- docs/usage/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/usage/README.md b/docs/usage/README.md index 099fb7c..bcc48fc 100644 --- a/docs/usage/README.md +++ b/docs/usage/README.md @@ -137,9 +137,10 @@ sidebar: auto ::: warning 本节假设`COMMAND_START`设置中包含`''` + - 如果出现 bot 不响应的问题,请先排查这个设置 - 尝试在命令前添加设置的命令前缀,如`COMMAND_START=['/']`,则尝试使用`/添加订阅` -::: + ::: ### 命令 From 05ec0659d1876a0712c1833f6f4b829ccb9e9f9a Mon Sep 17 00:00:00 2001 From: felinae98 <731499577@qq.com> Date: Sun, 13 Mar 2022 16:45:50 +0800 Subject: [PATCH 30/42] add @staticmethod for util class --- tests/utils.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/utils.py b/tests/utils.py index 4d957f4..bd1930d 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -70,10 +70,9 @@ from nonebot.adapters.onebot.v11.event import Sender fake_admin_user = Sender(nickname="test", role="admin") fake_superuser = Sender(user_id=10001, nickname="superuser") -from nonebot.adapters.onebot.v11.message import Message, MessageSegment - class BotReply: + @staticmethod def add_reply_on_platform(platform_manager, common_platform): return ( "请输入想要订阅的平台,目前支持,请输入冒号左边的名称:\n" @@ -88,6 +87,7 @@ class BotReply: + "要查看全部平台请输入:“全部”\n中止订阅过程请输入:“取消”" ) + @staticmethod def add_reply_on_platform_input_allplatform(platform_manager): return "全部平台\n" + "\n".join( [ @@ -96,6 +96,7 @@ class BotReply: ] ) + @staticmethod def add_reply_on_id_input_search(): search_url = "https://nonebot-bison.vercel.app/usage/#%E6%89%80%E6%94%AF%E6%8C%81%E5%B9%B3%E5%8F%B0%E7%9A%84-uid" search_title = "Bison所支持的平台UID" @@ -111,17 +112,21 @@ class BotReply: msg = [type, data] return msg + @staticmethod def add_reply_on_target_confirm(platform, name, id): return f"即将订阅的用户为:{platform} {name} {id}\n如有错误请输入“取消”重新订阅" + @staticmethod def add_reply_on_cats(platform_manager, platform: str): return "请输入要订阅的类别,以空格分隔,支持的类别有:{}".format( " ".join(list(platform_manager[platform].categories.values())) ) + @staticmethod def add_reply_on_cats_input_error(cat: str): return "不支持 {}".format(cat) + @staticmethod def add_reply_subscribe_success(name): return "添加 {} 成功".format(name) From e189845d7b9ab8fbb29572df1587f1155323ab62 Mon Sep 17 00:00:00 2001 From: felinae98 <731499577@qq.com> Date: Wed, 16 Mar 2022 00:25:08 +0800 Subject: [PATCH 31/42] update image path --- .../public/images}/forward-msg-simple1.png | Bin .../public/images}/forward-msg-simple2.png | Bin docs/usage/README.md | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename docs/{usage/pic => .vuepress/public/images}/forward-msg-simple1.png (100%) rename docs/{usage/pic => .vuepress/public/images}/forward-msg-simple2.png (100%) diff --git a/docs/usage/pic/forward-msg-simple1.png b/docs/.vuepress/public/images/forward-msg-simple1.png similarity index 100% rename from docs/usage/pic/forward-msg-simple1.png rename to docs/.vuepress/public/images/forward-msg-simple1.png diff --git a/docs/usage/pic/forward-msg-simple2.png b/docs/.vuepress/public/images/forward-msg-simple2.png similarity index 100% rename from docs/usage/pic/forward-msg-simple2.png rename to docs/.vuepress/public/images/forward-msg-simple2.png diff --git a/docs/usage/README.md b/docs/usage/README.md index bcc48fc..24a8be0 100644 --- a/docs/usage/README.md +++ b/docs/usage/README.md @@ -124,9 +124,9 @@ sidebar: auto ::: details 配置项示例 - 当`BISON_USE_PIC_MERGE=1`时: -  +  - 当`BISON_USE_PIC_MERGE=2`时: -  +  ::: ::: warning From 029f21defbe09232802dc694cc30a9a70cf1f1be Mon Sep 17 00:00:00 2001 From: felinae98 <731499577@qq.com> Date: Wed, 16 Mar 2022 00:32:02 +0800 Subject: [PATCH 32/42] fix site action --- docs/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index 704fce1..e1c53d8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,8 +2,9 @@ home: true heroText: Nonebot Bison tagline: 本bot励志做全泰拉骑车最快的信使 -actionText: 快速部署 -actionLink: /usage/ +actions: + - text: 快速部署 + link: /usage/ features: - title: 拓展性强 details: 没有自己想要的网站?只要简单的爬虫知识就可以给它适配一个新的网站 From 5f613bd6c8fb0fb0c38a460619d63d9fc2468aff Mon Sep 17 00:00:00 2001 From: Azide <rukuy@qq.com> Date: Mon, 14 Mar 2022 17:46:27 +0800 Subject: [PATCH 33/42] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E6=9D=83=E9=99=90=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/nonebot_bison/config_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/nonebot_bison/config_manager.py b/src/plugins/nonebot_bison/config_manager.py index de0f3bd..8d8b483 100644 --- a/src/plugins/nonebot_bison/config_manager.py +++ b/src/plugins/nonebot_bison/config_manager.py @@ -317,11 +317,11 @@ del_sub_matcher = on_command( del_sub_matcher.handle()(set_target_user_info) do_del_sub(del_sub_matcher) -group_manage_matcher = on_command("群管理") +group_manage_matcher = on_command("群管理",rule=to_me(),permission=SUPERUSER,priority=4) @group_manage_matcher.handle() -async def send_group_list(bot: Bot, state: T_State): +async def send_group_list(bot: Bot, event: PrivateMessageEvent, state: T_State): groups = await bot.call_api("get_group_list") res_text = "请选择需要管理的群:\n" group_number_idx = {} From 27bef47dc189c3e549d9b74f8992645300c6816f Mon Sep 17 00:00:00 2001 From: Azide <rukuy@qq.com> Date: Mon, 14 Mar 2022 20:04:14 +0800 Subject: [PATCH 34/42] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86group=5Fmanage?= =?UTF-8?q?=5Fmatcher=E7=9A=84=E9=87=8D=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/nonebot_bison/config_manager.py | 5 +++- tests/test_config_manager_admin.py | 26 +++++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/plugins/nonebot_bison/config_manager.py b/src/plugins/nonebot_bison/config_manager.py index 8d8b483..5a148d0 100644 --- a/src/plugins/nonebot_bison/config_manager.py +++ b/src/plugins/nonebot_bison/config_manager.py @@ -12,7 +12,7 @@ from nonebot.internal.params import ArgStr from nonebot.internal.rule import Rule from nonebot.log import logger from nonebot.matcher import Matcher -from nonebot.params import Depends, EventMessage, EventPlainText, EventToMe +from nonebot.params import Depends, EventMessage, EventPlainText, EventToMe, EventType from nonebot.permission import SUPERUSER from nonebot.rule import to_me from nonebot.typing import T_State @@ -319,6 +319,9 @@ do_del_sub(del_sub_matcher) group_manage_matcher = on_command("群管理",rule=to_me(),permission=SUPERUSER,priority=4) +@group_manage_matcher.handle() +async def send_group_list(bot: Bot, event: GroupMessageEvent, state: T_State): + await group_manage_matcher.finish(Message("该功能只支持私聊使用,请私聊Bot")) @group_manage_matcher.handle() async def send_group_list(bot: Bot, event: PrivateMessageEvent, state: T_State): diff --git a/tests/test_config_manager_admin.py b/tests/test_config_manager_admin.py index fc6581d..77e9ac1 100644 --- a/tests/test_config_manager_admin.py +++ b/tests/test_config_manager_admin.py @@ -1,9 +1,11 @@ from nonebug import App -from .utils import fake_admin_user, fake_private_message_event, fake_superuser +from .utils import fake_admin_user, fake_private_message_event, fake_group_message_event, fake_superuser +import pytest -async def test_query(app: App): +@pytest.mark.asyncio +async def test_query_with_superuser_private(app: App): from nonebot.adapters.onebot.v11.bot import Bot from nonebot.adapters.onebot.v11.message import Message from nonebot_bison.config_manager import group_manage_matcher @@ -43,3 +45,23 @@ async def test_query(app: App): message=Message("查询订阅"), sender=fake_superuser ) ctx.receive_event(bot, event_2_ok) + ctx.should_pass_rule() + ctx.should_pass_permission() + +@pytest.mark.asyncio +async def test_query_with_superuser_group_tome(app: App): + from nonebot.adapters.onebot.v11.bot import Bot + from nonebot.adapters.onebot.v11.message import Message + from nonebot_bison.config_manager import group_manage_matcher + + async with app.test_matcher(group_manage_matcher) as ctx: + bot = ctx.create_bot(base=Bot) + event = fake_group_message_event( + message=Message("群管理"), sender=fake_superuser,to_me=True + ) + ctx.receive_event(bot, event) + ctx.should_pass_rule() + ctx.should_pass_permission() + ctx.should_call_send( + event, '', True + ) \ No newline at end of file From 82e4ad1bb67ad51414a7bbbb3492ec2255a7bed9 Mon Sep 17 00:00:00 2001 From: felinae98 <731499577@qq.com> Date: Mon, 14 Mar 2022 23:26:50 +0800 Subject: [PATCH 35/42] fix some bug --- tests/test_config_manager_admin.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_config_manager_admin.py b/tests/test_config_manager_admin.py index 77e9ac1..34f8586 100644 --- a/tests/test_config_manager_admin.py +++ b/tests/test_config_manager_admin.py @@ -13,7 +13,10 @@ async def test_query_with_superuser_private(app: App): async with app.test_matcher(group_manage_matcher) as ctx: bot = ctx.create_bot(base=Bot) event = fake_private_message_event( - message=Message("群管理"), sender=fake_superuser + message=Message("群管理"), + sender=fake_superuser, + to_me=True, + user_id=fake_superuser.user_id, ) ctx.receive_event(bot, event) ctx.should_pass_rule() From 53273d2d6f109a048ad58e22dc1113349ad25721 Mon Sep 17 00:00:00 2001 From: Azide <rukuy@qq.com> Date: Mon, 14 Mar 2022 23:47:42 +0800 Subject: [PATCH 36/42] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BA=86test=5Fconfig?= =?UTF-8?q?=5Fmanager=5Fadmin=E4=B8=AD=E7=9A=84test=E9=A1=BA=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_config_manager_admin.py | 43 +++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/tests/test_config_manager_admin.py b/tests/test_config_manager_admin.py index 34f8586..1d8ebf8 100644 --- a/tests/test_config_manager_admin.py +++ b/tests/test_config_manager_admin.py @@ -1,8 +1,13 @@ +import pytest from nonebug import App -from .utils import fake_admin_user, fake_private_message_event, fake_group_message_event, fake_superuser +from .utils import ( + fake_admin_user, + fake_group_message_event, + fake_private_message_event, + fake_superuser, +) -import pytest @pytest.mark.asyncio async def test_query_with_superuser_private(app: App): @@ -28,29 +33,42 @@ async def test_query_with_superuser_private(app: App): event, Message("请选择需要管理的群:\n1. 101 - test group\n请输入左侧序号"), True ) event_1_err = fake_private_message_event( - message=Message("0"), sender=fake_superuser + message=Message("0"), + sender=fake_superuser, + to_me=True, + user_id=fake_superuser.user_id, ) ctx.receive_event(bot, event_1_err) - ctx.should_rejected() ctx.should_call_send(event_1_err, "请输入正确序号", True) + ctx.should_rejected() event_1_ok = fake_private_message_event( - message=Message("1"), sender=fake_superuser + message=Message("1"), + sender=fake_superuser, + to_me=True, + user_id=fake_superuser.user_id, ) ctx.receive_event(bot, event_1_ok) ctx.should_call_send(event_1_ok, "请输入需要使用的命令:添加订阅,查询订阅,删除订阅", True) event_2_err = fake_private_message_event( - message=Message("222"), sender=fake_superuser + message=Message("222"), + sender=fake_superuser, + to_me=True, + user_id=fake_superuser.user_id, ) ctx.receive_event(bot, event_2_err) - ctx.should_rejected() ctx.should_call_send(event_2_err, "请输入正确的命令", True) + ctx.should_rejected() event_2_ok = fake_private_message_event( - message=Message("查询订阅"), sender=fake_superuser + message=Message("查询订阅"), + sender=fake_superuser, + to_me=True, + user_id=fake_superuser.user_id, ) ctx.receive_event(bot, event_2_ok) ctx.should_pass_rule() ctx.should_pass_permission() + @pytest.mark.asyncio async def test_query_with_superuser_group_tome(app: App): from nonebot.adapters.onebot.v11.bot import Bot @@ -60,11 +78,12 @@ async def test_query_with_superuser_group_tome(app: App): async with app.test_matcher(group_manage_matcher) as ctx: bot = ctx.create_bot(base=Bot) event = fake_group_message_event( - message=Message("群管理"), sender=fake_superuser,to_me=True + message=Message("群管理"), + sender=fake_superuser, + to_me=True, + user_id=fake_superuser.user_id, ) ctx.receive_event(bot, event) ctx.should_pass_rule() ctx.should_pass_permission() - ctx.should_call_send( - event, '', True - ) \ No newline at end of file + ctx.should_call_send(event, "", True) From 067317d6df7d539b134f42c671ff37c6c0ce0d2e Mon Sep 17 00:00:00 2001 From: Azide <rukuy@qq.com> Date: Wed, 16 Mar 2022 12:39:36 +0800 Subject: [PATCH 37/42] =?UTF-8?q?=E5=90=8C=E6=AD=A5fix=E5=88=86=E6=94=AF?= =?UTF-8?q?=E7=9A=84test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_config_manager_admin.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/test_config_manager_admin.py b/tests/test_config_manager_admin.py index 1d8ebf8..51d7e35 100644 --- a/tests/test_config_manager_admin.py +++ b/tests/test_config_manager_admin.py @@ -1,12 +1,7 @@ import pytest from nonebug import App -from .utils import ( - fake_admin_user, - fake_group_message_event, - fake_private_message_event, - fake_superuser, -) +from .utils import fake_group_message_event, fake_private_message_event, fake_superuser @pytest.mark.asyncio @@ -86,4 +81,4 @@ async def test_query_with_superuser_group_tome(app: App): ctx.receive_event(bot, event) ctx.should_pass_rule() ctx.should_pass_permission() - ctx.should_call_send(event, "", True) + ctx.should_call_send(event, Message("该功能只支持私聊使用,请私聊Bot"), True) From 0daf27abdcd06b8b29f8fc109db6545642e1eed4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 16 Mar 2022 14:09:42 +0000 Subject: [PATCH 38/42] auto fix by pre-commit hooks --- src/plugins/nonebot_bison/config_manager.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/plugins/nonebot_bison/config_manager.py b/src/plugins/nonebot_bison/config_manager.py index 5a148d0..afb4f3b 100644 --- a/src/plugins/nonebot_bison/config_manager.py +++ b/src/plugins/nonebot_bison/config_manager.py @@ -317,12 +317,14 @@ del_sub_matcher = on_command( del_sub_matcher.handle()(set_target_user_info) do_del_sub(del_sub_matcher) -group_manage_matcher = on_command("群管理",rule=to_me(),permission=SUPERUSER,priority=4) +group_manage_matcher = on_command("群管理", rule=to_me(), permission=SUPERUSER, priority=4) + @group_manage_matcher.handle() async def send_group_list(bot: Bot, event: GroupMessageEvent, state: T_State): await group_manage_matcher.finish(Message("该功能只支持私聊使用,请私聊Bot")) + @group_manage_matcher.handle() async def send_group_list(bot: Bot, event: PrivateMessageEvent, state: T_State): groups = await bot.call_api("get_group_list") From dc55a6bf631b92a8241f3ba56b5e4a7096f2a4d6 Mon Sep 17 00:00:00 2001 From: AzideCupric <57004769+AzideCupric@users.noreply.github.com> Date: Wed, 16 Mar 2022 22:23:39 +0800 Subject: [PATCH 39/42] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=9C=AA=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E7=9A=84EventType?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/nonebot_bison/config_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/nonebot_bison/config_manager.py b/src/plugins/nonebot_bison/config_manager.py index afb4f3b..301cf25 100644 --- a/src/plugins/nonebot_bison/config_manager.py +++ b/src/plugins/nonebot_bison/config_manager.py @@ -12,7 +12,7 @@ from nonebot.internal.params import ArgStr from nonebot.internal.rule import Rule from nonebot.log import logger from nonebot.matcher import Matcher -from nonebot.params import Depends, EventMessage, EventPlainText, EventToMe, EventType +from nonebot.params import Depends, EventMessage, EventPlainText, EventToMe from nonebot.permission import SUPERUSER from nonebot.rule import to_me from nonebot.typing import T_State From f34cdaa211d21fdf1b9230a826d768f62cecaed9 Mon Sep 17 00:00:00 2001 From: felinae98 <731499577@qq.com> Date: Wed, 16 Mar 2022 22:31:33 +0800 Subject: [PATCH 40/42] update config_manager --- src/plugins/nonebot_bison/config_manager.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/plugins/nonebot_bison/config_manager.py b/src/plugins/nonebot_bison/config_manager.py index afb4f3b..6c184a1 100644 --- a/src/plugins/nonebot_bison/config_manager.py +++ b/src/plugins/nonebot_bison/config_manager.py @@ -1,5 +1,4 @@ import asyncio -from asyncio.tasks import Task from datetime import datetime from typing import Optional, Type @@ -12,7 +11,7 @@ from nonebot.internal.params import ArgStr from nonebot.internal.rule import Rule from nonebot.log import logger from nonebot.matcher import Matcher -from nonebot.params import Depends, EventMessage, EventPlainText, EventToMe, EventType +from nonebot.params import Depends, EventPlainText, EventToMe from nonebot.permission import SUPERUSER from nonebot.rule import to_me from nonebot.typing import T_State @@ -321,7 +320,7 @@ group_manage_matcher = on_command("群管理", rule=to_me(), permission=SUPERUSE @group_manage_matcher.handle() -async def send_group_list(bot: Bot, event: GroupMessageEvent, state: T_State): +async def send_group_list_private(bot: Bot, event: GroupMessageEvent, state: T_State): await group_manage_matcher.finish(Message("该功能只支持私聊使用,请私聊Bot")) From 4f4767a773df7d4b2a27225dd04029684973ea19 Mon Sep 17 00:00:00 2001 From: Azide <rukuy@qq.com> Date: Thu, 17 Mar 2022 00:23:51 +0800 Subject: [PATCH 41/42] update changelog.md --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 953fae6..8f68581 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,5 +66,8 @@ ## [0.5.1] -- 使用了新的私聊进行群管理的方式 +- 使用了新的在私聊中进行群管理的方式:从`管理-*`替换为`群管理`命令 - 默认关闭自动重发功能 +- 添加了 [推送消息合并转发功能](https://nonebot-bison.vercel.app/usage/#%E9%85%8D%E7%BD%AE) +- 添加了`添加订阅`命令事件的中途取消功能 +- 优化了`添加订阅`命令的聊天处理逻辑 From d10dcdaa0c5c55c8936cca18f4665800cbaea68a Mon Sep 17 00:00:00 2001 From: felinae98 <731499577@qq.com> Date: Thu, 17 Mar 2022 11:47:48 +0800 Subject: [PATCH 42/42] remove test --- tests/test_config_manager_query_del.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/tests/test_config_manager_query_del.py b/tests/test_config_manager_query_del.py index 0ae3f2d..51dd80d 100644 --- a/tests/test_config_manager_query_del.py +++ b/tests/test_config_manager_query_del.py @@ -85,17 +85,3 @@ async def test_del_sub(app: App): ctx.should_finished() subs = config.list_subscribe(10000, "group") assert len(subs) == 0 - - -async def test_test(app: App): - from nonebot.adapters.onebot.v11.bot import Bot - from nonebot.adapters.onebot.v11.message import Message - from nonebot_bison.config_manager import test_matcher - - async with app.test_matcher(test_matcher) as ctx: - bot = ctx.create_bot(base=Bot) - event = fake_group_message_event(message=Message("testtt")) - ctx.receive_event(bot, event) - ctx.should_pass_permission() - ctx.should_pass_rule() - ctx.should_call_send(event, "666", True)