diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..76a04f73 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,6 @@ +dist +node_modules +.yarn +.history +build +lib diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..b9d2a257 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,85 @@ +module.exports = { + root: true, + env: { + browser: true, + commonjs: true, + node: true, + }, + parser: "@typescript-eslint/parser", + parserOptions: { + tsconfigRootDir: __dirname, + project: ["./tsconfig.json", "./website/tsconfig.json"], + }, + globals: { + JSX: true, + }, + extends: [ + "eslint:recommended", + "plugin:react/recommended", + "plugin:react-hooks/recommended", + "plugin:@typescript-eslint/recommended", + "plugin:import/recommended", + "plugin:regexp/recommended", + "plugin:prettier/recommended", + ], + settings: { + "import/resolver": { + node: { + extensions: [".js", ".jsx", ".ts", ".tsx"], + }, + typescript: true, + }, + react: { + version: "detect", + }, + }, + overrides: [ + { + files: ["*.ts", "*.tsx"], + rules: { + "import/no-unresolved": "off", + }, + }, + { + files: ["*.js", "*.cjs"], + rules: { + "@typescript-eslint/no-var-requires": "off", + }, + }, + ], + plugins: ["@typescript-eslint"], + rules: { + "linebreak-style": ["error", "unix"], + quotes: ["error", "double", { avoidEscape: true }], + semi: ["error", "always"], + "@typescript-eslint/no-non-null-assertion": "off", + "import/order": [ + "error", + { + groups: [ + "builtin", + "external", + "internal", + "parent", + "sibling", + "index", + ], + pathGroups: [ + { pattern: "react", group: "builtin", position: "before" }, + { pattern: "fs-extra", group: "builtin" }, + { pattern: "lodash", group: "external", position: "before" }, + { pattern: "clsx", group: "external", position: "before" }, + { pattern: "@theme/**", group: "internal" }, + { pattern: "@site/**", group: "internal" }, + { pattern: "@theme-init/**", group: "internal" }, + { pattern: "@theme-original/**", group: "internal" }, + ], + pathGroupsExcludedImportTypes: [], + "newlines-between": "always", + alphabetize: { + order: "asc", + }, + }, + ], + }, +}; diff --git a/.github/actions/setup-node/action.yml b/.github/actions/setup-node/action.yml index f247c4b4..707f799c 100644 --- a/.github/actions/setup-node/action.yml +++ b/.github/actions/setup-node/action.yml @@ -7,15 +7,7 @@ runs: - uses: actions/setup-node@v3 with: node-version: "18" + cache: "yarn" - - id: yarn-cache-dir-path - run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT - shell: bash - - - uses: actions/cache@v3 - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - - - run: yarn install + - run: yarn install --frozen-lockfile shell: bash diff --git a/.gitignore b/.gitignore index e8413f88..833f6c9c 100644 --- a/.gitignore +++ b/.gitignore @@ -139,7 +139,7 @@ fabric.properties .LSOverride # Icon must end with two \r -Icon +# Icon # Thumbnails ._* diff --git a/.stylelintrc.js b/.stylelintrc.js new file mode 100644 index 00000000..a4e1fd2f --- /dev/null +++ b/.stylelintrc.js @@ -0,0 +1,31 @@ +module.exports = { + extends: ["stylelint-config-standard", "stylelint-prettier/recommended"], + overrides: [ + { + files: ["*.css"], + rules: { + "function-no-unknown": [true, { ignoreFunctions: ["theme"] }], + "selector-class-pattern": [ + "^([a-z][a-z0-9]*)(-[a-z0-9]+)*$", + { + resolveNestedSelectors: true, + message: (selector) => + `Expected class selector "${selector}" to be kebab-case`, + }, + ], + }, + }, + { + files: ["*.module.css"], + rules: { + "selector-class-pattern": [ + "^[a-z][a-zA-Z0-9]+$", + { + message: (selector) => + `Expected class selector "${selector}" to be lowerCamelCase`, + }, + ], + }, + }, + ], +}; diff --git a/README.md b/README.md index 91815769..effd5d34 100644 --- a/README.md +++ b/README.md @@ -207,7 +207,7 @@ NoneBot2 不是 NoneBot1 的替代品。事实上,它们都在被积极的维 - [文档镜像(中国境内)](https://nb2.baka.icu) -- 其他插件请查看 [商店](https://nonebot.dev/store) +- 其他插件请查看 [商店](https://nonebot.dev/store/plugins) ## 许可证 diff --git a/nonebot/internal/driver/__init__.py b/nonebot/internal/driver/__init__.py index aad18257..bf6db531 100644 --- a/nonebot/internal/driver/__init__.py +++ b/nonebot/internal/driver/__init__.py @@ -1,30 +1,30 @@ from .model import URL as URL -from .driver import Mixin as Mixin from .model import RawURL as RawURL -from .driver import Driver as Driver +from .abstract import Mixin as Mixin from .model import Cookies as Cookies from .model import Request as Request +from .abstract import Driver as Driver from .model import FileType as FileType from .model import Response as Response from .model import DataTypes as DataTypes from .model import FileTypes as FileTypes from .model import WebSocket as WebSocket -from .driver import ASGIMixin as ASGIMixin from .model import FilesTypes as FilesTypes from .model import QueryTypes as QueryTypes +from .abstract import ASGIMixin as ASGIMixin from .model import CookieTypes as CookieTypes from .model import FileContent as FileContent from .model import HTTPVersion as HTTPVersion from .model import HeaderTypes as HeaderTypes from .model import SimpleQuery as SimpleQuery from .model import ContentTypes as ContentTypes -from .driver import ForwardMixin as ForwardMixin -from .driver import ReverseMixin as ReverseMixin from .model import QueryVariable as QueryVariable -from .driver import ForwardDriver as ForwardDriver -from .driver import ReverseDriver as ReverseDriver -from .driver import combine_driver as combine_driver +from .abstract import ForwardMixin as ForwardMixin +from .abstract import ReverseMixin as ReverseMixin +from .abstract import ForwardDriver as ForwardDriver +from .abstract import ReverseDriver as ReverseDriver +from .combine import combine_driver as combine_driver from .model import HTTPServerSetup as HTTPServerSetup -from .driver import HTTPClientMixin as HTTPClientMixin +from .abstract import HTTPClientMixin as HTTPClientMixin from .model import WebSocketServerSetup as WebSocketServerSetup -from .driver import WebSocketClientMixin as WebSocketClientMixin +from .abstract import WebSocketClientMixin as WebSocketClientMixin diff --git a/nonebot/internal/driver/driver.py b/nonebot/internal/driver/abstract.py similarity index 88% rename from nonebot/internal/driver/driver.py rename to nonebot/internal/driver/abstract.py index 3d620635..d5fd8352 100644 --- a/nonebot/internal/driver/driver.py +++ b/nonebot/internal/driver/abstract.py @@ -2,18 +2,7 @@ import abc import asyncio from typing_extensions import TypeAlias from contextlib import AsyncExitStack, asynccontextmanager -from typing import ( - TYPE_CHECKING, - Any, - Set, - Dict, - Type, - Union, - TypeVar, - Callable, - AsyncGenerator, - overload, -) +from typing import TYPE_CHECKING, Any, Set, Dict, Type, Callable, AsyncGenerator from nonebot.log import logger from nonebot.config import Env, Config @@ -33,8 +22,6 @@ if TYPE_CHECKING: from nonebot.internal.adapter import Bot, Adapter -D = TypeVar("D", bound="Driver") - BOT_HOOK_PARAMS = [DependParam, BotParam, DefaultParam] @@ -295,44 +282,3 @@ ReverseDriver: TypeAlias = ReverseMixin **Deprecated**,请使用 {ref}`nonebot.drivers.ReverseMixin` 或其子类代替。 """ - - -if TYPE_CHECKING: - - class CombinedDriver(Driver, Mixin): - ... - - -@overload -def combine_driver(driver: Type[D]) -> Type[D]: - ... - - -@overload -def combine_driver(driver: Type[D], *mixins: Type[Mixin]) -> Type["CombinedDriver"]: - ... - - -def combine_driver( - driver: Type[D], *mixins: Type[Mixin] -) -> Union[Type[D], Type["CombinedDriver"]]: - """将一个驱动器和多个混入类合并。""" - # check first - assert issubclass(driver, Driver), "`driver` must be subclass of Driver" - assert all( - issubclass(m, Mixin) for m in mixins - ), "`mixins` must be subclass of Mixin" - - if not mixins: - return driver - - def type_(self: "CombinedDriver") -> str: - return ( - driver.type.__get__(self) - + "+" - + "+".join(x.type.__get__(self) for x in mixins) - ) - - return type( - "CombinedDriver", (*mixins, driver), {"type": property(type_)} - ) # type: ignore diff --git a/nonebot/internal/driver/combine.py b/nonebot/internal/driver/combine.py new file mode 100644 index 00000000..3d4b2597 --- /dev/null +++ b/nonebot/internal/driver/combine.py @@ -0,0 +1,45 @@ +from typing import TYPE_CHECKING, Type, Union, TypeVar, overload + +from .abstract import Mixin, Driver + +D = TypeVar("D", bound="Driver") + +if TYPE_CHECKING: + + class CombinedDriver(Driver, Mixin): + ... + + +@overload +def combine_driver(driver: Type[D]) -> Type[D]: + ... + + +@overload +def combine_driver(driver: Type[D], *mixins: Type[Mixin]) -> Type["CombinedDriver"]: + ... + + +def combine_driver( + driver: Type[D], *mixins: Type[Mixin] +) -> Union[Type[D], Type["CombinedDriver"]]: + """将一个驱动器和多个混入类合并。""" + # check first + if not issubclass(driver, Driver): + raise TypeError("`driver` must be subclass of Driver") + if not all(issubclass(m, Mixin) for m in mixins): + raise TypeError("`mixins` must be subclass of Mixin") + + if not mixins: + return driver + + def type_(self: "CombinedDriver") -> str: + return ( + driver.type.__get__(self) + + "+" + + "+".join(x.type.__get__(self) for x in mixins) + ) + + return type( + "CombinedDriver", (*mixins, driver), {"type": property(type_)} + ) # type: ignore diff --git a/nonebot/plugin/__init__.py b/nonebot/plugin/__init__.py index 4f0df882..d876428b 100644 --- a/nonebot/plugin/__init__.py +++ b/nonebot/plugin/__init__.py @@ -29,7 +29,7 @@ - `load_builtin_plugins` => {ref}``load_builtin_plugins` ` - `require` => {ref}``require` ` -- `PluginMetadata` => {ref}``PluginMetadata` ` +- `PluginMetadata` => {ref}``PluginMetadata` ` FrontMatter: sidebar_position: 0 @@ -77,7 +77,7 @@ def get_plugin(name: str) -> Optional["Plugin"]: 如果为 `load_plugins` 文件夹导入的插件,则为文件(夹)名。 参数: - name: 插件名,即 {ref}`nonebot.plugin.plugin.Plugin.name`。 + name: 插件名,即 {ref}`nonebot.plugin.model.Plugin.name`。 """ return _plugins.get(name) @@ -88,7 +88,7 @@ def get_plugin_by_module_name(module_name: str) -> Optional["Plugin"]: 如果提供的模块名为某个插件的子模块,同样会返回该插件。 参数: - module_name: 模块名,即 {ref}`nonebot.plugin.plugin.Plugin.module_name`。 + module_name: 模块名,即 {ref}`nonebot.plugin.model.Plugin.module_name`。 """ loaded = {plugin.module_name: plugin for plugin in _plugins.values()} has_parent = True @@ -111,9 +111,9 @@ def get_available_plugin_names() -> Set[str]: from .on import on as on from .manager import PluginManager from .on import on_type as on_type +from .model import Plugin as Plugin from .load import require as require from .on import on_regex as on_regex -from .plugin import Plugin as Plugin from .on import on_notice as on_notice from .on import on_command as on_command from .on import on_keyword as on_keyword @@ -129,8 +129,8 @@ from .load import load_plugins as load_plugins from .on import on_startswith as on_startswith from .load import load_from_json as load_from_json from .load import load_from_toml as load_from_toml +from .model import PluginMetadata as PluginMetadata from .on import on_shell_command as on_shell_command -from .plugin import PluginMetadata as PluginMetadata from .load import load_all_plugins as load_all_plugins from .load import load_builtin_plugin as load_builtin_plugin from .load import load_builtin_plugins as load_builtin_plugins diff --git a/nonebot/plugin/load.py b/nonebot/plugin/load.py index 375fb205..3ce1a711 100644 --- a/nonebot/plugin/load.py +++ b/nonebot/plugin/load.py @@ -12,7 +12,7 @@ from typing import Set, Union, Iterable, Optional from nonebot.utils import path_to_module_name -from .plugin import Plugin +from .model import Plugin from .manager import PluginManager from . import _managers, get_plugin, _current_plugin_chain, _module_name_to_plugin_name @@ -160,7 +160,7 @@ def require(name: str) -> ModuleType: 如果为 `load_plugins` 文件夹导入的插件,则为文件(夹)名。 参数: - name: 插件名,即 {ref}`nonebot.plugin.plugin.Plugin.name`。 + name: 插件名,即 {ref}`nonebot.plugin.model.Plugin.name`。 异常: RuntimeError: 插件无法加载 diff --git a/nonebot/plugin/manager.py b/nonebot/plugin/manager.py index 8a2d99a0..adbc5b67 100644 --- a/nonebot/plugin/manager.py +++ b/nonebot/plugin/manager.py @@ -20,7 +20,7 @@ from typing import Set, Dict, List, Iterable, Optional, Sequence from nonebot.log import logger from nonebot.utils import escape_tag, path_to_module_name -from .plugin import Plugin, PluginMetadata +from .model import Plugin, PluginMetadata from . import ( _managers, _new_plugin, diff --git a/nonebot/plugin/plugin.py b/nonebot/plugin/model.py similarity index 98% rename from nonebot/plugin/plugin.py rename to nonebot/plugin/model.py index 7a83c4b4..b691bbc2 100644 --- a/nonebot/plugin/plugin.py +++ b/nonebot/plugin/model.py @@ -2,7 +2,7 @@ FrontMatter: sidebar_position: 3 - description: nonebot.plugin.plugin 模块 + description: nonebot.plugin.model 模块 """ import contextlib diff --git a/nonebot/plugin/on.py b/nonebot/plugin/on.py index 2934a71e..676905f3 100644 --- a/nonebot/plugin/on.py +++ b/nonebot/plugin/on.py @@ -30,7 +30,7 @@ from nonebot.rule import ( shell_command, ) -from .plugin import Plugin +from .model import Plugin from . import get_plugin_by_module_name from .manager import _current_plugin_chain diff --git a/nonebot/plugin/on.pyi b/nonebot/plugin/on.pyi index 5a18092e..c9b9182f 100644 --- a/nonebot/plugin/on.pyi +++ b/nonebot/plugin/on.pyi @@ -10,7 +10,7 @@ from nonebot.rule import Rule, ArgumentParser from nonebot.matcher import Matcher, MatcherSource from nonebot.typing import T_State, T_Handler, T_RuleChecker, T_PermissionChecker -from .plugin import Plugin +from .model import Plugin def store_matcher(matcher: type[Matcher]) -> None: ... def get_matcher_plugin(depth: int = ...) -> Plugin | None: ... diff --git a/nonebot/rule.py b/nonebot/rule.py index f7d8254c..da02b3d5 100644 --- a/nonebot/rule.py +++ b/nonebot/rule.py @@ -599,7 +599,7 @@ def shell_command( 通过 {ref}`nonebot.params.ShellCommandArgs` 获取解析后的参数字典 (例: `{"arg": "arg", "h": True}`)。 - :::warning 警告 + :::caution 警告 如果参数解析失败,则通过 {ref}`nonebot.params.ShellCommandArgs` 获取的将是 {ref}`nonebot.exception.ParserExit` 异常。 ::: diff --git a/package.json b/package.json index fdefbf5c..9b041e7e 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,30 @@ "serve": "yarn workspace nonebot serve", "clear": "yarn workspace nonebot clear", "prettier": "prettier --config ./.prettierrc --write \"./website/\"", + "lint": "yarn lint:js && yarn lint:style", + "lint:js": "eslint --cache --report-unused-disable-directives \"**/*.{js,jsx,ts,tsx,mjs}\"", + "lint:js:fix": "eslint --cache --report-unused-disable-directives --fix \"**/*.{js,jsx,ts,tsx,mjs}\"", + "lint:style": "stylelint \"**/*.css\"", + "lint:style:fix": "stylelint --fix \"**/*.css\"", "pyright": "pyright" }, "devDependencies": { + "@typescript-eslint/eslint-plugin": "^6.6.0", + "@typescript-eslint/parser": "^6.6.0", "cross-env": "^7.0.3", - "prettier": "^2.5.0", - "pyright": "^1.1.317" + "eslint": "^8.48.0", + "eslint-config-prettier": "^9.0.0", + "eslint-import-resolver-typescript": "^3.6.0", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-prettier": "^5.0.0", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-regexp": "^1.15.0", + "prettier": "^3.0.3", + "pyright": "^1.1.317", + "stylelint": "^15.10.3", + "stylelint-config-standard": "^34.0.0", + "stylelint-prettier": "^4.0.2" } } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..e2176982 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,45 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ESNext"], + "module": "NodeNext", + "declaration": true, + "declarationMap": false, + "sourceMap": false, + "jsx": "react-native", + "noEmit": true, + + /* Strict Type-Checking Options */ + "strict": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictBindCallApply": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "alwaysStrict": true, + + /* Additional Checks */ + // "noUnusedLocals": false, // ensured by eslint, should not block compilation + // "noImplicitReturns": true, + // "noFallthroughCasesInSwitch": true, + + /* Disabled on purpose (handled by ESLint, should not block compilation) */ + "noUnusedParameters": false, + + /* Module Resolution Options */ + "moduleResolution": "nodenext", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "isolatedModules": true, + + /* Advanced Options */ + "resolveJsonModule": true, + "skipLibCheck": true, // @types/webpack and webpack/types.d.ts are not the same thing + + /* Use tslib */ + "importHelpers": true, + "noEmitHelpers": true + }, + "include": ["./**/.eslintrc.js", "./**/.stylelintrc.js"], + "exclude": ["node_modules", "**/lib/**/*"] +} diff --git a/website/README.md b/website/README.md deleted file mode 100644 index 55d0c3ef..00000000 --- a/website/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# Website - -This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. - -### Installation - -``` -$ yarn -``` - -### Local Development - -``` -$ yarn start -``` - -This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. - -### Build - -``` -$ yarn build -``` - -This command generates static content into the `build` directory and can be served using any static contents hosting service. - -### Deployment - -``` -$ GIT_USER= USE_SSH=true yarn deploy -``` - -If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. diff --git a/website/docs/advanced/adapter.md b/website/docs/advanced/adapter.md index dbde95d2..61f52a69 100644 --- a/website/docs/advanced/adapter.md +++ b/website/docs/advanced/adapter.md @@ -4,8 +4,8 @@ description: 注册适配器与指定平台交互 options: menu: - weight: 20 - category: advanced + - category: advanced + weight: 20 --- # 使用适配器 @@ -158,4 +158,4 @@ is_tome: bool = event.is_tome() ## 更多 -官方支持的适配器和社区贡献的适配器均可在[商店](/store)中查看。如果你想要开发自己的适配器,可以参考[开发文档](../developer/adapter-writing.md)。欢迎通过商店发布你的适配器。 +官方支持的适配器和社区贡献的适配器均可在[商店](/store/adapters)中查看。如果你想要开发自己的适配器,可以参考[开发文档](../developer/adapter-writing.md)。欢迎通过商店发布你的适配器。 diff --git a/website/docs/advanced/dependency.mdx b/website/docs/advanced/dependency.mdx index 55b28f43..f1dbfeaa 100644 --- a/website/docs/advanced/dependency.mdx +++ b/website/docs/advanced/dependency.mdx @@ -4,8 +4,8 @@ description: 通过依赖注入获取上下文信息 options: menu: - weight: 70 - category: advanced + - category: advanced + weight: 70 --- # 依赖注入 @@ -557,7 +557,7 @@ async def _(x: httpx.AsyncClient = Depends(get_client)): -:::warning 注意 +:::caution 注意 生成器作为依赖时,其中只能进行一次 `yield`,否则将会触发异常。如果对此有疑问并想探究原因,可以参考 [contextmanager](https://docs.python.org/zh-cn/3/library/contextlib.html#contextlib.contextmanager) 和 [asynccontextmanager](https://docs.python.org/zh-cn/3/library/contextlib.html#contextlib.asynccontextmanager) 文档。事实上,NoneBot 内部就使用了这两个装饰器。 ::: diff --git a/website/docs/advanced/driver.md b/website/docs/advanced/driver.md index d9e3460e..222921de 100644 --- a/website/docs/advanced/driver.md +++ b/website/docs/advanced/driver.md @@ -4,8 +4,8 @@ description: 选择合适的驱动器运行机器人 options: menu: - weight: 10 - category: advanced + - category: advanced + weight: 10 --- # 选择驱动器 @@ -118,7 +118,7 @@ DRIVER=~fastapi ##### `fastapi_reload` -:::warning 警告 +:::caution 警告 不推荐开启该配置项,在 Windows 平台上开启该功能有可能会造成预料之外的影响!替代方案:使用 `nb-cli` 命令行工具以及参数 `--reload` 启动 NoneBot。 ```bash @@ -200,7 +200,7 @@ DRIVER=~quart ##### `quart_reload` -:::warning 警告 +:::caution 警告 不推荐开启该配置项,在 Windows 平台上开启该功能有可能会造成预料之外的影响!替代方案:使用 `nb-cli` 命令行工具以及参数 `--reload` 启动 NoneBot。 ```bash @@ -252,7 +252,7 @@ nonebot.run(app="bot:app") **类型:**HTTP 客户端驱动器 -:::warning 注意 +:::caution 注意 本驱动器仅支持 HTTP 请求,不支持 WebSocket 连接请求。 ::: @@ -266,7 +266,7 @@ DRIVER=~httpx **类型:**WebSocket 客户端驱动器 -:::warning 注意 +:::caution 注意 本驱动器仅支持 WebSocket 连接请求,不支持 HTTP 请求。 ::: diff --git a/website/docs/advanced/matcher-provider.md b/website/docs/advanced/matcher-provider.md index 9c5dbcce..6af8f003 100644 --- a/website/docs/advanced/matcher-provider.md +++ b/website/docs/advanced/matcher-provider.md @@ -4,8 +4,8 @@ description: 自定义事件响应器存储 options: menu: - weight: 110 - category: advanced + - category: advanced + weight: 110 --- # 事件响应器存储 diff --git a/website/docs/advanced/matcher.md b/website/docs/advanced/matcher.md index 80d7d89f..57af216c 100644 --- a/website/docs/advanced/matcher.md +++ b/website/docs/advanced/matcher.md @@ -4,8 +4,8 @@ description: 事件响应器组成与内置响应规则 options: menu: - weight: 60 - category: advanced + - category: advanced + weight: 60 --- # 事件响应器进阶 diff --git a/website/docs/advanced/plugin-info.md b/website/docs/advanced/plugin-info.md index ecd6d272..5f45dc7d 100644 --- a/website/docs/advanced/plugin-info.md +++ b/website/docs/advanced/plugin-info.md @@ -4,8 +4,8 @@ description: 填写与获取插件相关的信息 options: menu: - weight: 30 - category: advanced + - category: advanced + weight: 30 --- # 插件信息 @@ -14,7 +14,7 @@ NoneBot 是一个插件化的框架,可以通过加载插件来扩展功能。 ## 插件元数据 -在 NoneBot 中,插件 [`Plugin`](../api/plugin/plugin.md#Plugin) 对象中存储了插件系统所需要的一系列信息。包括插件的索引名称、插件模块、插件中的事件响应器、插件父子关系等。通常,只有插件开发者才需要关心这些信息,而插件使用者或者机器人用户想要看到的是插件使用方法等帮助信息。因此,我们可以为插件添加插件元数据 `PluginMetadata`,它允许插件开发者为插件添加一些额外的信息。这些信息编写于插件模块的顶层,可以直接通过源码查看,或者通过 NoneBot 插件系统获取收集到的信息,通过其他方式发送给机器人用户等。 +在 NoneBot 中,插件 [`Plugin`](../api/plugin/model.md#Plugin) 对象中存储了插件系统所需要的一系列信息。包括插件的索引名称、插件模块、插件中的事件响应器、插件父子关系等。通常,只有插件开发者才需要关心这些信息,而插件使用者或者机器人用户想要看到的是插件使用方法等帮助信息。因此,我们可以为插件添加插件元数据 `PluginMetadata`,它允许插件开发者为插件添加一些额外的信息。这些信息编写于插件模块的顶层,可以直接通过源码查看,或者通过 NoneBot 插件系统获取收集到的信息,通过其他方式发送给机器人用户等。 现在,假设我们有一个插件 `example`, 它的模块结构如下: diff --git a/website/docs/advanced/plugin-nesting.md b/website/docs/advanced/plugin-nesting.md index c5061cad..0a306d28 100644 --- a/website/docs/advanced/plugin-nesting.md +++ b/website/docs/advanced/plugin-nesting.md @@ -4,8 +4,8 @@ description: 编写与加载嵌套插件 options: menu: - weight: 40 - category: advanced + - category: advanced + weight: 40 --- # 嵌套插件 diff --git a/website/docs/advanced/requiring.md b/website/docs/advanced/requiring.md index 82bc4216..1a9fe4b0 100644 --- a/website/docs/advanced/requiring.md +++ b/website/docs/advanced/requiring.md @@ -4,8 +4,8 @@ description: 使用其他插件提供的功能 options: menu: - weight: 50 - category: advanced + - category: advanced + weight: 50 --- # 跨插件访问 diff --git a/website/docs/advanced/routing.md b/website/docs/advanced/routing.md index d16ad518..efa1ac55 100644 --- a/website/docs/advanced/routing.md +++ b/website/docs/advanced/routing.md @@ -4,8 +4,8 @@ description: 添加服务端路由规则 options: menu: - weight: 100 - category: advanced + - category: advanced + weight: 100 --- # 添加路由 @@ -21,10 +21,11 @@ NoneBot 中,我们可以通过两种途径向 ASGI 驱动器添加路由规则 在向驱动器添加路由规则时,我们需要注意驱动器是否为服务端类型,我们可以通过以下方式判断: -```python {3} +```python from nonebot import get_driver from nonebot.drivers import ASGIMixin +# highlight-next-line can_use = isinstance(get_driver(), ASGIMixin) ``` diff --git a/website/docs/advanced/runtime-hook.md b/website/docs/advanced/runtime-hook.md index e3298587..79fae7c5 100644 --- a/website/docs/advanced/runtime-hook.md +++ b/website/docs/advanced/runtime-hook.md @@ -4,8 +4,8 @@ description: 在特定的生命周期中执行代码 options: menu: - weight: 90 - category: advanced + - category: advanced + weight: 90 --- # 钩子函数 diff --git a/website/docs/advanced/session-updating.md b/website/docs/advanced/session-updating.md index 40ea3ff3..f804ba96 100644 --- a/website/docs/advanced/session-updating.md +++ b/website/docs/advanced/session-updating.md @@ -4,8 +4,8 @@ description: 控制会话响应对象 options: menu: - weight: 80 - category: advanced + - category: advanced + weight: 80 --- # 会话更新 @@ -56,4 +56,4 @@ async def _(matcher: Matcher) -> Permission: 请注意,此处为全大写字母的 `USER` 权限,它可以匹配多个会话 ID。通过这种方式,我们可以实现多用户同时参与的会话。 -我们已经了解了如何控制会话的更新,相信你已经能够实现更复杂的会话功能了,例如多人小游戏等等。欢迎将你的作品分享到[插件商店](/store)。 +我们已经了解了如何控制会话的更新,相信你已经能够实现更复杂的会话功能了,例如多人小游戏等等。欢迎将你的作品分享到[插件商店](/store/plugins)。 diff --git a/website/docs/appendices/api-calling.mdx b/website/docs/appendices/api-calling.mdx index 94a475c7..ab6ce23d 100644 --- a/website/docs/appendices/api-calling.mdx +++ b/website/docs/appendices/api-calling.mdx @@ -4,14 +4,13 @@ description: 使用平台接口,完成更多功能 options: menu: - weight: 50 - category: appendices + - category: appendices + weight: 50 --- # 使用平台接口 -import Messenger from "@site/src/components/Messenger"; -import MarkdownText from "!!raw-loader!./assets/console-markdown.txt"; +import Messenger from "@/components/Messenger"; 在 NoneBot 中,除了使用事件响应器操作发送文本消息外,我们还可以直接通过使用协议适配器提供的方法来使用平台特定的接口,完成发送特殊消息、获取信息等其他平台提供的功能。同时,在部分无法使用事件响应器的情况中,例如[定时任务](../best-practice/scheduler.md),我们也可以使用平台接口来完成需要的功能。 @@ -19,7 +18,7 @@ import MarkdownText from "!!raw-loader!./assets/console-markdown.txt"; 在之前的章节中,我们介绍了如何向用户发送文本消息以及[如何处理平台消息](../tutorial/message.md),现在我们来向用户发送平台特殊消息。 -:::warning 注意 +:::caution 注意 在以下的示例中,我们将使用 `Console` 协议适配器来演示如何发送平台消息。在实际使用中,你需要确保你使用的**消息序列类型**与你所要发送的**平台类型**一致。 ::: @@ -49,7 +48,11 @@ async def got_location(location: str = ArgPlainText()): { position: "right", msg: "/天气" }, { position: "left", msg: "❓请输入地名" }, { position: "right", msg: "北京" }, - { position: "left", msg: MarkdownText }, + { + position: "left", + monospace: true, + msg: "┏━━━━━━━━━━━━━━━━┓\n┃ 北京 ┃\n┗━━━━━━━━━━━━━━━━┛\n• 今天\n⛅ 多云 20℃~24℃", + }, ]} /> @@ -100,7 +103,7 @@ result = await bot.get_user_info(user_id=12345678) result = await bot.call_api("get_user_info", user_id=12345678) ``` -:::warning 注意 +:::caution 注意 实际可以使用的 API 以及参数取决于平台提供的接口以及协议适配器的实现,请参考协议适配器以及平台文档。 ::: diff --git a/website/docs/appendices/assets/console-markdown.txt b/website/docs/appendices/assets/console-markdown.txt deleted file mode 100644 index 63e524db..00000000 --- a/website/docs/appendices/assets/console-markdown.txt +++ /dev/null @@ -1,6 +0,0 @@ -┏━━━━━━━━━━━━━━━━┓ -┃ 北京 ┃ -┗━━━━━━━━━━━━━━━━┛ - - • 今天 - ⛅ 多云 20℃~24℃ diff --git a/website/docs/appendices/config.mdx b/website/docs/appendices/config.mdx index f7abcdce..da90780b 100644 --- a/website/docs/appendices/config.mdx +++ b/website/docs/appendices/config.mdx @@ -4,8 +4,8 @@ description: 读取用户配置来控制插件行为 options: menu: - weight: 10 - category: appendices + - category: appendices + weight: 10 --- # 配置 @@ -62,7 +62,7 @@ export CUSTOM_CONFIG="config in environment variables" 那最终 NoneBot 所读取的内容为环境变量中的内容,即 `config in environment variables`。 -:::warning 注意 +:::caution 注意 NoneBot 不会自发读取未被定义的配置项的环境变量,如果需要读取某一环境变量需要在 dotenv 配置文件中进行声明。 ::: diff --git a/website/docs/appendices/log.md b/website/docs/appendices/log.md index 2a9631d3..7fb68686 100644 --- a/website/docs/appendices/log.md +++ b/website/docs/appendices/log.md @@ -4,8 +4,8 @@ description: 记录与控制日志 options: menu: - weight: 70 - category: appendices + - category: appendices + weight: 70 --- # 日志 diff --git a/website/docs/appendices/overload.md b/website/docs/appendices/overload.md index a466a4d5..2ade4b5b 100644 --- a/website/docs/appendices/overload.md +++ b/website/docs/appendices/overload.md @@ -4,8 +4,8 @@ description: 根据事件类型进行不同的处理 options: menu: - weight: 80 - category: appendices + - category: appendices + weight: 80 --- # 事件类型与重载 @@ -28,7 +28,7 @@ async def got_location(event: MessageEvent, location: str = ArgPlainText()): 在上面的代码中,我们获取了 `Console` 协议适配器的消息事件提供的发送时间 `time` 属性。 -:::warning 注意 +:::caution 注意 如果**基类**就能满足你的需求,那么就**不要修改**事件参数类型注解,这样可以使你的代码更加**通用**,可以在更多平台上运行。如何根据不同平台事件类型进行不同的处理,我们将在[重载](#重载)一节中介绍。 ::: @@ -63,7 +63,7 @@ async def handle_onebot(bot: OneBot): await bot.send_group_message(group_id=123123, message="OneBot") ``` -:::warning 注意 +:::caution 注意 重载机制对所有的参数类型注解都有效,因此,依赖注入也可以使用这个特性来对不同的返回值进行处理。 但 Bot、Event 和 Matcher 三者的参数类型注解具有最高检查优先级,如果三者任一类型注解不匹配,那么其他依赖注入将不会执行(如:`Depends`)。 diff --git a/website/docs/appendices/permission.mdx b/website/docs/appendices/permission.mdx index 7d1573e8..eac45183 100644 --- a/website/docs/appendices/permission.mdx +++ b/website/docs/appendices/permission.mdx @@ -4,8 +4,8 @@ description: 控制事件响应器的权限 options: menu: - weight: 60 - category: appendices + - category: appendices + weight: 60 --- # 权限控制 diff --git a/website/docs/appendices/rule.md b/website/docs/appendices/rule.md index 6259d016..bccb413f 100644 --- a/website/docs/appendices/rule.md +++ b/website/docs/appendices/rule.md @@ -4,8 +4,8 @@ description: 自定义响应规则 options: menu: - weight: 20 - category: appendices + - category: appendices + weight: 20 --- # 响应规则 diff --git a/website/docs/appendices/session-control.mdx b/website/docs/appendices/session-control.mdx index 2373f2a3..c98bf2bf 100644 --- a/website/docs/appendices/session-control.mdx +++ b/website/docs/appendices/session-control.mdx @@ -4,8 +4,8 @@ description: 更灵活的会话控制 options: menu: - weight: 30 - category: appendices + - category: appendices + weight: 30 --- # 会话控制 @@ -322,7 +322,7 @@ async def _(matcher: Matcher): matcher.stop_propagation() ``` -:::warning 注意 +:::caution 注意 `stop_propagation` 操作是实例方法,需要先通过依赖注入获取事件响应器实例再进行调用。 ::: diff --git a/website/docs/appendices/session-state.md b/website/docs/appendices/session-state.md index e11d987c..d6780fc9 100644 --- a/website/docs/appendices/session-state.md +++ b/website/docs/appendices/session-state.md @@ -4,8 +4,8 @@ description: 会话状态信息 options: menu: - weight: 40 - category: appendices + - category: appendices + weight: 40 --- # 会话状态 diff --git a/website/docs/best-practice/deployment.mdx b/website/docs/best-practice/deployment.mdx index 001577e6..8c33520b 100644 --- a/website/docs/best-practice/deployment.mdx +++ b/website/docs/best-practice/deployment.mdx @@ -180,7 +180,7 @@ docker compose build 将以下文件添加至**项目目录**下的 `.github/workflows/` 目录下,并将文件中高亮行中的仓库名称替换为你的仓库名称: -```yaml title=.github/workflows/build.yml {34} +```yaml title=.github/workflows/build.yml name: Docker Hub Release on: @@ -213,6 +213,7 @@ jobs: id: metadata with: images: | + # highlight-next-line {organization}/{repository} tags: | type=semver,pattern={{version}} diff --git a/website/docs/best-practice/error-tracking.md b/website/docs/best-practice/error-tracking.md index 07f421e1..a28a22d1 100644 --- a/website/docs/best-practice/error-tracking.md +++ b/website/docs/best-practice/error-tracking.md @@ -27,7 +27,7 @@ nb plugin install nonebot-plugin-sentry ### 配置插件 -:::warning 注意 +:::caution 注意 错误跟踪通常在生产环境中使用,因此开发环境中 `sentry_dsn` 留空即会停用插件。 ::: diff --git a/website/docs/best-practice/scheduler.md b/website/docs/best-practice/scheduler.md index ca70816a..229b27ab 100644 --- a/website/docs/best-practice/scheduler.md +++ b/website/docs/best-practice/scheduler.md @@ -58,7 +58,7 @@ scheduler.add_job( ) ``` -:::warning 注意 +:::caution 注意 由于 APScheduler 的定时任务并不是**由事件响应器所触发的事件**,因此其任务函数无法同[事件处理函数](../tutorial/handler.mdx#事件处理函数)一样通过[依赖注入](../tutorial/event-data.mdx#认识依赖注入)获取上下文信息,也无法通过事件响应器对象的方法进行任何操作,因此我们需要使用[调用平台 API](../appendices/api-calling.mdx#调用平台-api)的方式来获取信息或收发消息。 相对于事件处理依赖而言,编写定时任务更像是编写普通的函数,需要我们自行获取信息以及发送信息,请**不要**将事件处理依赖的特殊语法用于定时任务! diff --git a/website/docs/best-practice/testing/behavior.mdx b/website/docs/best-practice/testing/behavior.mdx index 0cb74f98..a69540f1 100644 --- a/website/docs/best-practice/testing/behavior.mdx +++ b/website/docs/best-practice/testing/behavior.mdx @@ -189,7 +189,7 @@ async def _(bot: Bot): 然后我们对该插件进行测试: -```python {19,20,23,24} title=tests/test_example.py +```python title=tests/test_example.py from datetime import datetime import pytest @@ -210,12 +210,16 @@ async def test_example(app: App): from awesome_bot.plugins.example import foo async with app.test_matcher(foo) as ctx: + # highlight-start adapter = nonebot.get_adapter(Adapter) bot = ctx.create_bot(base=Bot, adapter=adapter) + # highlight-end event = make_event("/foo") ctx.receive_event(bot, event) + # highlight-start ctx.should_call_send(event, "message", result=None, bot=bot) ctx.should_call_api("bell", {}, result=None, adapter=adapter) + # highlight-end ``` 请注意,对于在依赖注入中使用了非基类对象的情况,我们需要在 `create_bot` 方法中指定 `base` 和 `adapter` 参数,确保不会因为重载功能而出现非预期情况。 diff --git a/website/docs/developer/adapter-writing.md b/website/docs/developer/adapter-writing.md index a9fdcfbc..2cef3f82 100644 --- a/website/docs/developer/adapter-writing.md +++ b/website/docs/developer/adapter-writing.md @@ -576,6 +576,6 @@ class Message(BaseMessage[MessageSegment]): ## 后续工作 -在完成适配器代码的编写后,如果想要将适配器发布到 NoneBot 商店,我们需要将适配器发布到 PyPI 中,然后前往[商店](/store)页面,切换到适配器页签,点击**发布适配器**按钮,填写适配器相关信息并提交。 +在完成适配器代码的编写后,如果想要将适配器发布到 NoneBot 商店,我们需要将适配器发布到 PyPI 中,然后前往[商店](/store/adapters)页面,切换到适配器页签,点击**发布适配器**按钮,填写适配器相关信息并提交。 另外建议编写适配器文档或者一些插件开发示例,以便其他开发者使用我们的适配器。 diff --git a/website/docs/developer/plugin-publishing.mdx b/website/docs/developer/plugin-publishing.mdx index 0920b22a..e6af1c66 100644 --- a/website/docs/developer/plugin-publishing.mdx +++ b/website/docs/developer/plugin-publishing.mdx @@ -62,7 +62,7 @@ NoneBot 插件使用下述命名规范: 依赖填写的基本原则:程序直接导入了什么第三方库,就添加什么第三方包依赖;能用哪些第三方库的特性,就根据使用的特性锁定第三方包版本。 -:::warning 注意 +:::caution 注意 1. 插件需要添加 `nonebot2` 为依赖以避免“幽灵依赖”; 2. 插件需要将使用的适配器加入依赖列表,如:使用 OneBot 适配器的插件应添加 `nonebot-adapter-onebot` 依赖; @@ -102,7 +102,7 @@ __plugin_meta__ = PluginMetadata( ) ``` -:::warning 注意 +:::caution 注意 `__plugin_meta__` 变量**必须**处于插件最外层(如 `__init__.py` 中),否则无法正常识别。 一般做法是在 `__init__.py` 中定义 `__plugin_meta__`。 @@ -183,7 +183,7 @@ twine upload dist/* # 只发布先前的构建 ### 提交申请 -完成在 PyPI 的插件发布流程后,前往[商店](/store)页面,切换到插件页签,点击 **发布插件** 按钮。 +完成在 PyPI 的插件发布流程后,前往[商店](/store/plugins)页面,切换到插件页签,点击 **发布插件** 按钮。 在弹出的插件信息提交表单内,填入您所要发布的相应插件信息。请注意,如果插件需要必要配置项才能正常导入,请在“插件配置项”中填写必要的内容(请勿填写密钥等敏感信息)。 @@ -199,4 +199,4 @@ twine upload dist/* # 只发布先前的构建 之后,NoneBot 的维护者和一些插件开发者会初步检查插件代码,帮助减少该插件的问题。 -完成这些步骤后,您的插件将会被自动合并到[商店](/store),而您也将成为 [**NoneBot 贡献者**](https://github.com/nonebot/nonebot2/graphs/contributors)的一员。 +完成这些步骤后,您的插件将会被自动合并到[商店](/store/plugins),而您也将成为 [**NoneBot 贡献者**](https://github.com/nonebot/nonebot2/graphs/contributors)的一员。 diff --git a/website/docs/quick-start.mdx b/website/docs/quick-start.mdx index bf2d1e23..52a66880 100644 --- a/website/docs/quick-start.mdx +++ b/website/docs/quick-start.mdx @@ -4,8 +4,8 @@ description: 尝试使用 NoneBot options: menu: - weight: 10 - category: tutorial + - category: tutorial + weight: 10 --- import Asciinema from "@site/src/components/Asciinema"; @@ -13,7 +13,7 @@ import Messenger from "@site/src/components/Messenger"; # 快速上手 -:::warning 前提条件 +:::caution 前提条件 - 请确保你的 Python 版本 >= 3.8 - **我们强烈建议使用虚拟环境进行开发**,如果没有使用虚拟环境,请确保已经卸载可能存在的 NoneBot v1!!! diff --git a/website/docs/tutorial/application.md b/website/docs/tutorial/application.md index e5ca5cc8..60f2a594 100644 --- a/website/docs/tutorial/application.md +++ b/website/docs/tutorial/application.md @@ -4,15 +4,15 @@ description: 创建一个 NoneBot 项目 options: menu: - weight: 20 - category: tutorial + - category: tutorial + weight: 20 --- # 手动创建项目 -在[快速上手](./quick-start.mdx)中,我们已经介绍了如何安装和使用 `nb-cli` 创建一个项目。在本章节中,我们将简要介绍如何在不使用 `nb-cli` 的方式创建一个机器人项目的**最小实例**并启动。如果你想要了解 NoneBot 的启动流程,也可以阅读本章节。 +在[快速上手](../quick-start.mdx)中,我们已经介绍了如何安装和使用 `nb-cli` 创建一个项目。在本章节中,我们将简要介绍如何在不使用 `nb-cli` 的方式创建一个机器人项目的**最小实例**并启动。如果你想要了解 NoneBot 的启动流程,也可以阅读本章节。 -:::warning +:::caution 警告 我们十分不推荐直接创建机器人项目,请优先考虑使用 nb-cli 进行项目创建。 ::: @@ -44,7 +44,7 @@ options: pip install 'nonebot2[fastapi]' ``` - 驱动器包名可以在 [驱动器商店](/store) 中找到。 + 驱动器包名可以在 [驱动器商店](/store/drivers) 中找到。 3. 安装适配器 @@ -52,7 +52,7 @@ options: pip install nonebot-adapter-console ``` - 适配器包名可以在 [适配器商店](/store) 中找到。 + 适配器包名可以在 [适配器商店](/store/adapters) 中找到。 ## 创建配置文件 diff --git a/website/docs/tutorial/create-plugin.md b/website/docs/tutorial/create-plugin.md index 428997bc..9ce4ed4d 100644 --- a/website/docs/tutorial/create-plugin.md +++ b/website/docs/tutorial/create-plugin.md @@ -4,8 +4,8 @@ description: 创建并加载自定义插件 options: menu: - weight: 50 - category: tutorial + - category: tutorial + weight: 50 --- # 插件编写准备 @@ -41,7 +41,7 @@ options: ## 创建插件 -:::warning 注意 +:::caution 注意 如果在之前的[快速上手](../quick-start.mdx)章节中已经使用 `bootstrap` 模板创建了项目,那么你需要做出如下修改: 1. 在项目目录中创建一个两层文件夹 `awesome_bot/plugins` @@ -63,7 +63,7 @@ options: ::: -:::warning 注意 +:::caution 注意 如果在之前的[创建项目](./application.md)章节中手动创建了相关文件,那么你需要做出如下修改: 1. 在项目目录中创建一个两层文件夹 `awesome_bot/plugins` @@ -144,7 +144,7 @@ nonebot.load_plugin("path.to.your.plugin") # 加载第三方插件 nonebot.load_plugin(Path("./path/to/your/plugin.py")) # 加载项目插件 ``` -:::warning 注意 +:::caution 注意 请注意,本地插件的路径应该为相对机器人 **入口文件(通常为 bot.py)** 可导入的,例如在项目 `plugins` 目录下。 ::: @@ -156,7 +156,7 @@ nonebot.load_plugin(Path("./path/to/your/plugin.py")) # 加载项目插件 nonebot.load_plugins("src/plugins", "path/to/your/plugins") ``` -:::warning 注意 +:::caution 注意 请注意,插件目录应该为相对机器人 **入口文件(通常为 bot.py)** 可导入的,例如在项目 `plugins` 目录下。 ::: diff --git a/website/docs/tutorial/event-data.mdx b/website/docs/tutorial/event-data.mdx index 59877577..5f8ccb54 100644 --- a/website/docs/tutorial/event-data.mdx +++ b/website/docs/tutorial/event-data.mdx @@ -4,8 +4,8 @@ description: 通过依赖注入获取所需事件信息 options: menu: - weight: 80 - category: tutorial + - category: tutorial + weight: 80 --- # 获取事件信息 diff --git a/website/docs/tutorial/fundamentals.md b/website/docs/tutorial/fundamentals.md index 7b72826c..bb5eea0b 100644 --- a/website/docs/tutorial/fundamentals.md +++ b/website/docs/tutorial/fundamentals.md @@ -4,8 +4,8 @@ description: NoneBot 机器人构成及基本使用 options: menu: - weight: 30 - category: tutorial + - category: tutorial + weight: 30 --- # 机器人的构成 diff --git a/website/docs/tutorial/handler.mdx b/website/docs/tutorial/handler.mdx index 20e21d6d..466b15b3 100644 --- a/website/docs/tutorial/handler.mdx +++ b/website/docs/tutorial/handler.mdx @@ -4,8 +4,8 @@ description: 处理接收到的特定事件 options: menu: - weight: 70 - category: tutorial + - category: tutorial + weight: 70 --- # 事件处理 diff --git a/website/docs/tutorial/matcher.md b/website/docs/tutorial/matcher.md index c3795b3b..72ca52b8 100644 --- a/website/docs/tutorial/matcher.md +++ b/website/docs/tutorial/matcher.md @@ -4,8 +4,8 @@ description: 响应接收到的特定事件 options: menu: - weight: 60 - category: tutorial + - category: tutorial + weight: 60 --- # 事件响应器 diff --git a/website/docs/tutorial/message.md b/website/docs/tutorial/message.md index b2bf7eb3..50e7878b 100644 --- a/website/docs/tutorial/message.md +++ b/website/docs/tutorial/message.md @@ -4,8 +4,8 @@ description: 处理消息序列与消息段 options: menu: - weight: 90 - category: tutorial + - category: tutorial + weight: 90 --- # 处理消息 @@ -26,7 +26,7 @@ options: 顾名思义,消息段 `MessageSegment` 是一段消息。由于消息序列的本质是由若干消息段所组成的序列,消息段可以被认为是构成消息序列的最小单位。简单来说,消息序列类似于一个自然段,而消息段则是组成自然段的一句话。同时,作为特殊消息载体的存在,绝大多数的平台都有着**独特的消息类型**,这些独特的内容均需要由对应的**协议适配器**所提供,以适应不同平台中的消息模式。**这也意味着,你需要导入对应的协议适配器中的消息序列和消息段后才能使用其特殊的工厂方法。** -:::warning 注意 +:::caution 注意 消息段的类型是由协议适配器提供的,因此你需要参考协议适配器的文档并导入对应的消息段后才能使用其特殊的消息类型。 在上一节的[使用依赖注入](./event-data.mdx#使用依赖注入)中,我们导入的为 `nonebot.adapters.Message` 抽象基类,因此我们无法使用平台特有的消息类型。仅能使用 `str` 作为纯文本消息回复。 @@ -34,7 +34,7 @@ options: ## 使用消息序列 -:::warning 注意 +:::caution 注意 在以下的示例中,为了更好的理解多种类型的消息组成方式,我们将使用 `Console` 协议适配器来演示消息序列的使用方法。在实际使用中,你需要确保你使用的**消息序列类型**与你所要发送的**平台类型**一致。 ::: @@ -297,7 +297,7 @@ msg == Message( 如果 `Message.template` 构建消息模板,那么消息模板将采用消息序列形式的格式化,此时的消息将会是平台特定的: -:::warning 注意 +:::caution 注意 使用 `Message.template` 构建消息模板时,应注意消息序列为平台适配器提供的类型,不能使用 `nonebot.adapters.Message` 基类作为模板构建。使用基类构建模板与使用 `str` 构建模板的效果是一样的,因此请使用上述的 `MessageTemplate` 类直接构建模板。: ::: @@ -337,7 +337,7 @@ Message( ) ``` -:::warning 注意 +:::caution 注意 只有消息序列中的文本类型消息段才能被格式化,其他类型的消息段将会原样添加。 ::: diff --git a/website/docs/tutorial/store.mdx b/website/docs/tutorial/store.mdx index e87d6eb5..9332ac8e 100644 --- a/website/docs/tutorial/store.mdx +++ b/website/docs/tutorial/store.mdx @@ -4,8 +4,8 @@ description: 从商店安装适配器和插件 options: menu: - weight: 40 - category: tutorial + - category: tutorial + weight: 40 --- # 获取商店内容 @@ -15,10 +15,12 @@ import TabItem from "@theme/TabItem"; import Asciinema from "@site/src/components/Asciinema"; :::tip 提示 + 如果你暂时没有获取商店内容的需求,可以跳过本章节。 + ::: -NoneBot 提供了一个[商店](/store),商店内容均由社区开发者贡献。你可以在商店中查找你需要的适配器和插件等,进行安装或者参考其文档等。 +NoneBot 提供了一个[商店](/store/plugins),商店内容均由社区开发者贡献。你可以在商店中查找你需要的适配器和插件等,进行安装或者参考其文档等。 商店中每个内容的卡片都包含了其名称和简介等信息,点击**卡片右上角**链接图标即可跳转到其主页。 diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index a90d54cf..485844e3 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -1,26 +1,226 @@ // @ts-check // Note: type annotations allow type checking and IDEs autocompletion +// color mode config +/** @type {import('@nullbot/docusaurus-preset-nonepress').ThemeConfig["colorMode"]} */ +const colorMode = { + defaultMode: "light", + respectPrefersColorScheme: true, +}; + +// navbar config +/** @type {import('@nullbot/docusaurus-preset-nonepress').ThemeConfig["navbar"]} */ +const navbar = { + title: "NoneBot", + logo: { + alt: "NoneBot", + src: "logo.png", + href: "/", + target: "_self", + height: 32, + width: 32, + }, + hideOnScroll: false, + items: [ + { + label: "指南", + type: "docsMenu", + category: "tutorial", + }, + { + label: "深入", + type: "docsMenu", + category: "appendices", + }, + { + label: "进阶", + type: "docsMenu", + category: "advanced", + }, + { + label: "API", + type: "doc", + docId: "api/index", + }, + { + label: "更多", + type: "dropdown", + to: "/store/plugins", + items: [ + { + label: "最佳实践", + type: "doc", + docId: "best-practice/scheduler", + }, + { + label: "开发者", + type: "doc", + docId: "developer/plugin-publishing", + }, + { label: "社区", type: "doc", docId: "community/contact" }, + { label: "开源之夏", type: "doc", docId: "ospp/2023" }, + { label: "商店", to: "/store/plugins" }, + { label: "更新日志", to: "/changelog" }, + { label: "论坛", href: "https://discussions.nonebot.dev" }, + ], + }, + ], +}; + +// footer config +/** @type {import('@nullbot/docusaurus-preset-nonepress').ThemeConfig["footer"]} */ +const footer = { + style: "light", + logo: { + alt: "NoneBot", + src: "logo.png", + href: "/", + target: "_self", + height: 32, + width: 32, + }, + copyright: `Copyright © ${new Date().getFullYear()} NoneBot. All rights reserved.`, + links: [ + { + title: "Learn", + items: [ + { label: "Introduction", to: "/docs/" }, + { label: "QuickStart", to: "/docs/quick-start" }, + { label: "Changelog", to: "/changelog" }, + ], + }, + { + title: "NoneBot Team", + items: [ + { + label: "Homepage", + href: "https://nonebot.dev", + }, + { + label: "NoneBot V1", + href: "https://v1.nonebot.dev", + }, + { label: "NoneBot CLI", href: "https://cli.nonebot.dev" }, + ], + }, + { + title: "Related", + items: [ + { label: "OneBot", href: "https://onebot.dev/" }, + { label: "go-cqhttp", href: "https://docs.go-cqhttp.org/" }, + { label: "Mirai", href: "https://mirai.mamoe.net/" }, + ], + }, + ], +}; + +// prism config +/** @type {import('prism-react-renderer').PrismTheme} */ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +// eslint-disable-next-line import/order const lightCodeTheme = require("prism-react-renderer/themes/github"); +/** @type {import('prism-react-renderer').PrismTheme} */ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +// eslint-disable-next-line import/order const darkCodeTheme = require("prism-react-renderer/themes/dracula"); +/** @type {import('@nullbot/docusaurus-preset-nonepress').ThemeConfig["prism"]} */ +const prism = { + theme: lightCodeTheme, + darkTheme: darkCodeTheme, + additionalLanguages: ["docker", "ini"], +}; + +// algolia config +/** @type {import('@nullbot/docusaurus-preset-nonepress').ThemeConfig["algolia"]} */ +const algolia = { + appId: "X0X5UACHZQ", + apiKey: "ac03e1ac2bd0812e2ea38c0cc1ea38c5", + indexName: "nonebot", + contextualSearch: true, +}; + +// nonepress config +/** @type {import('@nullbot/docusaurus-preset-nonepress').ThemeConfig["nonepress"]} */ +const nonepress = { + tailwindConfig: require("./tailwind.config"), + navbar: { + docsVersionDropdown: { + dropdownItemsAfter: [ + { + label: "1.x", + href: "https://v1.nonebot.dev/", + }, + ], + }, + socialLinks: [ + { + icon: ["fab", "github"], + href: "https://github.com/nonebot/nonebot2", + }, + ], + }, + footer: { + socialLinks: [ + { + icon: ["fab", "github"], + href: "https://github.com/nonebot/nonebot2", + }, + { + icon: ["fab", "qq"], + href: "https://jq.qq.com/?_wv=1027&k=5OFifDh", + }, + { + icon: ["fab", "telegram"], + href: "https://t.me/botuniverse", + }, + { + icon: ["fab", "discord"], + href: "https://discord.gg/VKtE6Gdc4h", + }, + ], + }, +}; + +// theme config +/** @type {import('@nullbot/docusaurus-preset-nonepress').ThemeConfig} */ +const themeConfig = { + colorMode, + navbar, + footer, + prism, + algolia, + nonepress, +}; + /** @type {import('@docusaurus/types').Config} */ -const config = { +const siteConfig = { title: "NoneBot", tagline: "跨平台 Python 异步机器人框架", - url: "https://nonebot.dev", - baseUrl: process.env.BASE_URL || "/", - onBrokenLinks: "throw", - onBrokenMarkdownLinks: "warn", favicon: "icons/favicon.ico", + + // Set the production url of your site here + url: "https://nonebot.dev", + // Set the // pathname under which your site is served + // For GitHub pages deployment, it is often '//' + baseUrl: process.env.BASE_URL || "/", + + // GitHub pages deployment config. + // If you aren't using GitHub pages, you don't need these. organizationName: "nonebot", // Usually your GitHub org/user name. projectName: "nonebot2", // Usually your repo name. + + onBrokenLinks: "throw", + onBrokenMarkdownLinks: "warn", + + // Even if you don't use internalization, you can use this field to set useful + // metadata like html lang. For example, if your site is Chinese, you may want + // to replace "en" with "zh-Hans". i18n: { defaultLocale: "zh-Hans", locales: ["zh-Hans"], - localeConfigs: { - "zh-Hans": { label: "简体中文" }, - }, }, scripts: [ @@ -34,8 +234,8 @@ const config = { presets: [ [ - "docusaurus-preset-nonepress", - /** @type {import('docusaurus-preset-nonepress').Options} */ + "@nullbot/docusaurus-preset-nonepress", + /** @type {import('@nullbot/docusaurus-preset-nonepress').Options} */ ({ docs: { sidebarPath: require.resolve("./sidebars.js"), @@ -49,160 +249,42 @@ const config = { // "**/*.test.{js,jsx,ts,tsx}", // "**/__tests__/**", // ], + // async sidebarItemsGenerator({ + // isCategoryIndex: defaultCategoryIndexMatcher, + // defaultSidebarItemsGenerator, + // ...args + // }) { + // return defaultSidebarItemsGenerator({ + // ...args, + // isCategoryIndex(doc) { + // // disable category index convention for generated API docs + // if ( + // doc.directories.length > 0 && + // doc.directories.at(-1) === "api" + // ) { + // return false; + // } + // return defaultCategoryIndexMatcher(doc); + // }, + // }); + // }, }, + // theme: { + // customCss: require.resolve("./src/css/custom.css"), + // }, sitemap: { changefreq: "daily", priority: 0.5, }, + gtag: { + trackingID: "G-MRS1GMZG0F", + }, }), ], ], + plugins: [require("./src/plugins/webpack-plugin.cjs")], - themeConfig: - /** @type {import('docusaurus-preset-nonepress').ThemeConfig} */ - ({ - colorMode: { - defaultMode: "light", - }, - logo: { - alt: "", - src: "logo.png", - href: "/", - target: "_self", - }, - navbar: { - hideOnScroll: true, - items: [ - { - label: "指南", - type: "docsMenu", - category: "tutorial", - }, - { - label: "深入", - type: "docsMenu", - category: "appendices", - }, - { - label: "进阶", - type: "docsMenu", - category: "advanced", - }, - { - label: "API", - type: "docLink", - docId: "api/index", - }, - { - label: "更多", - type: "dropdown", - to: "/store", - items: [ - { - label: "最佳实践", - type: "docLink", - docId: "best-practice/scheduler", - }, - { - label: "开发者", - type: "docLink", - docId: "developer/plugin-publishing", - }, - { label: "社区", type: "docLink", docId: "community/contact" }, - { label: "开源之夏", type: "docLink", docId: "ospp/2023" }, - { label: "商店", to: "/store" }, - { label: "更新日志", to: "/changelog" }, - { label: "论坛", href: "https://discussions.nonebot.dev" }, - ], - }, - { - icon: ["fab", "github"], - href: "https://github.com/nonebot/nonebot2", - }, - ], - docsVersionItemAfter: [ - { - label: "2.0.0a16", - href: "https://61d3d9dbcadf413fd3238e89--nonebot2.netlify.app/", - }, - { - label: "1.x", - href: "https://v1.nonebot.dev/", - }, - ], - }, - hideableSidebar: true, - footer: { - copyright: `Copyright © ${new Date().getFullYear()} NoneBot. All rights reserved.`, - iconLinks: [ - { - icon: ["fab", "github"], - href: "https://github.com/nonebot/nonebot2", - description: "GitHub", - }, - { - icon: ["fab", "qq"], - href: "https://jq.qq.com/?_wv=1027&k=5OFifDh", - }, - { - icon: ["fab", "telegram"], - href: "https://t.me/botuniverse", - }, - { - icon: ["fab", "discord"], - href: "https://discord.gg/VKtE6Gdc4h", - }, - ], - links: [ - { - title: "Learn", - icon: ["fas", "book"], - items: [ - { label: "Introduction", to: "/docs/" }, - // { label: "QuickStart", to: "/docs/quick-start" }, - { label: "Changelog", to: "/changelog" }, - ], - }, - { - title: "NoneBot Team", - icon: ["fas", "user-friends"], - items: [ - { - label: "Homepage", - href: "https://nonebot.dev", - }, - { - label: "NoneBot V1", - href: "https://docs.nonebot.dev", - }, - { label: "NoneBot CLI", href: "https://cli.nonebot.dev" }, - ], - }, - { - title: "Related", - icon: ["fas", "external-link-alt"], - items: [ - { label: "OneBot", href: "https://onebot.dev/" }, - { label: "go-cqhttp", href: "https://docs.go-cqhttp.org/" }, - { label: "Mirai", href: "https://mirai.mamoe.net/" }, - ], - }, - ], - }, - prism: { - theme: lightCodeTheme, - darkTheme: darkCodeTheme, - additionalLanguages: ["docker", "ini"], - }, - algolia: { - appId: "X0X5UACHZQ", - apiKey: "ac03e1ac2bd0812e2ea38c0cc1ea38c5", - indexName: "nonebot", - contextualSearch: true, - }, - tailwindConfig: require("./tailwind.config"), - customCss: [require.resolve("./src/css/custom.css")], - }), + themeConfig, }; -module.exports = config; +module.exports = siteConfig; diff --git a/website/package.json b/website/package.json index 36339e47..bc92fd51 100644 --- a/website/package.json +++ b/website/package.json @@ -11,7 +11,7 @@ "license": "MIT", "scripts": { "docusaurus": "docusaurus", - "start": "docusaurus start", + "start": "docusaurus start --host 0.0.0.0 --port 3000", "build": "docusaurus build", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", @@ -22,27 +22,23 @@ "typecheck": "tsc" }, "dependencies": { - "@docusaurus/core": "2.0.0-beta.9", - "@mdx-js/react": "^1.6.21", - "@svgr/webpack": "^5.5.0", - "clsx": "^1.1.1", - "copy-to-clipboard": "^3.3.1", - "docusaurus-preset-nonepress": "canary", - "file-loader": "^6.2.0", - "prism-react-renderer": "^1.2.1", + "@docusaurus/core": "^2.4.1", + "@mdx-js/react": "^1.6.22", + "@nullbot/docusaurus-preset-nonepress": "^2.1.2", + "clsx": "^1.2.1", + "copy-text-to-clipboard": "^3.0.1", + "prism-react-renderer": "^1.3.5", "raw-loader": "^4.0.2", "react": "^17.0.1", "react-color": "^2.19.3", "react-dom": "^17.0.1", - "react-use-pagination": "^2.0.1", - "resize-observer-polyfill": "^1.5.1", - "url-loader": "^4.1.1" + "react-use-pagination": "^2.0.1" }, "devDependencies": { - "@docusaurus/module-type-aliases": "2.0.0-beta.9", - "@tsconfig/docusaurus": "^1.0.4", - "asciinema-player": "^3.0.0-rc.1", - "typescript": "^4.3.5" + "@docusaurus/module-type-aliases": "^2.4.1", + "@tsconfig/docusaurus": "^1.0.5", + "asciinema-player": "^3.5.0", + "typescript": "^4.7.4" }, "browserslist": { "production": [ @@ -55,5 +51,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "engines": { + "node": ">=16.14" } } diff --git a/website/sidebars.js b/website/sidebars.js index d642caf5..ec07dcbf 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -87,7 +87,7 @@ const sidebars = { { type: "category", label: "开源之夏", - collapsible: false, + collapsible: true, items: [ { type: "autogenerated", @@ -102,8 +102,28 @@ const sidebars = { items: [ { type: "link", - label: "商店", - href: "/store", + label: "插件商店", + href: "/store/plugins", + }, + { + type: "link", + label: "适配器商店", + href: "/store/adapters", + }, + { + type: "link", + label: "驱动器商店", + href: "/store/drivers", + }, + { + type: "link", + label: "机器人商店", + href: "/store/bots", + }, + { + type: "link", + label: "Awesome NoneBot", + href: "https://awesome.nonebot.dev", }, { type: "link", diff --git a/website/src/components/Adapter.tsx b/website/src/components/Adapter.tsx deleted file mode 100644 index 71fe3c8d..00000000 --- a/website/src/components/Adapter.tsx +++ /dev/null @@ -1,260 +0,0 @@ -import clsx from "clsx"; -import React, { useRef, useState } from "react"; -import { ChromePicker } from "react-color"; -import { usePagination } from "react-use-pagination"; - -import adapters from "../../static/adapters.json"; -import { Tag, useFilteredObjs } from "../libs/store"; -import Card from "./Card"; -import Modal from "./Modal"; -import ModalAction from "./ModalAction"; -import ModalContent from "./ModalContent"; -import ModalTitle from "./ModalTitle"; -import Paginate from "./Paginate"; -import TagComponent from "./Tag"; - -export default function Adapter(): JSX.Element { - const [modalOpen, setModalOpen] = useState(false); - const { - filter, - setFilter, - filteredObjs: filteredAdapters, - } = useFilteredObjs(adapters); - - const props = usePagination({ - totalItems: filteredAdapters.length, - initialPageSize: 10, - }); - const { startIndex, endIndex } = props; - const currentAdapters = filteredAdapters.slice(startIndex, endIndex + 1); - - const [form, setForm] = useState<{ - name: string; - desc: string; - projectLink: string; - moduleName: string; - homepage: string; - }>({ name: "", desc: "", projectLink: "", moduleName: "", homepage: "" }); - - const ref = useRef(null); - const [tags, setTags] = useState([]); - const [label, setLabel] = useState(""); - const [color, setColor] = useState("#ea5252"); - - const urlEncode = (str: string) => - encodeURIComponent(str).replace(/%2B/gi, "+"); - - const onSubmit = () => { - setModalOpen(false); - const queries: { key: string; value: string }[] = [ - { key: "template", value: "adapter_publish.yml" }, - { key: "title", value: form.name && `Adapter: ${form.name}` }, - { key: "labels", value: "Adapter" }, - { key: "name", value: form.name }, - { key: "description", value: form.desc }, - { key: "pypi", value: form.projectLink }, - { key: "module", value: form.moduleName }, - { key: "homepage", value: form.homepage }, - { key: "tags", value: JSON.stringify(tags) }, - ]; - const urlQueries = queries - .filter((query) => !!query.value) - .map((query) => `${query.key}=${urlEncode(query.value)}`) - .join("&"); - window.open(`https://github.com/nonebot/nonebot2/issues/new?${urlQueries}`); - }; - const onChange = (event) => { - const target = event.target; - const value = target.type === "checkbox" ? target.checked : target.value; - const name = target.name; - - setForm({ - ...form, - [name]: value, - }); - event.preventDefault(); - }; - const onChangeLabel = (event) => { - setLabel(event.target.value); - }; - const onChangeColor = (color) => { - setColor(color.hex); - }; - const validateTag = () => { - return label.length >= 1 && label.length <= 10; - }; - const newTag = () => { - if (tags.length >= 3) { - return; - } - if (validateTag()) { - const tag = { label, color }; - setTags([...tags, tag]); - } - }; - const delTag = (index: number) => { - setTags(tags.filter((_, i) => i !== index)); - }; - const insertTagType = (text: string) => { - setLabel(text + label); - ref.current.value = text + label; - }; - - return ( - <> -
- setFilter(event.target.value)} - /> - -
-
- -
-
- {currentAdapters.map((adapter, index) => ( - - ))} -
-
- -
- - - -
-
- - - - - -
-
-
- -
-
- - -
- Type: - - -
-
- - -
-
-
- - - - -
- - ); -} diff --git a/website/src/components/Asciinema/container.tsx b/website/src/components/Asciinema/container.tsx index 68ca548f..8ccb7bc9 100644 --- a/website/src/components/Asciinema/container.tsx +++ b/website/src/components/Asciinema/container.tsx @@ -1,6 +1,7 @@ -import * as AsciinemaPlayer from "asciinema-player"; import React, { useEffect, useRef } from "react"; +import * as AsciinemaPlayer from "asciinema-player"; + export type AsciinemaOptions = { cols: number; rows: number; @@ -16,7 +17,7 @@ export type AsciinemaOptions = { fontSize: string; }; -export type AsciinemaProps = { +export type Props = { url: string; options?: Partial; }; @@ -24,12 +25,12 @@ export type AsciinemaProps = { export default function AsciinemaContainer({ url, options = {}, -}: AsciinemaProps): JSX.Element { +}: Props): JSX.Element { const ref = useRef(null); useEffect(() => { AsciinemaPlayer.create(url, ref.current, options); - }, []); + }, [url, options]); - return
; + return
; } diff --git a/website/src/components/Asciinema/index.tsx b/website/src/components/Asciinema/index.tsx index e8f8b04e..f7c98af4 100644 --- a/website/src/components/Asciinema/index.tsx +++ b/website/src/components/Asciinema/index.tsx @@ -1,15 +1,24 @@ -import "asciinema-player/dist/bundle/asciinema-player.css"; - -import "./styles.css"; - import React from "react"; +import "asciinema-player/dist/bundle/asciinema-player.css"; import BrowserOnly from "@docusaurus/BrowserOnly"; -export default function Asciinema(props): JSX.Element { +import "./styles.css"; +import type { Props } from "./container"; + +export type { Props } from "./container"; + +export default function Asciinema(props: Props): JSX.Element { return ( - }> + + Asciinema cast + + } + > {() => { + // eslint-disable-next-line @typescript-eslint/no-var-requires const AsciinemaContainer = require("./container.tsx").default; return ; }} diff --git a/website/src/components/Asciinema/styles.css b/website/src/components/Asciinema/styles.css index 48328460..8cd4b5fe 100644 --- a/website/src/components/Asciinema/styles.css +++ b/website/src/components/Asciinema/styles.css @@ -1,3 +1,7 @@ -.asciinema-player svg { - display: inline-block; +.ap-player svg { + @apply inline-block; +} + +.ap-container { + @apply w-full my-4; } diff --git a/website/src/components/Bot.tsx b/website/src/components/Bot.tsx deleted file mode 100644 index eaf20349..00000000 --- a/website/src/components/Bot.tsx +++ /dev/null @@ -1,233 +0,0 @@ -import clsx from "clsx"; -import React, { useRef, useState } from "react"; -import { ChromePicker } from "react-color"; -import { usePagination } from "react-use-pagination"; - -import bots from "../../static/bots.json"; -import { Tag, useFilteredObjs } from "../libs/store"; -import Card from "./Card"; -import Modal from "./Modal"; -import ModalAction from "./ModalAction"; -import ModalContent from "./ModalContent"; -import ModalTitle from "./ModalTitle"; -import Paginate from "./Paginate"; -import TagComponent from "./Tag"; - -export default function Bot(): JSX.Element { - const [modalOpen, setModalOpen] = useState(false); - const { - filter, - setFilter, - filteredObjs: filteredBots, - } = useFilteredObjs(bots); - - const props = usePagination({ - totalItems: filteredBots.length, - initialPageSize: 10, - }); - const { startIndex, endIndex } = props; - const currentBots = filteredBots.slice(startIndex, endIndex + 1); - - const [form, setForm] = useState<{ - name: string; - desc: string; - homepage: string; - }>({ name: "", desc: "", homepage: "" }); - - const ref = useRef(null); - const [tags, setTags] = useState([]); - const [label, setLabel] = useState(""); - const [color, setColor] = useState("#ea5252"); - - const urlEncode = (str: string) => - encodeURIComponent(str).replace(/%2B/gi, "+"); - - const onSubmit = () => { - setModalOpen(false); - const queries: { key: string; value: string }[] = [ - { key: "template", value: "bot_publish.yml" }, - { key: "title", value: form.name && `Bot: ${form.name}` }, - { key: "labels", value: "Bot" }, - { key: "name", value: form.name }, - { key: "description", value: form.desc }, - { key: "homepage", value: form.homepage }, - { key: "tags", value: JSON.stringify(tags) }, - ]; - const urlQueries = queries - .filter((query) => !!query.value) - .map((query) => `${query.key}=${urlEncode(query.value)}`) - .join("&"); - window.open(`https://github.com/nonebot/nonebot2/issues/new?${urlQueries}`); - }; - const onChange = (event) => { - const target = event.target; - const value = target.type === "checkbox" ? target.checked : target.value; - const name = target.name; - - setForm({ - ...form, - [name]: value, - }); - event.preventDefault(); - }; - const onChangeLabel = (event) => { - setLabel(event.target.value); - }; - const onChangeColor = (color) => { - setColor(color.hex); - }; - const validateTag = () => { - return label.length >= 1 && label.length <= 10; - }; - const newTag = () => { - if (tags.length >= 3) { - return; - } - if (validateTag()) { - const tag = { label, color }; - setTags([...tags, tag]); - } - }; - const delTag = (index: number) => { - setTags(tags.filter((_, i) => i !== index)); - }; - const insertTagType = (text: string) => { - setLabel(text + label); - ref.current.value = text + label; - }; - - return ( - <> -
- setFilter(event.target.value)} - /> - -
-
- -
-
- {currentBots.map((bot, index) => ( - - ))} -
-
- -
- - - -
-
- - - -
-
-
- -
-
- - -
- Type: - - -
-
- - -
-
-
- - - - -
- - ); -} diff --git a/website/src/components/Card/index.tsx b/website/src/components/Card/index.tsx deleted file mode 100644 index 3544cf20..00000000 --- a/website/src/components/Card/index.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import clsx from "clsx"; -import copy from "copy-to-clipboard"; -import React, { useState } from "react"; - -import Link from "@docusaurus/Link"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; - -import type { Obj } from "../../libs/store"; -import Tag from "../Tag"; - -export default function Card({ - module_name, - project_link, - name, - desc, - author, - homepage, - tags, - is_official, - action, - actionDisabled = false, - actionLabel = "点击复制安装命令", -}: Obj & { - action?: string; - actionLabel?: string; - actionDisabled?: boolean; -}): JSX.Element { - const isGithub = /^https:\/\/github.com\/[^/]+\/[^/]+/.test(homepage); - const [copied, setCopied] = useState(false); - - const copyAction = () => { - copy(action); - setCopied(true); - setTimeout(() => setCopied(false), 2000); - }; - - return ( -
-
- - {name} - {is_official && ( - - )} - - {homepage && ( - - {isGithub ? ( - - ) : ( - - )} - - )} -
- {tags && ( -
- {tags.map((tag, index) => ( - - ))} -
- )} - {/* FIXME: full height */} - {desc && ( -
{desc}
- )} - {project_link && ( -
- - {project_link} -
- )} - {module_name && ( -
- - {module_name} -
- )} - {/* TODO: add user avatar */} - {/* link: https://github.com/.png */} - {author && ( -
- - {author} -
- )} - {action && actionLabel && ( - - )} -
- ); -} diff --git a/website/src/components/Driver.tsx b/website/src/components/Driver.tsx deleted file mode 100644 index 85b648c7..00000000 --- a/website/src/components/Driver.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import React from "react"; -import { usePagination } from "react-use-pagination"; - -import drivers from "../../static/drivers.json"; -import { useFilteredObjs } from "../libs/store"; -import Card from "./Card"; -import Paginate from "./Paginate"; - -export default function Driver(): JSX.Element { - const { - filter, - setFilter, - filteredObjs: filteredDrivers, - } = useFilteredObjs(drivers); - - const props = usePagination({ - totalItems: filteredDrivers.length, - initialPageSize: 10, - }); - const { startIndex, endIndex } = props; - const currentDrivers = filteredDrivers.slice(startIndex, endIndex + 1); - - return ( - <> -
- setFilter(event.target.value)} - /> - -
-
- -
-
- {currentDrivers.map((driver, index) => ( - - ))} -
-
- -
- - ); -} diff --git a/website/src/components/Hero.tsx b/website/src/components/Hero.tsx deleted file mode 100644 index 7d6c6112..00000000 --- a/website/src/components/Hero.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import React, { PropsWithChildren } from "react"; - -import Link from "@docusaurus/Link"; -import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import Logo from "@theme/Logo"; - -export function Hero(): JSX.Element { - const { siteConfig } = useDocusaurusContext(); - - return ( -
-
- -

- None - Bot -

-

- {siteConfig.tagline} -

-
- - 开始使用 - -
-
-
-
- -
-
-
- ); -} - -export type Feature = { - readonly title: string; - readonly tagline?: string; - readonly description?: string; - readonly annotaion?: string; -}; - -export function HeroFeature(props: PropsWithChildren): JSX.Element { - const { title, tagline, description, annotaion, children } = props; - - return ( - <> -

- {tagline} -

-

- {title} -

-

{description}

- {children} -

{annotaion}

- - ); -} - -export function HeroFeatureSingle( - props: PropsWithChildren -): JSX.Element { - return ( -
- -
- ); -} - -export function HeroFeatureDouble( - props: PropsWithChildren<{ features: [Feature, Feature] }> -): JSX.Element { - const { - features: [feature1, feature2], - children, - } = props; - - let children1, children2; - if (Array.isArray(children) && children.length === 2) { - [children1, children2] = children; - } - - return ( -
-
- -
-
- -
-
- ); -} diff --git a/website/src/components/Home/Feature.tsx b/website/src/components/Home/Feature.tsx new file mode 100644 index 00000000..d631764b --- /dev/null +++ b/website/src/components/Home/Feature.tsx @@ -0,0 +1,174 @@ +import React from "react"; + +import CodeBlock from "@theme/CodeBlock"; + +export type Feature = { + title: string; + tagline?: string; + description?: string; + annotaion?: string; + children?: React.ReactNode; +}; + +export function HomeFeature({ + title, + tagline, + description, + annotaion, + children, +}: Feature): JSX.Element { + return ( +
+

+ {tagline} +

+

+ {title} +

+

{description}

+ {children} +

{annotaion}

+
+ ); +} + +function HomeFeatureSingleColumn(props: Feature): JSX.Element { + return ( +
+ +
+ ); +} + +function HomeFeatureDoubleColumn({ + features: [feature1, feature2], + children, +}: { + features: [Feature, Feature]; + children?: [React.ReactNode, React.ReactNode]; +}): JSX.Element { + const [children1, children2] = children ?? []; + + return ( +
+ {children1} + {children2} +
+ ); +} + +function HomeFeatures(): JSX.Element { + return ( + <> + + + {[ + "$ pipx install nb-cli", + "$ nb", + // "d8b db .d88b. d8b db d88888b d8888b. .d88b. d888888b", + // "888o 88 .8P Y8. 888o 88 88' 88 `8D .8P Y8. `~~88~~'", + // "88V8o 88 88 88 88V8o 88 88ooooo 88oooY' 88 88 88", + // "88 V8o88 88 88 88 V8o88 88~~~~~ 88~~~b. 88 88 88", + // "88 V888 `8b d8' 88 V888 88. 88 8D `8b d8' 88", + // "VP V8P `Y88P' VP V8P Y88888P Y8888P' `Y88P' YP", + "[?] What do you want to do?", + "❯ Create a NoneBot project.", + " Run the bot in current folder.", + " Manage bot driver.", + " Manage bot adapters.", + " Manage bot plugins.", + " ...", + ].join("\n")} + + + + + {[ + "import nonebot", + "# 加载一个插件", + 'nonebot.load_plugin("path.to.your.plugin")', + "# 从文件夹加载插件", + 'nonebot.load_plugins("plugins")', + "# 从配置文件加载多个插件", + 'nonebot.load_from_json("plugins.json")', + 'nonebot.load_from_toml("pyproject.toml")', + ].join("\n")} + + + {[ + "import nonebot", + "# OneBot", + "from nonebot.adapters.onebot.v11 import Adapter as OneBotAdapter", + "# QQ 频道", + "from nonebot.adapters.qqguild import Adapter as QQGuildAdapter", + "driver = nonebot.get_driver()", + "driver.register_adapter(OneBotAdapter)", + "driver.register_adapter(QQGuildAdapter)", + ].join("\n")} + + + + + {[ + "from nonebot import on_message", + "# 注册一个消息响应器", + "matcher = on_message()", + "# 注册一个消息处理器", + "# 并重复收到的消息", + "@matcher.handle()", + "async def handler(event: Event) -> None:", + " await matcher.send(event.get_message())", + ].join("\n")} + + + {[ + "from nonebot import on_command", + "# 注册一个命令响应器", + 'matcher = on_command("help", alias={"帮助"})', + "# 注册一个命令处理器", + "# 通过依赖注入获得命令名以及参数", + "@matcher.handle()", + "async def handler(cmd = Command(), arg = CommandArg()) -> None:", + " await matcher.finish()", + ].join("\n")} + + + + ); +} + +export default React.memo(HomeFeatures); diff --git a/website/src/components/Home/Hero.tsx b/website/src/components/Home/Hero.tsx new file mode 100644 index 00000000..8c1dc036 --- /dev/null +++ b/website/src/components/Home/Hero.tsx @@ -0,0 +1,69 @@ +import React, { useCallback, useEffect, useRef, useState } from "react"; + +import Link from "@docusaurus/Link"; +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useNonepressThemeConfig } from "@nullbot/docusaurus-theme-nonepress/client"; +// @ts-expect-error: we need to make package have type: module +import copy from "copy-text-to-clipboard"; + +import IconCopy from "@theme/Icon/Copy"; +import IconSuccess from "@theme/Icon/Success"; + +function HomeHeroInstallButton(): JSX.Element { + const code = "pipx run nb-cli create"; + + const [isCopied, setIsCopied] = useState(false); + const copyTimeout = useRef(undefined); + + const handleCopyCode = useCallback(() => { + copy(code); + setIsCopied(true); + copyTimeout.current = window.setTimeout(() => { + setIsCopied(false); + }, 1500); + }, [code]); + + useEffect(() => () => window.clearTimeout(copyTimeout.current), []); + + return ( + + ); +} + +function HomeHero(): JSX.Element { + const { + siteConfig: { tagline }, + } = useDocusaurusContext(); + const { + navbar: { logo }, + } = useNonepressThemeConfig(); + + return ( +
+ {logo!.alt} +

+ None + Bot +

+

{tagline}

+
+ + 开始使用 + + +
+
+ +
+
+ ); +} + +export default React.memo(HomeHero); diff --git a/website/src/components/Home/index.tsx b/website/src/components/Home/index.tsx new file mode 100644 index 00000000..c12db92b --- /dev/null +++ b/website/src/components/Home/index.tsx @@ -0,0 +1,14 @@ +import React from "react"; + +import "./styles.css"; +import HomeFeatures from "./Feature"; +import HomeHero from "./Hero"; + +export default function HomeContent(): JSX.Element { + return ( +
+ + +
+ ); +} diff --git a/website/src/components/Home/styles.css b/website/src/components/Home/styles.css new file mode 100644 index 00000000..fd15a2b9 --- /dev/null +++ b/website/src/components/Home/styles.css @@ -0,0 +1,41 @@ +.home { + &-container { + @apply -mt-16; + } + + &-hero { + @apply relative flex flex-col items-center justify-center gap-4 h-screen; + + &-logo { + @apply h-48 w-auto; + } + + &-title { + @apply text-5xl font-normal tracking-tight; + } + + &-tagline { + @apply text-sm font-medium uppercase tracking-wide text-base-content/70; + } + + &-actions { + @apply flex flex-col sm:flex-row gap-4; + } + + &-copy { + @apply font-normal normal-case text-base-content/70; + } + + &-next { + @apply absolute bottom-4; + + & svg { + @apply animate-bounce text-primary text-4xl; + } + } + } + + &-codeblock { + @apply inline-block !max-w-[600px]; + } +} diff --git a/website/src/components/Messenger/index.tsx b/website/src/components/Messenger/index.tsx index ad90c1f0..d5793b8a 100644 --- a/website/src/components/Messenger/index.tsx +++ b/website/src/components/Messenger/index.tsx @@ -1,40 +1,56 @@ -import clsx from "clsx"; import React from "react"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import Logo from "@theme/Logo"; +import clsx from "clsx"; -import styles from "./styles.module.css"; +import useBaseUrl from "@docusaurus/useBaseUrl"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useNonepressThemeConfig } from "@nullbot/docusaurus-theme-nonepress/client"; + +import "./styles.css"; +import ThemedImage from "@theme/ThemedImage"; export type Message = { - position?: "left" | "right"; msg: string; + position?: "left" | "right"; + monospace?: boolean; }; function MessageBox({ msg, - isRight, -}: { - msg: string; - isRight: boolean; -}): JSX.Element { + position = "left", + monospace = false, +}: Message): JSX.Element { + const { + navbar: { logo }, + } = useNonepressThemeConfig(); + const sources = { + light: useBaseUrl(logo!.src), + dark: useBaseUrl(logo!.srcDark || logo!.src), + }; + + const isRight = position === "right"; + return ( -
- {isRight ? ( -
- +
+
+
+ {isRight ? ( + + ) : ( + + )}
- ) : ( -
- -
- )} +
").replace(/ /g, " "), }} @@ -48,62 +64,55 @@ export default function Messenger({ }: { msgs?: Message[]; }): JSX.Element { - const isRight = (msg: Message): boolean => msg.position === "right"; - return ( -
-
-
+
+
+
-
- NoneBot +
+ NoneBot
-
- +
+
-
+
{msgs.map((msg, i) => ( - + ))}
-
-
-
+
+
+
-
-
-
-
+
+
-
+
-
+
-
+
-
+
-
+
diff --git a/website/src/components/Messenger/styles.css b/website/src/components/Messenger/styles.css new file mode 100644 index 00000000..1d9f0a67 --- /dev/null +++ b/website/src/components/Messenger/styles.css @@ -0,0 +1,66 @@ +.messenger { + &-container { + @apply block w-full my-4 overflow-hidden; + @apply rounded-lg outline-none bg-base-200; + @apply transition-[background-color] duration-500; + } + + &-title { + @apply flex items-center h-12 px-4 bg-info text-white; + + &-back { + @apply text-left text-base grow; + } + + &-name { + @apply flex-initial grow-0 text-xl font-bold; + } + + &-more { + @apply text-right text-base grow; + } + } + + &-chat { + @apply p-4 min-h-[150px]; + + &-avatar { + @apply !flex items-center justify-center; + @apply w-10 rounded-full; + + &-user { + @apply bg-info text-white; + } + } + + &-bubble { + @apply bg-base-100 text-base-content [word-break:break-word]; + @apply transition-[color,background-color] duration-500; + } + } + + &-footer { + @apply px-4; + + &-action { + @apply flex items-center gap-2; + + &-input { + @apply flex-1; + + & > input { + @apply transition-[color,background-color] duration-500; + } + } + + &-send { + @apply flex-initial w-fit; + } + } + + &-tools { + @apply grid grid-cols-6 items-center py-1; + @apply text-center text-base text-base-content/60; + } + } +} diff --git a/website/src/components/Messenger/styles.module.css b/website/src/components/Messenger/styles.module.css deleted file mode 100644 index 9a9ccd16..00000000 --- a/website/src/components/Messenger/styles.module.css +++ /dev/null @@ -1,40 +0,0 @@ -.message { - @apply flex flex-row flex-wrap justify-start; -} - -.messageRight { - @apply !justify-end; -} - -.message .messageAvatar { - @apply relative inline-flex items-center justify-center text-center align-middle h-9 w-9 rounded-full; -} - -.message .messageBox { - @apply relative w-fit max-w-[55%] px-2 py-[0.375rem] mx-3 my-2 rounded-lg bg-light; -} -:global(.dark) .message .messageBox { - @apply !bg-dark; -} - -.message .messageBox::after { - content: ""; - border-bottom: 7px solid; - @apply absolute top-0 right-full w-2 h-3 text-light rounded-bl-lg; -} -:global(.dark) .message .messageBox::after { - @apply !text-dark; -} -.message.messageRight .messageBox::after { - @apply !left-full !right-auto !rounded-bl-[0] !rounded-br-lg; -} - -.messageSendButton { - -webkit-tap-highlight-color: transparent; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} diff --git a/website/src/components/Modal/index.tsx b/website/src/components/Modal/index.tsx deleted file mode 100644 index b6dc8dce..00000000 --- a/website/src/components/Modal/index.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import clsx from "clsx"; -import React from "react"; - -export default function Modal({ - active, - setActive, - children, -}: { - active: boolean; - setActive: (active: boolean) => void; - children: React.ReactNode; -}): JSX.Element { - return ( - <> - {/* overlay */} -
setActive(false)} - > -
-
- {/* modal */} -
-
-
- {children} -
-
-
- - ); -} diff --git a/website/src/components/ModalAction/index.tsx b/website/src/components/ModalAction/index.tsx deleted file mode 100644 index e77e4add..00000000 --- a/website/src/components/ModalAction/index.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from "react"; - -export default function ModalAction({ - children, -}: { - children: React.ReactNode; -}): JSX.Element { - return
{children}
; -} diff --git a/website/src/components/ModalContent/index.tsx b/website/src/components/ModalContent/index.tsx deleted file mode 100644 index 4c2fab51..00000000 --- a/website/src/components/ModalContent/index.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from "react"; - -export default function ModalContent({ - children, -}: { - children: React.ReactNode; -}): JSX.Element { - return
{children}
; -} diff --git a/website/src/components/ModalTitle/index.tsx b/website/src/components/ModalTitle/index.tsx deleted file mode 100644 index d0f00b56..00000000 --- a/website/src/components/ModalTitle/index.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from "react"; - -export default function ModalTitle({ title }: { title: string }): JSX.Element { - return ( -
- {title} -
- ); -} diff --git a/website/src/components/Paginate/index.tsx b/website/src/components/Paginate/index.tsx index 7bbedc28..0d1fab5b 100644 --- a/website/src/components/Paginate/index.tsx +++ b/website/src/components/Paginate/index.tsx @@ -1,39 +1,55 @@ +import React, { useCallback } from "react"; + import clsx from "clsx"; -import React, { useCallback, useState } from "react"; -import { usePagination } from "react-use-pagination"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import type { usePagination } from "react-use-pagination"; -import { useContentWidth } from "../../libs/width"; -import styles from "./styles.module.css"; +import "./styles.css"; + +const MAX_LENGTH = 7; + +export type Props = Pick< + ReturnType, + | "totalPages" + | "currentPage" + | "setNextPage" + | "setPreviousPage" + | "setPage" + | "previousEnabled" + | "nextEnabled" +> & { + className?: string; +}; export default function Paginate({ + className, totalPages, + currentPage, setPreviousPage, setNextPage, setPage, - currentPage, previousEnabled, nextEnabled, -}: ReturnType): JSX.Element { - const [containerElement, setContainerElement] = useState( - null - ); +}: Props): JSX.Element { + // const [containerElement, setContainerElement] = useState( + // null + // ); - const ref = useCallback( - (element: HTMLElement | null) => { - setContainerElement(element); - }, - [setContainerElement] - ); + // const ref = useCallback( + // (element: HTMLElement | null) => { + // setContainerElement(element); + // }, + // [setContainerElement] + // ); - const maxWidth = useContentWidth( - containerElement?.parentElement ?? undefined - ); - const maxLength = Math.min( - (maxWidth && Math.floor(maxWidth / 50) - 2) || totalPages, - totalPages - ); + // const maxWidth = useContentWidth( + // containerElement?.parentElement ?? undefined + // ); + // const maxLength = Math.min( + // (maxWidth && Math.floor(maxWidth / 50) - 2) || totalPages, + // totalPages + // ); const range = useCallback((start: number, end: number) => { const result = []; @@ -47,12 +63,12 @@ export default function Paginate({ const pages: (React.ReactNode | number)[] = []; const ellipsis = ; - const even = maxLength % 2 === 0 ? 1 : 0; - const left = Math.floor(maxLength / 2); + const even = MAX_LENGTH % 2 === 0 ? 1 : 0; + const left = Math.floor(MAX_LENGTH / 2); const right = totalPages - left + even + 1; currentPage = currentPage + 1; - if (totalPages <= maxLength) { + if (totalPages <= MAX_LENGTH) { pages.push(...range(1, totalPages)); } else if (currentPage > left && currentPage < right) { const firstItem = 1; @@ -74,34 +90,44 @@ export default function Paginate({ } return ( -