diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 9c60473c..8f76a704 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -1,4 +1,6 @@ -module.exports = { +const path = require("path"); + +module.exports = context => ({ title: "NoneBot", description: "基于 酷Q 的 Python 异步 QQ 机器人框架", markdown: { @@ -20,54 +22,79 @@ module.exports = { { name: "apple-mobile-web-app-status-bar-style", content: "black" } ] ], - - themeConfig: { - repo: "nonebot/nonebot", - docsDir: "docs", - docsBranch: "dev2", - editLinks: true, - editLinkText: "在 GitHub 上编辑此页", - lastUpdated: "上次更新", - smoothScroll: true, - nav: [{ text: "API", link: "/api/" }], - sidebar: { - "/api/": [ - { - title: "NoneBot Api Reference", - path: "", - collapsable: false, - sidebarDepth: 3, - children: [ - { - title: "nonebot 模块", - path: "nonebot", - sidebar: "auto" - }, - { - title: "nonebot.typing 模块", - path: "typing", - sidebar: "auto" - }, - { - title: "nonebot.log 模块", - path: "log", - sidebar: "auto" - }, - { - title: "nonebot.exception 模块", - path: "exception", - sidebar: "auto" - }, - { - title: "nonebot.config 模块", - path: "config", - sidebar: "auto" - } - ] - } - ] + locales: { + "/": { + lang: "zh-CN", + title: "NoneBot", + description: "基于 酷Q 的 Python 异步 QQ 机器人框架" } }, - plugins: ["@vuepress/plugin-back-to-top", "@vuepress/plugin-medium-zoom"] -}; + theme: "titanium", + themeConfig: { + logo: "/logo.png", + repo: "nonebot/nonebot", + docsDir: "docs", + docsBranch: "dev2", + docsDirVersioned: "archive", + docsDirPages: "pages", + editLinks: true, + smoothScroll: true, + + locales: { + "/": { + label: "简体中文", + selectText: "Languages", + editLinkText: "在 GitHub 上编辑此页", + lastUpdated: "上次更新", + nav: [{ text: "API", link: "/api/" }], + sidebar: { + "/api/": [ + { + title: "NoneBot Api Reference", + path: "", + collapsable: false, + children: [ + { + title: "nonebot 模块", + path: "nonebot" + }, + { + title: "nonebot.typing 模块", + path: "typing" + }, + { + title: "nonebot.log 模块", + path: "log" + }, + { + title: "nonebot.exception 模块", + path: "exception" + }, + { + title: "nonebot.config 模块", + path: "config" + } + ] + } + ] + } + } + } + }, + + plugins: [ + "@vuepress/plugin-back-to-top", + "@vuepress/plugin-medium-zoom", + [ + "versioning", + { + versionedSourceDir: path.resolve(context.sourceDir, "..", "archive"), + pagesSourceDir: path.resolve(context.sourceDir, "..", "pages"), + onNewVersion(version, versionDestPath) { + console.log(`Created version ${version} in ${versionDestPath}`); + } + } + ] + ] +}); diff --git a/docs/api/config.md b/docs/api/config.md index 2d971fe3..9fe3464e 100644 --- a/docs/api/config.md +++ b/docs/api/config.md @@ -1,3 +1,8 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + # NoneBot.config 模块 ## 配置 diff --git a/docs/api/exception.md b/docs/api/exception.md index dd8bf049..2c7abad1 100644 --- a/docs/api/exception.md +++ b/docs/api/exception.md @@ -1,3 +1,8 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + # NoneBot.exception 模块 ## 异常 diff --git a/docs/api/log.md b/docs/api/log.md index d6f6048d..ecc3bdc3 100644 --- a/docs/api/log.md +++ b/docs/api/log.md @@ -1,3 +1,8 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + # NoneBot.log 模块 ## 日志 diff --git a/docs/api/nonebot.md b/docs/api/nonebot.md index 77a17cff..8dcd0c20 100644 --- a/docs/api/nonebot.md +++ b/docs/api/nonebot.md @@ -1,3 +1,8 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + # NoneBot 模块 diff --git a/docs/api/typing.md b/docs/api/typing.md index 71833b7a..92eb4fe4 100644 --- a/docs/api/typing.md +++ b/docs/api/typing.md @@ -1,3 +1,8 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + # NoneBot.typing 模块 ## 类型 diff --git a/docs_build/conf.py b/docs_build/conf.py index 3f440fc7..5ac18b11 100644 --- a/docs_build/conf.py +++ b/docs_build/conf.py @@ -10,9 +10,9 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # -# import os -# import sys -# sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(__file__)))) +import os +import sys +sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(__file__)))) # -- Project information ----------------------------------------------------- diff --git a/docs_build/config.rst b/docs_build/config.rst index a9e7bc21..7354a565 100644 --- a/docs_build/config.rst +++ b/docs_build/config.rst @@ -1,3 +1,8 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + NoneBot.config 模块 =================== diff --git a/docs_build/exception.rst b/docs_build/exception.rst index 443d4ec4..7b643758 100644 --- a/docs_build/exception.rst +++ b/docs_build/exception.rst @@ -1,3 +1,8 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + NoneBot.exception 模块 ====================== diff --git a/docs_build/log.rst b/docs_build/log.rst index d6839fc6..49799128 100644 --- a/docs_build/log.rst +++ b/docs_build/log.rst @@ -1,3 +1,8 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + NoneBot.log 模块 ================= diff --git a/docs_build/nonebot.rst b/docs_build/nonebot.rst index f921b3a5..1e8f5107 100644 --- a/docs_build/nonebot.rst +++ b/docs_build/nonebot.rst @@ -1,3 +1,8 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + NoneBot 模块 =============== diff --git a/docs_build/typing.rst b/docs_build/typing.rst index 8b927eaa..211cb6fa 100644 --- a/docs_build/typing.rst +++ b/docs_build/typing.rst @@ -1,3 +1,8 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + NoneBot.typing 模块 =================== diff --git a/nonebot/matcher.py b/nonebot/matcher.py index 762e891c..92fffbc1 100644 --- a/nonebot/matcher.py +++ b/nonebot/matcher.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- import typing +import inspect from functools import wraps from datetime import datetime from collections import defaultdict @@ -38,6 +39,13 @@ class Matcher: self.handlers = self.handlers.copy() self.state = self._default_state.copy() + def __repr__(self) -> str: + return (f"") + + def __str__(self) -> str: + return self.__repr__() + @classmethod def new(cls, type_: str = "", @@ -117,7 +125,7 @@ class Matcher: cls.handlers.append(_handler) def _decorator(func: Handler) -> Handler: - if cls.handlers[-1] is not func: + if not cls.handlers or cls.handlers[-1] is not func: cls.handlers.append(func) return func @@ -141,6 +149,8 @@ class Matcher: raise PausedException async def _key_parser(bot: Bot, event: Event, state: dict): + if key in state: + return parser = args_parser or cls._default_parser if parser: await parser(bot, event, state) @@ -151,8 +161,15 @@ class Matcher: cls.handlers.append(_key_parser) def _decorator(func: Handler) -> Handler: - if cls.handlers[-1] is not func: - cls.handlers.append(func) + if not hasattr(cls.handlers[-1], "__wrapped__"): + parser = cls.handlers.pop() + + @wraps(func) + async def wrapper(bot: Bot, event: Event, state: dict): + await parser(bot, event, state) + await func(bot, event, state) + + cls.handlers.append(wrapper) return func @@ -180,34 +197,35 @@ class Matcher: handler = self.handlers.pop(0) annotation = typing.get_type_hints(handler) BotType = annotation.get("bot") - if BotType and not isinstance(bot, BotType): + if BotType and inspect.isclass(BotType) and not isinstance( + bot, BotType): continue await handler(bot, event, self.state) except RejectedException: self.handlers.insert(0, handler) # type: ignore - matcher = Matcher.new( + Matcher.new( self.type, - self.rule, + Rule(), USER(event.user_id, perm=self.permission), # type:ignore self.handlers, temp=True, priority=0, + block=True, default_state=self.state, expire_time=datetime.now() + bot.config.session_expire_timeout) - matchers[0].append(matcher) return except PausedException: - matcher = Matcher.new( + Matcher.new( self.type, - self.rule, + Rule(), USER(event.user_id, perm=self.permission), # type:ignore self.handlers, temp=True, priority=0, + block=True, default_state=self.state, expire_time=datetime.now() + bot.config.session_expire_timeout) - matchers[0].append(matcher) return except FinishedException: return diff --git a/nonebot/message.py b/nonebot/message.py index d7963f00..0e883438 100644 --- a/nonebot/message.py +++ b/nonebot/message.py @@ -22,7 +22,7 @@ def event_preprocessor(func: PreProcessor) -> PreProcessor: async def _run_matcher(Matcher: Type[Matcher], bot: Bot, event: Event, state: dict) -> Union[None, NoReturn]: - if datetime.now() > Matcher.expire_time: + if Matcher.expire_time and datetime.now() > Matcher.expire_time: raise _ExceptionContainer([ExpiredException]) try: @@ -65,8 +65,7 @@ async def handle_event(bot: Bot, event: Event): return # Trie Match - if event.type == "message": - _, _ = TrieRule.get_value(bot, event, state) + _, _ = TrieRule.get_value(bot, event, state) break_flag = False for priority in sorted(matchers.keys()): diff --git a/nonebot/plugin.py b/nonebot/plugin.py index e49c5775..c6584615 100644 --- a/nonebot/plugin.py +++ b/nonebot/plugin.py @@ -2,9 +2,10 @@ # -*- coding: utf-8 -*- import re +import sys import pkgutil import importlib -from importlib.util import module_from_spec +from importlib._bootstrap import _load from nonebot.log import logger from nonebot.matcher import Matcher @@ -185,9 +186,12 @@ def load_plugins(*plugin_dir: str) -> Set[Plugin]: if name.startswith("_"): continue + spec = module_info.module_finder.find_spec(name) + if spec.name in sys.modules: + continue + try: - spec = module_info.module_finder.find_spec(name) - module = module_from_spec(spec) + module = _load(spec) plugin = Plugin(name, module, _tmp_matchers.copy()) plugins[name] = plugin diff --git a/nonebot/rule.py b/nonebot/rule.py index 39b1ec6f..34c1c7b4 100644 --- a/nonebot/rule.py +++ b/nonebot/rule.py @@ -59,6 +59,11 @@ class TrieRule: @classmethod def get_value(cls, bot: Bot, event: Event, state: dict) -> Tuple[Dict[str, Any], Dict[str, Any]]: + if event.type != "message": + state["_prefix"] = {} + state["_suffix"] = {} + return {}, {} + prefix = None suffix = None message = event.message[0] @@ -109,8 +114,12 @@ def command(command: Tuple[str, ...]) -> Rule: config = get_driver().config command_start = config.command_start command_sep = config.command_sep - for start, sep in product(command_start, command_sep): - TrieRule.add_prefix(f"{start}{sep.join(command)}", command) + if len(command) == 1: + for start in command_start: + TrieRule.add_prefix(f"{start}{command[0]}", command) + else: + for start, sep in product(command_start, command_sep): + TrieRule.add_prefix(f"{start}{sep.join(command)}", command) async def _command(bot: Bot, event: Event, state: dict) -> bool: return command in state["_prefix"].values() diff --git a/package-lock.json b/package-lock.json index 73b801e1..f6a0c4db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10157,6 +10157,56 @@ "smoothscroll-polyfill": "^0.4.3" } }, + "vuepress-plugin-versioning": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/vuepress-plugin-versioning/-/vuepress-plugin-versioning-4.5.0.tgz", + "integrity": "sha512-tnth/oXq5yqTHvfbSfl1GaPIQcr8ckSDX+wyecnm3tS/FSaZH55Fb8wPwhdM2jEEXWPe6DpQCh9O7fvsthIvRQ==", + "dev": true, + "requires": { + "@vuepress/shared-utils": "^1.5.2", + "fs-extra": "^8.1.0" + }, + "dependencies": { + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } + } + }, + "vuepress-theme-titanium": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/vuepress-theme-titanium/-/vuepress-theme-titanium-4.5.1.tgz", + "integrity": "sha512-yWDbv4Omwk611ftN0WEAf30n4dE0TbyH7ROPYTt3u185hhE8qRiba9HJBwm8CU2CKfp+Yjv5y24V/OHGfSqJzQ==", + "dev": true, + "requires": { + "@vuepress/plugin-nprogress": "^1.5.2", + "@vuepress/plugin-search": "^1.5.2", + "docsearch.js": "^2.6.3", + "lodash": "^4.17.15", + "stylus": "^0.54.5", + "stylus-loader": "^3.0.2", + "vuepress-plugin-container": "^2.0.2", + "vuepress-plugin-smooth-scroll": "^0.0.9" + }, + "dependencies": { + "vuepress-plugin-smooth-scroll": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/vuepress-plugin-smooth-scroll/-/vuepress-plugin-smooth-scroll-0.0.9.tgz", + "integrity": "sha512-UXX+HLZO1NKVwyiOJlj0smh8F9dKnwybjEi7w/Mj9EYLhKrNYr5uXs+N+OTt8VwKCn3f0vZ1XAwFIjsPlD7GJA==", + "dev": true, + "requires": { + "smoothscroll-polyfill": "^0.4.4" + } + } + } + }, "watchpack": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.4.tgz", diff --git a/package.json b/package.json index c2a0698a..06e5231f 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,7 @@ "description": "An asynchronous QQ bot framework.", "homepage": "https://nonebot.cqp.moe/", "main": "index.js", - "contributors": [ - { + "contributors": [{ "name": "Richard Chien", "email": "richardchienthebest@gmail.com" }, @@ -24,8 +23,10 @@ }, "license": "MIT", "devDependencies": { - "vuepress": "^1.3.1", "@vuepress/plugin-back-to-top": "^1.3.1", - "@vuepress/plugin-medium-zoom": "^1.3.1" + "@vuepress/plugin-medium-zoom": "^1.3.1", + "vuepress": "^1.3.1", + "vuepress-plugin-versioning": "^4.5.0", + "vuepress-theme-titanium": "^4.5.1" } } diff --git a/tests/test_plugins/test_metaevent.py b/tests/test_plugins/test_metaevent.py index 93609139..f683716a 100644 --- a/tests/test_plugins/test_metaevent.py +++ b/tests/test_plugins/test_metaevent.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from nonebot.plugin import on_metaevent from nonebot.typing import Bot, Event +from nonebot.plugin import on_metaevent async def heartbeat(bot: Bot, event: Event, state: dict) -> bool: diff --git a/tests/test_plugins/test_package/test_command.py b/tests/test_plugins/test_package/test_command.py index 34501ecd..57551450 100644 --- a/tests/test_plugins/test_package/test_command.py +++ b/tests/test_plugins/test_package/test_command.py @@ -11,8 +11,8 @@ test_command = on_command("帮助", to_me()) @test_command.handle() async def test_handler(bot: Bot, event: Event, state: dict): - print("[!] Command:", state["_prefix"]) - args = str(event.message)[len(state["_prefix"]):].strip() + args = str(event.message)[len(list(state["_prefix"].keys())[0]):].strip() + print("[!] Command:", state["_prefix"], "Args:", args) if args: state["help"] = args else: