diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 57251879..9c60473c 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -23,10 +23,12 @@ module.exports = { themeConfig: { repo: "nonebot/nonebot", + docsDir: "docs", + docsBranch: "dev2", editLinks: true, editLinkText: "在 GitHub 上编辑此页", - docsDir: "docs", lastUpdated: "上次更新", + smoothScroll: true, nav: [{ text: "API", link: "/api/" }], sidebar: { "/api/": [ diff --git a/docs/api/config.md b/docs/api/config.md index c160446d..fc1c446b 100644 --- a/docs/api/config.md +++ b/docs/api/config.md @@ -1,5 +1,11 @@ # NoneBot.config 模块 +## 配置 + +NoneBot 使用 [pydantic](https://pydantic-docs.helpmanual.io/) 以及 [python-dotenv](https://saurabh-kumar.com/python-dotenv/) 来读取配置。 + +配置项需符合特殊格式或 json 序列化格式。详情见 [pydantic Field Type](https://pydantic-docs.helpmanual.io/usage/types/) 文档 + ## _class_ `Env` @@ -29,7 +35,8 @@ NoneBot 主要配置。大小写不敏感。 -除了 NoneBot 的配置项外,还可以自行添加配置项到 `.env.{environment}` 文件中。这些配置将会一起带入 `Config` 类中。 +除了 NoneBot 的配置项外,还可以自行添加配置项到 `.env.{environment}` 文件中。 +这些配置将会在 json 反序列化后一起带入 `Config` 类中。 ### `driver` @@ -90,3 +97,143 @@ NoneBot 的 HTTP 和 WebSocket 服务端监听的端口。 POST /cqhttp/ HTTP/1.1 Authorization: Bearer kSLuTF2GC2Q4q4ugm3 ``` + + +### `debug` + + +* 类型: `bool` + + +* 默认值: `False` + + +* 说明: +是否以调试模式运行 NoneBot。 + + +### `api_root` + + +* 类型: `Dict[str, str]` + + +* 默认值: `{}` + + +* 说明: +以机器人 ID 为键,上报地址为值的字典,环境变量或文件中应使用 json 序列化。 + + +* 示例: + +```plain +API_ROOT={"123456": "http://127.0.0.1:5700"} +``` + + +### `api_timeout` + + +* 类型: `Optional[float]` + + +* 默认值: `60.` + + +* 说明: +API 请求超时时间,单位: 秒。 + + +### `access_token` + + +* 类型: `Optional[str]` + + +* 默认值: `None` + + +* 说明: +API 请求所需密钥,会在调用 API 时在请求头中携带。 + + +### `superusers` + + +* 类型: `Set[int]` + + +* 默认值: `set()` + + +* 说明: +机器人超级用户。 + + +* 示例: + +```plain +SUPER_USERS=[12345789] +``` + + +### `nickname` + + +* 类型: `Union[str, Set[str]]` + + +* 默认值: `""` + + +* 说明: +机器人昵称。 + + +### `command_start` + + +* 类型: `Set[str]` + + +* 默认值: `{"/"}` + + +* 说明: +命令的起始标记,用于判断一条消息是不是命令。 + + +### `command_sep` + + +* 类型: `Set[str]` + + +* 默认值: `{"."}` + + +* 说明: +命令的分隔标记,用于将文本形式的命令切分为元组(实际的命令名)。 + + +### `session_expire_timeout` + + +* 类型: `timedelta` + + +* 默认值: `timedelta(minutes=2)` + + +* 说明: +等待用户回复的超时时间。 + + +* 示例: + +```plain +SESSION_EXPIRE_TIMEOUT=120 # 单位: 秒 +SESSION_EXPIRE_TIMEOUT=[DD ][HH:MM]SS[.ffffff] +SESSION_EXPIRE_TIMEOUT=P[DD]DT[HH]H[MM]M[SS]S # ISO 8601 +``` diff --git a/nonebot/adapters/cqhttp.py b/nonebot/adapters/cqhttp.py index 07d2305f..2519710f 100644 --- a/nonebot/adapters/cqhttp.py +++ b/nonebot/adapters/cqhttp.py @@ -67,7 +67,7 @@ class ResultStore: future.set_result(result) @classmethod - async def fetch(cls, seq: int, timeout: float) -> Dict[str, Any]: + async def fetch(cls, seq: int, timeout: Optional[float]) -> Dict[str, Any]: future = asyncio.get_event_loop().create_future() cls._futures[seq] = future try: @@ -137,7 +137,7 @@ class Bot(BaseBot): api_root += "/" headers = {} - if self.config.access_token: + if self.config.access_token is not None: headers["Authorization"] = "Bearer " + self.config.access_token try: diff --git a/nonebot/config.py b/nonebot/config.py index 2b99e459..c80cc32e 100644 --- a/nonebot/config.py +++ b/nonebot/config.py @@ -1,5 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +""" +配置 +==== + +NoneBot 使用 `pydantic`_ 以及 `python-dotenv`_ 来读取配置。 + +配置项需符合特殊格式或 json 序列化格式。详情见 `pydantic Field Type`_ 文档。 + +.. _pydantic: + https://pydantic-docs.helpmanual.io/ +.. _python-dotenv: + https://saurabh-kumar.com/python-dotenv/ +.. _pydantic Field Type: + https://pydantic-docs.helpmanual.io/usage/types/ +""" import os from pathlib import Path @@ -100,7 +115,8 @@ class Config(BaseConfig): """ NoneBot 主要配置。大小写不敏感。 - 除了 NoneBot 的配置项外,还可以自行添加配置项到 ``.env.{environment}`` 文件中。这些配置将会一起带入 ``Config`` 类中。 + 除了 NoneBot 的配置项外,还可以自行添加配置项到 ``.env.{environment}`` 文件中。 + 这些配置将会在 json 反序列化后一起带入 ``Config`` 类中。 """ # nonebot configs driver: str = "nonebot.drivers.fastapi" @@ -147,15 +163,80 @@ class Config(BaseConfig): # bot connection configs api_root: Dict[str, str] = {} + """ + - 类型: ``Dict[str, str]`` + - 默认值: ``{}`` + - 说明: + 以机器人 ID 为键,上报地址为值的字典,环境变量或文件中应使用 json 序列化。 + - 示例: + + .. code-block:: plain + + API_ROOT={"123456": "http://127.0.0.1:5700"} + """ api_timeout: Optional[float] = 60. + """ + - 类型: ``Optional[float]`` + - 默认值: ``60.`` + - 说明: + API 请求超时时间,单位: 秒。 + """ access_token: Optional[str] = None + """ + - 类型: ``Optional[str]`` + - 默认值: ``None`` + - 说明: + API 请求所需密钥,会在调用 API 时在请求头中携带。 + """ # bot runtime configs superusers: Set[int] = set() + """ + - 类型: ``Set[int]`` + - 默认值: ``set()`` + - 说明: + 机器人超级用户。 + - 示例: + + .. code-block:: plain + + SUPER_USERS=[12345789] + """ nickname: Union[str, Set[str]] = "" + """ + - 类型: ``Union[str, Set[str]]`` + - 默认值: ``""`` + - 说明: + 机器人昵称。 + """ command_start: Set[str] = {"/"} + """ + - 类型: ``Set[str]`` + - 默认值: ``{"/"}`` + - 说明: + 命令的起始标记,用于判断一条消息是不是命令。 + """ command_sep: Set[str] = {"."} + """ + - 类型: ``Set[str]`` + - 默认值: ``{"."}`` + - 说明: + 命令的分隔标记,用于将文本形式的命令切分为元组(实际的命令名)。 + """ session_expire_timeout: timedelta = timedelta(minutes=2) + """ + - 类型: ``timedelta`` + - 默认值: ``timedelta(minutes=2)`` + - 说明: + 等待用户回复的超时时间。 + - 示例: + + .. code-block:: plain + + SESSION_EXPIRE_TIMEOUT=120 # 单位: 秒 + SESSION_EXPIRE_TIMEOUT=[DD ][HH:MM]SS[.ffffff] + SESSION_EXPIRE_TIMEOUT=P[DD]DT[HH]H[MM]M[SS]S # ISO 8601 + """ # custom configs # custom configs can be assigned during nonebot.init diff --git a/nonebot/drivers/fastapi.py b/nonebot/drivers/fastapi.py index 88664fa3..7e51f92a 100644 --- a/nonebot/drivers/fastapi.py +++ b/nonebot/drivers/fastapi.py @@ -5,10 +5,9 @@ import json import logging import uvicorn -from fastapi import FastAPI, status -from fastapi.security import OAuth2PasswordBearer -from starlette.websockets import WebSocketDisconnect -from fastapi import Body, Header, Response, WebSocket as FastAPIWebSocket +from fastapi import FastAPI, status, HTTPException +from fastapi import Body, Header, Response, Depends +from starlette.websockets import WebSocketDisconnect, WebSocket as FastAPIWebSocket from nonebot.log import logger from nonebot.config import Env, Config @@ -18,6 +17,18 @@ from nonebot.drivers import BaseDriver, BaseWebSocket from nonebot.typing import Union, Optional, Callable, overrides +def get_auth_bearer(access_token: Optional[str] = Header( + None, alias="Authorization")): + if not access_token: + return None + scheme, _, param = access_token.partition(" ") + if scheme.lower() != "bearer": + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, + detail="Not authenticated", + headers={"WWW-Authenticate": "Bearer"}) + return param + + class Driver(BaseDriver): def __init__(self, env: Env, config: Config): @@ -106,14 +117,18 @@ class Driver(BaseDriver): **kwargs) @overrides(BaseDriver) - async def _handle_http(self, - adapter: str, - response: Response, - data: dict = Body(...), - x_self_id: str = Header(None), - access_token: str = OAuth2PasswordBearer( - "/", auto_error=False)): - # TODO: Check authorization + async def _handle_http( + self, + adapter: str, + response: Response, + data: dict = Body(...), + x_self_id: str = Header(None), + access_token: Optional[str] = Depends(get_auth_bearer)): + secret = self.config.secret + if secret is not None and secret != access_token: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, + detail="Not authenticated", + headers={"WWW-Authenticate": "Bearer"}) # Create Bot Object if adapter in self._adapters: @@ -127,15 +142,19 @@ class Driver(BaseDriver): return {"status": 200, "message": "success"} @overrides(BaseDriver) - async def _handle_ws_reverse(self, - adapter: str, - websocket: FastAPIWebSocket, - x_self_id: str = Header(None), - access_token: str = OAuth2PasswordBearer( - "/", auto_error=False)): - websocket = WebSocket(websocket) + async def _handle_ws_reverse( + self, + adapter: str, + websocket: FastAPIWebSocket, + x_self_id: str = Header(None), + access_token: Optional[str] = Depends(get_auth_bearer)): + secret = self.config.secret + if secret is not None and secret != access_token: + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, + detail="Not authenticated", + headers={"WWW-Authenticate": "Bearer"}) - # TODO: Check authorization + websocket = WebSocket(websocket) # Create Bot Object if adapter == "coolq":