├── .devcontainer └── devcontainer.json ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── adapter_publish.yml │ ├── bot_publish.yml │ ├── bug_report.yml │ ├── config.yml │ ├── document.yml │ ├── feature_request.yml │ └── plugin_publish.yml ├── actions │ ├── build-api-doc │ │ └── action.yml │ ├── setup-node │ │ └── action.yml │ └── setup-python │ │ └── action.yml ├── dependabot.yml ├── release-drafter.yml └── workflows │ ├── codecov.yml │ ├── noneflow.yml │ ├── pyright.yml │ ├── release-drafter.yml │ ├── release.yml │ ├── ruff.yml │ ├── website-deploy.yml │ ├── website-preview-cd.yml │ └── website-preview-ci.yml ├── .gitignore ├── .markdownlint.yaml ├── .pre-commit-config.yaml ├── .prettierignore ├── .prettierrc ├── .stylelintrc.js ├── .yarnrc ├── CHANGELOG.md ├── CITATION.cff ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── assets ├── adapters.json5 ├── bots.json5 ├── drivers.json5 └── plugins.json5 ├── envs ├── pydantic-v1 │ ├── poetry.lock │ └── pyproject.toml ├── pydantic-v2 │ ├── poetry.lock │ └── pyproject.toml └── test │ ├── nonebot-test.py │ ├── poetry.lock │ └── pyproject.toml ├── nonebot ├── __init__.py ├── adapters │ └── __init__.py ├── compat.py ├── config.py ├── consts.py ├── dependencies │ ├── __init__.py │ └── utils.py ├── drivers │ ├── __init__.py │ ├── aiohttp.py │ ├── fastapi.py │ ├── httpx.py │ ├── none.py │ ├── quart.py │ └── websockets.py ├── exception.py ├── internal │ ├── __init__.py │ ├── adapter │ │ ├── __init__.py │ │ ├── adapter.py │ │ ├── bot.py │ │ ├── event.py │ │ ├── message.py │ │ └── template.py │ ├── driver │ │ ├── __init__.py │ │ ├── _lifespan.py │ │ ├── abstract.py │ │ ├── combine.py │ │ └── model.py │ ├── matcher │ │ ├── __init__.py │ │ ├── manager.py │ │ ├── matcher.py │ │ └── provider.py │ ├── params.py │ ├── permission.py │ └── rule.py ├── log.py ├── matcher.py ├── message.py ├── params.py ├── permission.py ├── plugin │ ├── __init__.py │ ├── load.py │ ├── manager.py │ ├── model.py │ ├── on.py │ └── on.pyi ├── plugins │ ├── echo.py │ └── single_session.py ├── py.typed ├── rule.py ├── typing.py └── utils.py ├── package.json ├── packages └── nonebot-plugin-docs │ ├── README.md │ ├── nonebot_plugin_docs │ ├── __init__.py │ └── drivers │ │ └── fastapi.py │ └── pyproject.toml ├── poetry.lock ├── pyproject.toml ├── scripts ├── build-api-docs.sh ├── run-tests.sh ├── setup-envs.sh └── update-envs.sh ├── tests ├── .coveragerc ├── .env ├── .env.example ├── .env.test ├── bad_plugins │ └── bad_plugin.py ├── conftest.py ├── dynamic │ ├── manager.py │ ├── path.py │ ├── require_not_declared.py │ ├── require_not_loaded │ │ ├── __init__.py │ │ ├── subplugin1.py │ │ └── subplugin2.py │ └── simple.py ├── fake_server.py ├── plugins.empty.toml ├── plugins.invalid.json ├── plugins.invalid.toml ├── plugins.json ├── plugins.toml ├── plugins │ ├── _hidden.py │ ├── export.py │ ├── matcher │ │ ├── __init__.py │ │ ├── matcher_expire.py │ │ ├── matcher_info.py │ │ ├── matcher_permission.py │ │ ├── matcher_process.py │ │ └── matcher_type.py │ ├── metadata.py │ ├── metadata_2.py │ ├── metadata_3.py │ ├── nested │ │ ├── __init__.py │ │ └── plugins │ │ │ ├── nested_subplugin.py │ │ │ └── nested_subplugin2.py │ ├── param │ │ ├── __init__.py │ │ ├── param_arg.py │ │ ├── param_bot.py │ │ ├── param_default.py │ │ ├── param_depend.py │ │ ├── param_event.py │ │ ├── param_exception.py │ │ ├── param_matcher.py │ │ ├── param_state.py │ │ └── priority.py │ ├── plugin │ │ ├── __init__.py │ │ └── matchers.py │ └── require.py ├── pyproject.toml ├── test_adapters │ ├── test_adapter.py │ ├── test_bot.py │ ├── test_message.py │ └── test_template.py ├── test_broadcast.py ├── test_compat.py ├── test_config.py ├── test_driver.py ├── test_echo.py ├── test_init.py ├── test_matcher │ ├── test_matcher.py │ └── test_provider.py ├── test_param.py ├── test_permission.py ├── test_plugin │ ├── test_get.py │ ├── test_load.py │ ├── test_manager.py │ └── test_on.py ├── test_rule.py ├── test_single_session.py ├── test_utils.py └── utils.py ├── tsconfig.json ├── website ├── docs │ ├── README.md │ ├── advanced │ │ ├── adapter.md │ │ ├── dependency.mdx │ │ ├── driver.md │ │ ├── matcher-provider.md │ │ ├── matcher.md │ │ ├── plugin-info.md │ │ ├── plugin-nesting.md │ │ ├── requiring.md │ │ ├── routing.md │ │ ├── runtime-hook.md │ │ └── session-updating.md │ ├── api │ │ ├── .gitkeep │ │ ├── adapters │ │ │ └── _category_.json │ │ ├── dependencies │ │ │ └── _category_.json │ │ ├── drivers │ │ │ └── _category_.json │ │ └── plugin │ │ │ └── _category_.json │ ├── appendices │ │ ├── api-calling.mdx │ │ ├── config.mdx │ │ ├── log.md │ │ ├── overload.md │ │ ├── permission.mdx │ │ ├── rule.md │ │ ├── session-control.mdx │ │ ├── session-state.md │ │ └── whats-next.md │ ├── best-practice │ │ ├── alconna │ │ │ ├── README.mdx │ │ │ ├── _category_.json │ │ │ ├── builtins.mdx │ │ │ ├── command.md │ │ │ ├── config.md │ │ │ ├── matcher.mdx │ │ │ ├── shortcut.md │ │ │ └── uniseg │ │ │ │ ├── README.md │ │ │ │ ├── _category_.json │ │ │ │ ├── message.mdx │ │ │ │ ├── segment.md │ │ │ │ └── utils.mdx │ │ ├── data-storing.md │ │ ├── database │ │ │ ├── README.mdx │ │ │ ├── _category_.json │ │ │ ├── developer │ │ │ │ ├── README.md │ │ │ │ ├── _category_.json │ │ │ │ ├── dependency.md │ │ │ │ └── test.md │ │ │ └── user.md │ │ ├── deployment.mdx │ │ ├── error-tracking.md │ │ ├── multi-adapter.mdx │ │ ├── scheduler.md │ │ └── testing │ │ │ ├── README.mdx │ │ │ ├── _category_.json │ │ │ ├── behavior.mdx │ │ │ └── mock-network.md │ ├── community │ │ ├── contact.md │ │ └── contributing.md │ ├── developer │ │ ├── adapter-writing.md │ │ └── plugin-publishing.mdx │ ├── editor-support.md │ ├── ospp │ │ ├── 2021.md │ │ ├── 2022.md │ │ ├── 2023.md │ │ ├── 2024.md │ │ └── 2025.md │ ├── quick-start.mdx │ └── tutorial │ │ ├── application.md │ │ ├── create-plugin.md │ │ ├── event-data.mdx │ │ ├── fundamentals.md │ │ ├── handler.mdx │ │ ├── matcher.md │ │ ├── message.md │ │ └── store.mdx ├── docusaurus.config.ts ├── package.json ├── sidebars.ts ├── src │ ├── changelog │ │ └── changelog.md │ ├── components │ │ ├── Asciinema │ │ │ ├── container.tsx │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── Form │ │ │ ├── Adapter.tsx │ │ │ ├── Bot.tsx │ │ │ ├── Items │ │ │ │ └── Tag │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styles.css │ │ │ ├── Plugin.tsx │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── Home │ │ │ ├── Feature.tsx │ │ │ ├── Hero.tsx │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── Messenger │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── Modal │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── Paginate │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── Resource │ │ │ ├── Avatar │ │ │ │ └── index.tsx │ │ │ ├── Card │ │ │ │ ├── index.tsx │ │ │ │ └── styles.css │ │ │ ├── DetailCard │ │ │ │ ├── index.tsx │ │ │ │ ├── styles.css │ │ │ │ └── types.ts │ │ │ ├── Tag │ │ │ │ ├── index.tsx │ │ │ │ └── styles.css │ │ │ └── ValidStatus │ │ │ │ └── index.tsx │ │ ├── Searcher │ │ │ ├── index.tsx │ │ │ └── styles.css │ │ ├── Store │ │ │ ├── Content │ │ │ │ ├── Adapter.tsx │ │ │ │ ├── Bot.tsx │ │ │ │ ├── Driver.tsx │ │ │ │ └── Plugin.tsx │ │ │ ├── Layout.tsx │ │ │ ├── Toolbar.tsx │ │ │ └── styles.css │ │ └── Tag │ │ │ ├── index.tsx │ │ │ └── styles.css │ ├── libs │ │ ├── color.ts │ │ ├── filter.ts │ │ ├── search.ts │ │ ├── sorter.ts │ │ ├── store.ts │ │ ├── toolbar.ts │ │ └── valid.ts │ ├── pages │ │ ├── index.tsx │ │ └── store │ │ │ ├── adapters.tsx │ │ │ ├── bots.tsx │ │ │ ├── drivers.tsx │ │ │ ├── index.tsx │ │ │ └── plugins.tsx │ ├── plugins │ │ └── webpack-plugin.ts │ ├── theme │ │ ├── Footer │ │ │ └── Copyright │ │ │ │ └── index.tsx │ │ ├── Icon │ │ │ ├── Cloudflare.tsx │ │ │ └── Netlify.tsx │ │ └── Page │ │ │ └── TOC │ │ │ └── Container │ │ │ ├── index.tsx │ │ │ └── styles.css │ └── types │ │ ├── adapter.ts │ │ ├── bot.ts │ │ ├── driver.ts │ │ ├── plugin.ts │ │ └── tag.ts ├── static │ ├── icons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-384x384.png │ │ ├── apple-touch-icon-180x180.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── mstile-150x150.png │ │ └── safari-pinned-tab.svg │ ├── img │ │ ├── setup.svg │ │ └── uwu.svg │ ├── logo.png │ ├── manifest.json │ ├── service-worker.js │ └── uwu.js ├── tailwind.config.ts ├── tsconfig.json ├── versioned_docs │ ├── version-2.4.0 │ │ ├── README.md │ │ ├── advanced │ │ │ ├── adapter.md │ │ │ ├── dependency.mdx │ │ │ ├── driver.md │ │ │ ├── matcher-provider.md │ │ │ ├── matcher.md │ │ │ ├── plugin-info.md │ │ │ ├── plugin-nesting.md │ │ │ ├── requiring.md │ │ │ ├── routing.md │ │ │ ├── runtime-hook.md │ │ │ └── session-updating.md │ │ ├── api │ │ │ ├── .gitkeep │ │ │ ├── adapters │ │ │ │ ├── _category_.json │ │ │ │ └── index.md │ │ │ ├── compat.md │ │ │ ├── config.md │ │ │ ├── consts.md │ │ │ ├── dependencies │ │ │ │ ├── _category_.json │ │ │ │ ├── index.md │ │ │ │ └── utils.md │ │ │ ├── drivers │ │ │ │ ├── _category_.json │ │ │ │ ├── aiohttp.md │ │ │ │ ├── fastapi.md │ │ │ │ ├── httpx.md │ │ │ │ ├── index.md │ │ │ │ ├── none.md │ │ │ │ ├── quart.md │ │ │ │ └── websockets.md │ │ │ ├── exception.md │ │ │ ├── index.md │ │ │ ├── log.md │ │ │ ├── matcher.md │ │ │ ├── message.md │ │ │ ├── params.md │ │ │ ├── permission.md │ │ │ ├── plugin │ │ │ │ ├── _category_.json │ │ │ │ ├── index.md │ │ │ │ ├── load.md │ │ │ │ ├── manager.md │ │ │ │ ├── model.md │ │ │ │ └── on.md │ │ │ ├── rule.md │ │ │ ├── typing.md │ │ │ └── utils.md │ │ ├── appendices │ │ │ ├── api-calling.mdx │ │ │ ├── config.mdx │ │ │ ├── log.md │ │ │ ├── overload.md │ │ │ ├── permission.mdx │ │ │ ├── rule.md │ │ │ ├── session-control.mdx │ │ │ ├── session-state.md │ │ │ └── whats-next.md │ │ ├── best-practice │ │ │ ├── alconna │ │ │ │ ├── README.mdx │ │ │ │ ├── _category_.json │ │ │ │ ├── command.md │ │ │ │ ├── config.md │ │ │ │ ├── matcher.mdx │ │ │ │ └── uniseg.mdx │ │ │ ├── data-storing.md │ │ │ ├── database │ │ │ │ ├── README.mdx │ │ │ │ ├── _category_.json │ │ │ │ ├── developer │ │ │ │ │ ├── README.md │ │ │ │ │ ├── _category_.json │ │ │ │ │ ├── dependency.md │ │ │ │ │ └── test.md │ │ │ │ └── user.md │ │ │ ├── deployment.mdx │ │ │ ├── error-tracking.md │ │ │ ├── multi-adapter.mdx │ │ │ ├── scheduler.md │ │ │ └── testing │ │ │ │ ├── README.mdx │ │ │ │ ├── _category_.json │ │ │ │ ├── behavior.mdx │ │ │ │ └── mock-network.md │ │ ├── community │ │ │ ├── contact.md │ │ │ └── contributing.md │ │ ├── developer │ │ │ ├── adapter-writing.md │ │ │ └── plugin-publishing.mdx │ │ ├── editor-support.md │ │ ├── ospp │ │ │ ├── 2021.md │ │ │ ├── 2022.md │ │ │ ├── 2023.md │ │ │ ├── 2024.md │ │ │ └── 2025.md │ │ ├── quick-start.mdx │ │ └── tutorial │ │ │ ├── application.md │ │ │ ├── create-plugin.md │ │ │ ├── event-data.mdx │ │ │ ├── fundamentals.md │ │ │ ├── handler.mdx │ │ │ ├── matcher.md │ │ │ ├── message.md │ │ │ └── store.mdx │ ├── version-2.4.1 │ │ ├── README.md │ │ ├── advanced │ │ │ ├── adapter.md │ │ │ ├── dependency.mdx │ │ │ ├── driver.md │ │ │ ├── matcher-provider.md │ │ │ ├── matcher.md │ │ │ ├── plugin-info.md │ │ │ ├── plugin-nesting.md │ │ │ ├── requiring.md │ │ │ ├── routing.md │ │ │ ├── runtime-hook.md │ │ │ └── session-updating.md │ │ ├── api │ │ │ ├── .gitkeep │ │ │ ├── adapters │ │ │ │ ├── _category_.json │ │ │ │ └── index.md │ │ │ ├── compat.md │ │ │ ├── config.md │ │ │ ├── consts.md │ │ │ ├── dependencies │ │ │ │ ├── _category_.json │ │ │ │ ├── index.md │ │ │ │ └── utils.md │ │ │ ├── drivers │ │ │ │ ├── _category_.json │ │ │ │ ├── aiohttp.md │ │ │ │ ├── fastapi.md │ │ │ │ ├── httpx.md │ │ │ │ ├── index.md │ │ │ │ ├── none.md │ │ │ │ ├── quart.md │ │ │ │ └── websockets.md │ │ │ ├── exception.md │ │ │ ├── index.md │ │ │ ├── log.md │ │ │ ├── matcher.md │ │ │ ├── message.md │ │ │ ├── params.md │ │ │ ├── permission.md │ │ │ ├── plugin │ │ │ │ ├── _category_.json │ │ │ │ ├── index.md │ │ │ │ ├── load.md │ │ │ │ ├── manager.md │ │ │ │ ├── model.md │ │ │ │ └── on.md │ │ │ ├── rule.md │ │ │ ├── typing.md │ │ │ └── utils.md │ │ ├── appendices │ │ │ ├── api-calling.mdx │ │ │ ├── config.mdx │ │ │ ├── log.md │ │ │ ├── overload.md │ │ │ ├── permission.mdx │ │ │ ├── rule.md │ │ │ ├── session-control.mdx │ │ │ ├── session-state.md │ │ │ └── whats-next.md │ │ ├── best-practice │ │ │ ├── alconna │ │ │ │ ├── README.mdx │ │ │ │ ├── _category_.json │ │ │ │ ├── command.md │ │ │ │ ├── config.md │ │ │ │ ├── matcher.mdx │ │ │ │ └── uniseg.mdx │ │ │ ├── data-storing.md │ │ │ ├── database │ │ │ │ ├── README.mdx │ │ │ │ ├── _category_.json │ │ │ │ ├── developer │ │ │ │ │ ├── README.md │ │ │ │ │ ├── _category_.json │ │ │ │ │ ├── dependency.md │ │ │ │ │ └── test.md │ │ │ │ └── user.md │ │ │ ├── deployment.mdx │ │ │ ├── error-tracking.md │ │ │ ├── multi-adapter.mdx │ │ │ ├── scheduler.md │ │ │ └── testing │ │ │ │ ├── README.mdx │ │ │ │ ├── _category_.json │ │ │ │ ├── behavior.mdx │ │ │ │ └── mock-network.md │ │ ├── community │ │ │ ├── contact.md │ │ │ └── contributing.md │ │ ├── developer │ │ │ ├── adapter-writing.md │ │ │ └── plugin-publishing.mdx │ │ ├── editor-support.md │ │ ├── ospp │ │ │ ├── 2021.md │ │ │ ├── 2022.md │ │ │ ├── 2023.md │ │ │ ├── 2024.md │ │ │ └── 2025.md │ │ ├── quick-start.mdx │ │ └── tutorial │ │ │ ├── application.md │ │ │ ├── create-plugin.md │ │ │ ├── event-data.mdx │ │ │ ├── fundamentals.md │ │ │ ├── handler.mdx │ │ │ ├── matcher.md │ │ │ ├── message.md │ │ │ └── store.mdx │ └── version-2.4.2 │ │ ├── README.md │ │ ├── advanced │ │ ├── adapter.md │ │ ├── dependency.mdx │ │ ├── driver.md │ │ ├── matcher-provider.md │ │ ├── matcher.md │ │ ├── plugin-info.md │ │ ├── plugin-nesting.md │ │ ├── requiring.md │ │ ├── routing.md │ │ ├── runtime-hook.md │ │ └── session-updating.md │ │ ├── api │ │ ├── .gitkeep │ │ ├── adapters │ │ │ ├── _category_.json │ │ │ └── index.md │ │ ├── compat.md │ │ ├── config.md │ │ ├── consts.md │ │ ├── dependencies │ │ │ ├── _category_.json │ │ │ ├── index.md │ │ │ └── utils.md │ │ ├── drivers │ │ │ ├── _category_.json │ │ │ ├── aiohttp.md │ │ │ ├── fastapi.md │ │ │ ├── httpx.md │ │ │ ├── index.md │ │ │ ├── none.md │ │ │ ├── quart.md │ │ │ └── websockets.md │ │ ├── exception.md │ │ ├── index.md │ │ ├── log.md │ │ ├── matcher.md │ │ ├── message.md │ │ ├── params.md │ │ ├── permission.md │ │ ├── plugin │ │ │ ├── _category_.json │ │ │ ├── index.md │ │ │ ├── load.md │ │ │ ├── manager.md │ │ │ ├── model.md │ │ │ └── on.md │ │ ├── rule.md │ │ ├── typing.md │ │ └── utils.md │ │ ├── appendices │ │ ├── api-calling.mdx │ │ ├── config.mdx │ │ ├── log.md │ │ ├── overload.md │ │ ├── permission.mdx │ │ ├── rule.md │ │ ├── session-control.mdx │ │ ├── session-state.md │ │ └── whats-next.md │ │ ├── best-practice │ │ ├── alconna │ │ │ ├── README.mdx │ │ │ ├── _category_.json │ │ │ ├── command.md │ │ │ ├── config.md │ │ │ ├── matcher.mdx │ │ │ └── uniseg.mdx │ │ ├── data-storing.md │ │ ├── database │ │ │ ├── README.mdx │ │ │ ├── _category_.json │ │ │ ├── developer │ │ │ │ ├── README.md │ │ │ │ ├── _category_.json │ │ │ │ ├── dependency.md │ │ │ │ └── test.md │ │ │ └── user.md │ │ ├── deployment.mdx │ │ ├── error-tracking.md │ │ ├── multi-adapter.mdx │ │ ├── scheduler.md │ │ └── testing │ │ │ ├── README.mdx │ │ │ ├── _category_.json │ │ │ ├── behavior.mdx │ │ │ └── mock-network.md │ │ ├── community │ │ ├── contact.md │ │ └── contributing.md │ │ ├── developer │ │ ├── adapter-writing.md │ │ └── plugin-publishing.mdx │ │ ├── editor-support.md │ │ ├── ospp │ │ ├── 2021.md │ │ ├── 2022.md │ │ ├── 2023.md │ │ ├── 2024.md │ │ └── 2025.md │ │ ├── quick-start.mdx │ │ └── tutorial │ │ ├── application.md │ │ ├── create-plugin.md │ │ ├── event-data.mdx │ │ ├── fundamentals.md │ │ ├── handler.mdx │ │ ├── matcher.md │ │ ├── message.md │ │ └── store.mdx ├── versioned_sidebars │ ├── version-2.4.0-sidebars.json │ ├── version-2.4.1-sidebars.json │ └── version-2.4.2-sidebars.json └── versions.json └── yarn.lock /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Default Linux Universal", 3 | "image": "mcr.microsoft.com/devcontainers/universal:2-linux", 4 | "features": { 5 | "ghcr.io/devcontainers-contrib/features/poetry:2": {} 6 | }, 7 | "postCreateCommand": "./scripts/setup-envs.sh", 8 | "customizations": { 9 | "vscode": { 10 | "settings": { 11 | "python.analysis.diagnosticMode": "workspace", 12 | "[python]": { 13 | "editor.defaultFormatter": "charliermarsh.ruff", 14 | "editor.codeActionsOnSave": { 15 | "source.fixAll.ruff": "explicit", 16 | "source.organizeImports": "explicit" 17 | } 18 | }, 19 | "[javascript]": { 20 | "editor.defaultFormatter": "esbenp.prettier-vscode" 21 | }, 22 | "[html]": { 23 | "editor.defaultFormatter": "esbenp.prettier-vscode" 24 | }, 25 | "[typescript]": { 26 | "editor.defaultFormatter": "esbenp.prettier-vscode" 27 | }, 28 | "[javascriptreact]": { 29 | "editor.defaultFormatter": "esbenp.prettier-vscode" 30 | }, 31 | "[typescriptreact]": { 32 | "editor.defaultFormatter": "esbenp.prettier-vscode" 33 | }, 34 | "files.exclude": { 35 | "**/__pycache__": true 36 | }, 37 | "files.watcherExclude": { 38 | "**/target/**": true, 39 | "**/__pycache__": true 40 | } 41 | }, 42 | "extensions": [ 43 | "ms-python.python", 44 | "ms-python.vscode-pylance", 45 | "charliermarsh.ruff", 46 | "EditorConfig.EditorConfig", 47 | "esbenp.prettier-vscode", 48 | "bradlc.vscode-tailwindcss" 49 | ] 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | # The JSON files contain newlines inconsistently 13 | [*.json] 14 | insert_final_newline = ignore 15 | 16 | # Minified JavaScript files shouldn't be changed 17 | [**.min.js] 18 | indent_style = ignore 19 | insert_final_newline = ignore 20 | 21 | # Makefiles always use tabs for indentation 22 | [Makefile] 23 | indent_style = tab 24 | 25 | # Batch files use tabs for indentation 26 | [*.bat] 27 | indent_style = tab 28 | 29 | [*.md] 30 | trim_trailing_whitespace = false 31 | 32 | # Matches the exact files either package.json or .travis.yml 33 | [{package.json,.travis.yml}] 34 | indent_size = 2 35 | 36 | [{*.py,*.pyi}] 37 | indent_size = 4 38 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .yarn 4 | .history 5 | build 6 | lib 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | website/versioned_*/** linguist-documentation 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: nonebot 2 | custom: ["https://afdian.com/@nonebot"] 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/adapter_publish.yml: -------------------------------------------------------------------------------- 1 | name: 发布适配器 2 | title: "Adapter: {name}" 3 | description: 发布适配器到 NoneBot 官方商店 4 | labels: ["Adapter", "Publish"] 5 | body: 6 | - type: input 7 | id: name 8 | attributes: 9 | label: 适配器名称 10 | description: 适配器名称 11 | validations: 12 | required: true 13 | 14 | - type: input 15 | id: description 16 | attributes: 17 | label: 适配器描述 18 | description: 适配器描述 19 | validations: 20 | required: true 21 | 22 | - type: input 23 | id: pypi 24 | attributes: 25 | label: PyPI 项目名 26 | description: PyPI 项目名 27 | placeholder: e.g. nonebot-adapter-xxx 28 | validations: 29 | required: true 30 | 31 | - type: input 32 | id: module 33 | attributes: 34 | label: 适配器 import 包名 35 | description: 适配器 import 包名 36 | placeholder: e.g. nonebot_adapter_xxx 37 | validations: 38 | required: true 39 | 40 | - type: input 41 | id: homepage 42 | attributes: 43 | label: 适配器项目仓库/主页链接 44 | description: 适配器项目仓库/主页链接 45 | placeholder: e.g. https://github.com/xxx/xxx 46 | validations: 47 | required: true 48 | 49 | - type: input 50 | id: tags 51 | attributes: 52 | label: 标签 53 | description: 标签 54 | placeholder: 'e.g. [{"label": "标签名", "color": "#ea5252"}]' 55 | value: "[]" 56 | validations: 57 | required: true 58 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bot_publish.yml: -------------------------------------------------------------------------------- 1 | name: 发布机器人 2 | title: "Bot: {name}" 3 | description: 发布机器人到 NoneBot 官方商店 4 | labels: ["Bot", "Publish"] 5 | body: 6 | - type: input 7 | id: name 8 | attributes: 9 | label: 机器人名称 10 | description: 机器人名称 11 | validations: 12 | required: true 13 | 14 | - type: input 15 | id: description 16 | attributes: 17 | label: 机器人描述 18 | description: 机器人描述 19 | validations: 20 | required: true 21 | 22 | - type: input 23 | id: homepage 24 | attributes: 25 | label: 机器人项目仓库/主页链接 26 | description: 机器人项目仓库/主页链接 27 | placeholder: e.g. https://github.com/xxx/xxx 28 | 29 | - type: input 30 | id: tags 31 | attributes: 32 | label: 标签 33 | description: 标签 34 | placeholder: 'e.g. [{"label": "标签名", "color": "#ea5252"}]' 35 | value: "[]" 36 | validations: 37 | required: true 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug 反馈 2 | title: "Bug: 出现异常" 3 | description: 提交 Bug 反馈以帮助我们改进代码 4 | labels: ["bug"] 5 | body: 6 | - type: dropdown 7 | id: env-os 8 | attributes: 9 | label: 操作系统 10 | description: 选择运行 NoneBot 的系统 11 | options: 12 | - Windows 13 | - MacOS 14 | - Linux 15 | - Other 16 | validations: 17 | required: true 18 | 19 | - type: input 20 | id: env-python-ver 21 | attributes: 22 | label: Python 版本 23 | description: 填写运行 NoneBot 的 Python 版本 24 | placeholder: e.g. 3.11.0 25 | validations: 26 | required: true 27 | 28 | - type: input 29 | id: env-nb-ver 30 | attributes: 31 | label: NoneBot 版本 32 | description: 填写 NoneBot 版本 33 | placeholder: e.g. 2.0.0 34 | validations: 35 | required: true 36 | 37 | - type: input 38 | id: env-adapter 39 | attributes: 40 | label: 适配器 41 | description: 填写使用的适配器以及版本 42 | placeholder: e.g. OneBot v11 2.2.2 43 | validations: 44 | required: true 45 | 46 | - type: input 47 | id: env-protocol 48 | attributes: 49 | label: 协议端 50 | description: 填写连接 NoneBot 的协议端及版本 51 | placeholder: e.g. go-cqhttp 1.0.0 52 | validations: 53 | required: true 54 | 55 | - type: textarea 56 | id: describe 57 | attributes: 58 | label: 描述问题 59 | description: 清晰简洁地说明问题是什么 60 | validations: 61 | required: true 62 | 63 | - type: textarea 64 | id: reproduction 65 | attributes: 66 | label: 复现步骤 67 | description: 提供能复现此问题的详细操作步骤 68 | placeholder: | 69 | 1. 首先…… 70 | 2. 然后…… 71 | 3. 发生…… 72 | validations: 73 | required: true 74 | 75 | - type: textarea 76 | id: expected 77 | attributes: 78 | label: 期望的结果 79 | description: 清晰简洁地描述你期望发生的事情 80 | 81 | - type: textarea 82 | id: logs 83 | attributes: 84 | label: 截图或日志 85 | description: 提供有助于诊断问题的任何日志和截图 86 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: NoneBot 论坛 4 | url: https://discussions.nonebot.dev/ 5 | about: 前往 NoneBot 论坛提问 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/document.yml: -------------------------------------------------------------------------------- 1 | name: 文档改进 2 | title: "Docs: 描述" 3 | description: 文档错误及改进意见反馈 4 | labels: ["documentation"] 5 | body: 6 | - type: textarea 7 | id: problem 8 | attributes: 9 | label: 描述问题或主题 10 | validations: 11 | required: true 12 | 13 | - type: textarea 14 | id: improve 15 | attributes: 16 | label: 需做出的修改 17 | validations: 18 | required: true 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 功能建议 2 | title: "Feature: 功能描述" 3 | description: 提出关于项目新功能的想法 4 | labels: ["enhancement"] 5 | body: 6 | - type: textarea 7 | id: problem 8 | attributes: 9 | label: 希望能解决的问题 10 | description: 在使用中遇到什么问题而需要新的功能? 11 | validations: 12 | required: true 13 | 14 | - type: textarea 15 | id: feature 16 | attributes: 17 | label: 描述所需要的功能 18 | description: 请说明需要的功能或解决方法 19 | validations: 20 | required: true 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/plugin_publish.yml: -------------------------------------------------------------------------------- 1 | name: 发布插件 2 | title: "Plugin: {name}" 3 | description: 发布插件到 NoneBot 官方商店 4 | labels: ["Plugin", "Publish"] 5 | body: 6 | - type: input 7 | id: pypi 8 | attributes: 9 | label: PyPI 项目名 10 | description: PyPI 项目名 11 | placeholder: e.g. nonebot-plugin-xxx 12 | validations: 13 | required: true 14 | 15 | - type: input 16 | id: module 17 | attributes: 18 | label: 插件 import 包名 19 | description: 插件 import 包名 20 | placeholder: e.g. nonebot_plugin_xxx 21 | validations: 22 | required: true 23 | 24 | - type: input 25 | id: tags 26 | attributes: 27 | label: 标签 28 | description: 标签 29 | placeholder: 'e.g. [{"label": "标签名", "color": "#ea5252"}]' 30 | value: "[]" 31 | validations: 32 | required: true 33 | 34 | - type: textarea 35 | id: config 36 | attributes: 37 | label: 插件配置项 38 | description: 插件配置项 39 | render: dotenv 40 | placeholder: | 41 | # e.g. 42 | # KEY=VALUE 43 | # KEY2=VALUE2 44 | -------------------------------------------------------------------------------- /.github/actions/build-api-doc/action.yml: -------------------------------------------------------------------------------- 1 | name: Build API Doc 2 | description: Build API Doc 3 | 4 | runs: 5 | using: "composite" 6 | steps: 7 | - run: | 8 | poetry run nb-autodoc nonebot \ 9 | -s nonebot.plugins \ 10 | -u nonebot.internal \ 11 | -u nonebot.internal.* 12 | cp -r ./build/nonebot/* ./website/docs/api/ 13 | yarn prettier 14 | shell: bash 15 | -------------------------------------------------------------------------------- /.github/actions/setup-node/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup Node 2 | description: Setup Node 3 | 4 | runs: 5 | using: "composite" 6 | steps: 7 | - uses: actions/setup-node@v4 8 | with: 9 | node-version: "18" 10 | cache: "yarn" 11 | 12 | - run: yarn install --frozen-lockfile 13 | shell: bash 14 | -------------------------------------------------------------------------------- /.github/actions/setup-python/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup Python 2 | description: Setup Python 3 | 4 | inputs: 5 | python-version: 6 | description: Python version 7 | required: false 8 | default: "3.10" 9 | env-dir: 10 | description: Environment directory 11 | required: false 12 | default: "." 13 | no-root: 14 | description: Do not install package in the environment 15 | required: false 16 | default: "false" 17 | 18 | runs: 19 | using: "composite" 20 | steps: 21 | - name: Install poetry 22 | run: pipx install poetry 23 | shell: bash 24 | 25 | - uses: actions/setup-python@v5 26 | with: 27 | python-version: ${{ inputs.python-version }} 28 | cache: "poetry" 29 | cache-dependency-path: | 30 | ./poetry.lock 31 | ${{ inputs.env-dir }}/poetry.lock 32 | 33 | - run: | 34 | cd ${{ inputs.env-dir }} 35 | if [ "${{ inputs.no-root }}" = "true" ]; then 36 | poetry install --all-extras --no-root 37 | else 38 | poetry install --all-extras 39 | fi 40 | shell: bash 41 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | groups: 8 | actions: 9 | patterns: 10 | - "*" 11 | 12 | - package-ecosystem: github-actions 13 | directory: "/.github/actions/build-api-doc" 14 | schedule: 15 | interval: daily 16 | groups: 17 | actions: 18 | patterns: 19 | - "*" 20 | 21 | - package-ecosystem: github-actions 22 | directory: "/.github/actions/setup-node" 23 | schedule: 24 | interval: daily 25 | groups: 26 | actions: 27 | patterns: 28 | - "*" 29 | 30 | - package-ecosystem: github-actions 31 | directory: "/.github/actions/setup-python" 32 | schedule: 33 | interval: daily 34 | groups: 35 | actions: 36 | patterns: 37 | - "*" 38 | 39 | - package-ecosystem: devcontainers 40 | directory: "/" 41 | schedule: 42 | interval: daily 43 | groups: 44 | devcontainers: 45 | patterns: 46 | - "*" 47 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | template: $CHANGES 2 | category-template: "### $TITLE" 3 | name-template: "Release v$RESOLVED_VERSION 🌈" 4 | tag-template: "v$RESOLVED_VERSION" 5 | change-template: "- $TITLE [@$AUTHOR](https://github.com/$AUTHOR) ([#$NUMBER]($URL))" 6 | change-title-escapes: '\<&' 7 | exclude-labels: 8 | - "dependencies" 9 | - "skip-changelog" 10 | categories: 11 | - title: "💥 破坏性变更" 12 | labels: 13 | - "Breaking" 14 | - title: "🚀 新功能" 15 | labels: 16 | - "feature" 17 | - "enhancement" 18 | - title: "🐛 Bug 修复" 19 | labels: 20 | - "fix" 21 | - "bugfix" 22 | - "bug" 23 | - title: "📝 文档" 24 | labels: 25 | - "documentation" 26 | - title: "💫 杂项" 27 | - title: "🍻 插件发布" 28 | label: "Plugin" 29 | - title: "🍻 机器人发布" 30 | label: "Bot" 31 | - title: "🍻 适配器发布" 32 | label: "Adapter" 33 | version-resolver: 34 | major: 35 | labels: 36 | - "major" 37 | minor: 38 | labels: 39 | - "minor" 40 | patch: 41 | labels: 42 | - "patch" 43 | default: patch 44 | -------------------------------------------------------------------------------- /.github/workflows/pyright.yml: -------------------------------------------------------------------------------- 1 | name: Pyright Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | paths: 9 | - "envs/**" 10 | - "nonebot/**" 11 | - "packages/**" 12 | - "tests/**" 13 | - ".github/actions/setup-python/**" 14 | - ".github/workflows/pyright.yml" 15 | - "pyproject.toml" 16 | - "poetry.lock" 17 | 18 | jobs: 19 | pyright: 20 | name: Pyright Lint 21 | runs-on: ubuntu-latest 22 | concurrency: 23 | group: pyright-${{ github.ref }}-${{ matrix.env }} 24 | cancel-in-progress: true 25 | strategy: 26 | matrix: 27 | env: [pydantic-v1, pydantic-v2] 28 | fail-fast: false 29 | 30 | steps: 31 | - uses: actions/checkout@v4 32 | 33 | - name: Setup Python environment 34 | uses: ./.github/actions/setup-python 35 | with: 36 | env-dir: ./envs/${{ matrix.env }} 37 | no-root: true 38 | 39 | - run: | 40 | (cd ./envs/${{ matrix.env }} && echo "$(poetry env info --path)/bin" >> $GITHUB_PATH) 41 | if [ "${{ matrix.env }}" = "pydantic-v1" ]; then 42 | sed -i 's/PYDANTIC_V2 = true/PYDANTIC_V2 = false/g' ./pyproject.toml 43 | fi 44 | shell: bash 45 | 46 | - name: Run Pyright 47 | uses: jakebailey/pyright-action@v2 48 | with: 49 | pylance-version: latest-release 50 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Generate token 11 | id: generate-token 12 | uses: tibdex/github-app-token@v2 13 | with: 14 | app_id: ${{ secrets.APP_ID }} 15 | private_key: ${{ secrets.APP_KEY }} 16 | 17 | - uses: actions/checkout@v4 18 | with: 19 | token: ${{ steps.generate-token.outputs.token }} 20 | 21 | - name: Setup Python Environment 22 | uses: ./.github/actions/setup-python 23 | 24 | - name: Setup Node Environment 25 | uses: ./.github/actions/setup-node 26 | 27 | - name: Build API Doc 28 | uses: ./.github/actions/build-api-doc 29 | 30 | - run: echo "TAG_NAME=v$(poetry version -s)" >> $GITHUB_ENV 31 | 32 | - name: Archive Changelog 33 | uses: docker://ghcr.io/nonebot/auto-changelog:master 34 | with: 35 | changelog_file: website/src/changelog/changelog.md 36 | archive_regex: '(?<=## )最近更新(?=\n)' 37 | archive_title: ${{ env.TAG_NAME }} 38 | commit_and_push: false 39 | 40 | - name: Archive Files 41 | run: | 42 | yarn archive $(poetry version -s) 43 | yarn prettier 44 | 45 | - name: Push Tag 46 | run: | 47 | git config user.name noneflow[bot] 48 | git config user.email 129742071+noneflow[bot]@users.noreply.github.com 49 | git add . 50 | git commit -m ":bookmark: Release $(poetry version -s)" 51 | git tag ${{ env.TAG_NAME }} 52 | git push && git push --tags 53 | -------------------------------------------------------------------------------- /.github/workflows/ruff.yml: -------------------------------------------------------------------------------- 1 | name: Ruff Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | paths: 9 | - "envs/**" 10 | - "nonebot/**" 11 | - "packages/**" 12 | - "tests/**" 13 | - ".github/actions/setup-python/**" 14 | - ".github/workflows/ruff.yml" 15 | - "pyproject.toml" 16 | - "poetry.lock" 17 | 18 | jobs: 19 | ruff: 20 | name: Ruff Lint 21 | runs-on: ubuntu-latest 22 | concurrency: 23 | group: ruff-${{ github.ref }} 24 | cancel-in-progress: true 25 | 26 | steps: 27 | - uses: actions/checkout@v4 28 | 29 | - name: Run Ruff Lint 30 | uses: astral-sh/ruff-action@v3 31 | -------------------------------------------------------------------------------- /.github/workflows/website-deploy.yml: -------------------------------------------------------------------------------- 1 | name: Site Deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | concurrency: 12 | group: website-deploy-${{ github.ref }} 13 | cancel-in-progress: true 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Setup Python Environment 21 | uses: ./.github/actions/setup-python 22 | 23 | - name: Setup Node Environment 24 | uses: ./.github/actions/setup-node 25 | 26 | - name: Build API Doc 27 | uses: ./.github/actions/build-api-doc 28 | 29 | - name: Build Doc 30 | run: yarn build 31 | 32 | - name: Get Branch Name 33 | run: echo "BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_ENV 34 | 35 | - name: Deploy to Netlify 36 | uses: nwtgck/actions-netlify@v3 37 | with: 38 | publish-dir: "./website/build" 39 | production-deploy: true 40 | github-token: ${{ secrets.GITHUB_TOKEN }} 41 | deploy-message: "Deploy ${{ env.BRANCH_NAME }}@${{ github.sha }}" 42 | enable-commit-comment: false 43 | alias: ${{ env.BRANCH_NAME }} 44 | env: 45 | NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} 46 | NETLIFY_SITE_ID: ${{ secrets.SITE_ID }} 47 | -------------------------------------------------------------------------------- /.github/workflows/website-preview-ci.yml: -------------------------------------------------------------------------------- 1 | name: Site Deploy (Preview CI) 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | preview-ci: 8 | runs-on: ubuntu-latest 9 | concurrency: 10 | group: pull-request-preview-${{ github.event.number }} 11 | cancel-in-progress: true 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | with: 16 | ref: ${{ github.event.pull_request.head.sha }} 17 | fetch-depth: 0 18 | 19 | - name: Setup Python Environment 20 | uses: ./.github/actions/setup-python 21 | 22 | - name: Setup Node Environment 23 | uses: ./.github/actions/setup-node 24 | 25 | - name: Build API Doc 26 | uses: ./.github/actions/build-api-doc 27 | 28 | - name: Build Doc 29 | run: yarn build 30 | 31 | - name: Export Context 32 | run: | 33 | echo "${{ github.event.pull_request.number }}" > ./pr-number 34 | 35 | - name: Upload Artifact 36 | uses: actions/upload-artifact@v4 37 | with: 38 | name: website-preview 39 | path: | 40 | ./website/build 41 | ./pr-number 42 | retention-days: 1 43 | -------------------------------------------------------------------------------- /.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | MD013: false 2 | MD024: # 重复标题 3 | siblings_only: true 4 | MD033: false # 允许 html 5 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | default_install_hook_types: [pre-commit, prepare-commit-msg] 2 | ci: 3 | autofix_commit_msg: ":rotating_light: auto fix by pre-commit hooks" 4 | autofix_prs: true 5 | autoupdate_branch: master 6 | autoupdate_schedule: monthly 7 | autoupdate_commit_msg: ":arrow_up: auto update by pre-commit hooks" 8 | repos: 9 | - repo: https://github.com/astral-sh/ruff-pre-commit 10 | rev: v0.11.8 11 | hooks: 12 | - id: ruff 13 | args: [--fix] 14 | stages: [pre-commit] 15 | - id: ruff-format 16 | stages: [pre-commit] 17 | 18 | - repo: https://github.com/nonebot/nonemoji 19 | rev: v0.1.4 20 | hooks: 21 | - id: nonemoji 22 | stages: [prepare-commit-msg] 23 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .github/**/*.md 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "endOfLine": "lf", 5 | "arrowParens": "always", 6 | "singleQuote": false, 7 | "trailingComma": "es5", 8 | "semi": true, 9 | "overrides": [ 10 | { 11 | "files": [ 12 | "**/devcontainer.json", 13 | "**/tsconfig.json", 14 | "**/tsconfig.*.json" 15 | ], 16 | "options": { 17 | "parser": "json" 18 | } 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["stylelint-config-standard", "stylelint-prettier/recommended"], 3 | overrides: [ 4 | { 5 | files: ["*.css"], 6 | rules: { 7 | "function-no-unknown": [true, { ignoreFunctions: ["theme"] }], 8 | "selector-class-pattern": [ 9 | "^([a-z][a-z0-9]*)(-[a-z0-9]+)*$", 10 | { 11 | resolveNestedSelectors: true, 12 | message: (selector) => 13 | `Expected class selector "${selector}" to be kebab-case`, 14 | }, 15 | ], 16 | }, 17 | }, 18 | { 19 | files: ["*.module.css"], 20 | rules: { 21 | "selector-class-pattern": [ 22 | "^[a-z][a-zA-Z0-9]+$", 23 | { 24 | message: (selector) => 25 | `Expected class selector "${selector}" to be lowerCamelCase`, 26 | }, 27 | ], 28 | }, 29 | }, 30 | ], 31 | }; 32 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | registry "https://registry.npmjs.org/" 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | See [changelog.md](./website/src/changelog/changelog.md) or 4 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | # This CITATION.cff file was generated with cffinit. 2 | # Visit https://bit.ly/cffinit to generate yours today! 3 | 4 | cff-version: 1.2.0 5 | title: NoneBot 6 | message: >- 7 | If you use this software, please cite it using the 8 | metadata from this file. 9 | type: software 10 | authors: 11 | - given-names: Yongyu 12 | family-names: Yan 13 | email: yyy@nonebot.dev 14 | - name: NoneBot Team 15 | email: contact@nonebot.dev 16 | website: 'https://github.com/nonebot' 17 | repository-code: 'https://github.com/nonebot/nonebot2' 18 | url: 'https://nonebot.dev/' 19 | abstract: >- 20 | NoneBot, an asynchronous multi-platform chatbot framework 21 | written in Python 22 | keywords: 23 | - nonebot 24 | - chatbot 25 | - pydantic 26 | license: MIT 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2020 NoneBot Team 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /assets/drivers.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "module_name": "~none", 4 | "project_link": "", 5 | "name": "None", 6 | "desc": "None 驱动器", 7 | "author_id": 42488585, 8 | "homepage": "/docs/advanced/driver", 9 | "tags": [], 10 | "is_official": true 11 | }, 12 | { 13 | "module_name": "~fastapi", 14 | "project_link": "nonebot2[fastapi]", 15 | "name": "FastAPI", 16 | "desc": "FastAPI 驱动器", 17 | "author_id": 42488585, 18 | "homepage": "/docs/advanced/driver", 19 | "tags": [], 20 | "is_official": true 21 | }, 22 | { 23 | "module_name": "~quart", 24 | "project_link": "nonebot2[quart]", 25 | "name": "Quart", 26 | "desc": "Quart 驱动器", 27 | "author_id": 42488585, 28 | "homepage": "/docs/advanced/driver", 29 | "tags": [], 30 | "is_official": true 31 | }, 32 | { 33 | "module_name": "~httpx", 34 | "project_link": "nonebot2[httpx]", 35 | "name": "HTTPX", 36 | "desc": "HTTPX 驱动器", 37 | "author_id": 42488585, 38 | "homepage": "/docs/advanced/driver", 39 | "tags": [], 40 | "is_official": true 41 | }, 42 | { 43 | "module_name": "~websockets", 44 | "project_link": "nonebot2[websockets]", 45 | "name": "websockets", 46 | "desc": "websockets 驱动器", 47 | "author_id": 42488585, 48 | "homepage": "/docs/advanced/driver", 49 | "tags": [], 50 | "is_official": true 51 | }, 52 | { 53 | "module_name": "~aiohttp", 54 | "project_link": "nonebot2[aiohttp]", 55 | "name": "AIOHTTP", 56 | "desc": "AIOHTTP 驱动器", 57 | "author_id": 42488585, 58 | "homepage": "/docs/advanced/driver", 59 | "tags": [], 60 | "is_official": true 61 | }, 62 | ] 63 | -------------------------------------------------------------------------------- /envs/pydantic-v1/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "nonebot-pydantic-v1" 3 | version = "0.1.0" 4 | description = "Private pydantic v1 test env for nonebot" 5 | authors = ["yanyongyu "] 6 | license = "MIT" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.9" 10 | 11 | [tool.poetry.group.dev.dependencies] 12 | pydantic = "^1.0.0" 13 | nonebot-test = { path = "../test/", develop = false } 14 | nonebot2 = { path = "../../", extras = ["all"], develop = true } 15 | 16 | [build-system] 17 | requires = ["poetry-core"] 18 | build-backend = "poetry.core.masonry.api" 19 | -------------------------------------------------------------------------------- /envs/pydantic-v2/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "nonebot-pydantic-v2" 3 | version = "0.1.0" 4 | description = "Private pydantic v2 test env for nonebot" 5 | authors = ["yanyongyu "] 6 | license = "MIT" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.9" 10 | 11 | [tool.poetry.group.dev.dependencies] 12 | pydantic = "^2.0.0" 13 | nonebot-test = { path = "../test/", develop = false } 14 | nonebot2 = { path = "../../", extras = ["all"], develop = true } 15 | 16 | [build-system] 17 | requires = ["poetry-core"] 18 | build-backend = "poetry.core.masonry.api" 19 | -------------------------------------------------------------------------------- /envs/test/nonebot-test.py: -------------------------------------------------------------------------------- 1 | # fake file to make project installable 2 | -------------------------------------------------------------------------------- /envs/test/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "nonebot-test" 3 | version = "0.1.0" 4 | description = "Private test env for nonebot" 5 | authors = ["yanyongyu "] 6 | license = "MIT" 7 | packages = [{ include = "nonebot-test.py" }] 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.9" 11 | trio = "^0.27.0" 12 | nonebug = "^0.4.1" 13 | wsproto = "^1.2.0" 14 | pytest-cov = "^6.0.0" 15 | pytest-xdist = "^3.0.2" 16 | werkzeug = ">=2.3.6,<4.0.0" 17 | coverage-conditional-plugin = "^0.9.0" 18 | 19 | [build-system] 20 | requires = ["poetry-core"] 21 | build-backend = "poetry.core.masonry.api" 22 | -------------------------------------------------------------------------------- /nonebot/adapters/__init__.py: -------------------------------------------------------------------------------- 1 | """本模块定义了协议适配基类,各协议请继承以下基类。 2 | 3 | 使用 {ref}`nonebot.drivers.Driver.register_adapter` 注册适配器。 4 | 5 | FrontMatter: 6 | mdx: 7 | format: md 8 | sidebar_position: 0 9 | description: nonebot.adapters 模块 10 | """ 11 | 12 | from nonebot.internal.adapter import Adapter as Adapter 13 | from nonebot.internal.adapter import Bot as Bot 14 | from nonebot.internal.adapter import Event as Event 15 | from nonebot.internal.adapter import Message as Message 16 | from nonebot.internal.adapter import MessageSegment as MessageSegment 17 | from nonebot.internal.adapter import MessageTemplate as MessageTemplate 18 | 19 | __autodoc__ = { 20 | "Bot": True, 21 | "Event": True, 22 | "Adapter": True, 23 | "Message": True, 24 | "Message.__getitem__": True, 25 | "Message.__contains__": True, 26 | "Message._construct": True, 27 | "MessageSegment": True, 28 | "MessageSegment.__str__": True, 29 | "MessageSegment.__add__": True, 30 | "MessageTemplate": True, 31 | } 32 | -------------------------------------------------------------------------------- /nonebot/consts.py: -------------------------------------------------------------------------------- 1 | """本模块包含了 NoneBot 事件处理过程中使用到的常量。 2 | 3 | FrontMatter: 4 | mdx: 5 | format: md 6 | sidebar_position: 9 7 | description: nonebot.consts 模块 8 | """ 9 | 10 | import os 11 | import sys 12 | from typing import Literal 13 | 14 | # used by Matcher 15 | RECEIVE_KEY: Literal["_receive_{id}"] = "_receive_{id}" 16 | """`receive` 存储 key""" 17 | LAST_RECEIVE_KEY: Literal["_last_receive"] = "_last_receive" 18 | """`last_receive` 存储 key""" 19 | ARG_KEY: Literal["{key}"] = "{key}" 20 | """`arg` 存储 key""" 21 | REJECT_TARGET: Literal["_current_target"] = "_current_target" 22 | """当前 `reject` 目标存储 key""" 23 | REJECT_CACHE_TARGET: Literal["_next_target"] = "_next_target" 24 | """下一个 `reject` 目标存储 key""" 25 | PAUSE_PROMPT_RESULT_KEY: Literal["_pause_result"] = "_pause_result" 26 | """`pause` prompt 发送结果存储 key""" 27 | REJECT_PROMPT_RESULT_KEY: Literal["_reject_{key}_result"] = "_reject_{key}_result" 28 | """`reject` prompt 发送结果存储 key""" 29 | 30 | # used by Rule 31 | PREFIX_KEY: Literal["_prefix"] = "_prefix" 32 | """命令前缀存储 key""" 33 | 34 | CMD_KEY: Literal["command"] = "command" 35 | """命令元组存储 key""" 36 | RAW_CMD_KEY: Literal["raw_command"] = "raw_command" 37 | """命令文本存储 key""" 38 | CMD_ARG_KEY: Literal["command_arg"] = "command_arg" 39 | """命令参数存储 key""" 40 | CMD_START_KEY: Literal["command_start"] = "command_start" 41 | """命令开头存储 key""" 42 | CMD_WHITESPACE_KEY: Literal["command_whitespace"] = "command_whitespace" 43 | """命令与参数间空白符存储 key""" 44 | 45 | SHELL_ARGS: Literal["_args"] = "_args" 46 | """shell 命令 parse 后参数字典存储 key""" 47 | SHELL_ARGV: Literal["_argv"] = "_argv" 48 | """shell 命令原始参数列表存储 key""" 49 | 50 | REGEX_MATCHED: Literal["_matched"] = "_matched" 51 | """正则匹配结果存储 key""" 52 | STARTSWITH_KEY: Literal["_startswith"] = "_startswith" 53 | """响应触发前缀 key""" 54 | ENDSWITH_KEY: Literal["_endswith"] = "_endswith" 55 | """响应触发后缀 key""" 56 | FULLMATCH_KEY: Literal["_fullmatch"] = "_fullmatch" 57 | """响应触发完整消息 key""" 58 | KEYWORD_KEY: Literal["_keyword"] = "_keyword" 59 | """响应触发关键字 key""" 60 | 61 | WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt") 62 | -------------------------------------------------------------------------------- /nonebot/dependencies/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | FrontMatter: 3 | mdx: 4 | format: md 5 | sidebar_position: 1 6 | description: nonebot.dependencies.utils 模块 7 | """ 8 | 9 | import inspect 10 | from typing import Any, Callable, ForwardRef 11 | 12 | from loguru import logger 13 | 14 | from nonebot.compat import ModelField 15 | from nonebot.exception import TypeMisMatch 16 | from nonebot.typing import evaluate_forwardref 17 | 18 | 19 | def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature: 20 | """获取可调用对象签名""" 21 | 22 | signature = inspect.signature(call) 23 | globalns = getattr(call, "__globals__", {}) 24 | typed_params = [ 25 | inspect.Parameter( 26 | name=param.name, 27 | kind=param.kind, 28 | default=param.default, 29 | annotation=get_typed_annotation(param, globalns), 30 | ) 31 | for param in signature.parameters.values() 32 | ] 33 | return inspect.Signature(typed_params) 34 | 35 | 36 | def get_typed_annotation(param: inspect.Parameter, globalns: dict[str, Any]) -> Any: 37 | """获取参数的类型注解""" 38 | 39 | annotation = param.annotation 40 | if isinstance(annotation, str): 41 | annotation = ForwardRef(annotation) 42 | try: 43 | annotation = evaluate_forwardref(annotation, globalns, globalns) 44 | except Exception as e: 45 | logger.opt(colors=True, exception=e).warning( 46 | f'Unknown ForwardRef["{param.annotation}"] for parameter {param.name}' 47 | ) 48 | return inspect.Parameter.empty 49 | return annotation 50 | 51 | 52 | def check_field_type(field: ModelField, value: Any) -> Any: 53 | """检查字段类型是否匹配""" 54 | 55 | try: 56 | return field.validate_value(value) 57 | except ValueError: 58 | raise TypeMisMatch(field, value) 59 | -------------------------------------------------------------------------------- /nonebot/drivers/__init__.py: -------------------------------------------------------------------------------- 1 | """本模块定义了驱动适配器基类。 2 | 3 | 各驱动请继承以下基类。 4 | 5 | FrontMatter: 6 | mdx: 7 | format: md 8 | sidebar_position: 0 9 | description: nonebot.drivers 模块 10 | """ 11 | 12 | from nonebot.internal.driver import URL as URL 13 | from nonebot.internal.driver import ASGIMixin as ASGIMixin 14 | from nonebot.internal.driver import Cookies as Cookies 15 | from nonebot.internal.driver import Driver as Driver 16 | from nonebot.internal.driver import ForwardDriver as ForwardDriver 17 | from nonebot.internal.driver import ForwardMixin as ForwardMixin 18 | from nonebot.internal.driver import HTTPClientMixin as HTTPClientMixin 19 | from nonebot.internal.driver import HTTPClientSession as HTTPClientSession 20 | from nonebot.internal.driver import HTTPServerSetup as HTTPServerSetup 21 | from nonebot.internal.driver import HTTPVersion as HTTPVersion 22 | from nonebot.internal.driver import Mixin as Mixin 23 | from nonebot.internal.driver import Request as Request 24 | from nonebot.internal.driver import Response as Response 25 | from nonebot.internal.driver import ReverseDriver as ReverseDriver 26 | from nonebot.internal.driver import ReverseMixin as ReverseMixin 27 | from nonebot.internal.driver import WebSocket as WebSocket 28 | from nonebot.internal.driver import WebSocketClientMixin as WebSocketClientMixin 29 | from nonebot.internal.driver import WebSocketServerSetup as WebSocketServerSetup 30 | from nonebot.internal.driver import combine_driver as combine_driver 31 | 32 | __autodoc__ = { 33 | "URL": True, 34 | "Cookies": True, 35 | "Request": True, 36 | "Response": True, 37 | "WebSocket": True, 38 | "HTTPVersion": True, 39 | "Driver": True, 40 | "Mixin": True, 41 | "ForwardMixin": True, 42 | "ForwardDriver": True, 43 | "HTTPClientMixin": True, 44 | "WebSocketClientMixin": True, 45 | "ReverseMixin": True, 46 | "ReverseDriver": True, 47 | "ASGIMixin": True, 48 | "combine_driver": True, 49 | "HTTPServerSetup": True, 50 | "WebSocketServerSetup": True, 51 | } 52 | -------------------------------------------------------------------------------- /nonebot/internal/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonebot/nonebot2/f8317444dfaaf720ff22fedf5b0a8d9f6138421c/nonebot/internal/__init__.py -------------------------------------------------------------------------------- /nonebot/internal/adapter/__init__.py: -------------------------------------------------------------------------------- 1 | from .adapter import Adapter as Adapter 2 | from .bot import Bot as Bot 3 | from .event import Event as Event 4 | from .message import Message as Message 5 | from .message import MessageSegment as MessageSegment 6 | from .template import MessageTemplate as MessageTemplate 7 | -------------------------------------------------------------------------------- /nonebot/internal/driver/__init__.py: -------------------------------------------------------------------------------- 1 | from .abstract import ASGIMixin as ASGIMixin 2 | from .abstract import Driver as Driver 3 | from .abstract import ForwardDriver as ForwardDriver 4 | from .abstract import ForwardMixin as ForwardMixin 5 | from .abstract import HTTPClientMixin as HTTPClientMixin 6 | from .abstract import HTTPClientSession as HTTPClientSession 7 | from .abstract import Mixin as Mixin 8 | from .abstract import ReverseDriver as ReverseDriver 9 | from .abstract import ReverseMixin as ReverseMixin 10 | from .abstract import WebSocketClientMixin as WebSocketClientMixin 11 | from .combine import combine_driver as combine_driver 12 | from .model import URL as URL 13 | from .model import ContentTypes as ContentTypes 14 | from .model import Cookies as Cookies 15 | from .model import CookieTypes as CookieTypes 16 | from .model import DataTypes as DataTypes 17 | from .model import FileContent as FileContent 18 | from .model import FilesTypes as FilesTypes 19 | from .model import FileType as FileType 20 | from .model import FileTypes as FileTypes 21 | from .model import HeaderTypes as HeaderTypes 22 | from .model import HTTPServerSetup as HTTPServerSetup 23 | from .model import HTTPVersion as HTTPVersion 24 | from .model import QueryTypes as QueryTypes 25 | from .model import QueryVariable as QueryVariable 26 | from .model import RawURL as RawURL 27 | from .model import Request as Request 28 | from .model import Response as Response 29 | from .model import SimpleQuery as SimpleQuery 30 | from .model import WebSocket as WebSocket 31 | from .model import WebSocketServerSetup as WebSocketServerSetup 32 | -------------------------------------------------------------------------------- /nonebot/internal/driver/combine.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, TypeVar, Union, overload 2 | 3 | from .abstract import Driver, Mixin 4 | 5 | D = TypeVar("D", bound="Driver") 6 | 7 | if TYPE_CHECKING: 8 | 9 | class CombinedDriver(Driver, Mixin): ... 10 | 11 | 12 | @overload 13 | def combine_driver(driver: type[D]) -> type[D]: ... 14 | 15 | 16 | @overload 17 | def combine_driver( 18 | driver: type[D], __m: type[Mixin], /, *mixins: type[Mixin] 19 | ) -> type["CombinedDriver"]: ... 20 | 21 | 22 | def combine_driver( 23 | driver: type[D], *mixins: type[Mixin] 24 | ) -> Union[type[D], type["CombinedDriver"]]: 25 | """将一个驱动器和多个混入类合并。""" 26 | # check first 27 | if not issubclass(driver, Driver): 28 | raise TypeError("`driver` must be subclass of Driver") 29 | if not all(issubclass(m, Mixin) for m in mixins): 30 | raise TypeError("`mixins` must be subclass of Mixin") 31 | 32 | if not mixins: 33 | return driver 34 | 35 | def type_(self: "CombinedDriver") -> str: 36 | return ( 37 | driver.type.__get__(self) # type: ignore 38 | + "+" 39 | + "+".join(x.type.__get__(self) for x in mixins) # type: ignore 40 | ) 41 | 42 | return type("CombinedDriver", (*mixins, driver), {"type": property(type_)}) # type: ignore 43 | -------------------------------------------------------------------------------- /nonebot/internal/matcher/__init__.py: -------------------------------------------------------------------------------- 1 | from .manager import MatcherManager as MatcherManager 2 | from .provider import DEFAULT_PROVIDER_CLASS as DEFAULT_PROVIDER_CLASS 3 | from .provider import MatcherProvider as MatcherProvider 4 | 5 | matchers = MatcherManager() 6 | 7 | from .matcher import Matcher as Matcher 8 | from .matcher import MatcherSource as MatcherSource 9 | from .matcher import current_bot as current_bot 10 | from .matcher import current_event as current_event 11 | from .matcher import current_handler as current_handler 12 | from .matcher import current_matcher as current_matcher 13 | -------------------------------------------------------------------------------- /nonebot/internal/matcher/provider.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from collections import defaultdict 3 | from collections.abc import Mapping, MutableMapping 4 | from typing import TYPE_CHECKING 5 | 6 | if TYPE_CHECKING: 7 | from .matcher import Matcher 8 | 9 | 10 | class MatcherProvider(abc.ABC, MutableMapping[int, list[type["Matcher"]]]): 11 | """事件响应器存储器基类 12 | 13 | 参数: 14 | matchers: 当前存储器中已有的事件响应器 15 | """ 16 | 17 | @abc.abstractmethod 18 | def __init__(self, matchers: Mapping[int, list[type["Matcher"]]]): 19 | raise NotImplementedError 20 | 21 | 22 | class _DictProvider(defaultdict[int, list[type["Matcher"]]], MatcherProvider): # type: ignore 23 | def __init__(self, matchers: Mapping[int, list[type["Matcher"]]]): 24 | super().__init__(list, matchers) 25 | 26 | 27 | DEFAULT_PROVIDER_CLASS = _DictProvider 28 | """默认存储器类型""" 29 | -------------------------------------------------------------------------------- /nonebot/matcher.py: -------------------------------------------------------------------------------- 1 | """本模块实现事件响应器的创建与运行,并提供一些快捷方法来帮助用户更好的与机器人进行对话。 2 | 3 | FrontMatter: 4 | mdx: 5 | format: md 6 | sidebar_position: 3 7 | description: nonebot.matcher 模块 8 | """ 9 | 10 | from nonebot.internal.matcher import DEFAULT_PROVIDER_CLASS as DEFAULT_PROVIDER_CLASS 11 | from nonebot.internal.matcher import Matcher as Matcher 12 | from nonebot.internal.matcher import MatcherManager as MatcherManager 13 | from nonebot.internal.matcher import MatcherProvider as MatcherProvider 14 | from nonebot.internal.matcher import MatcherSource as MatcherSource 15 | from nonebot.internal.matcher import current_bot as current_bot 16 | from nonebot.internal.matcher import current_event as current_event 17 | from nonebot.internal.matcher import current_handler as current_handler 18 | from nonebot.internal.matcher import current_matcher as current_matcher 19 | from nonebot.internal.matcher import matchers as matchers 20 | 21 | __autodoc__ = { 22 | "Matcher": True, 23 | "matchers": True, 24 | "MatcherManager": True, 25 | "MatcherProvider": True, 26 | "DEFAULT_PROVIDER_CLASS": True, 27 | } 28 | -------------------------------------------------------------------------------- /nonebot/plugins/echo.py: -------------------------------------------------------------------------------- 1 | from nonebot import on_command 2 | from nonebot.adapters import Message 3 | from nonebot.params import CommandArg 4 | from nonebot.plugin import PluginMetadata 5 | from nonebot.rule import to_me 6 | 7 | __plugin_meta__ = PluginMetadata( 8 | name="echo", 9 | description="重复你说的话", 10 | usage="/echo [text]", 11 | type="application", 12 | homepage="https://github.com/nonebot/nonebot2/blob/master/nonebot/plugins/echo.py", 13 | config=None, 14 | supported_adapters=None, 15 | ) 16 | 17 | echo = on_command("echo", to_me()) 18 | 19 | 20 | @echo.handle() 21 | async def handle_echo(message: Message = CommandArg()): 22 | if any((not seg.is_text()) or str(seg) for seg in message): 23 | await echo.send(message=message) 24 | -------------------------------------------------------------------------------- /nonebot/plugins/single_session.py: -------------------------------------------------------------------------------- 1 | from collections.abc import AsyncGenerator 2 | 3 | from nonebot.adapters import Event 4 | from nonebot.message import IgnoredException, event_preprocessor 5 | from nonebot.params import Depends 6 | from nonebot.plugin import PluginMetadata 7 | 8 | __plugin_meta__ = PluginMetadata( 9 | name="唯一会话", 10 | description="限制同一会话内同时只能运行一个响应器", 11 | usage="加载插件后自动生效", 12 | type="application", 13 | homepage="https://github.com/nonebot/nonebot2/blob/master/nonebot/plugins/single_session.py", 14 | config=None, 15 | supported_adapters=None, 16 | ) 17 | 18 | _running_matcher: dict[str, int] = {} 19 | 20 | 21 | async def matcher_mutex(event: Event) -> AsyncGenerator[bool, None]: 22 | result = False 23 | try: 24 | session_id = event.get_session_id() 25 | except Exception: 26 | yield result 27 | else: 28 | current_event_id = id(event) 29 | if event_id := _running_matcher.get(session_id): 30 | result = event_id != current_event_id 31 | else: 32 | _running_matcher[session_id] = current_event_id 33 | yield result 34 | if not result: 35 | del _running_matcher[session_id] 36 | 37 | 38 | @event_preprocessor 39 | async def preprocess(mutex: bool = Depends(matcher_mutex)): 40 | if mutex: 41 | raise IgnoredException("Another matcher running") 42 | -------------------------------------------------------------------------------- /nonebot/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonebot/nonebot2/f8317444dfaaf720ff22fedf5b0a8d9f6138421c/nonebot/py.typed -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "packageManager": "yarn@1.22.22", 5 | "workspaces": [ 6 | "website" 7 | ], 8 | "scripts": { 9 | "archive": "yarn workspace nonebot docusaurus docs:version", 10 | "build": "yarn workspace nonebot build", 11 | "build:plugin": "cross-env BASE_URL='/website/' yarn workspace nonebot build", 12 | "start": "yarn workspace nonebot start", 13 | "serve": "yarn workspace nonebot serve", 14 | "clear": "yarn workspace nonebot clear", 15 | "prettier": "prettier --config ./.prettierrc --write \"./website/\"", 16 | "lint": "yarn lint:js && yarn lint:style", 17 | "lint:js": "eslint --cache --report-unused-disable-directives \"**/*.{js,jsx,ts,tsx,mjs}\"", 18 | "lint:js:fix": "eslint --cache --report-unused-disable-directives --fix \"**/*.{js,jsx,ts,tsx,mjs}\"", 19 | "lint:style": "stylelint \"**/*.css\"", 20 | "lint:style:fix": "stylelint --fix \"**/*.css\"", 21 | "pyright": "pyright" 22 | }, 23 | "devDependencies": { 24 | "@typescript-eslint/eslint-plugin": "^5.62.0", 25 | "@typescript-eslint/parser": "^5.62.0", 26 | "cross-env": "^7.0.3", 27 | "eslint": "^8.48.0", 28 | "eslint-config-airbnb": "^19.0.4", 29 | "eslint-config-prettier": "^8.8.0", 30 | "eslint-plugin-import": "^2.27.5", 31 | "eslint-plugin-jsx-a11y": "^6.7.1", 32 | "eslint-plugin-react": "^7.32.2", 33 | "eslint-plugin-react-hooks": "^4.6.0", 34 | "eslint-plugin-regexp": "^1.15.0", 35 | "prettier": "^3.0.3", 36 | "pyright": "1.1.393", 37 | "stylelint": "^15.10.3", 38 | "stylelint-config-standard": "^34.0.0", 39 | "stylelint-prettier": "^4.0.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/nonebot-plugin-docs/README.md: -------------------------------------------------------------------------------- 1 |

2 | nonebot 3 |

4 | 5 |
6 | 7 | # nonebot-plugin-docs 8 | 9 | _✨ NoneBot 本地文档插件 ✨_ 10 | 11 |
12 | 13 |

14 | 15 | license 16 | 17 | 18 | pypi 19 | 20 | python 21 |

22 | 23 | ## 使用方式 24 | 25 | 加载插件并启动 Bot ,在浏览器内打开 `http://host:port/website/`。 26 | 27 | 具体网址会在控制台内输出。 28 | -------------------------------------------------------------------------------- /packages/nonebot-plugin-docs/nonebot_plugin_docs/__init__.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | 3 | import nonebot 4 | from nonebot.log import logger 5 | from nonebot.plugin import PluginMetadata 6 | 7 | __plugin_meta__ = PluginMetadata( 8 | name="NoneBot 离线文档", 9 | description="在本地查看 NoneBot 文档", 10 | usage="启动机器人后访问 http://localhost:port/website/ 查看文档", 11 | type="application", 12 | homepage="https://github.com/nonebot/nonebot2/blob/master/packages/nonebot-plugin-docs", 13 | config=None, 14 | supported_adapters=None, 15 | ) 16 | 17 | 18 | def init(): 19 | driver = nonebot.get_driver() 20 | try: 21 | _module = importlib.import_module( 22 | f"nonebot_plugin_docs.drivers.{driver.type.split('+')[0]}" 23 | ) 24 | except ImportError: 25 | logger.warning(f"Driver {driver.type} not supported") 26 | return 27 | register_route = getattr(_module, "register_route") 28 | register_route(driver) 29 | host = str(driver.config.host) 30 | port = driver.config.port 31 | if host in {"0.0.0.0", "127.0.0.1"}: 32 | host = "localhost" 33 | logger.opt(colors=True).info( 34 | f"Nonebot docs will be running at: http://{host}:{port}/website/" 35 | ) 36 | 37 | 38 | init() 39 | -------------------------------------------------------------------------------- /packages/nonebot-plugin-docs/nonebot_plugin_docs/drivers/fastapi.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from fastapi.staticfiles import StaticFiles 4 | 5 | from nonebot.drivers.fastapi import Driver 6 | 7 | 8 | def register_route(driver: Driver): 9 | app = driver.server_app 10 | 11 | static_path = str((Path(__file__).parent / ".." / "dist").resolve()) 12 | 13 | app.mount("/website", StaticFiles(directory=static_path, html=True), name="docs") 14 | -------------------------------------------------------------------------------- /packages/nonebot-plugin-docs/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "nonebot-plugin-docs" 3 | version = "2.0.0" 4 | description = "View NoneBot2 Docs Locally" 5 | authors = ["yanyongyu "] 6 | license = "MIT" 7 | readme = "README.md" 8 | homepage = "https://github.com/nonebot/nonebot2/blob/master/packages/nonebot-plugin-docs" 9 | repository = "https://github.com/nonebot/nonebot2" 10 | keywords = ["nonebot", "nonebot2", "docs"] 11 | include = ["nonebot_plugin_docs/dist/**/*"] 12 | 13 | 14 | [tool.poetry.dependencies] 15 | python = "^3.9" 16 | nonebot2 = "^2.0.0" 17 | 18 | [tool.poetry.dev-dependencies] 19 | 20 | [build-system] 21 | requires = ["poetry-core>=1.0.0"] 22 | build-backend = "poetry.core.masonry.api" 23 | -------------------------------------------------------------------------------- /scripts/build-api-docs.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | # cd to the root of the project 4 | cd "$(dirname "$0")/.." 5 | 6 | poetry run nb-autodoc nonebot \ 7 | -s nonebot.plugins \ 8 | -u nonebot.internal \ 9 | -u nonebot.internal.* 10 | cp -r ./build/nonebot/* ./website/docs/api/ 11 | yarn prettier 12 | -------------------------------------------------------------------------------- /scripts/run-tests.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | # cd to the root of the tests 4 | cd "$(dirname "$0")/../tests" 5 | 6 | # Run the tests 7 | pytest -n auto --cov-append --cov-report xml --junitxml=./junit.xml $@ 8 | -------------------------------------------------------------------------------- /scripts/setup-envs.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | # config poetry to install env in project 4 | poetry config virtualenvs.in-project true 5 | 6 | # setup dev environment 7 | echo "Setting up dev environment" 8 | poetry install --all-extras && poetry run pre-commit install && yarn install 9 | 10 | # setup pydantic v2 test environment 11 | for env in $(find ./envs/ -maxdepth 1 -mindepth 1 -type d -not -name test); do 12 | echo "Setting up $env environment" 13 | (cd $env && poetry install --no-root) 14 | done 15 | -------------------------------------------------------------------------------- /scripts/update-envs.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | # update test env 4 | echo "Updating test env..." 5 | (cd ./envs/test/ && poetry update --lock) 6 | 7 | # update dev env 8 | echo "Updating dev env..." 9 | poetry update 10 | 11 | # update other envs 12 | for env in $(find ./envs/ -maxdepth 1 -mindepth 1 -type d -not -name test); do 13 | echo "Updating $env env..." 14 | (cd $env && poetry update) 15 | done 16 | -------------------------------------------------------------------------------- /tests/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | plugins = 3 | coverage_conditional_plugin 4 | 5 | [report] 6 | exclude_lines = 7 | pragma: no cover 8 | def __repr__ 9 | def __str__ 10 | @(typing\.)?overload 11 | if (typing\.)?TYPE_CHECKING( is True)?: 12 | @(abc\.)?abstractmethod 13 | raise NotImplementedError 14 | warnings\.warn 15 | ^\.\.\.$ 16 | pass 17 | if __name__ == .__main__.: 18 | 19 | [coverage_conditional_plugin] 20 | rules = 21 | "sys_platform != 'win32'": py-win32 22 | "sys_platform != 'linux'": py-linux 23 | "sys_platform != 'darwin'": py-darwin 24 | "sys_version_info < (3, 11)": py-gte-311 25 | "sys_version_info >= (3, 11)": py-lt-311 26 | "package_version('pydantic') < (2,)": pydantic-v2 27 | "package_version('pydantic') >= (2,)": pydantic-v1 28 | -------------------------------------------------------------------------------- /tests/.env: -------------------------------------------------------------------------------- 1 | ENVIRONMENT=test 2 | COMMON_CONFIG=common 3 | COMMON_OVERRIDE=old 4 | -------------------------------------------------------------------------------- /tests/.env.example: -------------------------------------------------------------------------------- 1 | SIMPLE=simple 2 | COMPLEX=' 3 | [1, 2, 3] 4 | ' 5 | COMPLEX_NONE 6 | COMPLEX_UNION=[1, 2, 3] 7 | NESTED={"a": 1} 8 | NESTED__B=2 9 | NESTED__C__C=3 10 | NESTED__COMPLEX=[1, 2, 3] 11 | NESTED_INNER__A=1 12 | NESTED_INNER__B=2 13 | OTHER_SIMPLE=simple 14 | OTHER_NESTED={"a": 1} 15 | OTHER_NESTED__B=2 16 | OTHER_NESTED_INNER__A=1 17 | OTHER_NESTED_INNER__B=2 18 | -------------------------------------------------------------------------------- /tests/.env.test: -------------------------------------------------------------------------------- 1 | LOG_LEVEL=TRACE 2 | NICKNAME=["test"] 3 | SUPERUSERS=["test", "fake:faketest"] 4 | API_TIMEOUT 5 | SIMPLE_NONE 6 | COMMON_OVERRIDE=new 7 | CONFIG_FROM_ENV= 8 | CONFIG_OVERRIDE=old 9 | NESTED_DICT={"a": 1} 10 | NESTED_DICT__B=2 11 | NESTED_DICT__C__D=3 12 | NESTED_MISSING_DICT__A=1 13 | NESTED_MISSING_DICT__B__C=2 14 | NOT_NESTED=some string 15 | NOT_NESTED__A=1 16 | PLUGIN_CONFIG=1 17 | -------------------------------------------------------------------------------- /tests/bad_plugins/bad_plugin.py: -------------------------------------------------------------------------------- 1 | import nonebot 2 | 3 | plugin = nonebot.get_plugin("bad_plugin") 4 | assert plugin 5 | 6 | x = 1 / 0 7 | -------------------------------------------------------------------------------- /tests/dynamic/manager.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonebot/nonebot2/f8317444dfaaf720ff22fedf5b0a8d9f6138421c/tests/dynamic/manager.py -------------------------------------------------------------------------------- /tests/dynamic/path.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonebot/nonebot2/f8317444dfaaf720ff22fedf5b0a8d9f6138421c/tests/dynamic/path.py -------------------------------------------------------------------------------- /tests/dynamic/require_not_declared.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonebot/nonebot2/f8317444dfaaf720ff22fedf5b0a8d9f6138421c/tests/dynamic/require_not_declared.py -------------------------------------------------------------------------------- /tests/dynamic/require_not_loaded/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonebot/nonebot2/f8317444dfaaf720ff22fedf5b0a8d9f6138421c/tests/dynamic/require_not_loaded/__init__.py -------------------------------------------------------------------------------- /tests/dynamic/require_not_loaded/subplugin1.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonebot/nonebot2/f8317444dfaaf720ff22fedf5b0a8d9f6138421c/tests/dynamic/require_not_loaded/subplugin1.py -------------------------------------------------------------------------------- /tests/dynamic/require_not_loaded/subplugin2.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonebot/nonebot2/f8317444dfaaf720ff22fedf5b0a8d9f6138421c/tests/dynamic/require_not_loaded/subplugin2.py -------------------------------------------------------------------------------- /tests/dynamic/simple.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonebot/nonebot2/f8317444dfaaf720ff22fedf5b0a8d9f6138421c/tests/dynamic/simple.py -------------------------------------------------------------------------------- /tests/plugins.empty.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonebot/nonebot2/f8317444dfaaf720ff22fedf5b0a8d9f6138421c/tests/plugins.empty.toml -------------------------------------------------------------------------------- /tests/plugins.invalid.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /tests/plugins.invalid.toml: -------------------------------------------------------------------------------- 1 | [tool] 2 | nonebot = [] 3 | -------------------------------------------------------------------------------- /tests/plugins.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [], 3 | "plugin_dirs": [] 4 | } 5 | -------------------------------------------------------------------------------- /tests/plugins.toml: -------------------------------------------------------------------------------- 1 | [tool.nonebot] 2 | plugins = [] 3 | plugin_dirs = [] 4 | -------------------------------------------------------------------------------- /tests/plugins/_hidden.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | pytest.fail("should not be imported") 4 | -------------------------------------------------------------------------------- /tests/plugins/export.py: -------------------------------------------------------------------------------- 1 | def test(): 2 | return "export" 3 | -------------------------------------------------------------------------------- /tests/plugins/matcher/__init__.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from nonebot import load_plugins 4 | 5 | _sub_plugins = set() 6 | 7 | _sub_plugins |= load_plugins(str(Path(__file__).parent)) 8 | -------------------------------------------------------------------------------- /tests/plugins/matcher/matcher_expire.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | 3 | from nonebot.matcher import Matcher 4 | 5 | test_temp_matcher = Matcher.new("test", temp=True) 6 | test_datetime_matcher = Matcher.new( 7 | "test", expire_time=datetime.now() - timedelta(seconds=1) 8 | ) 9 | test_timedelta_matcher = Matcher.new("test", expire_time=timedelta(seconds=-1)) 10 | -------------------------------------------------------------------------------- /tests/plugins/matcher/matcher_info.py: -------------------------------------------------------------------------------- 1 | from nonebot import on 2 | 3 | matcher = on("message", temp=False, expire_time=None, priority=1, block=True) 4 | -------------------------------------------------------------------------------- /tests/plugins/matcher/matcher_permission.py: -------------------------------------------------------------------------------- 1 | from nonebot.matcher import Matcher 2 | from nonebot.permission import USER, Permission 3 | 4 | default_permission = Permission() 5 | new_permission = Permission() 6 | 7 | test_permission_updater = Matcher.new(permission=default_permission) 8 | 9 | test_user_permission_updater = Matcher.new( 10 | permission=USER("test", perm=default_permission) 11 | ) 12 | 13 | test_custom_updater = Matcher.new(permission=default_permission) 14 | 15 | 16 | @test_custom_updater.permission_updater 17 | async def _() -> Permission: 18 | return new_permission 19 | -------------------------------------------------------------------------------- /tests/plugins/matcher/matcher_type.py: -------------------------------------------------------------------------------- 1 | from nonebot.matcher import Matcher 2 | 3 | test_type_updater = Matcher.new(type_="test") 4 | 5 | test_custom_updater = Matcher.new(type_="test") 6 | 7 | 8 | @test_custom_updater.type_updater 9 | async def _() -> str: 10 | return "custom" 11 | -------------------------------------------------------------------------------- /tests/plugins/metadata.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | from nonebot.adapters import Adapter 4 | from nonebot.plugin import PluginMetadata 5 | 6 | 7 | class Config(BaseModel): 8 | custom: str = "" 9 | 10 | 11 | class FakeAdapter(Adapter): ... 12 | 13 | 14 | __plugin_meta__ = PluginMetadata( 15 | name="测试插件", 16 | description="测试插件元信息", 17 | usage="无法使用", 18 | type="application", 19 | homepage="https://nonebot.dev", 20 | config=Config, 21 | supported_adapters={"~onebot.v11", "plugins.metadata:FakeAdapter"}, 22 | extra={"author": "NoneBot"}, 23 | ) 24 | -------------------------------------------------------------------------------- /tests/plugins/metadata_2.py: -------------------------------------------------------------------------------- 1 | from nonebot.plugin import PluginMetadata 2 | 3 | __plugin_meta__ = PluginMetadata( 4 | name="测试插件2", 5 | description="测试继承适配器", 6 | usage="无法使用", 7 | type="application", 8 | homepage="https://nonebot.dev", 9 | supported_adapters={"~onebot.v11", "~onebot.v12"}, 10 | extra={"author": "NoneBot"}, 11 | ) 12 | -------------------------------------------------------------------------------- /tests/plugins/metadata_3.py: -------------------------------------------------------------------------------- 1 | from nonebot.plugin import PluginMetadata 2 | 3 | __plugin_meta__ = PluginMetadata( 4 | name="测试插件3", 5 | description="测试继承适配器, 使用内置适配器全名", 6 | usage="无法使用", 7 | type="application", 8 | homepage="https://nonebot.dev", 9 | supported_adapters={ 10 | "nonebot.adapters.onebot.v11", 11 | "nonebot.adapters.onebot.v12", 12 | "~qq", 13 | }, 14 | extra={"author": "NoneBot"}, 15 | ) 16 | -------------------------------------------------------------------------------- /tests/plugins/nested/__init__.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from nonebot.plugin import PluginManager, _managers 4 | 5 | manager = PluginManager( 6 | search_path=[str((Path(__file__).parent / "plugins").resolve())] 7 | ) 8 | _managers.append(manager) 9 | 10 | # test load nested plugin with require 11 | manager.load_plugin("plugins.nested.plugins.nested_subplugin") 12 | manager.load_plugin("nested:nested_subplugin2") 13 | -------------------------------------------------------------------------------- /tests/plugins/nested/plugins/nested_subplugin.py: -------------------------------------------------------------------------------- 1 | from .nested_subplugin2 import a # noqa: F401 2 | -------------------------------------------------------------------------------- /tests/plugins/nested/plugins/nested_subplugin2.py: -------------------------------------------------------------------------------- 1 | a = "required by another subplugin" 2 | -------------------------------------------------------------------------------- /tests/plugins/param/__init__.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from nonebot import load_plugins 4 | 5 | _sub_plugins = set() 6 | 7 | _sub_plugins |= load_plugins(str(Path(__file__).parent)) 8 | -------------------------------------------------------------------------------- /tests/plugins/param/param_arg.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated, Any 2 | 3 | from nonebot.adapters import Message 4 | from nonebot.params import Arg, ArgPlainText, ArgPromptResult, ArgStr 5 | 6 | 7 | async def arg(key: Message = Arg()) -> Message: 8 | return key 9 | 10 | 11 | async def arg_str(key: str = ArgStr()) -> str: 12 | return key 13 | 14 | 15 | async def arg_plain_text(key: str = ArgPlainText()) -> str: 16 | return key 17 | 18 | 19 | async def annotated_arg(key: Annotated[Message, Arg()]) -> Message: 20 | return key 21 | 22 | 23 | async def annotated_arg_str(key: Annotated[str, ArgStr()]) -> str: 24 | return key 25 | 26 | 27 | async def annotated_arg_plain_text(key: Annotated[str, ArgPlainText()]) -> str: 28 | return key 29 | 30 | 31 | async def annotated_arg_prompt_result(key: Annotated[Any, ArgPromptResult()]) -> Any: 32 | return key 33 | 34 | 35 | # test dependency priority 36 | async def annotated_prior_arg(key: Annotated[str, ArgStr("foo")] = ArgPlainText()): 37 | return key 38 | 39 | 40 | async def annotated_multi_arg( 41 | key: Annotated[Annotated[str, ArgStr("foo")], ArgPlainText()], 42 | ): 43 | return key 44 | -------------------------------------------------------------------------------- /tests/plugins/param/param_bot.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Union 2 | 3 | from nonebot.adapters import Bot 4 | 5 | 6 | async def get_bot(b: Bot) -> Bot: 7 | return b 8 | 9 | 10 | async def postpone_bot(b: "Bot") -> Bot: 11 | return b 12 | 13 | 14 | async def legacy_bot(bot): 15 | return bot 16 | 17 | 18 | async def not_legacy_bot(bot: int): ... 19 | 20 | 21 | class FooBot(Bot): ... 22 | 23 | 24 | async def sub_bot(b: FooBot) -> FooBot: 25 | return b 26 | 27 | 28 | class BarBot(Bot): ... 29 | 30 | 31 | async def union_bot(b: Union[FooBot, BarBot]) -> Union[FooBot, BarBot]: 32 | return b 33 | 34 | 35 | B = TypeVar("B", bound=Bot) 36 | 37 | 38 | async def generic_bot(b: B) -> B: 39 | return b 40 | 41 | 42 | CB = TypeVar("CB", Bot, None) 43 | 44 | 45 | async def generic_bot_none(b: CB) -> CB: 46 | return b 47 | 48 | 49 | async def not_bot(b: Union[int, Bot]): ... 50 | -------------------------------------------------------------------------------- /tests/plugins/param/param_default.py: -------------------------------------------------------------------------------- 1 | async def default(value: int = 1) -> int: 2 | return value 3 | -------------------------------------------------------------------------------- /tests/plugins/param/param_event.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Union 2 | 3 | from nonebot.adapters import Event, Message 4 | from nonebot.params import EventMessage, EventPlainText, EventToMe, EventType 5 | 6 | 7 | async def event(e: Event) -> Event: 8 | return e 9 | 10 | 11 | async def postpone_event(e: "Event") -> Event: 12 | return e 13 | 14 | 15 | async def legacy_event(event): 16 | return event 17 | 18 | 19 | async def not_legacy_event(event: int): ... 20 | 21 | 22 | class FooEvent(Event): ... 23 | 24 | 25 | async def sub_event(e: FooEvent) -> FooEvent: 26 | return e 27 | 28 | 29 | class BarEvent(Event): ... 30 | 31 | 32 | async def union_event(e: Union[FooEvent, BarEvent]) -> Union[FooEvent, BarEvent]: 33 | return e 34 | 35 | 36 | E = TypeVar("E", bound=Event) 37 | 38 | 39 | async def generic_event(e: E) -> E: 40 | return e 41 | 42 | 43 | CE = TypeVar("CE", Event, None) 44 | 45 | 46 | async def generic_event_none(e: CE) -> CE: 47 | return e 48 | 49 | 50 | async def not_event(e: Union[int, Event]): ... 51 | 52 | 53 | async def event_type(t: str = EventType()) -> str: 54 | return t 55 | 56 | 57 | async def event_message(msg: Message = EventMessage()) -> Message: 58 | return msg 59 | 60 | 61 | async def event_plain_text(text: str = EventPlainText()) -> str: 62 | return text 63 | 64 | 65 | async def event_to_me(to_me: bool = EventToMe()) -> bool: 66 | return to_me 67 | -------------------------------------------------------------------------------- /tests/plugins/param/param_exception.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | 4 | async def exc(e: Exception, x: Union[ValueError, TypeError]) -> Exception: 5 | assert e == x 6 | return e 7 | 8 | 9 | async def legacy_exc(exception) -> Exception: 10 | return exception 11 | -------------------------------------------------------------------------------- /tests/plugins/param/param_matcher.py: -------------------------------------------------------------------------------- 1 | from typing import Any, TypeVar, Union 2 | 3 | from nonebot.adapters import Event 4 | from nonebot.matcher import Matcher 5 | from nonebot.params import ( 6 | LastReceived, 7 | PausePromptResult, 8 | Received, 9 | ReceivePromptResult, 10 | ) 11 | 12 | 13 | async def matcher(m: Matcher) -> Matcher: 14 | return m 15 | 16 | 17 | async def postpone_matcher(m: "Matcher") -> Matcher: 18 | return m 19 | 20 | 21 | async def legacy_matcher(matcher): 22 | return matcher 23 | 24 | 25 | async def not_legacy_matcher(matcher: int): ... 26 | 27 | 28 | class FooMatcher(Matcher): ... 29 | 30 | 31 | async def sub_matcher(m: FooMatcher) -> FooMatcher: 32 | return m 33 | 34 | 35 | class BarMatcher(Matcher): ... 36 | 37 | 38 | async def union_matcher( 39 | m: Union[FooMatcher, BarMatcher], 40 | ) -> Union[FooMatcher, BarMatcher]: 41 | return m 42 | 43 | 44 | M = TypeVar("M", bound=Matcher) 45 | 46 | 47 | async def generic_matcher(m: M) -> M: 48 | return m 49 | 50 | 51 | CM = TypeVar("CM", Matcher, None) 52 | 53 | 54 | async def generic_matcher_none(m: CM) -> CM: 55 | return m 56 | 57 | 58 | async def not_matcher(m: Union[int, Matcher]): ... 59 | 60 | 61 | async def receive(e: Event = Received("test")) -> Event: 62 | return e 63 | 64 | 65 | async def last_receive(e: Event = LastReceived()) -> Event: 66 | return e 67 | 68 | 69 | async def receive_prompt_result(result: Any = ReceivePromptResult("test")) -> Any: 70 | return result 71 | 72 | 73 | async def pause_prompt_result(result: Any = PausePromptResult()) -> Any: 74 | return result 75 | -------------------------------------------------------------------------------- /tests/plugins/param/priority.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from nonebot.adapters import Bot, Event, Message 4 | from nonebot.matcher import Matcher 5 | from nonebot.params import Arg, Depends 6 | from nonebot.typing import T_State 7 | 8 | 9 | def dependency(): 10 | return 1 11 | 12 | 13 | async def complex_priority( 14 | sub: int = Depends(dependency), 15 | bot: Optional[Bot] = None, 16 | event: Optional[Event] = None, 17 | state: T_State = {}, 18 | matcher: Optional[Matcher] = None, 19 | arg: Message = Arg(), 20 | exception: Optional[Exception] = None, 21 | default: int = 1, 22 | ): ... 23 | -------------------------------------------------------------------------------- /tests/plugins/plugin/__init__.py: -------------------------------------------------------------------------------- 1 | from . import matchers as matchers 2 | -------------------------------------------------------------------------------- /tests/plugins/require.py: -------------------------------------------------------------------------------- 1 | from nonebot import require 2 | 3 | test_require = require("export").test 4 | 5 | from plugins.export import test 6 | 7 | assert test is test_require, "Export Require Error" 8 | assert test() == "export", "Export Require Error" 9 | -------------------------------------------------------------------------------- /tests/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.ruff] 2 | extend = "../pyproject.toml" 3 | 4 | [tool.ruff.lint.isort] 5 | known-first-party = ["nonebot", "fake_server", "utils"] 6 | -------------------------------------------------------------------------------- /tests/test_echo.py: -------------------------------------------------------------------------------- 1 | from nonebug import App 2 | import pytest 3 | 4 | from utils import FakeMessage, FakeMessageSegment, make_fake_event 5 | 6 | 7 | @pytest.mark.anyio 8 | async def test_echo(app: App): 9 | from nonebot.plugins.echo import echo 10 | 11 | async with app.test_matcher(echo) as ctx: 12 | bot = ctx.create_bot() 13 | 14 | message = FakeMessage("/echo 123") 15 | event = make_fake_event(_message=message)() 16 | ctx.receive_event(bot, event) 17 | ctx.should_call_send(event, FakeMessage("123"), True, bot=bot) 18 | 19 | message = FakeMessageSegment.text("/echo 123") + FakeMessageSegment.image( 20 | "test" 21 | ) 22 | event = make_fake_event(_message=message)() 23 | ctx.receive_event(bot, event) 24 | ctx.should_call_send( 25 | event, 26 | FakeMessageSegment.text("123") + FakeMessageSegment.image("test"), 27 | True, 28 | bot=bot, 29 | ) 30 | 31 | message = FakeMessage("/echo") 32 | event = make_fake_event(_message=message)() 33 | ctx.receive_event(bot, event) 34 | -------------------------------------------------------------------------------- /tests/test_matcher/test_provider.py: -------------------------------------------------------------------------------- 1 | from nonebug import App 2 | 3 | from nonebot.matcher import DEFAULT_PROVIDER_CLASS, matchers 4 | 5 | 6 | def test_manager(app: App): 7 | try: 8 | default_provider = matchers.provider 9 | matchers.set_provider(DEFAULT_PROVIDER_CLASS) 10 | assert default_provider == matchers.provider 11 | finally: 12 | matchers.provider = app.provider 13 | -------------------------------------------------------------------------------- /tests/test_plugin/test_manager.py: -------------------------------------------------------------------------------- 1 | from nonebot.plugin import PluginManager, _managers 2 | 3 | 4 | def test_load_plugin_name(): 5 | m = PluginManager(plugins=["dynamic.manager"]) 6 | try: 7 | _managers.append(m) 8 | 9 | # load by plugin id 10 | module1 = m.load_plugin("manager") 11 | # load by module name 12 | module2 = m.load_plugin("dynamic.manager") 13 | assert module1 14 | assert module2 15 | assert module1 is module2 16 | finally: 17 | _managers.remove(m) 18 | -------------------------------------------------------------------------------- /tests/test_single_session.py: -------------------------------------------------------------------------------- 1 | from contextlib import asynccontextmanager 2 | 3 | import pytest 4 | 5 | from utils import make_fake_event 6 | 7 | 8 | @pytest.mark.anyio 9 | async def test_matcher_mutex(): 10 | from nonebot.plugins.single_session import _running_matcher, matcher_mutex 11 | 12 | am = asynccontextmanager(matcher_mutex) 13 | event = make_fake_event()() 14 | event_1 = make_fake_event()() 15 | event_2 = make_fake_event(_session_id="test1")() 16 | event_3 = make_fake_event(_session_id=None)() 17 | 18 | async with am(event) as ctx: 19 | assert ctx is False 20 | assert not _running_matcher 21 | 22 | async with am(event) as ctx: 23 | async with am(event_1) as ctx_1: 24 | assert ctx is False 25 | assert ctx_1 is True 26 | assert not _running_matcher 27 | 28 | async with am(event) as ctx: 29 | async with am(event_2) as ctx_2: 30 | assert ctx is False 31 | assert ctx_2 is False 32 | assert not _running_matcher 33 | 34 | async with am(event_3) as ctx_3: 35 | assert ctx_3 is False 36 | assert not _running_matcher 37 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "lib": ["ESNext"], 5 | "module": "NodeNext", 6 | "declaration": true, 7 | "declarationMap": false, 8 | "sourceMap": false, 9 | "jsx": "react-native", 10 | "noEmit": true, 11 | 12 | /* Strict Type-Checking Options */ 13 | "strict": true, 14 | "strictNullChecks": true, 15 | "strictFunctionTypes": true, 16 | "strictBindCallApply": true, 17 | "strictPropertyInitialization": true, 18 | "noImplicitThis": true, 19 | "alwaysStrict": true, 20 | 21 | /* Additional Checks */ 22 | // "noUnusedLocals": false, // ensured by eslint, should not block compilation 23 | // "noImplicitReturns": true, 24 | // "noFallthroughCasesInSwitch": true, 25 | 26 | /* Disabled on purpose (handled by ESLint, should not block compilation) */ 27 | "noUnusedParameters": false, 28 | 29 | /* Module Resolution Options */ 30 | "moduleResolution": "nodenext", 31 | "allowSyntheticDefaultImports": true, 32 | "esModuleInterop": true, 33 | "isolatedModules": true, 34 | 35 | /* Advanced Options */ 36 | "resolveJsonModule": true, 37 | "skipLibCheck": true, // @types/webpack and webpack/types.d.ts are not the same thing 38 | 39 | /* Use tslib */ 40 | "importHelpers": true, 41 | "noEmitHelpers": true 42 | }, 43 | "include": ["./**/.eslintrc.js", "./**/.stylelintrc.js"], 44 | "exclude": ["node_modules", "**/lib/**/*"] 45 | } 46 | -------------------------------------------------------------------------------- /website/docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 0 3 | id: index 4 | slug: / 5 | --- 6 | 7 | # 概览 8 | 9 | NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架(下称 NoneBot),它基于 Python 的类型注解和异步优先特性(兼容同步),能够为你的需求实现提供便捷灵活的支持。同时,NoneBot 拥有大量的开发者为其开发插件,用户无需编写任何代码,仅需完成环境配置及插件安装,就可以正常使用 NoneBot。 10 | 11 | 需要注意的是,NoneBot 仅支持 **Python 3.9 以上版本** 12 | 13 | ## 特色 14 | 15 | ### 异步优先 16 | 17 | NoneBot 基于 Python [asyncio](https://docs.python.org/zh-cn/3/library/asyncio.html) / [trio](https://trio.readthedocs.io/en/stable/) 编写,并在异步机制的基础上进行了一定程度的同步函数兼容。 18 | 19 | ### 完整的类型注解 20 | 21 | NoneBot 参考 [PEP 484](https://www.python.org/dev/peps/pep-0484/) 等 PEP 完整实现了类型注解,通过 Pyright(Pylance) 检查。配合编辑器的类型推导功能,能将绝大多数的 Bug 杜绝在编辑器中([编辑器支持](./editor-support))。 22 | 23 | ### 开箱即用 24 | 25 | NoneBot 提供了使用便捷、具有交互式功能的命令行工具--`nb-cli`,使得用户初次接触 NoneBot 时更容易上手。使用方法请阅读本文档[指南](./quick-start.mdx)以及 [CLI 文档](https://cli.nonebot.dev/)。 26 | 27 | ### 插件系统 28 | 29 | 插件系统是 NoneBot 的核心,通过它可以实现机器人的模块化以及功能扩展,便于维护和管理。 30 | 31 | ### 依赖注入系统 32 | 33 | NoneBot 采用了一套自行定义的依赖注入系统,可以让事件的处理过程更加的简洁、清晰,增加代码的可读性,减少代码冗余。 34 | 35 | #### 什么是依赖注入 36 | 37 | [**『依赖注入』**](https://zh.wikipedia.org/wiki/%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%AC)意思是,在编程中,有一种方法可以让你的代码声明它工作和使用所需要的东西,即**『依赖』**。 38 | 39 | 系统(在这里是指 NoneBot)将负责做任何需要的事情,为你的代码提供这些必要依赖(即**『注入』**依赖性) 40 | 41 | 这在你有以下情形的需求时非常有用: 42 | 43 | - 这部分代码拥有共享的逻辑(同样的代码逻辑多次重复) 44 | - 共享数据库以及网络请求连接会话 45 | - 比如 `httpx.AsyncClient`、`aiohttp.ClientSession` 和 `sqlalchemy.Session` 46 | - 机器人用户权限检查以及认证 47 | - 还有更多... 48 | 49 | 它在完成上述工作的同时,还能尽量减少代码的耦合和重复 50 | -------------------------------------------------------------------------------- /website/docs/advanced/matcher-provider.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 10 3 | description: 自定义事件响应器存储 4 | 5 | options: 6 | menu: 7 | - category: advanced 8 | weight: 110 9 | --- 10 | 11 | # 事件响应器存储 12 | 13 | 事件响应器是 NoneBot 处理事件的核心,它们默认存储在一个字典中。在进入会话状态后,事件响应器将会转为临时响应器,作为最高优先级同样存储于该字典中。因此,事件响应器的存储类似于会话存储,它决定了整个 NoneBot 对事件的处理行为。 14 | 15 | NoneBot 默认使用 Python 的字典将事件响应器存储于内存中,但是我们也可以自定义事件响应器存储,将事件响应器存储于其他地方,例如 Redis 等。这样我们就可以实现持久化、在多实例间共享会话状态等功能。 16 | 17 | ## 编写存储提供者 18 | 19 | 事件响应器的存储提供者 `MatcherProvider` 抽象类继承自 `MutableMapping[int, list[type[Matcher]]]`,即以优先级为键,以事件响应器列表为值的映射。我们可以方便地进行逐优先级事件传播。 20 | 21 | 编写一个自定义的存储提供者,只需要继承并实现 `MatcherProvider` 抽象类: 22 | 23 | ```python 24 | from nonebot.matcher import MatcherProvider 25 | 26 | class CustomProvider(MatcherProvider): 27 | ... 28 | ``` 29 | 30 | ## 设置存储提供者 31 | 32 | 我们可以通过 `matchers.set_provider` 方法设置存储提供者: 33 | 34 | ```python {3} 35 | from nonebot.matcher import matchers 36 | 37 | matchers.set_provider(CustomProvider) 38 | 39 | assert isinstance(matchers.provider, CustomProvider) 40 | ``` 41 | -------------------------------------------------------------------------------- /website/docs/advanced/plugin-nesting.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | description: 编写与加载嵌套插件 4 | 5 | options: 6 | menu: 7 | - category: advanced 8 | weight: 40 9 | --- 10 | 11 | # 嵌套插件 12 | 13 | NoneBot 支持嵌套插件,即一个插件可以包含其他插件。通过这种方式,我们可以将一个大型插件拆分成多个功能子插件,使得插件更加清晰、易于维护。我们可以直接在插件中使用 NoneBot 加载插件的方法来加载子插件。 14 | 15 | ## 创建嵌套插件 16 | 17 | 我们可以在使用 `nb-cli` 命令[创建插件](../tutorial/create-plugin.md#创建插件)时,选择直接通过模板创建一个嵌套插件: 18 | 19 | ```bash 20 | $ nb plugin create 21 | [?] 插件名称: parent 22 | [?] 使用嵌套插件? (y/N) Y 23 | [?] 输出目录: awesome_bot/plugins 24 | ``` 25 | 26 | 或者使用 `nb plugin create --sub-plugin` 选项直接创建一个嵌套插件。 27 | 28 | ## 已有插件 29 | 30 | 如果你已经有一个插件,想要在其中嵌套加载子插件,可以在插件的 `__init__.py` 中添加如下代码: 31 | 32 | ```python title=parent/__init__.py 33 | import nonebot 34 | from pathlib import Path 35 | 36 | sub_plugins = nonebot.load_plugins( 37 | str(Path(__file__).parent.joinpath("plugins").resolve()) 38 | ) 39 | ``` 40 | 41 | 这样,`parent` 插件就会加载 `parent/plugins` 目录下的所有插件。NoneBot 会正确识别这些插件的父子关系,你可以在 `parent` 的插件信息中看到这些子插件的信息,也可以在子插件信息中看到它们的父插件信息。 42 | -------------------------------------------------------------------------------- /website/docs/advanced/requiring.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | description: 使用其他插件提供的功能 4 | 5 | options: 6 | menu: 7 | - category: advanced 8 | weight: 50 9 | --- 10 | 11 | # 跨插件访问 12 | 13 | NoneBot 插件化系统的设计使得插件之间可以功能独立、各司其职,我们可以更好地维护和扩展插件。但是,有时候我们可能需要在不同插件之间调用功能。NoneBot 生态中就有一类插件,它们专为其他插件提供功能支持,如:[定时任务插件](../best-practice/scheduler.md)、[数据存储插件](../best-practice/data-storing.md)等。这时候我们就需要在插件之间进行跨插件访问。 14 | 15 | ## 插件跟踪 16 | 17 | 由于 NoneBot 插件系统通过 [Import Hooks](https://docs.python.org/3/reference/import.html#import-hooks) 的方式实现插件加载与跟踪管理,因此我们**不能**在 NoneBot 跟踪插件前进行模块 import,这会导致插件加载失败。即,我们不能在使用 NoneBot 提供的加载插件方法前,直接使用 `import` 语句导入插件。 18 | 19 | 对于在项目目录下的插件,我们通常直接使用 `load_from_toml` 等方法一次性加载所有插件。由于这些插件已经被声明,即便插件导入顺序不同,NoneBot 也能正确跟踪插件。此时,我们不需要对跨插件访问进行特殊处理。但当我们使用了外部插件,如果没有事先声明或加载插件,NoneBot 并不会将其当作插件进行跟踪,可能会出现意料之外的错误出现。 20 | 21 | 简单来说,我们必须在 `import` 外部插件之前,确保依赖的外部插件已经被声明或加载。 22 | 23 | ## 插件依赖声明 24 | 25 | NoneBot 提供了一种方法来确保我们依赖的插件已经被正确加载,即使用 `require` 函数。通过 `require` 函数,我们可以在当前插件中声明依赖的插件,NoneBot 会在加载当前插件时,检查依赖的插件是否已经被加载,如果没有,会尝试优先加载依赖的插件。 26 | 27 | 假设我们有一个插件 `a` 依赖于插件 `b`,我们可以在插件 `a` 中使用 `require` 函数声明其依赖于插件 `b`: 28 | 29 | ```python {3} title=a/__init__.py 30 | from nonebot import require 31 | 32 | require("b") 33 | 34 | from b import some_function 35 | ``` 36 | 37 | 其中,`require` 函数的参数为插件索引名称或者外部插件的模块名称。在完成依赖声明后,我们可以在插件 `a` 中直接导入插件 `b` 所提供的功能。 38 | -------------------------------------------------------------------------------- /website/docs/advanced/session-updating.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 7 3 | description: 控制会话响应对象 4 | 5 | options: 6 | menu: 7 | - category: advanced 8 | weight: 80 9 | --- 10 | 11 | # 会话更新 12 | 13 | 在 NoneBot 中,在某个事件响应器对事件响应后,即是进入了会话状态,会话状态会持续到整个事件响应流程结束。会话过程中,机器人可以与用户进行多次交互。每次需要等待用户事件时,NoneBot 将会复制一个新的临时事件响应器,并更新该事件响应器使其响应当前会话主体的消息,这个过程称为会话更新。 14 | 15 | 会话更新分为两部分:**更新[事件响应器类型](./matcher.md#事件响应器类型)**和**更新[事件触发权限](./matcher.md#事件触发权限)**。 16 | 17 | ## 更新事件响应器类型 18 | 19 | 通常情况下,与机器人用户进行的会话都是通过消息事件进行的,因此会话更新后的默认响应事件类型为 `message`。如果希望接收一个特定类型的消息,比如 `notice` 等,我们需要自定义响应事件类型更新函数。响应事件类型更新函数是一个 `Dependent`,可以使用依赖注入。 20 | 21 | ```python {3-5} 22 | foo = on_message() 23 | 24 | @foo.type_updater 25 | async def _() -> str: 26 | return "notice" 27 | ``` 28 | 29 | 在注册了上述响应事件类型更新函数后,当我们需要等待用户事件时,将只会响应 `notice` 类型的事件。如果希望在会话过程中的不同阶段响应不同类型的事件,我们就需要使用更复杂的逻辑来更新响应事件类型(如:根据会话状态),这里将不再展示。 30 | 31 | ## 更新事件触发权限 32 | 33 | 会话通常是由机器人与用户进行的一对一交互,因此会话更新后的默认触发权限为当前事件的会话 ID。这个会话 ID 由协议适配器生成,通常由用户 ID 和群 ID 等组成。如果希望实现更复杂的会话功能(如:多用户同时参与的会话),我们需要自定义触发权限更新函数。触发权限更新函数是一个 `Dependent`,可以使用依赖注入。 34 | 35 | ```python {5-7} 36 | from nonebot.permission import User 37 | 38 | foo = on_message() 39 | 40 | @foo.permission_updater 41 | async def _(event: Event, matcher: Matcher) -> Permission: 42 | return Permission(User.from_event(event, perm=matcher.permission)) 43 | ``` 44 | 45 | 上述权限更新函数是默认的权限更新函数,它将会话的触发权限更新为当前事件的会话 ID。如果我们希望响应多个用户的消息,我们可以如下修改: 46 | 47 | ```python {5-7} 48 | from nonebot.permission import USER 49 | 50 | foo = on_message() 51 | 52 | @foo.permission_updater 53 | async def _(matcher: Matcher) -> Permission: 54 | return USER("session1", "session2", perm=matcher.permission) 55 | ``` 56 | 57 | 请注意,此处为全大写字母的 `USER` 权限,它可以匹配多个会话 ID。通过这种方式,我们可以实现多用户同时参与的会话。 58 | 59 | 我们已经了解了如何控制会话的更新,相信你已经能够实现更复杂的会话功能了,例如多人小游戏等等。欢迎将你的作品分享到[插件商店](/store/plugins)。 60 | -------------------------------------------------------------------------------- /website/docs/api/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonebot/nonebot2/f8317444dfaaf720ff22fedf5b0a8d9f6138421c/website/docs/api/.gitkeep -------------------------------------------------------------------------------- /website/docs/api/adapters/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 15 3 | } 4 | -------------------------------------------------------------------------------- /website/docs/api/dependencies/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 13 3 | } 4 | -------------------------------------------------------------------------------- /website/docs/api/drivers/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 14 3 | } 4 | -------------------------------------------------------------------------------- /website/docs/api/plugin/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 12 3 | } 4 | -------------------------------------------------------------------------------- /website/docs/appendices/session-state.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | description: 会话状态信息 4 | 5 | options: 6 | menu: 7 | - category: appendices 8 | weight: 40 9 | --- 10 | 11 | # 会话状态 12 | 13 | 在事件处理流程中,和用户交互的过程即是会话。在会话中,我们可能需要记录一些信息,例如用户的重试次数等等,以便在会话中的不同阶段进行判断和处理。这些信息都可以存储于会话状态中。 14 | 15 | NoneBot 中的会话状态是一个字典,可以通过类型 `T_State` 来获取。字典内可以存储任意类型的数据,但是要注意的是,NoneBot 本身会在会话状态中存储一些信息,因此不要使用 [NoneBot 使用的键名](../api/consts.md)。 16 | 17 | ```python 18 | from nonebot.typing import T_State 19 | 20 | @matcher.got("key", prompt="请输入密码") 21 | async def _(state: T_State, key: str = ArgPlainText()): 22 | if key != "some password": 23 | try_count = state.get("try_count", 1) 24 | if try_count >= 3: 25 | await matcher.finish("密码错误次数过多") 26 | else: 27 | state["try_count"] = try_count + 1 28 | await matcher.reject("密码错误,请重新输入") 29 | await matcher.finish("密码正确") 30 | ``` 31 | 32 | 会话状态的生命周期与事件处理流程相同,在期间的任何一个事件处理函数都可以进行读写。 33 | 34 | ```python 35 | from nonebot.typing import T_State 36 | 37 | @matcher.handle() 38 | async def _(state: T_State): 39 | state["key"] = "value" 40 | 41 | @matcher.handle() 42 | async def _(state: T_State): 43 | await matcher.finish(state["key"]) 44 | ``` 45 | 46 | 会话状态还可以用于发送动态消息,消息模板在发送时会使用会话状态字典进行渲染。消息模板的使用方法已经在[消息处理](../tutorial/message.md#使用消息模板)中介绍过,这里不再赘述。 47 | 48 | ```python 49 | from nonebot.typing import T_State 50 | from nonebot.adapters import MessageTemplate 51 | 52 | @matcher.handle() 53 | async def _(state: T_State): 54 | state["username"] = "user" 55 | 56 | @matcher.got("password", prompt=MessageTemplate("请输入 {username} 的密码")) 57 | async def _(): 58 | await matcher.finish(MessageTemplate("密码为 {password}")) 59 | ``` 60 | -------------------------------------------------------------------------------- /website/docs/appendices/whats-next.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 99 3 | description: 下一步──进阶! 4 | --- 5 | 6 | # 下一步 7 | 8 | 至此,我们已经了解了 NoneBot 的大多数功能用法,相信你已经可以独自写出一个插件了。现在你可以选择: 9 | 10 | - 即刻开始插件编写! 11 | - 更深入地了解 NoneBot 的[更多功能和原理](../advanced/plugin-info.md)! 12 | -------------------------------------------------------------------------------- /website/docs/best-practice/alconna/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "命令解析拓展", 3 | "position": 6 4 | } 5 | -------------------------------------------------------------------------------- /website/docs/best-practice/alconna/uniseg/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "通用消息组件", 3 | "position": 5 4 | } 5 | -------------------------------------------------------------------------------- /website/docs/best-practice/database/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "数据库", 3 | "position": 7 4 | } 5 | -------------------------------------------------------------------------------- /website/docs/best-practice/database/developer/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "开发者指南", 3 | "position": 3 4 | } 5 | -------------------------------------------------------------------------------- /website/docs/best-practice/error-tracking.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | description: 使用 sentry 进行错误跟踪 4 | --- 5 | 6 | # 错误跟踪 7 | 8 | 在应用实际运行过程中,可能会出现各种各样的错误。可能是由于代码逻辑错误,也可能是由于用户输入错误,甚至是由于第三方服务的错误。这些错误都会导致应用的运行出现问题,这时候就需要对错误进行跟踪,以便及时发现问题并进行修复。NoneBot 提供了 `nonebot-plugin-sentry` 插件,支持 [sentry](https://sentry.io/) 平台,可以方便地进行错误跟踪。 9 | 10 | ## 安装插件 11 | 12 | 在使用前请先安装 `nonebot-plugin-sentry` 插件至项目环境中,可参考[获取商店插件](../tutorial/store.mdx#安装插件)来了解并选择安装插件的方式。如: 13 | 14 | 在**项目目录**下执行以下命令: 15 | 16 | ```bash 17 | nb plugin install nonebot-plugin-sentry 18 | ``` 19 | 20 | ## 使用插件 21 | 22 | 在安装完成之后,仅需要对插件进行简单的配置即可使用。 23 | 24 | ### 获取 sentry DSN 25 | 26 | 前往 [sentry](https://sentry.io/) 平台,注册并创建一个新的项目,然后在项目设置中找到 `Client Keys (DSN)`,复制其中的 `DSN` 值。 27 | 28 | ### 配置插件 29 | 30 | :::caution 注意 31 | 错误跟踪通常在生产环境中使用,因此开发环境中 `sentry_dsn` 留空即会停用插件。 32 | ::: 33 | 34 | 在项目 dotenv 配置文件中添加以下配置即可使用: 35 | 36 | ```dotenv 37 | SENTRY_DSN= 38 | ``` 39 | 40 | ## 配置项 41 | 42 | 配置项具体含义参考 [Sentry Docs](https://docs.sentry.io/platforms/python/configuration/options/)。 43 | 44 | - `sentry_dsn: str` 45 | - `sentry_debug: bool = False` 46 | - `sentry_release: str | None = None` 47 | - `sentry_release: str | None = None` 48 | - `sentry_environment: str | None = nonebot env` 49 | - `sentry_server_name: str | None = None` 50 | - `sentry_sample_rate: float = 1.` 51 | - `sentry_max_breadcrumbs: int = 100` 52 | - `sentry_attach_stacktrace: bool = False` 53 | - `sentry_send_default_pii: bool = False` 54 | - `sentry_in_app_include: List[str] = Field(default_factory=list)` 55 | - `sentry_in_app_exclude: List[str] = Field(default_factory=list)` 56 | - `sentry_request_bodies: str = "medium"` 57 | - `sentry_with_locals: bool = True` 58 | - `sentry_ca_certs: str | None = None` 59 | - `sentry_before_send: Callable[[Any, Any], Any | None] | None = None` 60 | - `sentry_before_breadcrumb: Callable[[Any, Any], Any | None] | None = None` 61 | - `sentry_transport: Any | None = None` 62 | - `sentry_http_proxy: str | None = None` 63 | - `sentry_https_proxy: str | None = None` 64 | - `sentry_shutdown_timeout: int = 2` 65 | -------------------------------------------------------------------------------- /website/docs/best-practice/testing/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "单元测试", 3 | "position": 5 4 | } 5 | -------------------------------------------------------------------------------- /website/docs/community/contact.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar-position: 0 3 | description: 遇到问题如何获取帮助 4 | --- 5 | 6 | # 参与讨论 7 | 8 | 如果在安装或者开发 NoneBot 过程中遇到了任何问题,或者有新奇的点子,欢迎参与我们的社区讨论: 9 | 10 | 1. 点击下方链接前往 GitHub,前往 Issues 页面,在 `New Issue` Template 中选择 `Question` 11 | 12 | NoneBot:[![NoneBot project link](https://img.shields.io/github/stars/nonebot/nonebot2?style=social)](https://github.com/nonebot/nonebot2) 13 | 14 | 2. 通过 QQ 群(点击下方链接直达) 15 | 16 | [![QQ Chat Group](https://img.shields.io/badge/QQ%E7%BE%A4-768887710-orange?style=social)](https://jq.qq.com/?_wv=1027&k=5OFifDh) 17 | 18 | 3. 通过 QQ 频道 19 | 20 | [![QQ Channel](https://img.shields.io/badge/QQ%E9%A2%91%E9%81%93-NoneBot-orange?style=social)](https://qun.qq.com/qqweb/qunpro/share?_wv=3&_wwv=128&appChannel=share&inviteCode=7b4a3&appChannel=share&businessType=9&from=246610&biz=ka) 21 | 22 | 4. 通过 Discord 服务器(点击下方链接直达) 23 | 24 | [![Discord Server](https://discordapp.com/api/guilds/847819937858584596/widget.png?style=shield)](https://discord.gg/VKtE6Gdc4h) 25 | -------------------------------------------------------------------------------- /website/docs/community/contributing.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar-position: 1 3 | description: 如何为 NoneBot 贡献代码 4 | --- 5 | 6 | # 贡献指南 7 | 8 | ## Code of Conduct 9 | 10 | 请参阅 [Code of Conduct](https://github.com/nonebot/nonebot2/blob/master/CODE_OF_CONDUCT.md)。 11 | 12 | ## 参与开发 13 | 14 | 请参阅 [Contributing](https://github.com/nonebot/nonebot2/blob/master/CONTRIBUTING.md)。 15 | 16 | ## 鸣谢 17 | 18 | 感谢以下开发者对 NoneBot2 作出的贡献: 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /website/docs/editor-support.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | description: 配置编辑器以获得最佳体验 4 | --- 5 | 6 | # 编辑器支持 7 | 8 | 框架基于 [PEP484](https://www.python.org/dev/peps/pep-0484/)、[PEP 561](https://www.python.org/dev/peps/pep-0561/)、[PEP8](https://www.python.org/dev/peps/pep-0008/) 等规范进行开发并且**拥有完整类型注解**。框架使用 Pyright(Pylance)工具进行类型检查,确保代码可以被编辑器正确解析。 9 | 10 | ## 编辑器推荐配置 11 | 12 | ### Visual Studio Code 13 | 14 | 在 Visual Studio Code 中,可以使用 Pylance Language Server 并启用 `Type Checking` 配置以达到最佳开发体验。 15 | 16 | 1. 在 VSCode 插件视图搜索并安装 `Python (ms-python.python)` 和 `Pylance (ms-python.vscode-pylance)` 插件。 17 | 2. 修改 VSCode 配置 18 | 在 VSCode 设置视图搜索配置项 `Python: Language Server` 并将其值设置为 `Pylance`,搜索配置项 `Python > Analysis: Type Checking Mode` 并将其值设置为 `basic`。 19 | 20 | 或者向项目 `.vscode` 文件夹中配置文件添加以下内容: 21 | 22 | ```json title=settings.json 23 | { 24 | "python.languageServer": "Pylance", 25 | "python.analysis.typeCheckingMode": "basic" 26 | } 27 | ``` 28 | 29 | ### 其他 30 | 31 | 欢迎提交 Pull Request 添加其他编辑器配置推荐。点击左下角 `Edit this page` 前往编辑。 32 | -------------------------------------------------------------------------------- /website/docs/tutorial/fundamentals.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | description: NoneBot 机器人构成及基本使用 4 | 5 | options: 6 | menu: 7 | - category: tutorial 8 | weight: 30 9 | --- 10 | 11 | # 机器人的构成 12 | 13 | 了解机器人的基本构成有助于你更好地使用 NoneBot,本章节将介绍 NoneBot 中的基本组成部分,稍后的文档中将会使用到这些概念。 14 | 15 | 使用 NoneBot 框架搭建的机器人具有以下几个基本组成部分: 16 | 17 | 1. NoneBot 机器人框架主体:负责连接各个组成部分,提供基本的机器人功能 18 | 2. 驱动器 `Driver`:客户端/服务端的功能实现,负责接收和发送消息(通常为 HTTP 通信) 19 | 3. 适配器 `Adapter`:驱动器的上层,负责将**平台消息**与 NoneBot 事件/操作系统的消息格式相互转换 20 | 4. 插件 `Plugin`:机器人的功能实现,通常为负责处理事件并进行一系列的操作 21 | 22 | 除 NoneBot 机器人框架主体外,其他部分均可按需选择、互相搭配,但由于平台的兼容性问题,部分插件可能仅在某些特定平台上可用(这由插件编写者决定)。 23 | 24 | 在接下来的章节中,我们将重点介绍机器人功能实现,即插件 `Plugin` 部分。 25 | -------------------------------------------------------------------------------- /website/docs/tutorial/matcher.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | description: 响应接收到的特定事件 4 | 5 | options: 6 | menu: 7 | - category: tutorial 8 | weight: 60 9 | --- 10 | 11 | # 事件响应器 12 | 13 | 事件响应器(Matcher)是对接收到的事件进行响应的基本单元,所有的事件响应器都继承自 `Matcher` 基类。 14 | 15 | 在 NoneBot 中,事件响应器可以通过一系列特定的规则**筛选**出**具有某种特征的事件**,并按照**特定的流程**交由**预定义的事件处理依赖**进行处理。例如,在[快速上手](../quick-start.mdx)中,我们使用了内置插件 `echo` ,它定义的事件响应器能响应机器人用户发送的“/echo hello world”消息,提取“hello world”信息并作为回复消息发送。 16 | 17 | ## 事件响应器辅助函数 18 | 19 | NoneBot 中所有事件响应器均继承自 `Matcher` 基类,但直接使用 `Matcher.new()` 方法创建事件响应器过于繁琐且不能记录插件信息。因此,NoneBot 中提供了一系列“事件响应器辅助函数”(下称“辅助函数”)来辅助我们用**最简的方式**创建**带有不同规则预设**的事件响应器,提高代码可读性和书写效率。通常情况下,我们只需要使用辅助函数即可完成事件响应器的创建。 20 | 21 | 在 NoneBot 中,辅助函数以 `on()` 或 `on_()` 形式出现(例如 `on_command()`),调用后根据不同的参数返回一个 `Type[Matcher]` 类型的新事件响应器。 22 | 23 | 目前 NoneBot 提供了多种功能各异的辅助函数、具有共同命令名称前缀的命令组以及具有共同参数的响应器组,均可以从 `nonebot` 模块直接导入使用,具体内容参考[事件响应器进阶](../advanced/matcher.md)。 24 | 25 | ## 创建事件响应器 26 | 27 | 在上一节[创建插件](./create-plugin.md#创建插件)中,我们创建了一个 `weather` 插件,现在我们来实现他的功能。 28 | 29 | 我们直接使用 `on_command()` 辅助函数来创建一个事件响应器: 30 | 31 | ```python {3} title=weather/__init__.py 32 | from nonebot import on_command 33 | 34 | weather = on_command("天气") 35 | ``` 36 | 37 | 这样,我们就获得一个名为 `weather` 的事件响应器了,这个事件响应器会对 `/天气` 开头的消息进行响应。 38 | 39 | :::tip 提示 40 | 如果一条消息中包含“@机器人”或以“机器人的昵称”开始,例如 `@bot /天气` 时,协议适配器会将 `event.is_tome()` 判断为 `True` ,同时也会自动去除 `@bot`,即事件响应器收到的信息内容为 `/天气`,方便进行命令匹配。 41 | ::: 42 | 43 | ### 为事件响应器添加参数 44 | 45 | 在辅助函数中,我们可以添加一些参数来对事件响应器进行更加精细的调整,例如事件响应器的优先级、匹配规则等。例如: 46 | 47 | ```python {4} title=weather/__init__.py 48 | from nonebot import on_command 49 | from nonebot.rule import to_me 50 | 51 | weather = on_command("天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True) 52 | ``` 53 | 54 | 这样,我们就获得了一个可以响应 `天气`、`weather`、`查天气` 三个命令的响应规则,需要私聊或 `@bot` 时才会响应,优先级为 10(越小越优先),阻断事件向后续优先级传播的事件响应器了。这些内容的意义和使用方法将会在后续的章节中一一介绍。 55 | 56 | :::tip 提示 57 | 需要注意的是,不同的辅助函数有不同的可选参数,在使用之前可以参考[事件响应器进阶 - 基本辅助函数](../advanced/matcher.md#基本辅助函数)或 [API 文档](../api/plugin/on.md#on)。 58 | ::: 59 | -------------------------------------------------------------------------------- /website/src/components/Asciinema/container.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from "react"; 2 | 3 | import * as AsciinemaPlayer from "asciinema-player"; 4 | 5 | export type AsciinemaOptions = { 6 | cols: number; 7 | rows: number; 8 | autoPlay: boolean; 9 | preload: boolean; 10 | loop: boolean; 11 | startAt: number | string; 12 | speed: number; 13 | idleTimeLimit: number; 14 | theme: string; 15 | poster: string; 16 | fit: string; 17 | fontSize: string; 18 | }; 19 | 20 | export type Props = { 21 | url: string; 22 | options?: Partial; 23 | }; 24 | 25 | export default function AsciinemaContainer({ 26 | url, 27 | options = {}, 28 | }: Props): React.ReactNode { 29 | const ref = useRef(null); 30 | 31 | useEffect(() => { 32 | AsciinemaPlayer.create(url, ref.current, options); 33 | }, [url, options]); 34 | 35 | return
; 36 | } 37 | -------------------------------------------------------------------------------- /website/src/components/Asciinema/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import BrowserOnly from "@docusaurus/BrowserOnly"; 4 | 5 | import "asciinema-player/dist/bundle/asciinema-player.css"; 6 | 7 | import type { Props } from "./container"; 8 | 9 | import "./styles.css"; 10 | 11 | export type { Props } from "./container"; 12 | 13 | export default function Asciinema(props: Props): React.ReactNode { 14 | return ( 15 | 18 | Asciinema cast 19 | 20 | } 21 | > 22 | {() => { 23 | // eslint-disable-next-line @typescript-eslint/no-var-requires 24 | const AsciinemaContainer = require("./container.tsx").default; 25 | return ; 26 | }} 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /website/src/components/Asciinema/styles.css: -------------------------------------------------------------------------------- 1 | .ap-player svg { 2 | @apply inline-block; 3 | } 4 | 5 | .ap-container { 6 | @apply w-full my-4; 7 | } 8 | -------------------------------------------------------------------------------- /website/src/components/Form/Adapter.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { Form } from "."; 4 | 5 | export default function AdapterForm(): React.ReactNode { 6 | const formItems = [ 7 | { 8 | name: "基本信息", 9 | items: [ 10 | { 11 | type: "text", 12 | name: "name", 13 | labelText: "适配器名称", 14 | }, 15 | { type: "text", name: "description", labelText: "适配器描述" }, 16 | { 17 | type: "text", 18 | name: "homepage", 19 | labelText: "适配器项目仓库/主页链接", 20 | }, 21 | ], 22 | }, 23 | { 24 | name: "包信息", 25 | items: [ 26 | { type: "text", name: "pypi", labelText: "PyPI 项目名" }, 27 | { type: "text", name: "module", labelText: "适配器 import 包名" }, 28 | ], 29 | }, 30 | { 31 | name: "其他信息", 32 | items: [{ type: "tag", name: "tags", labelText: "标签" }], 33 | }, 34 | ]; 35 | const handleSubmit = (result: Record) => { 36 | window.open( 37 | `https://github.com/nonebot/nonebot2/issues/new?${new URLSearchParams({ 38 | assignees: "", 39 | labels: "Adapter", 40 | projects: "", 41 | template: "adapter_publish.yml", 42 | title: `Adapter: ${result.name}`, 43 | ...result, 44 | })}` 45 | ); 46 | }; 47 | 48 | return ( 49 |
50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /website/src/components/Form/Bot.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { Form } from "."; 4 | 5 | export default function BotForm(): React.ReactNode { 6 | const formItems = [ 7 | { 8 | name: "基本信息", 9 | items: [ 10 | { 11 | type: "text", 12 | name: "name", 13 | labelText: "机器人名称", 14 | }, 15 | { type: "text", name: "description", labelText: "机器人描述" }, 16 | { 17 | type: "text", 18 | name: "homepage", 19 | labelText: "机器人项目仓库/主页链接", 20 | }, 21 | ], 22 | }, 23 | { 24 | name: "其他信息", 25 | items: [{ type: "tag", name: "tags", labelText: "标签" }], 26 | }, 27 | ]; 28 | 29 | const handleSubmit = (result: Record) => { 30 | window.open( 31 | `https://github.com/nonebot/nonebot2/issues/new?${new URLSearchParams({ 32 | assignees: "", 33 | labels: "Bot", 34 | projects: "", 35 | template: "bot_publish.yml", 36 | title: `Bot: ${result.name}`, 37 | ...result, 38 | })}` 39 | ); 40 | }; 41 | 42 | return ; 43 | } 44 | -------------------------------------------------------------------------------- /website/src/components/Form/Items/Tag/styles.css: -------------------------------------------------------------------------------- 1 | .add-btn { 2 | @apply px-2 select-none cursor-pointer min-w-[64px] rounded-full hover:bg-opacity-[.08]; 3 | @apply flex justify-center items-center border-dashed border-2; 4 | 5 | &-disabled { 6 | @apply pointer-events-none opacity-60; 7 | } 8 | } 9 | 10 | .form-item { 11 | @apply basis-3/4; 12 | 13 | &-title { 14 | @apply basis-1/4 label-text; 15 | } 16 | 17 | &-input { 18 | @apply input input-sm input-bordered; 19 | } 20 | 21 | &-select { 22 | @apply select select-sm select-bordered; 23 | } 24 | 25 | &-container { 26 | @apply flex items-center mt-2; 27 | } 28 | } 29 | 30 | .fix-input-color { 31 | @apply !text-base-content !bg-base-100; 32 | input { 33 | @apply !text-base-content !bg-base-100; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /website/src/components/Form/Plugin.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { Form } from "."; 4 | 5 | export default function PluginForm(): React.ReactNode { 6 | const formItems = [ 7 | { 8 | name: "包信息", 9 | items: [ 10 | { type: "text", name: "pypi", labelText: "PyPI 项目名" }, 11 | { type: "text", name: "module", labelText: "插件 import 包名" }, 12 | ], 13 | }, 14 | { 15 | name: "其他信息", 16 | items: [{ type: "tag", name: "tags", labelText: "标签" }], 17 | }, 18 | ]; 19 | const handleSubmit = (result: Record) => { 20 | window.open( 21 | `https://github.com/nonebot/nonebot2/issues/new?${new URLSearchParams({ 22 | assignees: "", 23 | labels: "Plugin", 24 | projects: "", 25 | template: "plugin_publish.yml", 26 | title: `Plugin: ${result.pypi}`, 27 | ...result, 28 | })}` 29 | ); 30 | }; 31 | 32 | return ( 33 | 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /website/src/components/Form/styles.css: -------------------------------------------------------------------------------- 1 | .form-btn { 2 | @apply btn btn-sm btn-primary no-animation; 3 | 4 | &-prev { 5 | @apply mr-auto; 6 | } 7 | 8 | &-next { 9 | @apply ml-auto; 10 | } 11 | 12 | &-hidden { 13 | @apply !hidden; 14 | } 15 | } 16 | 17 | .form-input { 18 | @apply input input-bordered w-full; 19 | 20 | &-error { 21 | @apply input-error; 22 | } 23 | } 24 | 25 | .form-label { 26 | @apply text-xs; 27 | 28 | &-error { 29 | @apply text-error; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /website/src/components/Home/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import HomeFeatures from "./Feature"; 4 | import HomeHero from "./Hero"; 5 | 6 | import "./styles.css"; 7 | 8 | export default function HomeContent(): React.ReactNode { 9 | return ( 10 |
11 | 12 | 13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /website/src/components/Home/styles.css: -------------------------------------------------------------------------------- 1 | .home { 2 | &-container { 3 | @apply -mt-16; 4 | } 5 | 6 | &-hero { 7 | @apply relative flex flex-col items-center justify-center gap-4 h-screen; 8 | 9 | &-logo { 10 | @apply h-48 w-auto; 11 | } 12 | 13 | &-title { 14 | @apply text-5xl font-normal tracking-tight; 15 | } 16 | 17 | &-tagline { 18 | @apply text-sm font-medium uppercase tracking-wide text-base-content/70; 19 | } 20 | 21 | &-actions { 22 | @apply flex flex-col sm:flex-row gap-4; 23 | } 24 | 25 | &-copy { 26 | @apply font-normal normal-case text-base-content/70; 27 | } 28 | 29 | &-next { 30 | @apply absolute bottom-4; 31 | 32 | & svg { 33 | @apply animate-bounce text-primary text-4xl; 34 | } 35 | } 36 | } 37 | 38 | &-codeblock { 39 | @apply inline-block !max-w-[600px]; 40 | } 41 | } 42 | 43 | .home-hero-uwu { 44 | @apply hidden; 45 | } 46 | 47 | [data-uwu="true"] .home-hero-uwu { 48 | @apply block max-w-xs; 49 | } 50 | 51 | [data-uwu="true"] .home-hero-logo, 52 | [data-uwu="true"] .home-hero-title { 53 | @apply hidden; 54 | } 55 | -------------------------------------------------------------------------------- /website/src/components/Messenger/styles.css: -------------------------------------------------------------------------------- 1 | .messenger { 2 | &-container { 3 | @apply block w-full my-4 overflow-hidden; 4 | @apply rounded-lg outline-none bg-base-200; 5 | @apply transition-[background-color] duration-500; 6 | } 7 | 8 | &-title { 9 | @apply flex items-center h-12 px-4 bg-info text-white; 10 | 11 | &-back { 12 | @apply text-left text-base grow; 13 | } 14 | 15 | &-name { 16 | @apply flex-initial grow-0 text-xl font-bold; 17 | } 18 | 19 | &-more { 20 | @apply text-right text-base grow; 21 | } 22 | } 23 | 24 | &-chat { 25 | @apply p-4 min-h-[150px]; 26 | 27 | &-avatar { 28 | @apply !flex items-center justify-center; 29 | @apply w-10 rounded-full; 30 | 31 | &-user { 32 | @apply bg-info text-white; 33 | } 34 | } 35 | 36 | &-bubble { 37 | @apply bg-base-100 text-base-content [word-break:break-word]; 38 | @apply transition-[color,background-color] duration-500; 39 | } 40 | } 41 | 42 | &-footer { 43 | @apply px-4; 44 | 45 | &-action { 46 | @apply flex items-center gap-2; 47 | 48 | &-input { 49 | @apply flex-1; 50 | 51 | & > input { 52 | @apply transition-[color,background-color] duration-500; 53 | } 54 | } 55 | 56 | &-send { 57 | @apply flex-initial w-fit; 58 | } 59 | } 60 | 61 | &-tools { 62 | @apply grid grid-cols-6 items-center py-1; 63 | @apply text-center text-base text-base-content/60; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /website/src/components/Modal/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | 3 | import clsx from "clsx"; 4 | 5 | import IconClose from "@theme/Icon/Close"; 6 | 7 | import "./styles.css"; 8 | 9 | export type Props = { 10 | children?: React.ReactNode; 11 | className?: string; 12 | title: string; 13 | useCustomTitle?: boolean; 14 | backdropExit?: boolean; 15 | setOpenModal: (isOpen: boolean) => void; 16 | }; 17 | 18 | export default function Modal({ 19 | setOpenModal, 20 | className, 21 | children, 22 | useCustomTitle, 23 | backdropExit, 24 | title, 25 | }: Props): React.ReactNode { 26 | const [transitionClass, setTransitionClass] = useState(""); 27 | 28 | const onFadeIn = () => setTransitionClass("fade-in"); 29 | const onFadeOut = () => setTransitionClass("fade-out"); 30 | const onTransitionEnd = () => 31 | transitionClass === "fade-out" && setOpenModal(false); 32 | 33 | useEffect(onFadeIn, []); 34 | 35 | return ( 36 |
37 |
backdropExit && onFadeOut()} 41 | /> 42 |
43 |
44 |
45 | {!useCustomTitle && ( 46 |
47 | {title} 48 |
49 | 52 |
53 |
54 | )} 55 | {children} 56 |
57 |
58 |
59 |
60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /website/src/components/Modal/styles.css: -------------------------------------------------------------------------------- 1 | .nb-modal { 2 | &-title { 3 | @apply flex items-center font-bold; 4 | } 5 | 6 | &-root { 7 | @apply fixed z-[1300] inset-0 flex items-center justify-center; 8 | } 9 | 10 | &-container { 11 | @apply absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 min-w-[400px] lg:min-w-[600px]; 12 | @apply opacity-0; 13 | @apply transition-opacity duration-[225ms] ease-in-out delay-0; 14 | 15 | &.fade-in { 16 | @apply opacity-100; 17 | } 18 | 19 | &.fade-out { 20 | @apply opacity-0; 21 | } 22 | } 23 | 24 | &-backdrop { 25 | @apply fixed flex right-0 bottom-0 top-0 left-0 bg-transparent opacity-0; 26 | @apply transition-all duration-[225ms] ease-in-out delay-0 -z-[1]; 27 | 28 | &.fade-in { 29 | @apply opacity-100 bg-black/50; 30 | } 31 | 32 | &.fade-out { 33 | @apply opacity-0 bg-transparent; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /website/src/components/Paginate/styles.css: -------------------------------------------------------------------------------- 1 | .paginate { 2 | &-container { 3 | @apply flex items-center justify-center gap-2; 4 | } 5 | 6 | &-button { 7 | @apply flex items-center justify-center cursor-pointer select-none; 8 | @apply w-8 h-8 text-sm leading-8 text-center bg-base-200 text-base-content; 9 | 10 | &.ellipsis { 11 | @apply !text-base-content cursor-default; 12 | } 13 | 14 | &.active { 15 | @apply bg-primary !text-primary-content cursor-default; 16 | } 17 | 18 | &:hover { 19 | @apply text-primary; 20 | } 21 | 22 | &:disabled { 23 | @apply !text-base-content/80 cursor-not-allowed; 24 | } 25 | } 26 | 27 | &-pager { 28 | @apply flex items-center justify-center gap-2; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /website/src/components/Resource/Avatar/index.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import Link from "@docusaurus/Link"; 3 | import clsx from "clsx"; 4 | 5 | interface Props { 6 | className?: string; 7 | authorLink: string; 8 | authorAvatar: string; 9 | } 10 | 11 | export default function Avatar({ authorLink, authorAvatar, className }: Props) { 12 | const [loaded, setLoaded] = useState(false); 13 | const onLoad = () => setLoaded(true); 14 | 15 | return ( 16 |
17 |
18 | 19 |
20 | {!loaded && ( 21 |
27 | )} 28 | Avatar 38 |
39 | 40 |
41 |
42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /website/src/components/Resource/Card/styles.css: -------------------------------------------------------------------------------- 1 | .resource-card { 2 | &-container { 3 | @apply flex flex-col gap-y-2 w-full min-h-[12rem] p-4; 4 | @apply transition-colors duration-500 bg-base-200; 5 | @apply border-2 border-base-200 rounded-lg; 6 | @apply hover:border-primary; 7 | } 8 | 9 | &-header { 10 | @apply min-w-0 flex items-center w-full text-lg font-medium; 11 | 12 | &-title { 13 | @apply min-w-0 grow justify-start flex items-center flex-initial; 14 | } 15 | 16 | &-text { 17 | @apply min-w-0 cursor-help tooltip; 18 | } 19 | 20 | &-check { 21 | @apply ml-2 text-green-600 dark:text-green-400 fill-current; 22 | } 23 | 24 | &-expand { 25 | @apply flex-none fill-current cursor-pointer; 26 | } 27 | } 28 | 29 | &-desc { 30 | @apply flex-1 w-full text-sm text-ellipsis break-words; 31 | } 32 | 33 | &-footer { 34 | @apply flex flex-col w-full cursor-default; 35 | 36 | &-tags { 37 | @apply flex flex-wrap gap-1; 38 | } 39 | 40 | &-tag { 41 | @apply cursor-pointer; 42 | } 43 | 44 | &-divider { 45 | @apply m-0; 46 | } 47 | 48 | &-info { 49 | @apply flex items-center justify-between w-full; 50 | } 51 | 52 | &-group { 53 | @apply flex items-center justify-center gap-x-1 leading-none; 54 | } 55 | 56 | &-icon { 57 | @apply w-5 h-5 fill-current opacity-80; 58 | 59 | &:hover { 60 | @apply opacity-100; 61 | } 62 | } 63 | 64 | &-avatar { 65 | @apply w-5 h-5 rounded-full transition-shadow; 66 | 67 | &:hover { 68 | @apply ring-1 ring-primary ring-offset-base-100 ring-offset-1; 69 | } 70 | } 71 | 72 | &-author { 73 | @apply text-sm cursor-pointer; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /website/src/components/Resource/DetailCard/styles.css: -------------------------------------------------------------------------------- 1 | .detail-card { 2 | &-header { 3 | @apply flex items-center align-middle; 4 | 5 | &-divider { 6 | @apply m-0; 7 | } 8 | } 9 | 10 | &-avatar { 11 | @apply mr-3 w-12 h-12 rounded-full; 12 | } 13 | 14 | &-title { 15 | @apply inline-flex flex-col h-12 justify-start; 16 | 17 | &-main { 18 | @apply font-bold; 19 | } 20 | 21 | &-sub { 22 | @apply text-sm; 23 | } 24 | } 25 | 26 | &-actions { 27 | @apply flex items-center gap-x-2 lg:ml-auto; 28 | 29 | &-button { 30 | @apply btn btn-sm; 31 | } 32 | 33 | &-mobile { 34 | @apply lg:hidden; 35 | } 36 | 37 | &-desktop { 38 | @apply max-lg:hidden; 39 | } 40 | } 41 | 42 | &-body { 43 | @apply flex flex-col w-full lg:flex-row; 44 | 45 | &-left { 46 | @apply flex flex-col min-h-[150px] lg:basis-3/4 max-w-[65%]; 47 | } 48 | 49 | &-divider { 50 | @apply divider lg:divider-horizontal; 51 | } 52 | 53 | &-right { 54 | @apply flex flex-col justify-start gap-y-2 lg:basis-1/4 lg:max-w-[45%]; 55 | } 56 | } 57 | 58 | &-meta-item { 59 | @apply text-sm truncate; 60 | 61 | &-link { 62 | @apply hover:text-primary hover:transition; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /website/src/components/Resource/DetailCard/types.ts: -------------------------------------------------------------------------------- 1 | export type Downloads = { 2 | last_day: number; 3 | last_month: number; 4 | last_week: number; 5 | }; 6 | 7 | export type Info = { 8 | author: string; 9 | author_email: string; 10 | bugtrack_url: null; 11 | classifiers: string[]; 12 | description: string; 13 | description_content_type: string; 14 | docs_url: null; 15 | download_url: string; 16 | downloads: Downloads; 17 | home_page: string; 18 | keywords: string; 19 | license: string; 20 | maintainer: string; 21 | maintainer_email: string; 22 | name: string; 23 | package_url: string; 24 | platform: null; 25 | project_url: string; 26 | release_url: string; 27 | requires_dist: string[]; 28 | requires_python: string; 29 | summary: string; 30 | version: string; 31 | yanked: boolean; 32 | yanked_reason: null; 33 | }; 34 | 35 | export interface Digests { 36 | blake2b_256: string; 37 | md5: string; 38 | sha256: string; 39 | } 40 | 41 | export type Releases = { 42 | comment_text: string; 43 | digests: Digests; 44 | downloads: number; 45 | filename: string; 46 | has_sig: boolean; 47 | md5_digest: string; 48 | packagetype: string; 49 | python_version: string; 50 | requires_python: string; 51 | size: number; 52 | upload_time: Date; 53 | upload_time_iso_8601: Date; 54 | url: string; 55 | yanked: boolean; 56 | yanked_reason: null; 57 | }; 58 | export type PyPIData = { 59 | info: Info; 60 | last_serial: number; 61 | releases: { [key: string]: Releases[] }; 62 | urls: URL[]; 63 | vulnerabilities: unknown[]; 64 | }; 65 | -------------------------------------------------------------------------------- /website/src/components/Resource/Tag/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import clsx from "clsx"; 4 | 5 | import { pickTextColor } from "@/libs/color"; 6 | 7 | import type { Tag } from "@/types/tag"; 8 | 9 | import "./styles.css"; 10 | 11 | export type Props = Tag & { 12 | className?: string; 13 | onClick?: React.MouseEventHandler; 14 | }; 15 | 16 | export default function ResourceTag({ 17 | label, 18 | color, 19 | className, 20 | onClick, 21 | }: Props): React.ReactNode { 22 | return ( 23 | 31 | {label} 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /website/src/components/Resource/Tag/styles.css: -------------------------------------------------------------------------------- 1 | .resource-tag { 2 | @apply inline-flex items-center justify-center; 3 | @apply text-xs font-mono w-fit h-5 px-2 rounded; 4 | } 5 | -------------------------------------------------------------------------------- /website/src/components/Searcher/styles.css: -------------------------------------------------------------------------------- 1 | .searcher { 2 | &-box { 3 | @apply flex items-center w-full rounded-3xl bg-base-200; 4 | @apply transition-[color,background-color] duration-500; 5 | } 6 | 7 | &-container { 8 | @apply flex-1 flex items-center flex-wrap gap-x-1 gap-y-2; 9 | @apply pl-5 py-3; 10 | } 11 | 12 | &-tag { 13 | @apply flex-initial shrink-0; 14 | @apply font-medium cursor-pointer select-none; 15 | } 16 | 17 | &-input { 18 | @apply flex-1 text-sm min-w-[10rem]; 19 | @apply bg-transparent border-none outline-none; 20 | } 21 | 22 | &-action { 23 | @apply flex-initial shrink-0 flex items-center justify-center cursor-pointer w-12 h-10; 24 | 25 | &-icon { 26 | @apply h-4 opacity-50; 27 | } 28 | 29 | &:hover &-icon.search { 30 | @apply hidden; 31 | } 32 | 33 | &:not(:hover) &-icon.close { 34 | @apply hidden; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /website/src/components/Store/Layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { useDocsVersionCandidates } from "@docusaurus/plugin-content-docs/client"; 4 | import { PageMetadata } from "@docusaurus/theme-common"; 5 | import { useVersionedSidebar } from "@nullbot/docusaurus-plugin-getsidebar/client"; 6 | import { SidebarContentFiller } from "@nullbot/docusaurus-theme-nonepress/contexts"; 7 | 8 | import BackToTopButton from "@theme/BackToTopButton"; 9 | import Heading from "@theme/Heading"; 10 | import Layout from "@theme/Layout"; 11 | import Page from "@theme/Page"; 12 | 13 | import "./styles.css"; 14 | 15 | const SIDEBAR_ID = "ecosystem"; 16 | 17 | type Props = { 18 | title: string; 19 | children: React.ReactNode; 20 | }; 21 | 22 | function StorePage({ title, children }: Props): React.ReactNode { 23 | const sidebarItems = useVersionedSidebar( 24 | useDocsVersionCandidates()[0].name, 25 | SIDEBAR_ID 26 | )!; 27 | 28 | return ( 29 | 30 | 31 |
32 | 33 | {title} 34 | 35 | {children} 36 |
37 |
38 | ); 39 | } 40 | 41 | export default function StoreLayout({ 42 | title, 43 | ...props 44 | }: Props): React.ReactNode { 45 | return ( 46 | <> 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /website/src/components/Store/styles.css: -------------------------------------------------------------------------------- 1 | .store { 2 | &-title { 3 | @apply text-center; 4 | } 5 | 6 | &-description { 7 | @apply text-center; 8 | } 9 | 10 | &-searcher { 11 | @apply max-w-2xl mx-auto my-4; 12 | } 13 | 14 | &-toolbar { 15 | @apply flex items-center justify-center my-4; 16 | 17 | &-second { 18 | @apply lg:hidden; 19 | } 20 | 21 | &-sorter { 22 | @apply max-lg:flex-1; 23 | 24 | &-desktop { 25 | @apply max-lg:hidden; 26 | } 27 | } 28 | 29 | &-filters { 30 | @apply flex grow gap-2; 31 | } 32 | 33 | &-dropdown { 34 | @apply w-36 z-10 m-0 p-2; 35 | @apply rounded-md bg-base-100 shadow-lg border border-base-200; 36 | } 37 | } 38 | 39 | &-loading { 40 | @apply text-primary; 41 | 42 | &-container { 43 | @apply text-center; 44 | } 45 | } 46 | 47 | &-container { 48 | @apply grid grid-cols-[repeat(auto-fill,minmax(300px,1fr))] gap-6 mt-4 mb-8; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /website/src/components/Tag/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import clsx from "clsx"; 4 | 5 | import "./styles.css"; 6 | 7 | import { pickTextColor } from "@/libs/color"; 8 | 9 | import type { Tag as TagType } from "@/types/tag"; 10 | 11 | export default function Tag({ 12 | label, 13 | color, 14 | className, 15 | onClick, 16 | }: TagType & { 17 | className?: string; 18 | onClick?: React.MouseEventHandler; 19 | }): React.ReactNode { 20 | return ( 21 | 29 | {label} 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /website/src/components/Tag/styles.css: -------------------------------------------------------------------------------- 1 | .tag { 2 | @apply font-mono inline-flex px-3 rounded-full items-center align-middle; 3 | } 4 | -------------------------------------------------------------------------------- /website/src/libs/color.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Choose fg color by bg color 3 | * @see https://www.npmjs.com/package/colord 4 | * @see https://www.w3.org/TR/AERT/#color-contrast 5 | */ 6 | export function pickTextColor( 7 | bgColor: string, 8 | lightColor: string, 9 | darkColor: string 10 | ) { 11 | const color = bgColor.charAt(0) === "#" ? bgColor.substring(1, 7) : bgColor; 12 | const r = parseInt(color.substring(0, 2), 16); // hexToR 13 | const g = parseInt(color.substring(2, 4), 16); // hexToG 14 | const b = parseInt(color.substring(4, 6), 16); // hexToB 15 | return r * 0.299 + g * 0.587 + b * 0.114 > 186 ? darkColor : lightColor; 16 | } 17 | -------------------------------------------------------------------------------- /website/src/libs/sorter.ts: -------------------------------------------------------------------------------- 1 | export enum SortMode { 2 | Default, 3 | UpdateDesc, 4 | } 5 | -------------------------------------------------------------------------------- /website/src/libs/store.ts: -------------------------------------------------------------------------------- 1 | import { translate } from "@docusaurus/Translate"; 2 | 3 | import type { Adapter, AdaptersResponse } from "@/types/adapter"; 4 | import type { Bot, BotsResponse } from "@/types/bot"; 5 | import type { Driver, DriversResponse } from "@/types/driver"; 6 | import type { Plugin, PluginsResponse } from "@/types/plugin"; 7 | 8 | type RegistryDataResponseTypes = { 9 | adapter: AdaptersResponse; 10 | bot: BotsResponse; 11 | driver: DriversResponse; 12 | plugin: PluginsResponse; 13 | }; 14 | type RegistryDataType = keyof RegistryDataResponseTypes; 15 | 16 | type ResourceTypes = { 17 | adapter: Adapter; 18 | bot: Bot; 19 | driver: Driver; 20 | plugin: Plugin; 21 | }; 22 | 23 | export type Resource = Adapter | Bot | Driver | Plugin; 24 | 25 | export async function fetchRegistryData( 26 | dataType: T 27 | ): Promise { 28 | const resp = await fetch( 29 | `https://registry.nonebot.dev/${dataType}s.json` 30 | ).catch((e) => { 31 | throw new Error(`Failed to fetch ${dataType}s: ${e}`); 32 | }); 33 | if (!resp.ok) { 34 | throw new Error( 35 | `Failed to fetch ${dataType}s: ${resp.status} ${resp.statusText}` 36 | ); 37 | } 38 | const data = (await resp.json()) as RegistryDataResponseTypes[T]; 39 | return data.map( 40 | (resource) => ({ ...resource, resourceType: dataType }) as ResourceTypes[T] 41 | ); 42 | } 43 | 44 | export const loadFailedTitle = translate({ 45 | id: "pages.store.loadFailed.title", 46 | message: "加载失败", 47 | description: "Title to display when loading content failed", 48 | }); 49 | -------------------------------------------------------------------------------- /website/src/libs/valid.ts: -------------------------------------------------------------------------------- 1 | export enum ValidStatus { 2 | VALID = "valid", 3 | INVALID = "invalid", 4 | SKIP = "skip", 5 | MISSING = "missing", 6 | } 7 | -------------------------------------------------------------------------------- /website/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import Layout from "@theme/Layout"; 4 | import HomeContent from "@/components/Home"; 5 | 6 | export default function Homepage(): React.ReactNode { 7 | return ( 8 | 9 | 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /website/src/pages/store/adapters.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { translate } from "@docusaurus/Translate"; 4 | 5 | import AdapterPageContent from "@/components/Store/Content/Adapter"; 6 | import StoreLayout from "@/components/Store/Layout"; 7 | 8 | export default function StoreAdapters(): React.ReactNode { 9 | const title = translate({ 10 | id: "pages.store.adapter.title", 11 | message: "适配器商店", 12 | description: "Title for the adapter store page", 13 | }); 14 | 15 | return ( 16 | 17 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /website/src/pages/store/bots.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { translate } from "@docusaurus/Translate"; 4 | 5 | import BotPageContent from "@/components/Store/Content/Bot"; 6 | import StoreLayout from "@/components/Store/Layout"; 7 | 8 | export default function StoreBots(): React.ReactNode { 9 | const title = translate({ 10 | id: "pages.store.bot.title", 11 | message: "机器人商店", 12 | description: "Title for the bot store page", 13 | }); 14 | 15 | return ( 16 | 17 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /website/src/pages/store/drivers.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { translate } from "@docusaurus/Translate"; 4 | 5 | import DriverPageContent from "@/components/Store/Content/Driver"; 6 | import StoreLayout from "@/components/Store/Layout"; 7 | 8 | export default function StoreDrivers(): React.ReactNode { 9 | const title = translate({ 10 | id: "pages.store.driver.title", 11 | message: "驱动器商店", 12 | description: "Title for the driver store page", 13 | }); 14 | 15 | return ( 16 | 17 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /website/src/pages/store/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { Redirect } from "@docusaurus/router"; 4 | 5 | export default function Store(): React.ReactNode { 6 | return ; 7 | } 8 | -------------------------------------------------------------------------------- /website/src/pages/store/plugins.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { translate } from "@docusaurus/Translate"; 4 | 5 | import PluginPageContent from "@/components/Store/Content/Plugin"; 6 | import StoreLayout from "@/components/Store/Layout"; 7 | 8 | export default function StorePlugins(): React.ReactNode { 9 | const title = translate({ 10 | id: "pages.store.plugin.title", 11 | message: "插件商店", 12 | description: "Title for the plugin store page", 13 | }); 14 | 15 | return ( 16 | 17 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /website/src/plugins/webpack-plugin.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | 3 | import type { PluginConfig } from "@docusaurus/types"; 4 | 5 | export default (function webpackPlugin() { 6 | return { 7 | name: "webpack-plugin", 8 | configureWebpack() { 9 | return { 10 | resolve: { 11 | alias: { 12 | "@": path.resolve(__dirname, "../"), 13 | }, 14 | }, 15 | }; 16 | }, 17 | }; 18 | } satisfies PluginConfig); 19 | -------------------------------------------------------------------------------- /website/src/theme/Footer/Copyright/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import Link from "@docusaurus/Link"; 4 | import Translate, { translate } from "@docusaurus/Translate"; 5 | 6 | import type { Props } from "@theme/Footer/Copyright"; 7 | import IconCloudflare from "@theme/Icon/Cloudflare"; 8 | import IconNetlify from "@theme/Icon/Netlify"; 9 | import OriginCopyright from "@theme-original/Footer/Copyright"; 10 | 11 | export default function FooterCopyright(props: Props) { 12 | return ( 13 | <> 14 | 15 |
16 | 20 | Deployed by 21 | 22 | 30 | 31 | 32 | 40 | 41 | 42 |
43 | 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /website/src/theme/Page/TOC/Container/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { useWindowSize } from "@nullbot/docusaurus-theme-nonepress/client"; 4 | 5 | import type { Props } from "@theme/Page/TOC/Container"; 6 | import OriginTOCContainer from "@theme-original/Page/TOC/Container"; 7 | 8 | import "./styles.css"; 9 | 10 | export default function TOCContainer({ 11 | children, 12 | ...props 13 | }: Props): React.ReactNode { 14 | const windowSize = useWindowSize(); 15 | const isClient = windowSize !== "ssr"; 16 | 17 | return ( 18 | 19 | {children} 20 | {isClient && ( 21 |
22 |
23 |
24 | )} 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /website/src/theme/Page/TOC/Container/styles.css: -------------------------------------------------------------------------------- 1 | .toc-ads { 2 | @apply max-w-full !m-0 !bg-transparent; 3 | 4 | & .wwads-text { 5 | @apply !text-base-content; 6 | @apply transition-[color] duration-500; 7 | } 8 | 9 | &-container { 10 | @apply shrink-0 w-full max-w-full px-4; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /website/src/types/adapter.ts: -------------------------------------------------------------------------------- 1 | import type { Tag } from "./tag"; 2 | 3 | type BaseAdapter = { 4 | module_name: string; 5 | project_link: string; 6 | name: string; 7 | desc: string; 8 | author: string; 9 | homepage: string; 10 | tags: Tag[]; 11 | is_official: boolean; 12 | }; 13 | 14 | export type Adapter = { resourceType: "adapter" } & BaseAdapter; 15 | 16 | export type AdaptersResponse = BaseAdapter[]; 17 | -------------------------------------------------------------------------------- /website/src/types/bot.ts: -------------------------------------------------------------------------------- 1 | import type { Tag } from "./tag"; 2 | 3 | type BaseBot = { 4 | name: string; 5 | desc: string; 6 | author: string; 7 | homepage: string; 8 | tags: Tag[]; 9 | is_official: boolean; 10 | }; 11 | 12 | export type Bot = { resourceType: "bot" } & BaseBot; 13 | 14 | export type BotsResponse = BaseBot[]; 15 | -------------------------------------------------------------------------------- /website/src/types/driver.ts: -------------------------------------------------------------------------------- 1 | import type { Tag } from "./tag"; 2 | 3 | type BaseDriver = { 4 | module_name: string; 5 | project_link: string; 6 | name: string; 7 | desc: string; 8 | author: string; 9 | homepage: string; 10 | tags: Tag[]; 11 | is_official: boolean; 12 | }; 13 | 14 | export type Driver = { resourceType: "driver" } & BaseDriver; 15 | 16 | export type DriversResponse = BaseDriver[]; 17 | -------------------------------------------------------------------------------- /website/src/types/plugin.ts: -------------------------------------------------------------------------------- 1 | import type { Tag } from "./tag"; 2 | 3 | type BasePlugin = { 4 | author: string; 5 | name: string; 6 | desc: string; 7 | homepage: string; 8 | is_official: boolean; 9 | module_name: string; 10 | project_link: string; 11 | skip_test: boolean; 12 | supported_adapters: string[] | null; 13 | tags: Array; 14 | time: string; 15 | type: string; 16 | valid: boolean; 17 | version: string; 18 | }; 19 | 20 | export type Plugin = { resourceType: "plugin" } & BasePlugin; 21 | 22 | export type PluginsResponse = BasePlugin[]; 23 | -------------------------------------------------------------------------------- /website/src/types/tag.ts: -------------------------------------------------------------------------------- 1 | export type Tag = { 2 | label: string; 3 | color: `#${string}`; 4 | }; 5 | -------------------------------------------------------------------------------- /website/static/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonebot/nonebot2/f8317444dfaaf720ff22fedf5b0a8d9f6138421c/website/static/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /website/static/icons/android-chrome-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonebot/nonebot2/f8317444dfaaf720ff22fedf5b0a8d9f6138421c/website/static/icons/android-chrome-384x384.png -------------------------------------------------------------------------------- /website/static/icons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonebot/nonebot2/f8317444dfaaf720ff22fedf5b0a8d9f6138421c/website/static/icons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /website/static/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonebot/nonebot2/f8317444dfaaf720ff22fedf5b0a8d9f6138421c/website/static/icons/favicon-16x16.png -------------------------------------------------------------------------------- /website/static/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonebot/nonebot2/f8317444dfaaf720ff22fedf5b0a8d9f6138421c/website/static/icons/favicon-32x32.png -------------------------------------------------------------------------------- /website/static/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonebot/nonebot2/f8317444dfaaf720ff22fedf5b0a8d9f6138421c/website/static/icons/favicon.ico -------------------------------------------------------------------------------- /website/static/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonebot/nonebot2/f8317444dfaaf720ff22fedf5b0a8d9f6138421c/website/static/icons/mstile-150x150.png -------------------------------------------------------------------------------- /website/static/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /website/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonebot/nonebot2/f8317444dfaaf720ff22fedf5b0a8d9f6138421c/website/static/logo.png -------------------------------------------------------------------------------- /website/static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "NoneBot", 3 | "short_name": "NoneBot", 4 | "background-color": "#ffffff", 5 | "theme-color": "#ea5252", 6 | "description": "跨平台 Python 异步聊天机器人框架", 7 | "display": "standalone", 8 | "icons": [ 9 | { 10 | "src": "/icons/android-chrome-192x192.png", 11 | "sizes": "192x192", 12 | "type": "image/png" 13 | }, 14 | { 15 | "src": "/icons/android-chrome-384x384.png", 16 | "sizes": "384x384", 17 | "type": "image/png" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /website/static/service-worker.js: -------------------------------------------------------------------------------- 1 | self.addEventListener("install", () => { 2 | self.skipWaiting(); 3 | }); 4 | 5 | self.addEventListener("activate", () => { 6 | self.registration 7 | .unregister() 8 | .then(() => { 9 | return self.clients.matchAll(); 10 | }) 11 | .then((clients) => { 12 | clients.forEach((client) => client.navigate(client.url)); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /website/static/uwu.js: -------------------------------------------------------------------------------- 1 | if (location.search.includes("?uwu")) { 2 | document.documentElement.setAttribute("data-uwu", "true"); 3 | } 4 | -------------------------------------------------------------------------------- /website/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import typography from "@tailwindcss/typography"; 2 | import daisyui from "daisyui"; 3 | import themes from "daisyui/src/theming/themes"; 4 | 5 | const lightTheme = themes.light; 6 | const darkTheme = themes.dark; 7 | 8 | function excludeThemeColor( 9 | theme: { [key: string]: string }, 10 | exclude: string[] 11 | ): { [key: string]: string } { 12 | const newObj: { [key: string]: string } = {}; 13 | for (const key in theme) { 14 | if (exclude.includes(key)) { 15 | continue; 16 | } 17 | newObj[key] = theme[key]!; 18 | } 19 | return newObj; 20 | } 21 | 22 | export default { 23 | plugins: [typography, daisyui], 24 | daisyui: { 25 | base: false, 26 | themes: [ 27 | { 28 | light: { 29 | ...excludeThemeColor(lightTheme, [ 30 | "primary-content", 31 | "secondary-content", 32 | "accent-content", 33 | ]), 34 | primary: "#ea5252", 35 | "primary-content": "#ffffff", 36 | secondary: "#ef9fbc", 37 | accent: "#65c3c8", 38 | }, 39 | }, 40 | { 41 | dark: { 42 | ...excludeThemeColor(darkTheme, [ 43 | "primary-content", 44 | "secondary-content", 45 | "accent-content", 46 | ]), 47 | primary: "#ea5252", 48 | "primary-content": "#ffffff", 49 | secondary: "#ef9fbc", 50 | accent: "#65c3c8", 51 | }, 52 | }, 53 | ], 54 | darkTheme: false, 55 | }, 56 | darkMode: ["class", '[data-theme="dark"]'], 57 | }; 58 | -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@nullbot/docusaurus-tsconfig", 4 | "compilerOptions": { 5 | "lib": ["DOM", "ESNext"], 6 | "baseUrl": ".", 7 | "paths": { 8 | "@/*": ["./src/*"], 9 | "@theme/*": ["./src/theme/*"] 10 | }, 11 | "resolveJsonModule": true, 12 | "allowArbitraryExtensions": true, 13 | 14 | // Duplicated from the root config, because TS does not support extending 15 | // multiple configs and we want to dogfood the @docusaurus/tsconfig one 16 | "allowUnreachableCode": false, 17 | "exactOptionalPropertyTypes": false, 18 | "noFallthroughCasesInSwitch": true, 19 | "noImplicitOverride": true, 20 | "noImplicitReturns": true, 21 | "noPropertyAccessFromIndexSignature": false, 22 | "noUncheckedIndexedAccess": true, 23 | "strict": true, 24 | "alwaysStrict": true, 25 | "noImplicitAny": true, 26 | "noImplicitThis": true, 27 | "strictBindCallApply": true, 28 | "strictFunctionTypes": true, 29 | "strictNullChecks": true, 30 | "strictPropertyInitialization": true, 31 | "useUnknownInCatchVariables": true, 32 | "noUnusedLocals": false, 33 | "noUnusedParameters": false, 34 | "importsNotUsedAsValues": "remove", 35 | 36 | // This is important. We run `yarn tsc` in website so we can catch issues 37 | // with our declaration files (mostly names that are forgotten to be 38 | // imported, invalid semantics...). Because we don't have end-to-end type 39 | // tests, removing this would make things much harder to catch. 40 | "skipLibCheck": false 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.0/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 0 3 | id: index 4 | slug: / 5 | --- 6 | 7 | # 概览 8 | 9 | NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架(下称 NoneBot),它基于 Python 的类型注解和异步优先特性(兼容同步),能够为你的需求实现提供便捷灵活的支持。同时,NoneBot 拥有大量的开发者为其开发插件,用户无需编写任何代码,仅需完成环境配置及插件安装,就可以正常使用 NoneBot。 10 | 11 | 需要注意的是,NoneBot 仅支持 **Python 3.9 以上版本** 12 | 13 | ## 特色 14 | 15 | ### 异步优先 16 | 17 | NoneBot 基于 Python [asyncio](https://docs.python.org/zh-cn/3/library/asyncio.html) / [trio](https://trio.readthedocs.io/en/stable/) 编写,并在异步机制的基础上进行了一定程度的同步函数兼容。 18 | 19 | ### 完整的类型注解 20 | 21 | NoneBot 参考 [PEP 484](https://www.python.org/dev/peps/pep-0484/) 等 PEP 完整实现了类型注解,通过 Pyright(Pylance) 检查。配合编辑器的类型推导功能,能将绝大多数的 Bug 杜绝在编辑器中([编辑器支持](./editor-support))。 22 | 23 | ### 开箱即用 24 | 25 | NoneBot 提供了使用便捷、具有交互式功能的命令行工具--`nb-cli`,使得用户初次接触 NoneBot 时更容易上手。使用方法请阅读本文档[指南](./quick-start.mdx)以及 [CLI 文档](https://cli.nonebot.dev/)。 26 | 27 | ### 插件系统 28 | 29 | 插件系统是 NoneBot 的核心,通过它可以实现机器人的模块化以及功能扩展,便于维护和管理。 30 | 31 | ### 依赖注入系统 32 | 33 | NoneBot 采用了一套自行定义的依赖注入系统,可以让事件的处理过程更加的简洁、清晰,增加代码的可读性,减少代码冗余。 34 | 35 | #### 什么是依赖注入 36 | 37 | [**『依赖注入』**](https://zh.wikipedia.org/wiki/%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%AC)意思是,在编程中,有一种方法可以让你的代码声明它工作和使用所需要的东西,即**『依赖』**。 38 | 39 | 系统(在这里是指 NoneBot)将负责做任何需要的事情,为你的代码提供这些必要依赖(即**『注入』**依赖性) 40 | 41 | 这在你有以下情形的需求时非常有用: 42 | 43 | - 这部分代码拥有共享的逻辑(同样的代码逻辑多次重复) 44 | - 共享数据库以及网络请求连接会话 45 | - 比如 `httpx.AsyncClient`、`aiohttp.ClientSession` 和 `sqlalchemy.Session` 46 | - 机器人用户权限检查以及认证 47 | - 还有更多... 48 | 49 | 它在完成上述工作的同时,还能尽量减少代码的耦合和重复 50 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.0/advanced/matcher-provider.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 10 3 | description: 自定义事件响应器存储 4 | 5 | options: 6 | menu: 7 | - category: advanced 8 | weight: 110 9 | --- 10 | 11 | # 事件响应器存储 12 | 13 | 事件响应器是 NoneBot 处理事件的核心,它们默认存储在一个字典中。在进入会话状态后,事件响应器将会转为临时响应器,作为最高优先级同样存储于该字典中。因此,事件响应器的存储类似于会话存储,它决定了整个 NoneBot 对事件的处理行为。 14 | 15 | NoneBot 默认使用 Python 的字典将事件响应器存储于内存中,但是我们也可以自定义事件响应器存储,将事件响应器存储于其他地方,例如 Redis 等。这样我们就可以实现持久化、在多实例间共享会话状态等功能。 16 | 17 | ## 编写存储提供者 18 | 19 | 事件响应器的存储提供者 `MatcherProvider` 抽象类继承自 `MutableMapping[int, list[type[Matcher]]]`,即以优先级为键,以事件响应器列表为值的映射。我们可以方便地进行逐优先级事件传播。 20 | 21 | 编写一个自定义的存储提供者,只需要继承并实现 `MatcherProvider` 抽象类: 22 | 23 | ```python 24 | from nonebot.matcher import MatcherProvider 25 | 26 | class CustomProvider(MatcherProvider): 27 | ... 28 | ``` 29 | 30 | ## 设置存储提供者 31 | 32 | 我们可以通过 `matchers.set_provider` 方法设置存储提供者: 33 | 34 | ```python {3} 35 | from nonebot.matcher import matchers 36 | 37 | matchers.set_provider(CustomProvider) 38 | 39 | assert isinstance(matchers.provider, CustomProvider) 40 | ``` 41 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.0/advanced/plugin-nesting.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | description: 编写与加载嵌套插件 4 | 5 | options: 6 | menu: 7 | - category: advanced 8 | weight: 40 9 | --- 10 | 11 | # 嵌套插件 12 | 13 | NoneBot 支持嵌套插件,即一个插件可以包含其他插件。通过这种方式,我们可以将一个大型插件拆分成多个功能子插件,使得插件更加清晰、易于维护。我们可以直接在插件中使用 NoneBot 加载插件的方法来加载子插件。 14 | 15 | ## 创建嵌套插件 16 | 17 | 我们可以在使用 `nb-cli` 命令[创建插件](../tutorial/create-plugin.md#创建插件)时,选择直接通过模板创建一个嵌套插件: 18 | 19 | ```bash 20 | $ nb plugin create 21 | [?] 插件名称: parent 22 | [?] 使用嵌套插件? (y/N) Y 23 | [?] 输出目录: awesome_bot/plugins 24 | ``` 25 | 26 | 或者使用 `nb plugin create --sub-plugin` 选项直接创建一个嵌套插件。 27 | 28 | ## 已有插件 29 | 30 | 如果你已经有一个插件,想要在其中嵌套加载子插件,可以在插件的 `__init__.py` 中添加如下代码: 31 | 32 | ```python title=parent/__init__.py 33 | import nonebot 34 | from pathlib import Path 35 | 36 | sub_plugins = nonebot.load_plugins( 37 | str(Path(__file__).parent.joinpath("plugins").resolve()) 38 | ) 39 | ``` 40 | 41 | 这样,`parent` 插件就会加载 `parent/plugins` 目录下的所有插件。NoneBot 会正确识别这些插件的父子关系,你可以在 `parent` 的插件信息中看到这些子插件的信息,也可以在子插件信息中看到它们的父插件信息。 42 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.0/advanced/requiring.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | description: 使用其他插件提供的功能 4 | 5 | options: 6 | menu: 7 | - category: advanced 8 | weight: 50 9 | --- 10 | 11 | # 跨插件访问 12 | 13 | NoneBot 插件化系统的设计使得插件之间可以功能独立、各司其职,我们可以更好地维护和扩展插件。但是,有时候我们可能需要在不同插件之间调用功能。NoneBot 生态中就有一类插件,它们专为其他插件提供功能支持,如:[定时任务插件](../best-practice/scheduler.md)、[数据存储插件](../best-practice/data-storing.md)等。这时候我们就需要在插件之间进行跨插件访问。 14 | 15 | ## 插件跟踪 16 | 17 | 由于 NoneBot 插件系统通过 [Import Hooks](https://docs.python.org/3/reference/import.html#import-hooks) 的方式实现插件加载与跟踪管理,因此我们**不能**在 NoneBot 跟踪插件前进行模块 import,这会导致插件加载失败。即,我们不能在使用 NoneBot 提供的加载插件方法前,直接使用 `import` 语句导入插件。 18 | 19 | 对于在项目目录下的插件,我们通常直接使用 `load_from_toml` 等方法一次性加载所有插件。由于这些插件已经被声明,即便插件导入顺序不同,NoneBot 也能正确跟踪插件。此时,我们不需要对跨插件访问进行特殊处理。但当我们使用了外部插件,如果没有事先声明或加载插件,NoneBot 并不会将其当作插件进行跟踪,可能会出现意料之外的错误出现。 20 | 21 | 简单来说,我们必须在 `import` 外部插件之前,确保依赖的外部插件已经被声明或加载。 22 | 23 | ## 插件依赖声明 24 | 25 | NoneBot 提供了一种方法来确保我们依赖的插件已经被正确加载,即使用 `require` 函数。通过 `require` 函数,我们可以在当前插件中声明依赖的插件,NoneBot 会在加载当前插件时,检查依赖的插件是否已经被加载,如果没有,会尝试优先加载依赖的插件。 26 | 27 | 假设我们有一个插件 `a` 依赖于插件 `b`,我们可以在插件 `a` 中使用 `require` 函数声明其依赖于插件 `b`: 28 | 29 | ```python {3} title=a/__init__.py 30 | from nonebot import require 31 | 32 | require("b") 33 | 34 | from b import some_function 35 | ``` 36 | 37 | 其中,`require` 函数的参数为插件索引名称或者外部插件的模块名称。在完成依赖声明后,我们可以在插件 `a` 中直接导入插件 `b` 所提供的功能。 38 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.0/advanced/session-updating.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 7 3 | description: 控制会话响应对象 4 | 5 | options: 6 | menu: 7 | - category: advanced 8 | weight: 80 9 | --- 10 | 11 | # 会话更新 12 | 13 | 在 NoneBot 中,在某个事件响应器对事件响应后,即是进入了会话状态,会话状态会持续到整个事件响应流程结束。会话过程中,机器人可以与用户进行多次交互。每次需要等待用户事件时,NoneBot 将会复制一个新的临时事件响应器,并更新该事件响应器使其响应当前会话主体的消息,这个过程称为会话更新。 14 | 15 | 会话更新分为两部分:**更新[事件响应器类型](./matcher.md#事件响应器类型)**和**更新[事件触发权限](./matcher.md#事件触发权限)**。 16 | 17 | ## 更新事件响应器类型 18 | 19 | 通常情况下,与机器人用户进行的会话都是通过消息事件进行的,因此会话更新后的默认响应事件类型为 `message`。如果希望接收一个特定类型的消息,比如 `notice` 等,我们需要自定义响应事件类型更新函数。响应事件类型更新函数是一个 `Dependent`,可以使用依赖注入。 20 | 21 | ```python {3-5} 22 | foo = on_message() 23 | 24 | @foo.type_updater 25 | async def _() -> str: 26 | return "notice" 27 | ``` 28 | 29 | 在注册了上述响应事件类型更新函数后,当我们需要等待用户事件时,将只会响应 `notice` 类型的事件。如果希望在会话过程中的不同阶段响应不同类型的事件,我们就需要使用更复杂的逻辑来更新响应事件类型(如:根据会话状态),这里将不再展示。 30 | 31 | ## 更新事件触发权限 32 | 33 | 会话通常是由机器人与用户进行的一对一交互,因此会话更新后的默认触发权限为当前事件的会话 ID。这个会话 ID 由协议适配器生成,通常由用户 ID 和群 ID 等组成。如果希望实现更复杂的会话功能(如:多用户同时参与的会话),我们需要自定义触发权限更新函数。触发权限更新函数是一个 `Dependent`,可以使用依赖注入。 34 | 35 | ```python {5-7} 36 | from nonebot.permission import User 37 | 38 | foo = on_message() 39 | 40 | @foo.permission_updater 41 | async def _(event: Event, matcher: Matcher) -> Permission: 42 | return Permission(User.from_event(event, perm=matcher.permission)) 43 | ``` 44 | 45 | 上述权限更新函数是默认的权限更新函数,它将会话的触发权限更新为当前事件的会话 ID。如果我们希望响应多个用户的消息,我们可以如下修改: 46 | 47 | ```python {5-7} 48 | from nonebot.permission import USER 49 | 50 | foo = on_message() 51 | 52 | @foo.permission_updater 53 | async def _(matcher: Matcher) -> Permission: 54 | return USER("session1", "session2", perm=matcher.permission) 55 | ``` 56 | 57 | 请注意,此处为全大写字母的 `USER` 权限,它可以匹配多个会话 ID。通过这种方式,我们可以实现多用户同时参与的会话。 58 | 59 | 我们已经了解了如何控制会话的更新,相信你已经能够实现更复杂的会话功能了,例如多人小游戏等等。欢迎将你的作品分享到[插件商店](/store/plugins)。 60 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.0/api/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonebot/nonebot2/f8317444dfaaf720ff22fedf5b0a8d9f6138421c/website/versioned_docs/version-2.4.0/api/.gitkeep -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.0/api/adapters/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 15 3 | } 4 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.0/api/dependencies/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 13 3 | } 4 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.0/api/dependencies/utils.md: -------------------------------------------------------------------------------- 1 | --- 2 | mdx: 3 | format: md 4 | sidebar_position: 1 5 | description: nonebot.dependencies.utils 模块 6 | --- 7 | 8 | # nonebot.dependencies.utils 9 | 10 | ## _def_ `get_typed_signature(call)` {#get-typed-signature} 11 | 12 | - **说明:** 获取可调用对象签名 13 | 14 | - **参数** 15 | 16 | - `call` ((...) -> Any) 17 | 18 | - **返回** 19 | 20 | - inspect.Signature 21 | 22 | ## _def_ `get_typed_annotation(param, globalns)` {#get-typed-annotation} 23 | 24 | - **说明:** 获取参数的类型注解 25 | 26 | - **参数** 27 | 28 | - `param` (inspect.Parameter) 29 | 30 | - `globalns` (dict[str, Any]) 31 | 32 | - **返回** 33 | 34 | - Any 35 | 36 | ## _def_ `check_field_type(field, value)` {#check-field-type} 37 | 38 | - **说明:** 检查字段类型是否匹配 39 | 40 | - **参数** 41 | 42 | - `field` ([ModelField](../compat.md#ModelField)) 43 | 44 | - `value` (Any) 45 | 46 | - **返回** 47 | 48 | - Any 49 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.0/api/drivers/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 14 3 | } 4 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.0/api/drivers/none.md: -------------------------------------------------------------------------------- 1 | --- 2 | mdx: 3 | format: md 4 | sidebar_position: 6 5 | description: nonebot.drivers.none 模块 6 | --- 7 | 8 | # nonebot.drivers.none 9 | 10 | None 驱动适配 11 | 12 | :::tip 提示 13 | 本驱动不支持任何服务器或客户端连接 14 | ::: 15 | 16 | ## _class_ `Driver(env, config)` {#Driver} 17 | 18 | - **说明:** None 驱动框架 19 | 20 | - **参数** 21 | 22 | - `env` ([Env](../config.md#Env)) 23 | 24 | - `config` ([Config](../config.md#Config)) 25 | 26 | ### _property_ `type` {#Driver-type} 27 | 28 | - **类型:** str 29 | 30 | - **说明:** 驱动名称: `none` 31 | 32 | ### _property_ `logger` {#Driver-logger} 33 | 34 | - **类型:** untyped 35 | 36 | - **说明:** none driver 使用的 logger 37 | 38 | ### _method_ `run(*args, **kwargs)` {#Driver-run} 39 | 40 | - **说明:** 启动 none driver 41 | 42 | - **参数** 43 | 44 | - `*args` 45 | 46 | - `**kwargs` 47 | 48 | - **返回** 49 | 50 | - untyped 51 | 52 | ### _method_ `exit(force=False)` {#Driver-exit} 53 | 54 | - **说明:** 退出 none driver 55 | 56 | - **参数** 57 | 58 | - `force` (bool): 强制退出 59 | 60 | - **返回** 61 | 62 | - untyped 63 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.0/api/log.md: -------------------------------------------------------------------------------- 1 | --- 2 | mdx: 3 | format: md 4 | sidebar_position: 7 5 | description: nonebot.log 模块 6 | --- 7 | 8 | # nonebot.log 9 | 10 | 本模块定义了 NoneBot 的日志记录 Logger。 11 | 12 | NoneBot 使用 [`loguru`][loguru] 来记录日志信息。 13 | 14 | 自定义 logger 请参考 [自定义日志](https://nonebot.dev/docs/appendices/log) 15 | 以及 [`loguru`][loguru] 文档。 16 | 17 | [loguru]: https://github.com/Delgan/loguru 18 | 19 | ## _var_ `logger` {#logger} 20 | 21 | - **类型:** Logger 22 | 23 | - **说明** 24 | 25 | NoneBot 日志记录器对象。 26 | 27 | 默认信息: 28 | 29 | - 格式: `[%(asctime)s %(name)s] %(levelname)s: %(message)s` 30 | - 等级: `INFO` ,根据 `config.log_level` 配置改变 31 | - 输出: 输出至 stdout 32 | 33 | - **用法** 34 | 35 | ```python 36 | from nonebot.log import logger 37 | ``` 38 | 39 | ## _class_ `LoguruHandler()` {#LoguruHandler} 40 | 41 | - **说明:** logging 与 loguru 之间的桥梁,将 logging 的日志转发到 loguru。 42 | 43 | - **参数** 44 | 45 | auto 46 | 47 | ### _method_ `emit(record)` {#LoguruHandler-emit} 48 | 49 | - **参数** 50 | 51 | - `record` (logging.LogRecord) 52 | 53 | - **返回** 54 | 55 | - untyped 56 | 57 | ## _def_ `default_filter(record)` {#default-filter} 58 | 59 | - **说明:** 默认的日志过滤器,根据 `config.log_level` 配置改变日志等级。 60 | 61 | - **参数** 62 | 63 | - `record` (Record) 64 | 65 | - **返回** 66 | 67 | - untyped 68 | 69 | ## _var_ `default_format` {#default-format} 70 | 71 | - **类型:** str 72 | 73 | - **说明:** 默认日志格式 74 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.0/api/plugin/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 12 3 | } 4 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.0/appendices/session-state.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | description: 会话状态信息 4 | 5 | options: 6 | menu: 7 | - category: appendices 8 | weight: 40 9 | --- 10 | 11 | # 会话状态 12 | 13 | 在事件处理流程中,和用户交互的过程即是会话。在会话中,我们可能需要记录一些信息,例如用户的重试次数等等,以便在会话中的不同阶段进行判断和处理。这些信息都可以存储于会话状态中。 14 | 15 | NoneBot 中的会话状态是一个字典,可以通过类型 `T_State` 来获取。字典内可以存储任意类型的数据,但是要注意的是,NoneBot 本身会在会话状态中存储一些信息,因此不要使用 [NoneBot 使用的键名](../api/consts.md)。 16 | 17 | ```python 18 | from nonebot.typing import T_State 19 | 20 | @matcher.got("key", prompt="请输入密码") 21 | async def _(state: T_State, key: str = ArgPlainText()): 22 | if key != "some password": 23 | try_count = state.get("try_count", 1) 24 | if try_count >= 3: 25 | await matcher.finish("密码错误次数过多") 26 | else: 27 | state["try_count"] = try_count + 1 28 | await matcher.reject("密码错误,请重新输入") 29 | await matcher.finish("密码正确") 30 | ``` 31 | 32 | 会话状态的生命周期与事件处理流程相同,在期间的任何一个事件处理函数都可以进行读写。 33 | 34 | ```python 35 | from nonebot.typing import T_State 36 | 37 | @matcher.handle() 38 | async def _(state: T_State): 39 | state["key"] = "value" 40 | 41 | @matcher.handle() 42 | async def _(state: T_State): 43 | await matcher.finish(state["key"]) 44 | ``` 45 | 46 | 会话状态还可以用于发送动态消息,消息模板在发送时会使用会话状态字典进行渲染。消息模板的使用方法已经在[消息处理](../tutorial/message.md#使用消息模板)中介绍过,这里不再赘述。 47 | 48 | ```python 49 | from nonebot.typing import T_State 50 | from nonebot.adapters import MessageTemplate 51 | 52 | @matcher.handle() 53 | async def _(state: T_State): 54 | state["username"] = "user" 55 | 56 | @matcher.got("password", prompt=MessageTemplate("请输入 {username} 的密码")) 57 | async def _(): 58 | await matcher.finish(MessageTemplate("密码为 {password}")) 59 | ``` 60 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.0/appendices/whats-next.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 99 3 | description: 下一步──进阶! 4 | --- 5 | 6 | # 下一步 7 | 8 | 至此,我们已经了解了 NoneBot 的大多数功能用法,相信你已经可以独自写出一个插件了。现在你可以选择: 9 | 10 | - 即刻开始插件编写! 11 | - 更深入地了解 NoneBot 的[更多功能和原理](../advanced/plugin-info.md)! 12 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.0/best-practice/alconna/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Alconna 命令解析拓展", 3 | "position": 6 4 | } 5 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.0/best-practice/alconna/config.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | description: 配置项 4 | --- 5 | 6 | # 配置项 7 | 8 | ## alconna_auto_send_output 9 | 10 | - **类型**: `bool` 11 | - **默认值**: `False` 12 | 13 | 是否全局启用输出信息自动发送,不启用则会在触发特殊内置选项后仍然将解析结果传递至响应器。 14 | 15 | ## alconna_use_command_start 16 | 17 | - **类型**: `bool` 18 | - **默认值**: `False` 19 | 20 | 是否读取 Nonebot 的配置项 `COMMAND_START` 来作为全局的 Alconna 命令前缀 21 | 22 | ## alconna_auto_completion 23 | 24 | - **类型**: `bool` 25 | - **默认值**: `False` 26 | 27 | 是否全局启用命令自动补全,启用后会在参数缺失或触发 `--comp` 选项时自自动启用交互式补全。 28 | 29 | ## alconna_use_origin 30 | 31 | - **类型**: `bool` 32 | - **默认值**: `False` 33 | 34 | 是否全局使用原始消息 (即未经过 to_me 等处理的),该选项会影响到 Alconna 的匹配行为。 35 | 36 | ## alconna_use_command_sep 37 | 38 | - **类型**: `bool` 39 | - **默认值**: `False` 40 | 41 | 是否读取 Nonebot 的配置项 `COMMAND_SEP` 来作为全局的 Alconna 命令分隔符。 42 | 43 | ## alconna_global_extensions 44 | 45 | - **类型**: `List[str]` 46 | - **默认值**: `[]` 47 | 48 | 全局加载的扩展,路径以 . 分隔,如 `foo.bar.baz:DemoExtension`。 49 | 50 | ## alconna_context_style 51 | 52 | - **类型**: `Optional[Literal["bracket", "parentheses"]]` 53 | - **默认值**: `None` 54 | 55 | 全局命令上下文插值的风格,None 为关闭,bracket 为 `{...}`,parentheses 为 `$(...)`。 56 | 57 | ## alconna_enable_saa_patch 58 | 59 | - **类型**: `bool` 60 | - **默认值**: `False` 61 | 62 | 是否启用 SAA 补丁。 63 | 64 | ## alconna_apply_filehost 65 | 66 | - **类型**: `bool` 67 | - **默认值**: `False` 68 | 69 | 是否启用文件托管。 70 | 71 | ## alconna_apply_fetch_targets 72 | 73 | - **类型**: `bool` 74 | - **默认值**: `False` 75 | 76 | 是否启动时拉取一次发送对象列表。 77 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.0/best-practice/database/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "数据库", 3 | "position": 7 4 | } 5 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.0/best-practice/database/developer/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "开发者指南", 3 | "position": 3 4 | } 5 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.0/best-practice/error-tracking.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | description: 使用 sentry 进行错误跟踪 4 | --- 5 | 6 | # 错误跟踪 7 | 8 | 在应用实际运行过程中,可能会出现各种各样的错误。可能是由于代码逻辑错误,也可能是由于用户输入错误,甚至是由于第三方服务的错误。这些错误都会导致应用的运行出现问题,这时候就需要对错误进行跟踪,以便及时发现问题并进行修复。NoneBot 提供了 `nonebot-plugin-sentry` 插件,支持 [sentry](https://sentry.io/) 平台,可以方便地进行错误跟踪。 9 | 10 | ## 安装插件 11 | 12 | 在使用前请先安装 `nonebot-plugin-sentry` 插件至项目环境中,可参考[获取商店插件](../tutorial/store.mdx#安装插件)来了解并选择安装插件的方式。如: 13 | 14 | 在**项目目录**下执行以下命令: 15 | 16 | ```bash 17 | nb plugin install nonebot-plugin-sentry 18 | ``` 19 | 20 | ## 使用插件 21 | 22 | 在安装完成之后,仅需要对插件进行简单的配置即可使用。 23 | 24 | ### 获取 sentry DSN 25 | 26 | 前往 [sentry](https://sentry.io/) 平台,注册并创建一个新的项目,然后在项目设置中找到 `Client Keys (DSN)`,复制其中的 `DSN` 值。 27 | 28 | ### 配置插件 29 | 30 | :::caution 注意 31 | 错误跟踪通常在生产环境中使用,因此开发环境中 `sentry_dsn` 留空即会停用插件。 32 | ::: 33 | 34 | 在项目 dotenv 配置文件中添加以下配置即可使用: 35 | 36 | ```dotenv 37 | SENTRY_DSN= 38 | ``` 39 | 40 | ## 配置项 41 | 42 | 配置项具体含义参考 [Sentry Docs](https://docs.sentry.io/platforms/python/configuration/options/)。 43 | 44 | - `sentry_dsn: str` 45 | - `sentry_debug: bool = False` 46 | - `sentry_release: str | None = None` 47 | - `sentry_release: str | None = None` 48 | - `sentry_environment: str | None = nonebot env` 49 | - `sentry_server_name: str | None = None` 50 | - `sentry_sample_rate: float = 1.` 51 | - `sentry_max_breadcrumbs: int = 100` 52 | - `sentry_attach_stacktrace: bool = False` 53 | - `sentry_send_default_pii: bool = False` 54 | - `sentry_in_app_include: List[str] = Field(default_factory=list)` 55 | - `sentry_in_app_exclude: List[str] = Field(default_factory=list)` 56 | - `sentry_request_bodies: str = "medium"` 57 | - `sentry_with_locals: bool = True` 58 | - `sentry_ca_certs: str | None = None` 59 | - `sentry_before_send: Callable[[Any, Any], Any | None] | None = None` 60 | - `sentry_before_breadcrumb: Callable[[Any, Any], Any | None] | None = None` 61 | - `sentry_transport: Any | None = None` 62 | - `sentry_http_proxy: str | None = None` 63 | - `sentry_https_proxy: str | None = None` 64 | - `sentry_shutdown_timeout: int = 2` 65 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.0/best-practice/testing/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "单元测试", 3 | "position": 5 4 | } 5 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.0/community/contact.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar-position: 0 3 | description: 遇到问题如何获取帮助 4 | --- 5 | 6 | # 参与讨论 7 | 8 | 如果在安装或者开发 NoneBot 过程中遇到了任何问题,或者有新奇的点子,欢迎参与我们的社区讨论: 9 | 10 | 1. 点击下方链接前往 GitHub,前往 Issues 页面,在 `New Issue` Template 中选择 `Question` 11 | 12 | NoneBot:[![NoneBot project link](https://img.shields.io/github/stars/nonebot/nonebot2?style=social)](https://github.com/nonebot/nonebot2) 13 | 14 | 2. 通过 QQ 群(点击下方链接直达) 15 | 16 | [![QQ Chat Group](https://img.shields.io/badge/QQ%E7%BE%A4-768887710-orange?style=social)](https://jq.qq.com/?_wv=1027&k=5OFifDh) 17 | 18 | 3. 通过 QQ 频道 19 | 20 | [![QQ Channel](https://img.shields.io/badge/QQ%E9%A2%91%E9%81%93-NoneBot-orange?style=social)](https://qun.qq.com/qqweb/qunpro/share?_wv=3&_wwv=128&appChannel=share&inviteCode=7b4a3&appChannel=share&businessType=9&from=246610&biz=ka) 21 | 22 | 4. 通过 Discord 服务器(点击下方链接直达) 23 | 24 | [![Discord Server](https://discordapp.com/api/guilds/847819937858584596/widget.png?style=shield)](https://discord.gg/VKtE6Gdc4h) 25 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.0/community/contributing.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar-position: 1 3 | description: 如何为 NoneBot 贡献代码 4 | --- 5 | 6 | # 贡献指南 7 | 8 | ## Code of Conduct 9 | 10 | 请参阅 [Code of Conduct](https://github.com/nonebot/nonebot2/blob/master/CODE_OF_CONDUCT.md)。 11 | 12 | ## 参与开发 13 | 14 | 请参阅 [Contributing](https://github.com/nonebot/nonebot2/blob/master/CONTRIBUTING.md)。 15 | 16 | ## 鸣谢 17 | 18 | 感谢以下开发者对 NoneBot2 作出的贡献: 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.0/editor-support.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | description: 配置编辑器以获得最佳体验 4 | --- 5 | 6 | # 编辑器支持 7 | 8 | 框架基于 [PEP484](https://www.python.org/dev/peps/pep-0484/)、[PEP 561](https://www.python.org/dev/peps/pep-0561/)、[PEP8](https://www.python.org/dev/peps/pep-0008/) 等规范进行开发并且**拥有完整类型注解**。框架使用 Pyright(Pylance)工具进行类型检查,确保代码可以被编辑器正确解析。 9 | 10 | ## 编辑器推荐配置 11 | 12 | ### Visual Studio Code 13 | 14 | 在 Visual Studio Code 中,可以使用 Pylance Language Server 并启用 `Type Checking` 配置以达到最佳开发体验。 15 | 16 | 1. 在 VSCode 插件视图搜索并安装 `Python (ms-python.python)` 和 `Pylance (ms-python.vscode-pylance)` 插件。 17 | 2. 修改 VSCode 配置 18 | 在 VSCode 设置视图搜索配置项 `Python: Language Server` 并将其值设置为 `Pylance`,搜索配置项 `Python > Analysis: Type Checking Mode` 并将其值设置为 `basic`。 19 | 20 | 或者向项目 `.vscode` 文件夹中配置文件添加以下内容: 21 | 22 | ```json title=settings.json 23 | { 24 | "python.languageServer": "Pylance", 25 | "python.analysis.typeCheckingMode": "basic" 26 | } 27 | ``` 28 | 29 | ### 其他 30 | 31 | 欢迎提交 Pull Request 添加其他编辑器配置推荐。点击左下角 `Edit this page` 前往编辑。 32 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.0/tutorial/fundamentals.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | description: NoneBot 机器人构成及基本使用 4 | 5 | options: 6 | menu: 7 | - category: tutorial 8 | weight: 30 9 | --- 10 | 11 | # 机器人的构成 12 | 13 | 了解机器人的基本构成有助于你更好地使用 NoneBot,本章节将介绍 NoneBot 中的基本组成部分,稍后的文档中将会使用到这些概念。 14 | 15 | 使用 NoneBot 框架搭建的机器人具有以下几个基本组成部分: 16 | 17 | 1. NoneBot 机器人框架主体:负责连接各个组成部分,提供基本的机器人功能 18 | 2. 驱动器 `Driver`:客户端/服务端的功能实现,负责接收和发送消息(通常为 HTTP 通信) 19 | 3. 适配器 `Adapter`:驱动器的上层,负责将**平台消息**与 NoneBot 事件/操作系统的消息格式相互转换 20 | 4. 插件 `Plugin`:机器人的功能实现,通常为负责处理事件并进行一系列的操作 21 | 22 | 除 NoneBot 机器人框架主体外,其他部分均可按需选择、互相搭配,但由于平台的兼容性问题,部分插件可能仅在某些特定平台上可用(这由插件编写者决定)。 23 | 24 | 在接下来的章节中,我们将重点介绍机器人功能实现,即插件 `Plugin` 部分。 25 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.0/tutorial/matcher.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | description: 响应接收到的特定事件 4 | 5 | options: 6 | menu: 7 | - category: tutorial 8 | weight: 60 9 | --- 10 | 11 | # 事件响应器 12 | 13 | 事件响应器(Matcher)是对接收到的事件进行响应的基本单元,所有的事件响应器都继承自 `Matcher` 基类。 14 | 15 | 在 NoneBot 中,事件响应器可以通过一系列特定的规则**筛选**出**具有某种特征的事件**,并按照**特定的流程**交由**预定义的事件处理依赖**进行处理。例如,在[快速上手](../quick-start.mdx)中,我们使用了内置插件 `echo` ,它定义的事件响应器能响应机器人用户发送的“/echo hello world”消息,提取“hello world”信息并作为回复消息发送。 16 | 17 | ## 事件响应器辅助函数 18 | 19 | NoneBot 中所有事件响应器均继承自 `Matcher` 基类,但直接使用 `Matcher.new()` 方法创建事件响应器过于繁琐且不能记录插件信息。因此,NoneBot 中提供了一系列“事件响应器辅助函数”(下称“辅助函数”)来辅助我们用**最简的方式**创建**带有不同规则预设**的事件响应器,提高代码可读性和书写效率。通常情况下,我们只需要使用辅助函数即可完成事件响应器的创建。 20 | 21 | 在 NoneBot 中,辅助函数以 `on()` 或 `on_()` 形式出现(例如 `on_command()`),调用后根据不同的参数返回一个 `Type[Matcher]` 类型的新事件响应器。 22 | 23 | 目前 NoneBot 提供了多种功能各异的辅助函数、具有共同命令名称前缀的命令组以及具有共同参数的响应器组,均可以从 `nonebot` 模块直接导入使用,具体内容参考[事件响应器进阶](../advanced/matcher.md)。 24 | 25 | ## 创建事件响应器 26 | 27 | 在上一节[创建插件](./create-plugin.md#创建插件)中,我们创建了一个 `weather` 插件,现在我们来实现他的功能。 28 | 29 | 我们直接使用 `on_command()` 辅助函数来创建一个事件响应器: 30 | 31 | ```python {3} title=weather/__init__.py 32 | from nonebot import on_command 33 | 34 | weather = on_command("天气") 35 | ``` 36 | 37 | 这样,我们就获得一个名为 `weather` 的事件响应器了,这个事件响应器会对 `/天气` 开头的消息进行响应。 38 | 39 | :::tip 提示 40 | 如果一条消息中包含“@机器人”或以“机器人的昵称”开始,例如 `@bot /天气` 时,协议适配器会将 `event.is_tome()` 判断为 `True` ,同时也会自动去除 `@bot`,即事件响应器收到的信息内容为 `/天气`,方便进行命令匹配。 41 | ::: 42 | 43 | ### 为事件响应器添加参数 44 | 45 | 在辅助函数中,我们可以添加一些参数来对事件响应器进行更加精细的调整,例如事件响应器的优先级、匹配规则等。例如: 46 | 47 | ```python {4} title=weather/__init__.py 48 | from nonebot import on_command 49 | from nonebot.rule import to_me 50 | 51 | weather = on_command("天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True) 52 | ``` 53 | 54 | 这样,我们就获得了一个可以响应 `天气`、`weather`、`查天气` 三个命令的响应规则,需要私聊或 `@bot` 时才会响应,优先级为 10(越小越优先),阻断事件向后续优先级传播的事件响应器了。这些内容的意义和使用方法将会在后续的章节中一一介绍。 55 | 56 | :::tip 提示 57 | 需要注意的是,不同的辅助函数有不同的可选参数,在使用之前可以参考[事件响应器进阶 - 基本辅助函数](../advanced/matcher.md#基本辅助函数)或 [API 文档](../api/plugin/on.md#on)。 58 | ::: 59 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.1/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 0 3 | id: index 4 | slug: / 5 | --- 6 | 7 | # 概览 8 | 9 | NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架(下称 NoneBot),它基于 Python 的类型注解和异步优先特性(兼容同步),能够为你的需求实现提供便捷灵活的支持。同时,NoneBot 拥有大量的开发者为其开发插件,用户无需编写任何代码,仅需完成环境配置及插件安装,就可以正常使用 NoneBot。 10 | 11 | 需要注意的是,NoneBot 仅支持 **Python 3.9 以上版本** 12 | 13 | ## 特色 14 | 15 | ### 异步优先 16 | 17 | NoneBot 基于 Python [asyncio](https://docs.python.org/zh-cn/3/library/asyncio.html) / [trio](https://trio.readthedocs.io/en/stable/) 编写,并在异步机制的基础上进行了一定程度的同步函数兼容。 18 | 19 | ### 完整的类型注解 20 | 21 | NoneBot 参考 [PEP 484](https://www.python.org/dev/peps/pep-0484/) 等 PEP 完整实现了类型注解,通过 Pyright(Pylance) 检查。配合编辑器的类型推导功能,能将绝大多数的 Bug 杜绝在编辑器中([编辑器支持](./editor-support))。 22 | 23 | ### 开箱即用 24 | 25 | NoneBot 提供了使用便捷、具有交互式功能的命令行工具--`nb-cli`,使得用户初次接触 NoneBot 时更容易上手。使用方法请阅读本文档[指南](./quick-start.mdx)以及 [CLI 文档](https://cli.nonebot.dev/)。 26 | 27 | ### 插件系统 28 | 29 | 插件系统是 NoneBot 的核心,通过它可以实现机器人的模块化以及功能扩展,便于维护和管理。 30 | 31 | ### 依赖注入系统 32 | 33 | NoneBot 采用了一套自行定义的依赖注入系统,可以让事件的处理过程更加的简洁、清晰,增加代码的可读性,减少代码冗余。 34 | 35 | #### 什么是依赖注入 36 | 37 | [**『依赖注入』**](https://zh.wikipedia.org/wiki/%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%AC)意思是,在编程中,有一种方法可以让你的代码声明它工作和使用所需要的东西,即**『依赖』**。 38 | 39 | 系统(在这里是指 NoneBot)将负责做任何需要的事情,为你的代码提供这些必要依赖(即**『注入』**依赖性) 40 | 41 | 这在你有以下情形的需求时非常有用: 42 | 43 | - 这部分代码拥有共享的逻辑(同样的代码逻辑多次重复) 44 | - 共享数据库以及网络请求连接会话 45 | - 比如 `httpx.AsyncClient`、`aiohttp.ClientSession` 和 `sqlalchemy.Session` 46 | - 机器人用户权限检查以及认证 47 | - 还有更多... 48 | 49 | 它在完成上述工作的同时,还能尽量减少代码的耦合和重复 50 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.1/advanced/matcher-provider.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 10 3 | description: 自定义事件响应器存储 4 | 5 | options: 6 | menu: 7 | - category: advanced 8 | weight: 110 9 | --- 10 | 11 | # 事件响应器存储 12 | 13 | 事件响应器是 NoneBot 处理事件的核心,它们默认存储在一个字典中。在进入会话状态后,事件响应器将会转为临时响应器,作为最高优先级同样存储于该字典中。因此,事件响应器的存储类似于会话存储,它决定了整个 NoneBot 对事件的处理行为。 14 | 15 | NoneBot 默认使用 Python 的字典将事件响应器存储于内存中,但是我们也可以自定义事件响应器存储,将事件响应器存储于其他地方,例如 Redis 等。这样我们就可以实现持久化、在多实例间共享会话状态等功能。 16 | 17 | ## 编写存储提供者 18 | 19 | 事件响应器的存储提供者 `MatcherProvider` 抽象类继承自 `MutableMapping[int, list[type[Matcher]]]`,即以优先级为键,以事件响应器列表为值的映射。我们可以方便地进行逐优先级事件传播。 20 | 21 | 编写一个自定义的存储提供者,只需要继承并实现 `MatcherProvider` 抽象类: 22 | 23 | ```python 24 | from nonebot.matcher import MatcherProvider 25 | 26 | class CustomProvider(MatcherProvider): 27 | ... 28 | ``` 29 | 30 | ## 设置存储提供者 31 | 32 | 我们可以通过 `matchers.set_provider` 方法设置存储提供者: 33 | 34 | ```python {3} 35 | from nonebot.matcher import matchers 36 | 37 | matchers.set_provider(CustomProvider) 38 | 39 | assert isinstance(matchers.provider, CustomProvider) 40 | ``` 41 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.1/advanced/plugin-nesting.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | description: 编写与加载嵌套插件 4 | 5 | options: 6 | menu: 7 | - category: advanced 8 | weight: 40 9 | --- 10 | 11 | # 嵌套插件 12 | 13 | NoneBot 支持嵌套插件,即一个插件可以包含其他插件。通过这种方式,我们可以将一个大型插件拆分成多个功能子插件,使得插件更加清晰、易于维护。我们可以直接在插件中使用 NoneBot 加载插件的方法来加载子插件。 14 | 15 | ## 创建嵌套插件 16 | 17 | 我们可以在使用 `nb-cli` 命令[创建插件](../tutorial/create-plugin.md#创建插件)时,选择直接通过模板创建一个嵌套插件: 18 | 19 | ```bash 20 | $ nb plugin create 21 | [?] 插件名称: parent 22 | [?] 使用嵌套插件? (y/N) Y 23 | [?] 输出目录: awesome_bot/plugins 24 | ``` 25 | 26 | 或者使用 `nb plugin create --sub-plugin` 选项直接创建一个嵌套插件。 27 | 28 | ## 已有插件 29 | 30 | 如果你已经有一个插件,想要在其中嵌套加载子插件,可以在插件的 `__init__.py` 中添加如下代码: 31 | 32 | ```python title=parent/__init__.py 33 | import nonebot 34 | from pathlib import Path 35 | 36 | sub_plugins = nonebot.load_plugins( 37 | str(Path(__file__).parent.joinpath("plugins").resolve()) 38 | ) 39 | ``` 40 | 41 | 这样,`parent` 插件就会加载 `parent/plugins` 目录下的所有插件。NoneBot 会正确识别这些插件的父子关系,你可以在 `parent` 的插件信息中看到这些子插件的信息,也可以在子插件信息中看到它们的父插件信息。 42 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.1/advanced/requiring.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | description: 使用其他插件提供的功能 4 | 5 | options: 6 | menu: 7 | - category: advanced 8 | weight: 50 9 | --- 10 | 11 | # 跨插件访问 12 | 13 | NoneBot 插件化系统的设计使得插件之间可以功能独立、各司其职,我们可以更好地维护和扩展插件。但是,有时候我们可能需要在不同插件之间调用功能。NoneBot 生态中就有一类插件,它们专为其他插件提供功能支持,如:[定时任务插件](../best-practice/scheduler.md)、[数据存储插件](../best-practice/data-storing.md)等。这时候我们就需要在插件之间进行跨插件访问。 14 | 15 | ## 插件跟踪 16 | 17 | 由于 NoneBot 插件系统通过 [Import Hooks](https://docs.python.org/3/reference/import.html#import-hooks) 的方式实现插件加载与跟踪管理,因此我们**不能**在 NoneBot 跟踪插件前进行模块 import,这会导致插件加载失败。即,我们不能在使用 NoneBot 提供的加载插件方法前,直接使用 `import` 语句导入插件。 18 | 19 | 对于在项目目录下的插件,我们通常直接使用 `load_from_toml` 等方法一次性加载所有插件。由于这些插件已经被声明,即便插件导入顺序不同,NoneBot 也能正确跟踪插件。此时,我们不需要对跨插件访问进行特殊处理。但当我们使用了外部插件,如果没有事先声明或加载插件,NoneBot 并不会将其当作插件进行跟踪,可能会出现意料之外的错误出现。 20 | 21 | 简单来说,我们必须在 `import` 外部插件之前,确保依赖的外部插件已经被声明或加载。 22 | 23 | ## 插件依赖声明 24 | 25 | NoneBot 提供了一种方法来确保我们依赖的插件已经被正确加载,即使用 `require` 函数。通过 `require` 函数,我们可以在当前插件中声明依赖的插件,NoneBot 会在加载当前插件时,检查依赖的插件是否已经被加载,如果没有,会尝试优先加载依赖的插件。 26 | 27 | 假设我们有一个插件 `a` 依赖于插件 `b`,我们可以在插件 `a` 中使用 `require` 函数声明其依赖于插件 `b`: 28 | 29 | ```python {3} title=a/__init__.py 30 | from nonebot import require 31 | 32 | require("b") 33 | 34 | from b import some_function 35 | ``` 36 | 37 | 其中,`require` 函数的参数为插件索引名称或者外部插件的模块名称。在完成依赖声明后,我们可以在插件 `a` 中直接导入插件 `b` 所提供的功能。 38 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.1/advanced/session-updating.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 7 3 | description: 控制会话响应对象 4 | 5 | options: 6 | menu: 7 | - category: advanced 8 | weight: 80 9 | --- 10 | 11 | # 会话更新 12 | 13 | 在 NoneBot 中,在某个事件响应器对事件响应后,即是进入了会话状态,会话状态会持续到整个事件响应流程结束。会话过程中,机器人可以与用户进行多次交互。每次需要等待用户事件时,NoneBot 将会复制一个新的临时事件响应器,并更新该事件响应器使其响应当前会话主体的消息,这个过程称为会话更新。 14 | 15 | 会话更新分为两部分:**更新[事件响应器类型](./matcher.md#事件响应器类型)**和**更新[事件触发权限](./matcher.md#事件触发权限)**。 16 | 17 | ## 更新事件响应器类型 18 | 19 | 通常情况下,与机器人用户进行的会话都是通过消息事件进行的,因此会话更新后的默认响应事件类型为 `message`。如果希望接收一个特定类型的消息,比如 `notice` 等,我们需要自定义响应事件类型更新函数。响应事件类型更新函数是一个 `Dependent`,可以使用依赖注入。 20 | 21 | ```python {3-5} 22 | foo = on_message() 23 | 24 | @foo.type_updater 25 | async def _() -> str: 26 | return "notice" 27 | ``` 28 | 29 | 在注册了上述响应事件类型更新函数后,当我们需要等待用户事件时,将只会响应 `notice` 类型的事件。如果希望在会话过程中的不同阶段响应不同类型的事件,我们就需要使用更复杂的逻辑来更新响应事件类型(如:根据会话状态),这里将不再展示。 30 | 31 | ## 更新事件触发权限 32 | 33 | 会话通常是由机器人与用户进行的一对一交互,因此会话更新后的默认触发权限为当前事件的会话 ID。这个会话 ID 由协议适配器生成,通常由用户 ID 和群 ID 等组成。如果希望实现更复杂的会话功能(如:多用户同时参与的会话),我们需要自定义触发权限更新函数。触发权限更新函数是一个 `Dependent`,可以使用依赖注入。 34 | 35 | ```python {5-7} 36 | from nonebot.permission import User 37 | 38 | foo = on_message() 39 | 40 | @foo.permission_updater 41 | async def _(event: Event, matcher: Matcher) -> Permission: 42 | return Permission(User.from_event(event, perm=matcher.permission)) 43 | ``` 44 | 45 | 上述权限更新函数是默认的权限更新函数,它将会话的触发权限更新为当前事件的会话 ID。如果我们希望响应多个用户的消息,我们可以如下修改: 46 | 47 | ```python {5-7} 48 | from nonebot.permission import USER 49 | 50 | foo = on_message() 51 | 52 | @foo.permission_updater 53 | async def _(matcher: Matcher) -> Permission: 54 | return USER("session1", "session2", perm=matcher.permission) 55 | ``` 56 | 57 | 请注意,此处为全大写字母的 `USER` 权限,它可以匹配多个会话 ID。通过这种方式,我们可以实现多用户同时参与的会话。 58 | 59 | 我们已经了解了如何控制会话的更新,相信你已经能够实现更复杂的会话功能了,例如多人小游戏等等。欢迎将你的作品分享到[插件商店](/store/plugins)。 60 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.1/api/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonebot/nonebot2/f8317444dfaaf720ff22fedf5b0a8d9f6138421c/website/versioned_docs/version-2.4.1/api/.gitkeep -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.1/api/adapters/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 15 3 | } 4 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.1/api/dependencies/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 13 3 | } 4 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.1/api/dependencies/utils.md: -------------------------------------------------------------------------------- 1 | --- 2 | mdx: 3 | format: md 4 | sidebar_position: 1 5 | description: nonebot.dependencies.utils 模块 6 | --- 7 | 8 | # nonebot.dependencies.utils 9 | 10 | ## _def_ `get_typed_signature(call)` {#get-typed-signature} 11 | 12 | - **说明:** 获取可调用对象签名 13 | 14 | - **参数** 15 | 16 | - `call` ((...) -> Any) 17 | 18 | - **返回** 19 | 20 | - inspect.Signature 21 | 22 | ## _def_ `get_typed_annotation(param, globalns)` {#get-typed-annotation} 23 | 24 | - **说明:** 获取参数的类型注解 25 | 26 | - **参数** 27 | 28 | - `param` (inspect.Parameter) 29 | 30 | - `globalns` (dict[str, Any]) 31 | 32 | - **返回** 33 | 34 | - Any 35 | 36 | ## _def_ `check_field_type(field, value)` {#check-field-type} 37 | 38 | - **说明:** 检查字段类型是否匹配 39 | 40 | - **参数** 41 | 42 | - `field` ([ModelField](../compat.md#ModelField)) 43 | 44 | - `value` (Any) 45 | 46 | - **返回** 47 | 48 | - Any 49 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.1/api/drivers/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 14 3 | } 4 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.1/api/drivers/none.md: -------------------------------------------------------------------------------- 1 | --- 2 | mdx: 3 | format: md 4 | sidebar_position: 6 5 | description: nonebot.drivers.none 模块 6 | --- 7 | 8 | # nonebot.drivers.none 9 | 10 | None 驱动适配 11 | 12 | :::tip 提示 13 | 本驱动不支持任何服务器或客户端连接 14 | ::: 15 | 16 | ## _class_ `Driver(env, config)` {#Driver} 17 | 18 | - **说明:** None 驱动框架 19 | 20 | - **参数** 21 | 22 | - `env` ([Env](../config.md#Env)) 23 | 24 | - `config` ([Config](../config.md#Config)) 25 | 26 | ### _property_ `type` {#Driver-type} 27 | 28 | - **类型:** str 29 | 30 | - **说明:** 驱动名称: `none` 31 | 32 | ### _property_ `logger` {#Driver-logger} 33 | 34 | - **类型:** untyped 35 | 36 | - **说明:** none driver 使用的 logger 37 | 38 | ### _method_ `run(*args, **kwargs)` {#Driver-run} 39 | 40 | - **说明:** 启动 none driver 41 | 42 | - **参数** 43 | 44 | - `*args` 45 | 46 | - `**kwargs` 47 | 48 | - **返回** 49 | 50 | - untyped 51 | 52 | ### _method_ `exit(force=False)` {#Driver-exit} 53 | 54 | - **说明:** 退出 none driver 55 | 56 | - **参数** 57 | 58 | - `force` (bool): 强制退出 59 | 60 | - **返回** 61 | 62 | - untyped 63 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.1/api/log.md: -------------------------------------------------------------------------------- 1 | --- 2 | mdx: 3 | format: md 4 | sidebar_position: 7 5 | description: nonebot.log 模块 6 | --- 7 | 8 | # nonebot.log 9 | 10 | 本模块定义了 NoneBot 的日志记录 Logger。 11 | 12 | NoneBot 使用 [`loguru`][loguru] 来记录日志信息。 13 | 14 | 自定义 logger 请参考 [自定义日志](https://nonebot.dev/docs/appendices/log) 15 | 以及 [`loguru`][loguru] 文档。 16 | 17 | [loguru]: https://github.com/Delgan/loguru 18 | 19 | ## _var_ `logger` {#logger} 20 | 21 | - **类型:** Logger 22 | 23 | - **说明** 24 | 25 | NoneBot 日志记录器对象。 26 | 27 | 默认信息: 28 | 29 | - 格式: `[%(asctime)s %(name)s] %(levelname)s: %(message)s` 30 | - 等级: `INFO` ,根据 `config.log_level` 配置改变 31 | - 输出: 输出至 stdout 32 | 33 | - **用法** 34 | 35 | ```python 36 | from nonebot.log import logger 37 | ``` 38 | 39 | ## _class_ `LoguruHandler()` {#LoguruHandler} 40 | 41 | - **说明:** logging 与 loguru 之间的桥梁,将 logging 的日志转发到 loguru。 42 | 43 | - **参数** 44 | 45 | auto 46 | 47 | ### _method_ `emit(record)` {#LoguruHandler-emit} 48 | 49 | - **参数** 50 | 51 | - `record` (logging.LogRecord) 52 | 53 | - **返回** 54 | 55 | - untyped 56 | 57 | ## _def_ `default_filter(record)` {#default-filter} 58 | 59 | - **说明:** 默认的日志过滤器,根据 `config.log_level` 配置改变日志等级。 60 | 61 | - **参数** 62 | 63 | - `record` (Record) 64 | 65 | - **返回** 66 | 67 | - untyped 68 | 69 | ## _var_ `default_format` {#default-format} 70 | 71 | - **类型:** str 72 | 73 | - **说明:** 默认日志格式 74 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.1/api/plugin/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 12 3 | } 4 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.1/appendices/session-state.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | description: 会话状态信息 4 | 5 | options: 6 | menu: 7 | - category: appendices 8 | weight: 40 9 | --- 10 | 11 | # 会话状态 12 | 13 | 在事件处理流程中,和用户交互的过程即是会话。在会话中,我们可能需要记录一些信息,例如用户的重试次数等等,以便在会话中的不同阶段进行判断和处理。这些信息都可以存储于会话状态中。 14 | 15 | NoneBot 中的会话状态是一个字典,可以通过类型 `T_State` 来获取。字典内可以存储任意类型的数据,但是要注意的是,NoneBot 本身会在会话状态中存储一些信息,因此不要使用 [NoneBot 使用的键名](../api/consts.md)。 16 | 17 | ```python 18 | from nonebot.typing import T_State 19 | 20 | @matcher.got("key", prompt="请输入密码") 21 | async def _(state: T_State, key: str = ArgPlainText()): 22 | if key != "some password": 23 | try_count = state.get("try_count", 1) 24 | if try_count >= 3: 25 | await matcher.finish("密码错误次数过多") 26 | else: 27 | state["try_count"] = try_count + 1 28 | await matcher.reject("密码错误,请重新输入") 29 | await matcher.finish("密码正确") 30 | ``` 31 | 32 | 会话状态的生命周期与事件处理流程相同,在期间的任何一个事件处理函数都可以进行读写。 33 | 34 | ```python 35 | from nonebot.typing import T_State 36 | 37 | @matcher.handle() 38 | async def _(state: T_State): 39 | state["key"] = "value" 40 | 41 | @matcher.handle() 42 | async def _(state: T_State): 43 | await matcher.finish(state["key"]) 44 | ``` 45 | 46 | 会话状态还可以用于发送动态消息,消息模板在发送时会使用会话状态字典进行渲染。消息模板的使用方法已经在[消息处理](../tutorial/message.md#使用消息模板)中介绍过,这里不再赘述。 47 | 48 | ```python 49 | from nonebot.typing import T_State 50 | from nonebot.adapters import MessageTemplate 51 | 52 | @matcher.handle() 53 | async def _(state: T_State): 54 | state["username"] = "user" 55 | 56 | @matcher.got("password", prompt=MessageTemplate("请输入 {username} 的密码")) 57 | async def _(): 58 | await matcher.finish(MessageTemplate("密码为 {password}")) 59 | ``` 60 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.1/appendices/whats-next.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 99 3 | description: 下一步──进阶! 4 | --- 5 | 6 | # 下一步 7 | 8 | 至此,我们已经了解了 NoneBot 的大多数功能用法,相信你已经可以独自写出一个插件了。现在你可以选择: 9 | 10 | - 即刻开始插件编写! 11 | - 更深入地了解 NoneBot 的[更多功能和原理](../advanced/plugin-info.md)! 12 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.1/best-practice/alconna/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Alconna 命令解析拓展", 3 | "position": 6 4 | } 5 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.1/best-practice/alconna/config.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | description: 配置项 4 | --- 5 | 6 | # 配置项 7 | 8 | ## alconna_auto_send_output 9 | 10 | - **类型**: `bool` 11 | - **默认值**: `False` 12 | 13 | 是否全局启用输出信息自动发送,不启用则会在触发特殊内置选项后仍然将解析结果传递至响应器。 14 | 15 | ## alconna_use_command_start 16 | 17 | - **类型**: `bool` 18 | - **默认值**: `False` 19 | 20 | 是否读取 Nonebot 的配置项 `COMMAND_START` 来作为全局的 Alconna 命令前缀 21 | 22 | ## alconna_auto_completion 23 | 24 | - **类型**: `bool` 25 | - **默认值**: `False` 26 | 27 | 是否全局启用命令自动补全,启用后会在参数缺失或触发 `--comp` 选项时自自动启用交互式补全。 28 | 29 | ## alconna_use_origin 30 | 31 | - **类型**: `bool` 32 | - **默认值**: `False` 33 | 34 | 是否全局使用原始消息 (即未经过 to_me 等处理的),该选项会影响到 Alconna 的匹配行为。 35 | 36 | ## alconna_use_command_sep 37 | 38 | - **类型**: `bool` 39 | - **默认值**: `False` 40 | 41 | 是否读取 Nonebot 的配置项 `COMMAND_SEP` 来作为全局的 Alconna 命令分隔符。 42 | 43 | ## alconna_global_extensions 44 | 45 | - **类型**: `List[str]` 46 | - **默认值**: `[]` 47 | 48 | 全局加载的扩展,路径以 . 分隔,如 `foo.bar.baz:DemoExtension`。 49 | 50 | ## alconna_context_style 51 | 52 | - **类型**: `Optional[Literal["bracket", "parentheses"]]` 53 | - **默认值**: `None` 54 | 55 | 全局命令上下文插值的风格,None 为关闭,bracket 为 `{...}`,parentheses 为 `$(...)`。 56 | 57 | ## alconna_enable_saa_patch 58 | 59 | - **类型**: `bool` 60 | - **默认值**: `False` 61 | 62 | 是否启用 SAA 补丁。 63 | 64 | ## alconna_apply_filehost 65 | 66 | - **类型**: `bool` 67 | - **默认值**: `False` 68 | 69 | 是否启用文件托管。 70 | 71 | ## alconna_apply_fetch_targets 72 | 73 | - **类型**: `bool` 74 | - **默认值**: `False` 75 | 76 | 是否启动时拉取一次发送对象列表。 77 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.1/best-practice/database/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "数据库", 3 | "position": 7 4 | } 5 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.1/best-practice/database/developer/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "开发者指南", 3 | "position": 3 4 | } 5 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.1/best-practice/error-tracking.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | description: 使用 sentry 进行错误跟踪 4 | --- 5 | 6 | # 错误跟踪 7 | 8 | 在应用实际运行过程中,可能会出现各种各样的错误。可能是由于代码逻辑错误,也可能是由于用户输入错误,甚至是由于第三方服务的错误。这些错误都会导致应用的运行出现问题,这时候就需要对错误进行跟踪,以便及时发现问题并进行修复。NoneBot 提供了 `nonebot-plugin-sentry` 插件,支持 [sentry](https://sentry.io/) 平台,可以方便地进行错误跟踪。 9 | 10 | ## 安装插件 11 | 12 | 在使用前请先安装 `nonebot-plugin-sentry` 插件至项目环境中,可参考[获取商店插件](../tutorial/store.mdx#安装插件)来了解并选择安装插件的方式。如: 13 | 14 | 在**项目目录**下执行以下命令: 15 | 16 | ```bash 17 | nb plugin install nonebot-plugin-sentry 18 | ``` 19 | 20 | ## 使用插件 21 | 22 | 在安装完成之后,仅需要对插件进行简单的配置即可使用。 23 | 24 | ### 获取 sentry DSN 25 | 26 | 前往 [sentry](https://sentry.io/) 平台,注册并创建一个新的项目,然后在项目设置中找到 `Client Keys (DSN)`,复制其中的 `DSN` 值。 27 | 28 | ### 配置插件 29 | 30 | :::caution 注意 31 | 错误跟踪通常在生产环境中使用,因此开发环境中 `sentry_dsn` 留空即会停用插件。 32 | ::: 33 | 34 | 在项目 dotenv 配置文件中添加以下配置即可使用: 35 | 36 | ```dotenv 37 | SENTRY_DSN= 38 | ``` 39 | 40 | ## 配置项 41 | 42 | 配置项具体含义参考 [Sentry Docs](https://docs.sentry.io/platforms/python/configuration/options/)。 43 | 44 | - `sentry_dsn: str` 45 | - `sentry_debug: bool = False` 46 | - `sentry_release: str | None = None` 47 | - `sentry_release: str | None = None` 48 | - `sentry_environment: str | None = nonebot env` 49 | - `sentry_server_name: str | None = None` 50 | - `sentry_sample_rate: float = 1.` 51 | - `sentry_max_breadcrumbs: int = 100` 52 | - `sentry_attach_stacktrace: bool = False` 53 | - `sentry_send_default_pii: bool = False` 54 | - `sentry_in_app_include: List[str] = Field(default_factory=list)` 55 | - `sentry_in_app_exclude: List[str] = Field(default_factory=list)` 56 | - `sentry_request_bodies: str = "medium"` 57 | - `sentry_with_locals: bool = True` 58 | - `sentry_ca_certs: str | None = None` 59 | - `sentry_before_send: Callable[[Any, Any], Any | None] | None = None` 60 | - `sentry_before_breadcrumb: Callable[[Any, Any], Any | None] | None = None` 61 | - `sentry_transport: Any | None = None` 62 | - `sentry_http_proxy: str | None = None` 63 | - `sentry_https_proxy: str | None = None` 64 | - `sentry_shutdown_timeout: int = 2` 65 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.1/best-practice/testing/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "单元测试", 3 | "position": 5 4 | } 5 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.1/community/contact.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar-position: 0 3 | description: 遇到问题如何获取帮助 4 | --- 5 | 6 | # 参与讨论 7 | 8 | 如果在安装或者开发 NoneBot 过程中遇到了任何问题,或者有新奇的点子,欢迎参与我们的社区讨论: 9 | 10 | 1. 点击下方链接前往 GitHub,前往 Issues 页面,在 `New Issue` Template 中选择 `Question` 11 | 12 | NoneBot:[![NoneBot project link](https://img.shields.io/github/stars/nonebot/nonebot2?style=social)](https://github.com/nonebot/nonebot2) 13 | 14 | 2. 通过 QQ 群(点击下方链接直达) 15 | 16 | [![QQ Chat Group](https://img.shields.io/badge/QQ%E7%BE%A4-768887710-orange?style=social)](https://jq.qq.com/?_wv=1027&k=5OFifDh) 17 | 18 | 3. 通过 QQ 频道 19 | 20 | [![QQ Channel](https://img.shields.io/badge/QQ%E9%A2%91%E9%81%93-NoneBot-orange?style=social)](https://qun.qq.com/qqweb/qunpro/share?_wv=3&_wwv=128&appChannel=share&inviteCode=7b4a3&appChannel=share&businessType=9&from=246610&biz=ka) 21 | 22 | 4. 通过 Discord 服务器(点击下方链接直达) 23 | 24 | [![Discord Server](https://discordapp.com/api/guilds/847819937858584596/widget.png?style=shield)](https://discord.gg/VKtE6Gdc4h) 25 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.1/community/contributing.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar-position: 1 3 | description: 如何为 NoneBot 贡献代码 4 | --- 5 | 6 | # 贡献指南 7 | 8 | ## Code of Conduct 9 | 10 | 请参阅 [Code of Conduct](https://github.com/nonebot/nonebot2/blob/master/CODE_OF_CONDUCT.md)。 11 | 12 | ## 参与开发 13 | 14 | 请参阅 [Contributing](https://github.com/nonebot/nonebot2/blob/master/CONTRIBUTING.md)。 15 | 16 | ## 鸣谢 17 | 18 | 感谢以下开发者对 NoneBot2 作出的贡献: 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.1/editor-support.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | description: 配置编辑器以获得最佳体验 4 | --- 5 | 6 | # 编辑器支持 7 | 8 | 框架基于 [PEP484](https://www.python.org/dev/peps/pep-0484/)、[PEP 561](https://www.python.org/dev/peps/pep-0561/)、[PEP8](https://www.python.org/dev/peps/pep-0008/) 等规范进行开发并且**拥有完整类型注解**。框架使用 Pyright(Pylance)工具进行类型检查,确保代码可以被编辑器正确解析。 9 | 10 | ## 编辑器推荐配置 11 | 12 | ### Visual Studio Code 13 | 14 | 在 Visual Studio Code 中,可以使用 Pylance Language Server 并启用 `Type Checking` 配置以达到最佳开发体验。 15 | 16 | 1. 在 VSCode 插件视图搜索并安装 `Python (ms-python.python)` 和 `Pylance (ms-python.vscode-pylance)` 插件。 17 | 2. 修改 VSCode 配置 18 | 在 VSCode 设置视图搜索配置项 `Python: Language Server` 并将其值设置为 `Pylance`,搜索配置项 `Python > Analysis: Type Checking Mode` 并将其值设置为 `basic`。 19 | 20 | 或者向项目 `.vscode` 文件夹中配置文件添加以下内容: 21 | 22 | ```json title=settings.json 23 | { 24 | "python.languageServer": "Pylance", 25 | "python.analysis.typeCheckingMode": "basic" 26 | } 27 | ``` 28 | 29 | ### 其他 30 | 31 | 欢迎提交 Pull Request 添加其他编辑器配置推荐。点击左下角 `Edit this page` 前往编辑。 32 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.1/tutorial/fundamentals.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | description: NoneBot 机器人构成及基本使用 4 | 5 | options: 6 | menu: 7 | - category: tutorial 8 | weight: 30 9 | --- 10 | 11 | # 机器人的构成 12 | 13 | 了解机器人的基本构成有助于你更好地使用 NoneBot,本章节将介绍 NoneBot 中的基本组成部分,稍后的文档中将会使用到这些概念。 14 | 15 | 使用 NoneBot 框架搭建的机器人具有以下几个基本组成部分: 16 | 17 | 1. NoneBot 机器人框架主体:负责连接各个组成部分,提供基本的机器人功能 18 | 2. 驱动器 `Driver`:客户端/服务端的功能实现,负责接收和发送消息(通常为 HTTP 通信) 19 | 3. 适配器 `Adapter`:驱动器的上层,负责将**平台消息**与 NoneBot 事件/操作系统的消息格式相互转换 20 | 4. 插件 `Plugin`:机器人的功能实现,通常为负责处理事件并进行一系列的操作 21 | 22 | 除 NoneBot 机器人框架主体外,其他部分均可按需选择、互相搭配,但由于平台的兼容性问题,部分插件可能仅在某些特定平台上可用(这由插件编写者决定)。 23 | 24 | 在接下来的章节中,我们将重点介绍机器人功能实现,即插件 `Plugin` 部分。 25 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.1/tutorial/matcher.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | description: 响应接收到的特定事件 4 | 5 | options: 6 | menu: 7 | - category: tutorial 8 | weight: 60 9 | --- 10 | 11 | # 事件响应器 12 | 13 | 事件响应器(Matcher)是对接收到的事件进行响应的基本单元,所有的事件响应器都继承自 `Matcher` 基类。 14 | 15 | 在 NoneBot 中,事件响应器可以通过一系列特定的规则**筛选**出**具有某种特征的事件**,并按照**特定的流程**交由**预定义的事件处理依赖**进行处理。例如,在[快速上手](../quick-start.mdx)中,我们使用了内置插件 `echo` ,它定义的事件响应器能响应机器人用户发送的“/echo hello world”消息,提取“hello world”信息并作为回复消息发送。 16 | 17 | ## 事件响应器辅助函数 18 | 19 | NoneBot 中所有事件响应器均继承自 `Matcher` 基类,但直接使用 `Matcher.new()` 方法创建事件响应器过于繁琐且不能记录插件信息。因此,NoneBot 中提供了一系列“事件响应器辅助函数”(下称“辅助函数”)来辅助我们用**最简的方式**创建**带有不同规则预设**的事件响应器,提高代码可读性和书写效率。通常情况下,我们只需要使用辅助函数即可完成事件响应器的创建。 20 | 21 | 在 NoneBot 中,辅助函数以 `on()` 或 `on_()` 形式出现(例如 `on_command()`),调用后根据不同的参数返回一个 `Type[Matcher]` 类型的新事件响应器。 22 | 23 | 目前 NoneBot 提供了多种功能各异的辅助函数、具有共同命令名称前缀的命令组以及具有共同参数的响应器组,均可以从 `nonebot` 模块直接导入使用,具体内容参考[事件响应器进阶](../advanced/matcher.md)。 24 | 25 | ## 创建事件响应器 26 | 27 | 在上一节[创建插件](./create-plugin.md#创建插件)中,我们创建了一个 `weather` 插件,现在我们来实现他的功能。 28 | 29 | 我们直接使用 `on_command()` 辅助函数来创建一个事件响应器: 30 | 31 | ```python {3} title=weather/__init__.py 32 | from nonebot import on_command 33 | 34 | weather = on_command("天气") 35 | ``` 36 | 37 | 这样,我们就获得一个名为 `weather` 的事件响应器了,这个事件响应器会对 `/天气` 开头的消息进行响应。 38 | 39 | :::tip 提示 40 | 如果一条消息中包含“@机器人”或以“机器人的昵称”开始,例如 `@bot /天气` 时,协议适配器会将 `event.is_tome()` 判断为 `True` ,同时也会自动去除 `@bot`,即事件响应器收到的信息内容为 `/天气`,方便进行命令匹配。 41 | ::: 42 | 43 | ### 为事件响应器添加参数 44 | 45 | 在辅助函数中,我们可以添加一些参数来对事件响应器进行更加精细的调整,例如事件响应器的优先级、匹配规则等。例如: 46 | 47 | ```python {4} title=weather/__init__.py 48 | from nonebot import on_command 49 | from nonebot.rule import to_me 50 | 51 | weather = on_command("天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True) 52 | ``` 53 | 54 | 这样,我们就获得了一个可以响应 `天气`、`weather`、`查天气` 三个命令的响应规则,需要私聊或 `@bot` 时才会响应,优先级为 10(越小越优先),阻断事件向后续优先级传播的事件响应器了。这些内容的意义和使用方法将会在后续的章节中一一介绍。 55 | 56 | :::tip 提示 57 | 需要注意的是,不同的辅助函数有不同的可选参数,在使用之前可以参考[事件响应器进阶 - 基本辅助函数](../advanced/matcher.md#基本辅助函数)或 [API 文档](../api/plugin/on.md#on)。 58 | ::: 59 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.2/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 0 3 | id: index 4 | slug: / 5 | --- 6 | 7 | # 概览 8 | 9 | NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架(下称 NoneBot),它基于 Python 的类型注解和异步优先特性(兼容同步),能够为你的需求实现提供便捷灵活的支持。同时,NoneBot 拥有大量的开发者为其开发插件,用户无需编写任何代码,仅需完成环境配置及插件安装,就可以正常使用 NoneBot。 10 | 11 | 需要注意的是,NoneBot 仅支持 **Python 3.9 以上版本** 12 | 13 | ## 特色 14 | 15 | ### 异步优先 16 | 17 | NoneBot 基于 Python [asyncio](https://docs.python.org/zh-cn/3/library/asyncio.html) / [trio](https://trio.readthedocs.io/en/stable/) 编写,并在异步机制的基础上进行了一定程度的同步函数兼容。 18 | 19 | ### 完整的类型注解 20 | 21 | NoneBot 参考 [PEP 484](https://www.python.org/dev/peps/pep-0484/) 等 PEP 完整实现了类型注解,通过 Pyright(Pylance) 检查。配合编辑器的类型推导功能,能将绝大多数的 Bug 杜绝在编辑器中([编辑器支持](./editor-support))。 22 | 23 | ### 开箱即用 24 | 25 | NoneBot 提供了使用便捷、具有交互式功能的命令行工具--`nb-cli`,使得用户初次接触 NoneBot 时更容易上手。使用方法请阅读本文档[指南](./quick-start.mdx)以及 [CLI 文档](https://cli.nonebot.dev/)。 26 | 27 | ### 插件系统 28 | 29 | 插件系统是 NoneBot 的核心,通过它可以实现机器人的模块化以及功能扩展,便于维护和管理。 30 | 31 | ### 依赖注入系统 32 | 33 | NoneBot 采用了一套自行定义的依赖注入系统,可以让事件的处理过程更加的简洁、清晰,增加代码的可读性,减少代码冗余。 34 | 35 | #### 什么是依赖注入 36 | 37 | [**『依赖注入』**](https://zh.wikipedia.org/wiki/%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%AC)意思是,在编程中,有一种方法可以让你的代码声明它工作和使用所需要的东西,即**『依赖』**。 38 | 39 | 系统(在这里是指 NoneBot)将负责做任何需要的事情,为你的代码提供这些必要依赖(即**『注入』**依赖性) 40 | 41 | 这在你有以下情形的需求时非常有用: 42 | 43 | - 这部分代码拥有共享的逻辑(同样的代码逻辑多次重复) 44 | - 共享数据库以及网络请求连接会话 45 | - 比如 `httpx.AsyncClient`、`aiohttp.ClientSession` 和 `sqlalchemy.Session` 46 | - 机器人用户权限检查以及认证 47 | - 还有更多... 48 | 49 | 它在完成上述工作的同时,还能尽量减少代码的耦合和重复 50 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.2/advanced/matcher-provider.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 10 3 | description: 自定义事件响应器存储 4 | 5 | options: 6 | menu: 7 | - category: advanced 8 | weight: 110 9 | --- 10 | 11 | # 事件响应器存储 12 | 13 | 事件响应器是 NoneBot 处理事件的核心,它们默认存储在一个字典中。在进入会话状态后,事件响应器将会转为临时响应器,作为最高优先级同样存储于该字典中。因此,事件响应器的存储类似于会话存储,它决定了整个 NoneBot 对事件的处理行为。 14 | 15 | NoneBot 默认使用 Python 的字典将事件响应器存储于内存中,但是我们也可以自定义事件响应器存储,将事件响应器存储于其他地方,例如 Redis 等。这样我们就可以实现持久化、在多实例间共享会话状态等功能。 16 | 17 | ## 编写存储提供者 18 | 19 | 事件响应器的存储提供者 `MatcherProvider` 抽象类继承自 `MutableMapping[int, list[type[Matcher]]]`,即以优先级为键,以事件响应器列表为值的映射。我们可以方便地进行逐优先级事件传播。 20 | 21 | 编写一个自定义的存储提供者,只需要继承并实现 `MatcherProvider` 抽象类: 22 | 23 | ```python 24 | from nonebot.matcher import MatcherProvider 25 | 26 | class CustomProvider(MatcherProvider): 27 | ... 28 | ``` 29 | 30 | ## 设置存储提供者 31 | 32 | 我们可以通过 `matchers.set_provider` 方法设置存储提供者: 33 | 34 | ```python {3} 35 | from nonebot.matcher import matchers 36 | 37 | matchers.set_provider(CustomProvider) 38 | 39 | assert isinstance(matchers.provider, CustomProvider) 40 | ``` 41 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.2/advanced/plugin-nesting.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | description: 编写与加载嵌套插件 4 | 5 | options: 6 | menu: 7 | - category: advanced 8 | weight: 40 9 | --- 10 | 11 | # 嵌套插件 12 | 13 | NoneBot 支持嵌套插件,即一个插件可以包含其他插件。通过这种方式,我们可以将一个大型插件拆分成多个功能子插件,使得插件更加清晰、易于维护。我们可以直接在插件中使用 NoneBot 加载插件的方法来加载子插件。 14 | 15 | ## 创建嵌套插件 16 | 17 | 我们可以在使用 `nb-cli` 命令[创建插件](../tutorial/create-plugin.md#创建插件)时,选择直接通过模板创建一个嵌套插件: 18 | 19 | ```bash 20 | $ nb plugin create 21 | [?] 插件名称: parent 22 | [?] 使用嵌套插件? (y/N) Y 23 | [?] 输出目录: awesome_bot/plugins 24 | ``` 25 | 26 | 或者使用 `nb plugin create --sub-plugin` 选项直接创建一个嵌套插件。 27 | 28 | ## 已有插件 29 | 30 | 如果你已经有一个插件,想要在其中嵌套加载子插件,可以在插件的 `__init__.py` 中添加如下代码: 31 | 32 | ```python title=parent/__init__.py 33 | import nonebot 34 | from pathlib import Path 35 | 36 | sub_plugins = nonebot.load_plugins( 37 | str(Path(__file__).parent.joinpath("plugins").resolve()) 38 | ) 39 | ``` 40 | 41 | 这样,`parent` 插件就会加载 `parent/plugins` 目录下的所有插件。NoneBot 会正确识别这些插件的父子关系,你可以在 `parent` 的插件信息中看到这些子插件的信息,也可以在子插件信息中看到它们的父插件信息。 42 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.2/advanced/requiring.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | description: 使用其他插件提供的功能 4 | 5 | options: 6 | menu: 7 | - category: advanced 8 | weight: 50 9 | --- 10 | 11 | # 跨插件访问 12 | 13 | NoneBot 插件化系统的设计使得插件之间可以功能独立、各司其职,我们可以更好地维护和扩展插件。但是,有时候我们可能需要在不同插件之间调用功能。NoneBot 生态中就有一类插件,它们专为其他插件提供功能支持,如:[定时任务插件](../best-practice/scheduler.md)、[数据存储插件](../best-practice/data-storing.md)等。这时候我们就需要在插件之间进行跨插件访问。 14 | 15 | ## 插件跟踪 16 | 17 | 由于 NoneBot 插件系统通过 [Import Hooks](https://docs.python.org/3/reference/import.html#import-hooks) 的方式实现插件加载与跟踪管理,因此我们**不能**在 NoneBot 跟踪插件前进行模块 import,这会导致插件加载失败。即,我们不能在使用 NoneBot 提供的加载插件方法前,直接使用 `import` 语句导入插件。 18 | 19 | 对于在项目目录下的插件,我们通常直接使用 `load_from_toml` 等方法一次性加载所有插件。由于这些插件已经被声明,即便插件导入顺序不同,NoneBot 也能正确跟踪插件。此时,我们不需要对跨插件访问进行特殊处理。但当我们使用了外部插件,如果没有事先声明或加载插件,NoneBot 并不会将其当作插件进行跟踪,可能会出现意料之外的错误出现。 20 | 21 | 简单来说,我们必须在 `import` 外部插件之前,确保依赖的外部插件已经被声明或加载。 22 | 23 | ## 插件依赖声明 24 | 25 | NoneBot 提供了一种方法来确保我们依赖的插件已经被正确加载,即使用 `require` 函数。通过 `require` 函数,我们可以在当前插件中声明依赖的插件,NoneBot 会在加载当前插件时,检查依赖的插件是否已经被加载,如果没有,会尝试优先加载依赖的插件。 26 | 27 | 假设我们有一个插件 `a` 依赖于插件 `b`,我们可以在插件 `a` 中使用 `require` 函数声明其依赖于插件 `b`: 28 | 29 | ```python {3} title=a/__init__.py 30 | from nonebot import require 31 | 32 | require("b") 33 | 34 | from b import some_function 35 | ``` 36 | 37 | 其中,`require` 函数的参数为插件索引名称或者外部插件的模块名称。在完成依赖声明后,我们可以在插件 `a` 中直接导入插件 `b` 所提供的功能。 38 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.2/advanced/session-updating.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 7 3 | description: 控制会话响应对象 4 | 5 | options: 6 | menu: 7 | - category: advanced 8 | weight: 80 9 | --- 10 | 11 | # 会话更新 12 | 13 | 在 NoneBot 中,在某个事件响应器对事件响应后,即是进入了会话状态,会话状态会持续到整个事件响应流程结束。会话过程中,机器人可以与用户进行多次交互。每次需要等待用户事件时,NoneBot 将会复制一个新的临时事件响应器,并更新该事件响应器使其响应当前会话主体的消息,这个过程称为会话更新。 14 | 15 | 会话更新分为两部分:**更新[事件响应器类型](./matcher.md#事件响应器类型)**和**更新[事件触发权限](./matcher.md#事件触发权限)**。 16 | 17 | ## 更新事件响应器类型 18 | 19 | 通常情况下,与机器人用户进行的会话都是通过消息事件进行的,因此会话更新后的默认响应事件类型为 `message`。如果希望接收一个特定类型的消息,比如 `notice` 等,我们需要自定义响应事件类型更新函数。响应事件类型更新函数是一个 `Dependent`,可以使用依赖注入。 20 | 21 | ```python {3-5} 22 | foo = on_message() 23 | 24 | @foo.type_updater 25 | async def _() -> str: 26 | return "notice" 27 | ``` 28 | 29 | 在注册了上述响应事件类型更新函数后,当我们需要等待用户事件时,将只会响应 `notice` 类型的事件。如果希望在会话过程中的不同阶段响应不同类型的事件,我们就需要使用更复杂的逻辑来更新响应事件类型(如:根据会话状态),这里将不再展示。 30 | 31 | ## 更新事件触发权限 32 | 33 | 会话通常是由机器人与用户进行的一对一交互,因此会话更新后的默认触发权限为当前事件的会话 ID。这个会话 ID 由协议适配器生成,通常由用户 ID 和群 ID 等组成。如果希望实现更复杂的会话功能(如:多用户同时参与的会话),我们需要自定义触发权限更新函数。触发权限更新函数是一个 `Dependent`,可以使用依赖注入。 34 | 35 | ```python {5-7} 36 | from nonebot.permission import User 37 | 38 | foo = on_message() 39 | 40 | @foo.permission_updater 41 | async def _(event: Event, matcher: Matcher) -> Permission: 42 | return Permission(User.from_event(event, perm=matcher.permission)) 43 | ``` 44 | 45 | 上述权限更新函数是默认的权限更新函数,它将会话的触发权限更新为当前事件的会话 ID。如果我们希望响应多个用户的消息,我们可以如下修改: 46 | 47 | ```python {5-7} 48 | from nonebot.permission import USER 49 | 50 | foo = on_message() 51 | 52 | @foo.permission_updater 53 | async def _(matcher: Matcher) -> Permission: 54 | return USER("session1", "session2", perm=matcher.permission) 55 | ``` 56 | 57 | 请注意,此处为全大写字母的 `USER` 权限,它可以匹配多个会话 ID。通过这种方式,我们可以实现多用户同时参与的会话。 58 | 59 | 我们已经了解了如何控制会话的更新,相信你已经能够实现更复杂的会话功能了,例如多人小游戏等等。欢迎将你的作品分享到[插件商店](/store/plugins)。 60 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.2/api/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonebot/nonebot2/f8317444dfaaf720ff22fedf5b0a8d9f6138421c/website/versioned_docs/version-2.4.2/api/.gitkeep -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.2/api/adapters/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 15 3 | } 4 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.2/api/dependencies/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 13 3 | } 4 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.2/api/dependencies/utils.md: -------------------------------------------------------------------------------- 1 | --- 2 | mdx: 3 | format: md 4 | sidebar_position: 1 5 | description: nonebot.dependencies.utils 模块 6 | --- 7 | 8 | # nonebot.dependencies.utils 9 | 10 | ## _def_ `get_typed_signature(call)` {#get-typed-signature} 11 | 12 | - **说明:** 获取可调用对象签名 13 | 14 | - **参数** 15 | 16 | - `call` ((...) -> Any) 17 | 18 | - **返回** 19 | 20 | - inspect.Signature 21 | 22 | ## _def_ `get_typed_annotation(param, globalns)` {#get-typed-annotation} 23 | 24 | - **说明:** 获取参数的类型注解 25 | 26 | - **参数** 27 | 28 | - `param` (inspect.Parameter) 29 | 30 | - `globalns` (dict[str, Any]) 31 | 32 | - **返回** 33 | 34 | - Any 35 | 36 | ## _def_ `check_field_type(field, value)` {#check-field-type} 37 | 38 | - **说明:** 检查字段类型是否匹配 39 | 40 | - **参数** 41 | 42 | - `field` ([ModelField](../compat.md#ModelField)) 43 | 44 | - `value` (Any) 45 | 46 | - **返回** 47 | 48 | - Any 49 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.2/api/drivers/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 14 3 | } 4 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.2/api/drivers/none.md: -------------------------------------------------------------------------------- 1 | --- 2 | mdx: 3 | format: md 4 | sidebar_position: 6 5 | description: nonebot.drivers.none 模块 6 | --- 7 | 8 | # nonebot.drivers.none 9 | 10 | None 驱动适配 11 | 12 | :::tip 提示 13 | 本驱动不支持任何服务器或客户端连接 14 | ::: 15 | 16 | ## _class_ `Driver(env, config)` {#Driver} 17 | 18 | - **说明:** None 驱动框架 19 | 20 | - **参数** 21 | 22 | - `env` ([Env](../config.md#Env)) 23 | 24 | - `config` ([Config](../config.md#Config)) 25 | 26 | ### _property_ `type` {#Driver-type} 27 | 28 | - **类型:** str 29 | 30 | - **说明:** 驱动名称: `none` 31 | 32 | ### _property_ `logger` {#Driver-logger} 33 | 34 | - **类型:** untyped 35 | 36 | - **说明:** none driver 使用的 logger 37 | 38 | ### _method_ `run(*args, **kwargs)` {#Driver-run} 39 | 40 | - **说明:** 启动 none driver 41 | 42 | - **参数** 43 | 44 | - `*args` 45 | 46 | - `**kwargs` 47 | 48 | - **返回** 49 | 50 | - untyped 51 | 52 | ### _method_ `exit(force=False)` {#Driver-exit} 53 | 54 | - **说明:** 退出 none driver 55 | 56 | - **参数** 57 | 58 | - `force` (bool): 强制退出 59 | 60 | - **返回** 61 | 62 | - untyped 63 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.2/api/log.md: -------------------------------------------------------------------------------- 1 | --- 2 | mdx: 3 | format: md 4 | sidebar_position: 7 5 | description: nonebot.log 模块 6 | --- 7 | 8 | # nonebot.log 9 | 10 | 本模块定义了 NoneBot 的日志记录 Logger。 11 | 12 | NoneBot 使用 [`loguru`][loguru] 来记录日志信息。 13 | 14 | 自定义 logger 请参考 [自定义日志](https://nonebot.dev/docs/appendices/log) 15 | 以及 [`loguru`][loguru] 文档。 16 | 17 | [loguru]: https://github.com/Delgan/loguru 18 | 19 | ## _var_ `logger` {#logger} 20 | 21 | - **类型:** Logger 22 | 23 | - **说明** 24 | 25 | NoneBot 日志记录器对象。 26 | 27 | 默认信息: 28 | 29 | - 格式: `[%(asctime)s %(name)s] %(levelname)s: %(message)s` 30 | - 等级: `INFO` ,根据 `config.log_level` 配置改变 31 | - 输出: 输出至 stdout 32 | 33 | - **用法** 34 | 35 | ```python 36 | from nonebot.log import logger 37 | ``` 38 | 39 | ## _class_ `LoguruHandler()` {#LoguruHandler} 40 | 41 | - **说明:** logging 与 loguru 之间的桥梁,将 logging 的日志转发到 loguru。 42 | 43 | - **参数** 44 | 45 | auto 46 | 47 | ### _method_ `emit(record)` {#LoguruHandler-emit} 48 | 49 | - **参数** 50 | 51 | - `record` (logging.LogRecord) 52 | 53 | - **返回** 54 | 55 | - untyped 56 | 57 | ## _def_ `default_filter(record)` {#default-filter} 58 | 59 | - **说明:** 默认的日志过滤器,根据 `config.log_level` 配置改变日志等级。 60 | 61 | - **参数** 62 | 63 | - `record` (Record) 64 | 65 | - **返回** 66 | 67 | - untyped 68 | 69 | ## _var_ `default_format` {#default-format} 70 | 71 | - **类型:** str 72 | 73 | - **说明:** 默认日志格式 74 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.2/api/plugin/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "position": 12 3 | } 4 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.2/appendices/session-state.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | description: 会话状态信息 4 | 5 | options: 6 | menu: 7 | - category: appendices 8 | weight: 40 9 | --- 10 | 11 | # 会话状态 12 | 13 | 在事件处理流程中,和用户交互的过程即是会话。在会话中,我们可能需要记录一些信息,例如用户的重试次数等等,以便在会话中的不同阶段进行判断和处理。这些信息都可以存储于会话状态中。 14 | 15 | NoneBot 中的会话状态是一个字典,可以通过类型 `T_State` 来获取。字典内可以存储任意类型的数据,但是要注意的是,NoneBot 本身会在会话状态中存储一些信息,因此不要使用 [NoneBot 使用的键名](../api/consts.md)。 16 | 17 | ```python 18 | from nonebot.typing import T_State 19 | 20 | @matcher.got("key", prompt="请输入密码") 21 | async def _(state: T_State, key: str = ArgPlainText()): 22 | if key != "some password": 23 | try_count = state.get("try_count", 1) 24 | if try_count >= 3: 25 | await matcher.finish("密码错误次数过多") 26 | else: 27 | state["try_count"] = try_count + 1 28 | await matcher.reject("密码错误,请重新输入") 29 | await matcher.finish("密码正确") 30 | ``` 31 | 32 | 会话状态的生命周期与事件处理流程相同,在期间的任何一个事件处理函数都可以进行读写。 33 | 34 | ```python 35 | from nonebot.typing import T_State 36 | 37 | @matcher.handle() 38 | async def _(state: T_State): 39 | state["key"] = "value" 40 | 41 | @matcher.handle() 42 | async def _(state: T_State): 43 | await matcher.finish(state["key"]) 44 | ``` 45 | 46 | 会话状态还可以用于发送动态消息,消息模板在发送时会使用会话状态字典进行渲染。消息模板的使用方法已经在[消息处理](../tutorial/message.md#使用消息模板)中介绍过,这里不再赘述。 47 | 48 | ```python 49 | from nonebot.typing import T_State 50 | from nonebot.adapters import MessageTemplate 51 | 52 | @matcher.handle() 53 | async def _(state: T_State): 54 | state["username"] = "user" 55 | 56 | @matcher.got("password", prompt=MessageTemplate("请输入 {username} 的密码")) 57 | async def _(): 58 | await matcher.finish(MessageTemplate("密码为 {password}")) 59 | ``` 60 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.2/appendices/whats-next.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 99 3 | description: 下一步──进阶! 4 | --- 5 | 6 | # 下一步 7 | 8 | 至此,我们已经了解了 NoneBot 的大多数功能用法,相信你已经可以独自写出一个插件了。现在你可以选择: 9 | 10 | - 即刻开始插件编写! 11 | - 更深入地了解 NoneBot 的[更多功能和原理](../advanced/plugin-info.md)! 12 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.2/best-practice/alconna/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Alconna 命令解析拓展", 3 | "position": 6 4 | } 5 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.2/best-practice/alconna/config.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | description: 配置项 4 | --- 5 | 6 | # 配置项 7 | 8 | ## alconna_auto_send_output 9 | 10 | - **类型**: `bool` 11 | - **默认值**: `False` 12 | 13 | 是否全局启用输出信息自动发送,不启用则会在触发特殊内置选项后仍然将解析结果传递至响应器。 14 | 15 | ## alconna_use_command_start 16 | 17 | - **类型**: `bool` 18 | - **默认值**: `False` 19 | 20 | 是否读取 Nonebot 的配置项 `COMMAND_START` 来作为全局的 Alconna 命令前缀 21 | 22 | ## alconna_auto_completion 23 | 24 | - **类型**: `bool` 25 | - **默认值**: `False` 26 | 27 | 是否全局启用命令自动补全,启用后会在参数缺失或触发 `--comp` 选项时自自动启用交互式补全。 28 | 29 | ## alconna_use_origin 30 | 31 | - **类型**: `bool` 32 | - **默认值**: `False` 33 | 34 | 是否全局使用原始消息 (即未经过 to_me 等处理的),该选项会影响到 Alconna 的匹配行为。 35 | 36 | ## alconna_use_command_sep 37 | 38 | - **类型**: `bool` 39 | - **默认值**: `False` 40 | 41 | 是否读取 Nonebot 的配置项 `COMMAND_SEP` 来作为全局的 Alconna 命令分隔符。 42 | 43 | ## alconna_global_extensions 44 | 45 | - **类型**: `List[str]` 46 | - **默认值**: `[]` 47 | 48 | 全局加载的扩展,路径以 . 分隔,如 `foo.bar.baz:DemoExtension`。 49 | 50 | ## alconna_context_style 51 | 52 | - **类型**: `Optional[Literal["bracket", "parentheses"]]` 53 | - **默认值**: `None` 54 | 55 | 全局命令上下文插值的风格,None 为关闭,bracket 为 `{...}`,parentheses 为 `$(...)`。 56 | 57 | ## alconna_enable_saa_patch 58 | 59 | - **类型**: `bool` 60 | - **默认值**: `False` 61 | 62 | 是否启用 SAA 补丁。 63 | 64 | ## alconna_apply_filehost 65 | 66 | - **类型**: `bool` 67 | - **默认值**: `False` 68 | 69 | 是否启用文件托管。 70 | 71 | ## alconna_apply_fetch_targets 72 | 73 | - **类型**: `bool` 74 | - **默认值**: `False` 75 | 76 | 是否启动时拉取一次发送对象列表。 77 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.2/best-practice/database/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "数据库", 3 | "position": 7 4 | } 5 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.2/best-practice/database/developer/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "开发者指南", 3 | "position": 3 4 | } 5 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.2/best-practice/error-tracking.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | description: 使用 sentry 进行错误跟踪 4 | --- 5 | 6 | # 错误跟踪 7 | 8 | 在应用实际运行过程中,可能会出现各种各样的错误。可能是由于代码逻辑错误,也可能是由于用户输入错误,甚至是由于第三方服务的错误。这些错误都会导致应用的运行出现问题,这时候就需要对错误进行跟踪,以便及时发现问题并进行修复。NoneBot 提供了 `nonebot-plugin-sentry` 插件,支持 [sentry](https://sentry.io/) 平台,可以方便地进行错误跟踪。 9 | 10 | ## 安装插件 11 | 12 | 在使用前请先安装 `nonebot-plugin-sentry` 插件至项目环境中,可参考[获取商店插件](../tutorial/store.mdx#安装插件)来了解并选择安装插件的方式。如: 13 | 14 | 在**项目目录**下执行以下命令: 15 | 16 | ```bash 17 | nb plugin install nonebot-plugin-sentry 18 | ``` 19 | 20 | ## 使用插件 21 | 22 | 在安装完成之后,仅需要对插件进行简单的配置即可使用。 23 | 24 | ### 获取 sentry DSN 25 | 26 | 前往 [sentry](https://sentry.io/) 平台,注册并创建一个新的项目,然后在项目设置中找到 `Client Keys (DSN)`,复制其中的 `DSN` 值。 27 | 28 | ### 配置插件 29 | 30 | :::caution 注意 31 | 错误跟踪通常在生产环境中使用,因此开发环境中 `sentry_dsn` 留空即会停用插件。 32 | ::: 33 | 34 | 在项目 dotenv 配置文件中添加以下配置即可使用: 35 | 36 | ```dotenv 37 | SENTRY_DSN= 38 | ``` 39 | 40 | ## 配置项 41 | 42 | 配置项具体含义参考 [Sentry Docs](https://docs.sentry.io/platforms/python/configuration/options/)。 43 | 44 | - `sentry_dsn: str` 45 | - `sentry_debug: bool = False` 46 | - `sentry_release: str | None = None` 47 | - `sentry_release: str | None = None` 48 | - `sentry_environment: str | None = nonebot env` 49 | - `sentry_server_name: str | None = None` 50 | - `sentry_sample_rate: float = 1.` 51 | - `sentry_max_breadcrumbs: int = 100` 52 | - `sentry_attach_stacktrace: bool = False` 53 | - `sentry_send_default_pii: bool = False` 54 | - `sentry_in_app_include: List[str] = Field(default_factory=list)` 55 | - `sentry_in_app_exclude: List[str] = Field(default_factory=list)` 56 | - `sentry_request_bodies: str = "medium"` 57 | - `sentry_with_locals: bool = True` 58 | - `sentry_ca_certs: str | None = None` 59 | - `sentry_before_send: Callable[[Any, Any], Any | None] | None = None` 60 | - `sentry_before_breadcrumb: Callable[[Any, Any], Any | None] | None = None` 61 | - `sentry_transport: Any | None = None` 62 | - `sentry_http_proxy: str | None = None` 63 | - `sentry_https_proxy: str | None = None` 64 | - `sentry_shutdown_timeout: int = 2` 65 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.2/best-practice/testing/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "单元测试", 3 | "position": 5 4 | } 5 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.2/community/contact.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar-position: 0 3 | description: 遇到问题如何获取帮助 4 | --- 5 | 6 | # 参与讨论 7 | 8 | 如果在安装或者开发 NoneBot 过程中遇到了任何问题,或者有新奇的点子,欢迎参与我们的社区讨论: 9 | 10 | 1. 点击下方链接前往 GitHub,前往 Issues 页面,在 `New Issue` Template 中选择 `Question` 11 | 12 | NoneBot:[![NoneBot project link](https://img.shields.io/github/stars/nonebot/nonebot2?style=social)](https://github.com/nonebot/nonebot2) 13 | 14 | 2. 通过 QQ 群(点击下方链接直达) 15 | 16 | [![QQ Chat Group](https://img.shields.io/badge/QQ%E7%BE%A4-768887710-orange?style=social)](https://jq.qq.com/?_wv=1027&k=5OFifDh) 17 | 18 | 3. 通过 QQ 频道 19 | 20 | [![QQ Channel](https://img.shields.io/badge/QQ%E9%A2%91%E9%81%93-NoneBot-orange?style=social)](https://qun.qq.com/qqweb/qunpro/share?_wv=3&_wwv=128&appChannel=share&inviteCode=7b4a3&appChannel=share&businessType=9&from=246610&biz=ka) 21 | 22 | 4. 通过 Discord 服务器(点击下方链接直达) 23 | 24 | [![Discord Server](https://discordapp.com/api/guilds/847819937858584596/widget.png?style=shield)](https://discord.gg/VKtE6Gdc4h) 25 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.2/community/contributing.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar-position: 1 3 | description: 如何为 NoneBot 贡献代码 4 | --- 5 | 6 | # 贡献指南 7 | 8 | ## Code of Conduct 9 | 10 | 请参阅 [Code of Conduct](https://github.com/nonebot/nonebot2/blob/master/CODE_OF_CONDUCT.md)。 11 | 12 | ## 参与开发 13 | 14 | 请参阅 [Contributing](https://github.com/nonebot/nonebot2/blob/master/CONTRIBUTING.md)。 15 | 16 | ## 鸣谢 17 | 18 | 感谢以下开发者对 NoneBot2 作出的贡献: 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.2/editor-support.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | description: 配置编辑器以获得最佳体验 4 | --- 5 | 6 | # 编辑器支持 7 | 8 | 框架基于 [PEP484](https://www.python.org/dev/peps/pep-0484/)、[PEP 561](https://www.python.org/dev/peps/pep-0561/)、[PEP8](https://www.python.org/dev/peps/pep-0008/) 等规范进行开发并且**拥有完整类型注解**。框架使用 Pyright(Pylance)工具进行类型检查,确保代码可以被编辑器正确解析。 9 | 10 | ## 编辑器推荐配置 11 | 12 | ### Visual Studio Code 13 | 14 | 在 Visual Studio Code 中,可以使用 Pylance Language Server 并启用 `Type Checking` 配置以达到最佳开发体验。 15 | 16 | 1. 在 VSCode 插件视图搜索并安装 `Python (ms-python.python)` 和 `Pylance (ms-python.vscode-pylance)` 插件。 17 | 2. 修改 VSCode 配置 18 | 在 VSCode 设置视图搜索配置项 `Python: Language Server` 并将其值设置为 `Pylance`,搜索配置项 `Python > Analysis: Type Checking Mode` 并将其值设置为 `basic`。 19 | 20 | 或者向项目 `.vscode` 文件夹中配置文件添加以下内容: 21 | 22 | ```json title=settings.json 23 | { 24 | "python.languageServer": "Pylance", 25 | "python.analysis.typeCheckingMode": "basic" 26 | } 27 | ``` 28 | 29 | ### 其他 30 | 31 | 欢迎提交 Pull Request 添加其他编辑器配置推荐。点击左下角 `Edit this page` 前往编辑。 32 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.2/tutorial/fundamentals.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | description: NoneBot 机器人构成及基本使用 4 | 5 | options: 6 | menu: 7 | - category: tutorial 8 | weight: 30 9 | --- 10 | 11 | # 机器人的构成 12 | 13 | 了解机器人的基本构成有助于你更好地使用 NoneBot,本章节将介绍 NoneBot 中的基本组成部分,稍后的文档中将会使用到这些概念。 14 | 15 | 使用 NoneBot 框架搭建的机器人具有以下几个基本组成部分: 16 | 17 | 1. NoneBot 机器人框架主体:负责连接各个组成部分,提供基本的机器人功能 18 | 2. 驱动器 `Driver`:客户端/服务端的功能实现,负责接收和发送消息(通常为 HTTP 通信) 19 | 3. 适配器 `Adapter`:驱动器的上层,负责将**平台消息**与 NoneBot 事件/操作系统的消息格式相互转换 20 | 4. 插件 `Plugin`:机器人的功能实现,通常为负责处理事件并进行一系列的操作 21 | 22 | 除 NoneBot 机器人框架主体外,其他部分均可按需选择、互相搭配,但由于平台的兼容性问题,部分插件可能仅在某些特定平台上可用(这由插件编写者决定)。 23 | 24 | 在接下来的章节中,我们将重点介绍机器人功能实现,即插件 `Plugin` 部分。 25 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.4.2/tutorial/matcher.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | description: 响应接收到的特定事件 4 | 5 | options: 6 | menu: 7 | - category: tutorial 8 | weight: 60 9 | --- 10 | 11 | # 事件响应器 12 | 13 | 事件响应器(Matcher)是对接收到的事件进行响应的基本单元,所有的事件响应器都继承自 `Matcher` 基类。 14 | 15 | 在 NoneBot 中,事件响应器可以通过一系列特定的规则**筛选**出**具有某种特征的事件**,并按照**特定的流程**交由**预定义的事件处理依赖**进行处理。例如,在[快速上手](../quick-start.mdx)中,我们使用了内置插件 `echo` ,它定义的事件响应器能响应机器人用户发送的“/echo hello world”消息,提取“hello world”信息并作为回复消息发送。 16 | 17 | ## 事件响应器辅助函数 18 | 19 | NoneBot 中所有事件响应器均继承自 `Matcher` 基类,但直接使用 `Matcher.new()` 方法创建事件响应器过于繁琐且不能记录插件信息。因此,NoneBot 中提供了一系列“事件响应器辅助函数”(下称“辅助函数”)来辅助我们用**最简的方式**创建**带有不同规则预设**的事件响应器,提高代码可读性和书写效率。通常情况下,我们只需要使用辅助函数即可完成事件响应器的创建。 20 | 21 | 在 NoneBot 中,辅助函数以 `on()` 或 `on_()` 形式出现(例如 `on_command()`),调用后根据不同的参数返回一个 `Type[Matcher]` 类型的新事件响应器。 22 | 23 | 目前 NoneBot 提供了多种功能各异的辅助函数、具有共同命令名称前缀的命令组以及具有共同参数的响应器组,均可以从 `nonebot` 模块直接导入使用,具体内容参考[事件响应器进阶](../advanced/matcher.md)。 24 | 25 | ## 创建事件响应器 26 | 27 | 在上一节[创建插件](./create-plugin.md#创建插件)中,我们创建了一个 `weather` 插件,现在我们来实现他的功能。 28 | 29 | 我们直接使用 `on_command()` 辅助函数来创建一个事件响应器: 30 | 31 | ```python {3} title=weather/__init__.py 32 | from nonebot import on_command 33 | 34 | weather = on_command("天气") 35 | ``` 36 | 37 | 这样,我们就获得一个名为 `weather` 的事件响应器了,这个事件响应器会对 `/天气` 开头的消息进行响应。 38 | 39 | :::tip 提示 40 | 如果一条消息中包含“@机器人”或以“机器人的昵称”开始,例如 `@bot /天气` 时,协议适配器会将 `event.is_tome()` 判断为 `True` ,同时也会自动去除 `@bot`,即事件响应器收到的信息内容为 `/天气`,方便进行命令匹配。 41 | ::: 42 | 43 | ### 为事件响应器添加参数 44 | 45 | 在辅助函数中,我们可以添加一些参数来对事件响应器进行更加精细的调整,例如事件响应器的优先级、匹配规则等。例如: 46 | 47 | ```python {4} title=weather/__init__.py 48 | from nonebot import on_command 49 | from nonebot.rule import to_me 50 | 51 | weather = on_command("天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True) 52 | ``` 53 | 54 | 这样,我们就获得了一个可以响应 `天气`、`weather`、`查天气` 三个命令的响应规则,需要私聊或 `@bot` 时才会响应,优先级为 10(越小越优先),阻断事件向后续优先级传播的事件响应器了。这些内容的意义和使用方法将会在后续的章节中一一介绍。 55 | 56 | :::tip 提示 57 | 需要注意的是,不同的辅助函数有不同的可选参数,在使用之前可以参考[事件响应器进阶 - 基本辅助函数](../advanced/matcher.md#基本辅助函数)或 [API 文档](../api/plugin/on.md#on)。 58 | ::: 59 | -------------------------------------------------------------------------------- /website/versions.json: -------------------------------------------------------------------------------- 1 | ["2.4.2", "2.4.1", "2.4.0"] 2 | --------------------------------------------------------------------------------