Giter Site home page Giter Site logo

nonebug's Introduction

NoneBot

License PyPI Python Version OneBot Version QQ 群 Telegram 频道 Discord Server

简介

NoneBot 是一个基于 OneBot 标准(原 CQHTTP) 的 Python 异步 QQ 机器人框架,它会对 QQ 机器人收到的消息进行解析和处理,并以插件化的形式,分发给消息所对应的命令处理器和自然语言处理器,来完成具体的功能。

除了起到解析消息的作用,NoneBot 还为插件提供了大量实用的预设操作和权限控制机制,尤其对于命令处理器,它更是提供了完善且易用的会话机制和内部调用机制,以分别适应命令的连续交互和插件内部功能复用等需求。

NoneBot 在其底层与 OneBot 实现交互的部分使用 aiocqhttp 库,后者在 Quart 的基础上封装了与 OneBot 实现的网络交互。

得益于 Python 的 asyncio 机制,NoneBot 处理消息的吞吐量有了很大的保障,再配合 OneBot 标准的 WebSocket 通信方式(也是最建议的通信方式),NoneBot 的性能可以达到 HTTP 通信方式的两倍以上,相较于传统同步 I/O 的 HTTP 通信,更是有质的飞跃。

需要注意的是,NoneBot 仅支持 Python 3.7+。

文档

文档目前「指南」和「API」部分已经完成,「进阶」部分尚未完成,你可以在 这里 查看。

贡献

如果你在使用过程中发现任何问题,可以 提交 issue 或自行 fork 修改后提交 pull request。

如果你要提交 pull request,请确保你的代码风格和项目已有的代码保持一致,遵循 PEP 8,变量命名清晰,有适当的注释。

NoneBot

Description

NoneBot is an asynchronous and OneBot-compliant QQ robot framework written in Python. When NoneBot receives new messages, it parses the messages then pass them to user-defined command handlers or natural language processors accordingly using a plugin system to accomplish various tasks.

Beside message processing, NoneBot presents an amount of useful built-in actions and permission handling features. The command processors provide simple but comprehensive session-ing and calling mechanisms to handle continuous interactions and the reusing of functionalities inside plugins, respectively.

NoneBot communicates with OneBot implementations using aiocqhttp, a wrapper based on Quart for lower-level protocol work.

Thanks to asyncio and WebSocket messaging method (which is recommended), NoneBot ensures maximum possible message throughput to be twice as fast as HTTP messaging, and have great performance leap compared to traditional synchronous IO.

NoneBot only supports Python 3.7+.

Documentation

For Guide and API manuals, check out this page.

Contributing

If you encounter any problems in using the project, you can submit an issue or fork this project to submit an pull request.

For pull requests, please be sure to have consistent style to existing modules, follow PEP 8, have clear identifier naming, and have proper comments.

nonebug's People

Contributors

caesius-tim avatar he0119 avatar meetwq avatar pre-commit-ci[bot] avatar yanyongyu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

nonebug's Issues

更好的文本差异比较

目前的nonebug在测试时遇到和期望不相符的文本时,只会显示这样的格式:

f"Application got api call {api} with data {data} but expected {model.data}"

这经常会造成如下对人眼检查不友好的输出:

Application got api call ... with data {'foo': 1, 'bar': {'hello': 'world'}} but expected {'foo': 1, 'bar': {'hello': 'nonebug'}}

希望可以加入对 期望输出 和 实际输出 的比较,类似这样的格式:

  {
      "foo": 1,
      "bar": {
-         "hello": "world"
?                   ^ ^^^

+         "hello": "nonebot"
?                   ^ ^^^^^

      }
  }

这样的输出可以通过标准库difflib实现,大体类似于:

from difflib import ndiff
from json import dumps

print(
	"\n".join(
		ndiff(
			dumps(data, indent=4).splitlines(),
			dumps(model.data, indent=4).splitlines()
		)
	)
)

对on_command事件响应器的测试与预期不符

描述

  1. 在对 on_command 事件响应器进行测试时,无论 .env 文件中 COMMAND_START 设置如何,均需要在测试时对给定的测试消息添加 / 前缀才能正常进行测试,否则 should_call_sendshould_finished 等测试均不符合预期。

  2. 在已对测试消息添加 / 前缀的情况下继续测试,若被 handle 装饰器装饰的函数(我称其为处理函数)设置了 event 形参,并通过 typing hint 将 event 形参的类型指定为 MessageEvent,那么 should_call_sendshould_finished 等测试均依然不符合预期。

具体不符合预期的表现详见复现步骤和结果。

版本

  • nonebot2: 2.0.0rc3
  • nonebug: 0.2.3
  • pytest: 7.2.1
  • pytest-asyncio: 0.16.0

复现

插件:src/plugins/bug/__init__.py

from nonebot import on_command
from nonebot.adapters.onebot.v11 import Event, MessageEvent

t1 = on_command('t1')
t2 = on_command('t2')
t3 = on_command('t3')
t4 = on_command('t4')


@t1.handle()
async def _():
    await t1.finish("test1 ok")


@t2.handle()
async def _(event):
    await t2.finish("test2 ok")


@t3.handle()
async def _(event: Event):
    await t3.finish("test3 ok")


@t4.handle()
async def _(event: MessageEvent):
    await t4.finish("test4 ok")

单元测试:unittest/plugins/test_bug.py

import pytest
from nonebot.adapters.onebot.v11 import Message, MessageEvent
from nonebot.plugin import Plugin
from nonebug import App


@pytest.fixture
def load_plugin(nonebug_init: None) -> Plugin:
    import nonebot
    return nonebot.load_plugin('src.plugins.bug')


# 使用 / 前缀,并且处理函数没有加任何参数
@pytest.mark.asyncio
async def test_t1_prefix(app: App, load_plugin):
    from src.plugins.bug import t1

    async with app.test_matcher(t1) as ctx:
        bot = ctx.create_bot()

        msg = Message("/t1")
        event = MessageEvent(
            user_id=1, message=msg, message_id=1, time=1, self_id=1, post_type="message",
            message_type="private", sub_type="friend", raw_message=str(msg), font=1, sender={}
        )

        ctx.receive_event(bot, event)
        ctx.should_call_send(event, "test1 ok", True)
        ctx.should_finished()


# 不使用 / 前缀,并且处理函数没有加任何参数
@pytest.mark.asyncio
async def test_t1(app: App, load_plugin):
    from src.plugins.bug import t1

    async with app.test_matcher(t1) as ctx:
        bot = ctx.create_bot()

        msg = Message("t1")
        event = MessageEvent(
            user_id=1, message=msg, message_id=1, time=1, self_id=1, post_type="message",
            message_type="private", sub_type="friend", raw_message=str(msg), font=1, sender={}
        )

        ctx.receive_event(bot, event)
        ctx.should_call_send(event, "test1 ok", True)
        ctx.should_finished()


# 使用 / 前缀,处理函数添加 event 形参但不使用 typing hint
@pytest.mark.asyncio
async def test_t2(app: App, load_plugin):
    from src.plugins.bug import t2

    async with app.test_matcher(t2) as ctx:
        bot = ctx.create_bot()

        msg = Message("/t2")
        event = MessageEvent(
            user_id=1, message=msg, message_id=1, time=1, self_id=1, post_type="message",
            message_type="private", sub_type="friend", raw_message=str(msg), font=1, sender={}
        )

        ctx.receive_event(bot, event)
        ctx.should_call_send(event, "test2 ok", True)
        ctx.should_finished()


# 使用 / 前缀,处理函数添加 event 形参并指定类型为 Event
@pytest.mark.asyncio
async def test_t3(app: App, load_plugin):
    from src.plugins.bug import t3

    async with app.test_matcher(t3) as ctx:
        bot = ctx.create_bot()

        msg = Message("/t3")
        event = MessageEvent(
            user_id=1, message=msg, message_id=1, time=1, self_id=1, post_type="message",
            message_type="private", sub_type="friend", raw_message=str(msg), font=1, sender={}
        )

        ctx.receive_event(bot, event)
        ctx.should_call_send(event, "test3 ok", True)
        ctx.should_finished()


# 使用 / 前缀,处理函数添加 event 形参并指定类型为 MessageEvent
@pytest.mark.asyncio
async def test_t4(app: App, load_plugin):
    from src.plugins.bug import t4

    async with app.test_matcher(t4) as ctx:
        bot = ctx.create_bot()

        msg = Message("/t4")
        event = MessageEvent(
            user_id=1, message=msg, message_id=1, time=1, self_id=1, post_type="message",
            message_type="private", sub_type="friend", raw_message=str(msg), font=1, sender={}
        )

        ctx.receive_event(bot, event)
        ctx.should_call_send(event, "test4 ok", True)
        ctx.should_finished()

环境:.env

HOST=...
PORT=...

SUPERUSERS=...
COMMAND_START=["","!","/"]
SESSION_EXPIRE_TIMEOUT=60

USERS_DB=data/users.db
GROUPS_DB=data/groups.db
TEMP=temp

结果

预期

5 个测试均成功

实际

测试用例 测试消息 处理函数的形参声明 结果
test_t1_prefix /t1 async def _(): 通过
test_t1 t1 async def _(): 不通过
test_t2 /t2 async def _(event): 通过
test_t3 /t3 async def _(event: Event): 通过
test_t4 /t4 async def _(event: MessageEvent): 不通过

具体表现为:在未通过的两个测试中,无论测试用例填写什么预期结果,should_finished 方法都总是能通过。而 should_finished 方法则总是无法通过。

日志

============================= test session starts =============================
collecting ... collected 5 items

plugins/test_bug.py::test_t1_prefix 
plugins/test_bug.py::test_t1 02-03 00:21:23 [SUCCESS] nonebot | NoneBot is initializing...
02-03 00:21:23 [INFO] nonebot | Current Env: prod
02-03 00:21:24 [SUCCESS] nonebot | Succeeded to import "src.plugins.bug"
PASSED                               [ 20%]02-03 00:21:24 [INFO] nonebot | Matcher(type='message', module=src.plugins.bug) running complete
02-03 00:21:24 [SUCCESS] nonebot | NoneBot is initializing...
02-03 00:21:24 [INFO] nonebot | Current Env: prod
02-03 00:21:24 [SUCCESS] nonebot | Succeeded to import "src.plugins.bug"
FAILED                                      [ 40%]
plugins\test_bug.py:32 (test_t1)
app = <nonebug.app.App object at 0x0000028D9C27E100>
load_plugin = Plugin(name='bug', module=<module 'src.plugins.bug' from 'I:\\Developer\\Python\\project\\mokabot2\\src\\plugins\\bug\...c.plugins.bug), Matcher(type='message', module=src.plugins.bug)}, parent_plugin=None, sub_plugins=set(), metadata=None)

    @pytest.mark.asyncio
    async def test_t1(app: App, load_plugin):
        from src.plugins.bug import t1
    
        async with app.test_matcher(t1) as ctx:
            bot = ctx.create_bot()
    
            msg = Message("t1")
            event = MessageEvent(
                user_id=1, message=msg, message_id=1, time=1, self_id=1, post_type="message",
                message_type="private", sub_type="friend", raw_message=str(msg), font=1, sender={}
            )
    
            ctx.receive_event(bot, event)
            ctx.should_call_send(event, "test1 ok", True)
>           ctx.should_finished()

plugins\test_bug.py:48: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
..\venv\lib\site-packages\nonebug\base.py:17: in __aexit__
    await self.run()
..\venv\lib\site-packages\nonebug\base.py:24: in run
    await self.run_test()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <nonebug.mixin.process.MatcherContext object at 0x0000028D9C27E430>

    async def run_test(self):
        from nonebot.rule import Rule, TrieRule
        from nonebot.matcher import current_handler
        from nonebot.exception import (
            PausedException,
            FinishedException,
            RejectedException,
        )
    
        with self.monkeypatch.context() as m:
            while self.action_list:
                # prepare for next event
                stack = AsyncExitStack()
                dependency_cache: T_DependencyCache = {}
                # fake event received
                receive_event = self.action_list.pop(0)
>               assert isinstance(
                    receive_event, ReceiveEvent
                ), f"Unexpected model {receive_event} expected ReceiveEvent"
E               AssertionError: Unexpected model Finished() expected ReceiveEvent
E               assert False
E                +  where False = isinstance(Finished(), ReceiveEvent)

..\venv\lib\site-packages\nonebug\mixin\process\__init__.py:107: AssertionError
02-03 00:21:24 [SUCCESS] nonebot | NoneBot is initializing...
02-03 00:21:24 [INFO] nonebot | Current Env: prod
02-03 00:21:24 [SUCCESS] nonebot | Succeeded to import "src.plugins.bug"
PASSED                                      [ 60%]02-03 00:21:24 [INFO] nonebot | Matcher(type='message', module=src.plugins.bug) running complete
02-03 00:21:24 [SUCCESS] nonebot | NoneBot is initializing...
02-03 00:21:24 [INFO] nonebot | Current Env: prod
02-03 00:21:25 [SUCCESS] nonebot | Succeeded to import "src.plugins.bug"
PASSED                                      [ 80%]02-03 00:21:25 [INFO] nonebot | Matcher(type='message', module=src.plugins.bug) running complete
02-03 00:21:25 [SUCCESS] nonebot | NoneBot is initializing...
02-03 00:21:25 [INFO] nonebot | Current Env: prod
02-03 00:21:25 [SUCCESS] nonebot | Succeeded to import "src.plugins.bug"
FAILED                                      [100%]02-03 00:21:25 [INFO] nonebot | Matcher(type='message', module=src.plugins.bug) running complete

plugins\test_bug.py:89 (test_t4)
app = <nonebug.app.App object at 0x0000028D9E439FA0>
load_plugin = Plugin(name='bug', module=<module 'src.plugins.bug' from 'I:\\Developer\\Python\\project\\mokabot2\\src\\plugins\\bug\...c.plugins.bug), Matcher(type='message', module=src.plugins.bug)}, parent_plugin=None, sub_plugins=set(), metadata=None)

    @pytest.mark.asyncio
    async def test_t4(app: App, load_plugin):
        from src.plugins.bug import t4
    
        async with app.test_matcher(t4) as ctx:
            bot = ctx.create_bot()
    
            msg = Message("/t4")
            event = MessageEvent(
                user_id=1, message=msg, message_id=1, time=1, self_id=1, post_type="message",
                message_type="private", sub_type="friend", raw_message=str(msg), font=1, sender={}
            )
    
            ctx.receive_event(bot, event)
            ctx.should_call_send(event, "test4 ok", True)
>           ctx.should_finished()

plugins\test_bug.py:105: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
..\venv\lib\site-packages\nonebug\base.py:17: in __aexit__
    await self.run()
..\venv\lib\site-packages\nonebug\base.py:24: in run
    await self.run_test()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <nonebug.mixin.process.MatcherContext object at 0x0000028D9E5AF8B0>

    async def run_test(self):
        from nonebot.rule import Rule, TrieRule
        from nonebot.matcher import current_handler
        from nonebot.exception import (
            PausedException,
            FinishedException,
            RejectedException,
        )
    
        with self.monkeypatch.context() as m:
            while self.action_list:
                # prepare for next event
                stack = AsyncExitStack()
                dependency_cache: T_DependencyCache = {}
                # fake event received
                receive_event = self.action_list.pop(0)
>               assert isinstance(
                    receive_event, ReceiveEvent
                ), f"Unexpected model {receive_event} expected ReceiveEvent"
E               AssertionError: Unexpected model Finished() expected ReceiveEvent
E               assert False
E                +  where False = isinstance(Finished(), ReceiveEvent)

..\venv\lib\site-packages\nonebug\mixin\process\__init__.py:107: AssertionError




plugins/test_bug.py::test_t2 
plugins/test_bug.py::test_t3 
plugins/test_bug.py::test_t4 

========================= 2 failed, 3 passed in 2.22s =========================

额外补充

使用

@pytest.fixture
def load_plugin(nonebug_init: None) -> Plugin:
    import nonebot
    print(nonebot.get_driver().config)
    return nonebot.load_plugin('src.plugins.bug')

查看 Nonebug 的配置文件,可以得到:

driver='~fastapi' host=IPv4Address('127.0.0.1') port=8080 log_level='INFO' api_timeout=30.0 superusers=set() nickname=set() command_start={'/'} command_sep={'.'} session_expire_timeout=datetime.timedelta(seconds=120)

这与给定的 .env 明显不符,这应该是导致第一个问题的原因

更方便地进行收到消息的判断

  1. 字符串与Message的比较
    测试时经常会遇到assert '123' = Message([MessageSegment(type='text', data={'text': '123'})])的问题,能否自动处理一下这种情况
  2. 自定义消息判断
    现在只能在测试中完整给出与预期输出一模一样的输出才能通过测试,希望能够新增传入正则表达式等测试方法,或写传入自定义验证代码(否则可能连骰娘都测不了)

feat: Report need

The official document does not say the test report, so how to know the test situation?

Maybe there should be a report like this one:

test name test status
XXX PASS
VVVVVV NO PASS

total pass: 1
total reject: 1

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.