mirror of
https://github.com/nonebot/nonebot2.git
synced 2024-11-28 04:06:28 +08:00
commit
803223f31c
11
.github/actions/build-api-doc/action.yml
vendored
Normal file
11
.github/actions/build-api-doc/action.yml
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
name: Build API Doc
|
||||
description: Build API Doc
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- run: |
|
||||
poetry run nb-autodoc nonebot
|
||||
cp -r ./build/nonebot/* ./website/docs/api/
|
||||
yarn prettier
|
||||
shell: bash
|
21
.github/actions/setup-node/action.yml
vendored
Normal file
21
.github/actions/setup-node/action.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
name: Setup Node
|
||||
description: Setup Node
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "16"
|
||||
|
||||
- id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
shell: bash
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
|
||||
- run: yarn install
|
||||
shell: bash
|
26
.github/actions/setup-python/action.yml
vendored
Normal file
26
.github/actions/setup-python/action.yml
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
name: Setup Python
|
||||
description: Setup Python
|
||||
|
||||
inputs:
|
||||
python-version:
|
||||
description: Python version
|
||||
required: false
|
||||
default: "3.9"
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
architecture: "x64"
|
||||
|
||||
- uses: Gr1N/setup-poetry@v7
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache/pypoetry/virtualenvs
|
||||
key: ${{ runner.os }}-poetry-${{ inputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
|
||||
|
||||
- run: poetry install -E all
|
||||
shell: bash
|
70
.github/release-drafter.yml
vendored
70
.github/release-drafter.yml
vendored
@ -1,42 +1,46 @@
|
||||
name-template: 'Release v$RESOLVED_VERSION 🌈'
|
||||
tag-template: 'v$RESOLVED_VERSION'
|
||||
categories:
|
||||
- title: '💥 Breaking Changes'
|
||||
labels:
|
||||
- 'Breaking'
|
||||
- title: '🚀 Features'
|
||||
labels:
|
||||
- 'feature'
|
||||
- 'enhancement'
|
||||
- title: '🐛 Bug Fixes'
|
||||
labels:
|
||||
- 'fix'
|
||||
- 'bugfix'
|
||||
- 'bug'
|
||||
- title: '🍻 Plugin Publish'
|
||||
label: 'Plugin'
|
||||
- title: '🍻 Bot Publish'
|
||||
label: 'Bot'
|
||||
- title: '🍻 Adapter Publish'
|
||||
label: 'Adapter'
|
||||
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
|
||||
header: |
|
||||
### Documentation
|
||||
|
||||
See: https://v2.nonebot.dev
|
||||
template: |
|
||||
### 💫 Changes
|
||||
|
||||
$CHANGES
|
||||
category-template: "### $TITLE"
|
||||
name-template: "Release v$RESOLVED_VERSION 🌈"
|
||||
tag-template: "v$RESOLVED_VERSION"
|
||||
change-template: "- $TITLE @$AUTHOR (#$NUMBER)"
|
||||
change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
|
||||
categories:
|
||||
- title: "💥 Breaking Changes"
|
||||
labels:
|
||||
- "Breaking"
|
||||
- title: "🚀 Features"
|
||||
labels:
|
||||
- "feature"
|
||||
- "enhancement"
|
||||
- title: "🐛 Bug Fixes"
|
||||
labels:
|
||||
- "fix"
|
||||
- "bugfix"
|
||||
- "bug"
|
||||
- title: "📝 Documentation"
|
||||
labels:
|
||||
- "documentation"
|
||||
- title: "🍻 Plugin Publish"
|
||||
label: "Plugin"
|
||||
- title: "🍻 Bot Publish"
|
||||
label: "Bot"
|
||||
- title: "🍻 Adapter Publish"
|
||||
label: "Adapter"
|
||||
version-resolver:
|
||||
major:
|
||||
labels:
|
||||
- 'major'
|
||||
- "major"
|
||||
minor:
|
||||
labels:
|
||||
- 'minor'
|
||||
- "minor"
|
||||
patch:
|
||||
labels:
|
||||
- 'patch'
|
||||
- "patch"
|
||||
default: patch
|
||||
template: |
|
||||
## Documentation
|
||||
|
||||
See: https://v2.nonebot.dev
|
||||
|
||||
## 💫 Changes
|
||||
|
||||
$CHANGES
|
||||
|
36
.github/workflows/codecov.yml
vendored
36
.github/workflows/codecov.yml
vendored
@ -2,38 +2,40 @@ name: Code Coverage
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
name: Run Pytest and Upload Coverage
|
||||
name: Test Coverage
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.7", "3.8", "3.9", "3.10"]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
fail-fast: false
|
||||
env:
|
||||
OS: ${{ matrix.os }}
|
||||
PYTHON_VERSION: ${{ matrix.python-version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Python environment
|
||||
uses: actions/setup-python@v2
|
||||
- name: Setup Python environment
|
||||
uses: ./.github/actions/setup-python
|
||||
with:
|
||||
python-version: "3.9"
|
||||
architecture: "x64"
|
||||
|
||||
- uses: Gr1N/setup-poetry@v7
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache/pypoetry/virtualenvs
|
||||
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: poetry install -E all
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Run Pytest
|
||||
run: |
|
||||
cd tests/
|
||||
poetry run pytest --cov-report xml
|
||||
poetry run pytest -n auto --cov-report xml
|
||||
|
||||
- name: Upload coverage report
|
||||
uses: codecov/codecov-action@v2
|
||||
with:
|
||||
env_vars: OS,PYTHON_VERSION
|
||||
files: ./tests/coverage.xml
|
||||
flags: unittests
|
||||
|
74
.github/workflows/release-drafter.yml
vendored
Normal file
74
.github/workflows/release-drafter.yml
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
name: Release Drafter
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
pull_request_target:
|
||||
branches:
|
||||
- master
|
||||
types:
|
||||
- closed
|
||||
|
||||
jobs:
|
||||
update-release-draft:
|
||||
if: github.event_name == 'pull_request_target'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: release-drafter/release-drafter@v5
|
||||
id: release-drafter
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# TODO
|
||||
- name: Update Changelog
|
||||
run: |
|
||||
echo ${{ steps.release-drafter.outputs.body }}
|
||||
|
||||
release:
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Python Environment
|
||||
uses: ./.github/actions/setup-python
|
||||
|
||||
- name: Setup Node Environment
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
- name: Build API Doc
|
||||
uses: ./.github/actions/build-api-doc
|
||||
|
||||
- run: |
|
||||
echo "TAG_NAME=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
|
||||
- uses: release-drafter/release-drafter@v5
|
||||
with:
|
||||
name: Release ${{ env.TAG_NAME }} 🌈
|
||||
tag: ${{ env.TAG_NAME }}
|
||||
publish: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and Publish Package
|
||||
run: |
|
||||
poetry build
|
||||
poetry publish -u ${{secrets.PYPI_USERNAME}} -p ${{secrets.PYPI_PASSWORD}}
|
||||
gh release upload --clobber ${{ env.TAG_NAME }} dist/*.tar.gz dist/*.whl
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and Publish Doc Package
|
||||
run: |
|
||||
yarn build:plugin --out-dir ../packages/nonebot-plugin-docs/nonebot_plugin_docs/dist
|
||||
export NONEBOT_VERSION=`poetry version -s`
|
||||
cd packages/nonebot-plugin-docs/
|
||||
poetry version $NONEBOT_VERSION
|
||||
poetry build
|
||||
poetry publish -u ${{secrets.PYPI_USERNAME}} -p ${{secrets.PYPI_PASSWORD}}
|
||||
gh release upload --clobber ${{ env.TAG_NAME }} dist/*.tar.gz dist/*.whl
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
36
.github/workflows/release-github.yml
vendored
36
.github/workflows/release-github.yml
vendored
@ -1,36 +0,0 @@
|
||||
name: Release Drafter
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- v*
|
||||
|
||||
jobs:
|
||||
update-release-draft:
|
||||
if: github.ref == 'refs/heads/master'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: release-drafter/release-drafter@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
release:
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- run: |
|
||||
echo "TAG_NAME=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
|
||||
- uses: release-drafter/release-drafter@v5
|
||||
with:
|
||||
name: Release ${{ env.TAG_NAME }} 🌈
|
||||
tag: ${{ env.TAG_NAME }}
|
||||
publish: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
58
.github/workflows/release-plugin-docs.yml
vendored
58
.github/workflows/release-plugin-docs.yml
vendored
@ -1,58 +0,0 @@
|
||||
name: Release Nonebot Plugin Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: "3.9"
|
||||
architecture: "x64"
|
||||
|
||||
- uses: Gr1N/setup-poetry@v7
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache/pypoetry/virtualenvs
|
||||
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "16"
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- name: Cache Packages
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
|
||||
- name: Install and build
|
||||
run: |
|
||||
poetry install -E all
|
||||
poetry run sphinx-build -M markdown ./docs_build ./build
|
||||
cp -r ./build/markdown/* ./website/docs/api/
|
||||
yarn install
|
||||
yarn prettier
|
||||
yarn build:plugin --out-dir ../packages/nonebot-plugin-docs/nonebot_plugin_docs/dist
|
||||
|
||||
- name: Publish Package
|
||||
run: |
|
||||
export NONEBOT_VERSION=`poetry version -s`
|
||||
cd packages/nonebot-plugin-docs/
|
||||
poetry version $NONEBOT_VERSION
|
||||
poetry build
|
||||
poetry publish -u ${{secrets.PYPI_USERNAME}} -p ${{secrets.PYPI_PASSWORD}}
|
54
.github/workflows/release.yml
vendored
54
.github/workflows/release.yml
vendored
@ -13,51 +13,23 @@ jobs:
|
||||
ref: master
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: "3.9"
|
||||
architecture: "x64"
|
||||
- name: Setup Python Environment
|
||||
uses: ./.github/actions/setup-python
|
||||
|
||||
- uses: Gr1N/setup-poetry@v7
|
||||
- name: Setup Node Environment
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache/pypoetry/virtualenvs
|
||||
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "16"
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- name: Cache Packages
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
|
||||
- name: Set up dependencies
|
||||
run: |
|
||||
poetry install -E all
|
||||
|
||||
- name: Build Doc
|
||||
run: poetry run sphinx-build -M markdown ./docs_build ./build
|
||||
|
||||
- name: Copy Files
|
||||
run: cp -r ./build/markdown/* ./website/docs/api/
|
||||
- name: Build API Doc
|
||||
uses: ./.github/actions/build-api-doc
|
||||
|
||||
- name: Archive Files
|
||||
run: |
|
||||
yarn install
|
||||
yarn prettier
|
||||
yarn archive $(poetry version -s)
|
||||
run: yarn archive $(poetry version -s)
|
||||
|
||||
- name: Push Tag and Release to PyPI
|
||||
# TODO
|
||||
- name: Archive Changelog
|
||||
run: cat CHANGELOG.md
|
||||
|
||||
- name: Push Tag
|
||||
run: |
|
||||
git config user.name github-actions
|
||||
git config user.email github-actions@github.com
|
||||
@ -65,5 +37,3 @@ jobs:
|
||||
git commit -m ":bookmark: Release $(poetry version -s)"
|
||||
git tag v$(poetry version -s)
|
||||
git push && git push --tags
|
||||
poetry build
|
||||
poetry publish -u ${{secrets.PYPI_USERNAME}} -p ${{secrets.PYPI_PASSWORD}}
|
||||
|
50
.github/workflows/website-deploy.yml
vendored
50
.github/workflows/website-deploy.yml
vendored
@ -1,7 +1,10 @@
|
||||
name: Build Upload Site
|
||||
name: Site Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
pull_request_target:
|
||||
|
||||
jobs:
|
||||
@ -17,42 +20,17 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
architecture: "x64"
|
||||
- name: Setup Python Environment
|
||||
uses: ./.github/actions/setup-python
|
||||
|
||||
- uses: Gr1N/setup-poetry@v7
|
||||
- name: Setup Node Environment
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache/pypoetry/virtualenvs
|
||||
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
|
||||
- name: Build API Doc
|
||||
uses: ./.github/actions/build-api-doc
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "16"
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- name: Cache Packages
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
|
||||
- name: Install and build
|
||||
run: |
|
||||
poetry install -E all
|
||||
poetry run sphinx-build -M markdown ./docs_build ./build
|
||||
cp -r ./build/markdown/* ./website/docs/api/
|
||||
yarn install
|
||||
yarn prettier
|
||||
yarn build
|
||||
- name: Build Doc
|
||||
run: yarn build
|
||||
|
||||
- name: Get Branch Name
|
||||
run: echo "BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_ENV
|
||||
@ -72,10 +50,10 @@ jobs:
|
||||
- name: Deploy to Netlify
|
||||
uses: nwtgck/actions-netlify@v1
|
||||
with:
|
||||
publish-dir: './website/build'
|
||||
publish-dir: "./website/build"
|
||||
production-deploy: ${{ env.PRODUCTION }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
deploy-message: 'Deploy ${{ env.DEPLOY_NAME }}@${{ github.sha }}'
|
||||
deploy-message: "Deploy ${{ env.DEPLOY_NAME }}@${{ github.sha }}"
|
||||
enable-commit-comment: false
|
||||
alias: ${{ env.DEPLOY_NAME }}
|
||||
env:
|
||||
|
22
.pre-commit-config.yaml
Normal file
22
.pre-commit-config.yaml
Normal file
@ -0,0 +1,22 @@
|
||||
ci:
|
||||
autofix_commit_msg: ":rotating_light: auto fix by pre-commit hooks"
|
||||
autofix_prs: true
|
||||
autoupdate_branch: dev
|
||||
autoupdate_schedule: weekly
|
||||
autoupdate_commit_msg: ":arrow_up: auto update by pre-commit hooks"
|
||||
repos:
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.10.1
|
||||
hooks:
|
||||
- id: isort
|
||||
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.1.0
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v2.5.1
|
||||
hooks:
|
||||
- id: prettier
|
||||
types_or: [javascript, jsx, ts, tsx, markdown]
|
86
CODE_OF_CONDUCT.md
Normal file
86
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,86 @@
|
||||
# NoneBot2 贡献者公约
|
||||
|
||||
## 我们的承诺
|
||||
|
||||
身为项目成员、贡献者、负责人,我们保证参与此社区的每个人都不受骚扰,不论其年龄、体型、身体条件、民族、性征、性别认同与表现、经验水平、教育程度、社会地位、国籍、相貌、种族、宗教信仰及性取向如何。
|
||||
|
||||
我们承诺致力于建设开放、友善、多元、包容、健康的社区环境。
|
||||
|
||||
## 我们的准则
|
||||
|
||||
有助于促进本社区积极环境的行为包括但不限于:
|
||||
|
||||
- 与人为善、推己及人
|
||||
- 尊重不同的主张、观点和经历
|
||||
- 积极提出、耐心接受有益批评
|
||||
- 面对过失,承担责任、认真道歉、从中学习
|
||||
- 关注社区共同诉求,而非一己私利
|
||||
|
||||
不当行为包括但不限于:
|
||||
|
||||
- 发布与性有关的言论或图像,以及任何形式的献殷勤或勾引
|
||||
- 挑衅行为、侮辱或贬损的言论、人身及政治攻击
|
||||
- 公开或私下骚扰
|
||||
- 未获明确授权擅自发布他人的资料,如地址、电子邮箱等
|
||||
- 其他有理由认定为违反职业操守的不当行为
|
||||
|
||||
## 落实之义务
|
||||
|
||||
社区负责人有责任诠释什么是“妥当行为”,并据此准则,妥善公正地认定与处置不当、威胁、冒犯及有害的行为。
|
||||
|
||||
社区负责人有权利和义务删除、编辑、拒绝违背本公约的评论(comment)、提交(commit)、代码、维基(wiki)编辑、问题(issue)等贡献。如有必要,需告知采取措施的理由。
|
||||
|
||||
## 适用范围
|
||||
|
||||
此行为标准适用于本社区全部场合,以及在其他场合代表本社区的个人。
|
||||
|
||||
代表本社区的情形包括但不限于:使用官方电子邮件与社交平台、作为指定代表参与在线或线下活动。
|
||||
|
||||
## 贯彻落实
|
||||
|
||||
如遇滥用、骚扰等不当行为,请通过 contact@nonebot.dev 向我们举报。我们将迅速审议并调查全部投诉。
|
||||
|
||||
社区全体负责人有义务保密举报者信息。
|
||||
|
||||
## 指导方针
|
||||
|
||||
社区负责人将依据下列方案判断并处置违纪行为:
|
||||
|
||||
### 一、督促
|
||||
|
||||
**社区影响**:用语不当、举止不符合道德或不受社区欢迎。
|
||||
|
||||
**处理意见**:由社区负责人予以非公开的书面警告,阐明违纪事由、解释举止如何不妥。或要求公开道歉。
|
||||
|
||||
### 二、警告
|
||||
|
||||
**社区影响**:一起或多起事件中的违纪行为。
|
||||
|
||||
**处理意见**:警告继续违纪的后果、违纪者在特定时间内禁止与当事人往来、不得擅自与社区执法者往来,禁令涵盖社区内外、社交网络在内的一切联络。如有违反,可致封禁乃至开除。
|
||||
|
||||
### 三、封禁
|
||||
|
||||
**社区影响**:严重违纪行为,包括屡教不改。
|
||||
|
||||
**处理意见**:违纪者在特定时间内禁止与社区的任何往来或公开联络,禁止任何与当事人公开或私下往来,不得擅自与社区管理者往来。如有违反,可导致开除。
|
||||
|
||||
### 四、开除
|
||||
|
||||
**社区影响**:典型违纪行为,例如屡教不改、骚扰某个人、敌对或贬低某个群体。
|
||||
|
||||
**处理意见**:无限期禁止违纪者与项目社区的一切公开往来。
|
||||
|
||||
## 来源
|
||||
|
||||
本行为标准改编自[参与者公约][homepage]2.0 版,可在此查阅:[https://www.contributor-covenant.org/zh-cn/version/2/0/code_of_conduct.html][v2.0]
|
||||
|
||||
指导方针借鉴自[Mozilla 纪检分级][mozilla coc]。
|
||||
|
||||
此行为标准常见问题请洽:[https://www.contributor-covenant.org/faq][faq]。
|
||||
另有诸译本:[https://www.contributor-covenant.org/translations][translations]。
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
|
||||
[mozilla coc]: https://github.com/mozilla/diversity
|
||||
[faq]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
71
CONTRIBUTING.md
Normal file
71
CONTRIBUTING.md
Normal file
@ -0,0 +1,71 @@
|
||||
# NoneBot2 贡献指南
|
||||
|
||||
首先,感谢你愿意为 NoneBot2 贡献自己的一份力量!
|
||||
|
||||
本指南旨在引导你更规范地向 NoneBot2 提交贡献,请务必认真阅读。
|
||||
|
||||
## 提交 Issue
|
||||
|
||||
在提交 Issue 前,我们建议你先查看 [FAQ](https://github.com/nonebot/discussions/discussions/13) 与 [已有的 Issues](https://github.com/nonebot/nonebot2/issues),以防重复提交。
|
||||
|
||||
### 报告问题、故障与漏洞
|
||||
|
||||
NoneBot2 仍然是一个不够稳定的开发中项目,如果你在使用过程中发现问题并确信是由 NoneBot2 引起的,欢迎提交 Issue。
|
||||
|
||||
### 建议功能
|
||||
|
||||
NoneBot2 还未进入正式版,欢迎在 Issue 中提议要加入哪些新功能。
|
||||
|
||||
为了让开发者更好地理解你的意图,请认真描述你所需要的特性,可能的话可以提出你认为可行的解决方案。
|
||||
|
||||
## Pull Request
|
||||
|
||||
NoneBot 使用 [poetry](https://python-poetry.org/) 管理项目依赖,由于 pre-commit 也经其管理,所以在此一并说明。
|
||||
|
||||
下面的命令能在已安装 poetry 和 npm 的情况下帮你快速配置开发环境。
|
||||
|
||||
```sh
|
||||
poetry install
|
||||
pre-commit install
|
||||
npm -g i gitmoji-cli
|
||||
gitmoji -i
|
||||
```
|
||||
|
||||
### Commit 规范
|
||||
|
||||
请确保你的每一个 commit 都能清晰地描述其意图,一个 commit 尽量只有一个意图。
|
||||
|
||||
NoneBot 的 commit message 格式遵循 [gitmoji](https://gitmoji.dev/) 规范,在创建 commit 时请牢记这一点。
|
||||
|
||||
### 撰写文档
|
||||
|
||||
NoneBot2 的文档使用 [docusaurus](https://docusaurus.io/),它有一些 [Markdown 特性](https://docusaurus.io/zh-CN/docs/markdown-features) 可能会帮助到你。
|
||||
|
||||
NoneBot2 文档并没有具体的行文风格规范,但我们建议你尽量写得简单易懂。
|
||||
|
||||
如果你需要在本地预览修改后的文档,可以使用 yarn 安装文档依赖后启动 dev server,如下所示:
|
||||
|
||||
```sh
|
||||
yarn install
|
||||
yarn start
|
||||
```
|
||||
|
||||
### 参与开发
|
||||
|
||||
NoneBot2 的代码风格遵循 [PEP 8](https://www.python.org/dev/peps/pep-0008/) 与 [PEP 484](https://www.python.org/dev/peps/pep-0484/) 规范,请确保你的代码风格和项目已有的代码保持一致,变量命名清晰,有适当的注释与测试代码。
|
||||
|
||||
## 为社区做贡献
|
||||
|
||||
你可以在 NoneBot 商店上架自己的适配器、插件、机器人,具体步骤可参考 [发布插件](https://v2.nonebot.dev/docs/advanced/publish-plugin) 一节。
|
||||
|
||||
我们仅对插件的兼容性进行简单测试,并会在下一个版本发布前对与该版本不兼容的插件作出处理。
|
||||
|
||||
虽然对插件的内容没有严格限制,但我们还是建议在上架插件之前先查看商店有无功能一致的插件。如果你想要上架商店的插件功能与现有插件不完全重合,请在插件说明中补充其与现有插件的区别。
|
||||
|
||||
同时,如果你参考或基于他人发行的代码进行开发,请注意遵守各代码所使用的开源许可协议。
|
||||
|
||||
## Git 工作流
|
||||
|
||||
`dev` 分支为 NoneBot 的开发分支,如无特殊情况请将更改提交到该分支。
|
||||
|
||||
如果你不是 NoneBot 团队的成员,可在 fork 本仓库后,向本仓库的 `dev` 分支发起 Pull Request,注意遵循先前提到的 commit message 规范创建 commit。
|
38
README.md
38
README.md
@ -23,7 +23,13 @@ _✨ 跨平台 Python 异步机器人框架 ✨_
|
||||
</a>
|
||||
<img src="https://img.shields.io/badge/python-3.7.3+-blue" alt="python">
|
||||
<a href="https://codecov.io/gh/nonebot/nonebot2">
|
||||
<img src="https://codecov.io/gh/nonebot/nonebot2/branch/master/graph/badge.svg?token=2P0G0VS7N4"/>
|
||||
<img src="https://codecov.io/gh/nonebot/nonebot2/branch/master/graph/badge.svg?token=2P0G0VS7N4" alt="codecov"/>
|
||||
</a>
|
||||
<a href="https://github.com/nonebot/nonebot2/actions/workflows/website-deploy.yml">
|
||||
<img src="https://github.com/nonebot/nonebot2/actions/workflows/website-deploy.yml/badge.svg?branch=master&event=push" alt="site"/>
|
||||
</a>
|
||||
<a href="https://results.pre-commit.ci/latest/github/nonebot/nonebot2/master">
|
||||
<img src="https://results.pre-commit.ci/badge/github/nonebot/nonebot2/master.svg" />
|
||||
</a>
|
||||
<br />
|
||||
<a href="https://onebot.dev/">
|
||||
@ -56,14 +62,18 @@ _✨ 跨平台 Python 异步机器人框架 ✨_
|
||||
<p align="center">
|
||||
<a href="https://v2.nonebot.dev/">文档</a>
|
||||
·
|
||||
<a href="https://v2.nonebot.dev/guide/installation.html">安装</a>
|
||||
<a href="https://v2.nonebot.dev/docs/start/installation">安装</a>
|
||||
·
|
||||
<a href="https://v2.nonebot.dev/guide/getting-started.html">开始使用</a>
|
||||
<a href="https://v2.nonebot.dev/docs/tutorial/create-project">开始使用</a>
|
||||
·
|
||||
<a href="#插件">文档打不开?</a>
|
||||
</p>
|
||||
|
||||
<!-- TODO: asciinema for install -->
|
||||
<p align="center">
|
||||
<a href="https://asciinema.org/a/464654">
|
||||
<img src="https://v2.nonebot.dev/img/setup.svg">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## 简介
|
||||
|
||||
@ -152,20 +162,18 @@ NoneBot2 不是 NoneBot1 的替代品。事实上,它们都在被积极的维
|
||||
|
||||
`NoneBot` 采用 `MIT` 许可证进行开源
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
```text
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
```
|
||||
|
||||
## 贡献
|
||||
|
||||
如果你在使用过程中发现任何问题,可以 [提交 Issue](https://github.com/nonebot/nonebot2/issues/new) 或自行 Fork 修改后提交 Pull Request。
|
||||
|
||||
如果你要提交 Pull Request,请确保你的代码风格和项目已有的代码保持一致,遵循 [PEP 8](https://www.python.org/dev/peps/pep-0008/) 与 [PEP 484](https://www.python.org/dev/peps/pep-0484/),变量命名清晰,有适当的注释与测试代码,**并且请以 `dev` 分支作为 Pull Request 目标分支**。
|
||||
|
||||
<!--TODO: Add a CONTRIBUTING.md-->
|
||||
请参考 [贡献指南](./CONTRIBUTING.md)
|
||||
|
||||
### 鸣谢
|
||||
|
||||
|
@ -1,20 +0,0 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
@ -1,12 +0,0 @@
|
||||
\-\-\-
|
||||
sidebar_position: 1
|
||||
id: index
|
||||
slug: /api
|
||||
\-\-\-
|
||||
|
||||
NoneBot 模块
|
||||
===============
|
||||
|
||||
.. automodule:: nonebot
|
||||
:members:
|
||||
:show-inheritance:
|
@ -1,43 +0,0 @@
|
||||
\-\-\-
|
||||
id: index
|
||||
slug: /api/adapters/
|
||||
\-\-\-
|
||||
|
||||
NoneBot.adapters 模块
|
||||
=====================
|
||||
|
||||
.. automodule:: nonebot.adapters
|
||||
:members:
|
||||
:private-members:
|
||||
:special-members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. automodule:: nonebot.adapters._adapter
|
||||
:members:
|
||||
:private-members:
|
||||
:special-members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. automodule:: nonebot.adapters._bot
|
||||
:members:
|
||||
:private-members:
|
||||
:special-members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. automodule:: nonebot.adapters._message
|
||||
:members:
|
||||
:private-members:
|
||||
:special-members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. automodule:: nonebot.adapters._event
|
||||
:members:
|
||||
:private-members:
|
||||
:special-members: __init__
|
||||
:show-inheritance:
|
||||
|
||||
.. automodule:: nonebot.adapters._template
|
||||
:members:
|
||||
:private-members:
|
||||
:special-members: __init__
|
||||
:show-inheritance:
|
@ -1,82 +0,0 @@
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file only contains a selection of the most common options. For a full
|
||||
# list see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(__file__))))
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'nonebot'
|
||||
copyright = '2020, richardchien'
|
||||
author = 'richardchien'
|
||||
|
||||
# The short X.Y version
|
||||
version = '2.0.0'
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '2.0.0'
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.viewcode',
|
||||
'sphinx.ext.todo',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = 'zh_CN'
|
||||
|
||||
master_doc = "README"
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = 'alabaster'
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# html_baseurl = '/api/'
|
||||
|
||||
# -- Extension configuration -------------------------------------------------
|
||||
|
||||
# -- Options for autodoc extension ----------------------------------------------
|
||||
autodoc_default_options = {'member-order': 'bysource'}
|
||||
autodoc_inherit_docstrings = False
|
||||
autodoc_typehints = 'none'
|
||||
|
||||
# -- Options for todo extension ----------------------------------------------
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = True
|
@ -1,10 +0,0 @@
|
||||
\-\-\-
|
||||
sidebar_position: 2
|
||||
\-\-\-
|
||||
|
||||
NoneBot.config 模块
|
||||
===================
|
||||
|
||||
.. automodule:: nonebot.config
|
||||
:members: Env, Config
|
||||
:show-inheritance:
|
@ -1,11 +0,0 @@
|
||||
\-\-\-
|
||||
sidebar_position: 8
|
||||
\-\-\-
|
||||
|
||||
NoneBot.dependencies 模块
|
||||
====================
|
||||
|
||||
.. automodule:: nonebot.dependencies
|
||||
:members:
|
||||
:private-members:
|
||||
:show-inheritance:
|
@ -1,13 +0,0 @@
|
||||
\-\-\-
|
||||
id: index
|
||||
slug: /api/drivers/
|
||||
\-\-\-
|
||||
|
||||
NoneBot.drivers 模块
|
||||
=====================
|
||||
|
||||
.. automodule:: nonebot.drivers
|
||||
:members:
|
||||
:private-members:
|
||||
:special-members: __init__
|
||||
:show-inheritance:
|
@ -1,9 +0,0 @@
|
||||
|
||||
|
||||
NoneBot.drivers.aiohttp 模块
|
||||
=============================
|
||||
|
||||
.. automodule:: nonebot.drivers.aiohttp
|
||||
:members:
|
||||
:private-members:
|
||||
:show-inheritance:
|
@ -1,9 +0,0 @@
|
||||
|
||||
|
||||
NoneBot.drivers.fastapi 模块
|
||||
=============================
|
||||
|
||||
.. automodule:: nonebot.drivers.fastapi
|
||||
:members:
|
||||
:private-members:
|
||||
:show-inheritance:
|
@ -1,9 +0,0 @@
|
||||
|
||||
|
||||
NoneBot.drivers.httpx 模块
|
||||
=============================
|
||||
|
||||
.. automodule:: nonebot.drivers.httpx
|
||||
:members:
|
||||
:private-members:
|
||||
:show-inheritance:
|
@ -1,9 +0,0 @@
|
||||
|
||||
|
||||
NoneBot.drivers.quart 模块
|
||||
==========================
|
||||
|
||||
.. automodule:: nonebot.drivers.quart
|
||||
:members:
|
||||
:private-members:
|
||||
:show-inheritance:
|
@ -1,9 +0,0 @@
|
||||
|
||||
|
||||
NoneBot.drivers.websockets 模块
|
||||
=============================
|
||||
|
||||
.. automodule:: nonebot.drivers.websockets
|
||||
:members:
|
||||
:private-members:
|
||||
:show-inheritance:
|
@ -1,10 +0,0 @@
|
||||
\-\-\-
|
||||
sidebar_position: 12
|
||||
\-\-\-
|
||||
|
||||
NoneBot.exception 模块
|
||||
======================
|
||||
|
||||
.. automodule:: nonebot.exception
|
||||
:members:
|
||||
:show-inheritance:
|
@ -1,10 +0,0 @@
|
||||
\-\-\-
|
||||
sidebar_position: 9
|
||||
\-\-\-
|
||||
|
||||
NoneBot.log 模块
|
||||
=================
|
||||
|
||||
.. automodule:: nonebot.log
|
||||
:members:
|
||||
:show-inheritance:
|
@ -1,35 +0,0 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
@ -1,12 +0,0 @@
|
||||
\-\-\-
|
||||
sidebar_position: 5
|
||||
\-\-\-
|
||||
|
||||
NoneBot.matcher 模块
|
||||
====================
|
||||
|
||||
.. automodule:: nonebot.matcher
|
||||
:members:
|
||||
:private-members:
|
||||
:special-members: __init__
|
||||
:show-inheritance:
|
@ -1,10 +0,0 @@
|
||||
\-\-\-
|
||||
sidebar_position: 4
|
||||
\-\-\-
|
||||
|
||||
NoneBot.message 模块
|
||||
======================
|
||||
|
||||
.. automodule:: nonebot.message
|
||||
:members:
|
||||
:show-inheritance:
|
@ -1,11 +0,0 @@
|
||||
\-\-\-
|
||||
sidebar_position: 7
|
||||
\-\-\-
|
||||
|
||||
NoneBot.permission 模块
|
||||
=======================
|
||||
|
||||
.. automodule:: nonebot.permission
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:special-members:
|
@ -1,31 +0,0 @@
|
||||
\-\-\-
|
||||
sidebar_position: 3
|
||||
\-\-\-
|
||||
|
||||
NoneBot.plugin 模块
|
||||
====================
|
||||
|
||||
.. automodule:: nonebot.plugin
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:special-members: __init__
|
||||
|
||||
.. automodule:: nonebot.plugin.plugin
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:special-members: __init__
|
||||
|
||||
.. automodule:: nonebot.plugin.on
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:special-members: __init__
|
||||
|
||||
.. automodule:: nonebot.plugin.load
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:special-members: __init__
|
||||
|
||||
.. automodule:: nonebot.plugin.export
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:special-members: __init__
|
@ -1,11 +0,0 @@
|
||||
\-\-\-
|
||||
sidebar_position: 6
|
||||
\-\-\-
|
||||
|
||||
NoneBot.rule 模块
|
||||
====================
|
||||
|
||||
.. automodule:: nonebot.rule
|
||||
:members:
|
||||
:special-members:
|
||||
:show-inheritance:
|
@ -1,10 +0,0 @@
|
||||
\-\-\-
|
||||
sidebar_position: 11
|
||||
\-\-\-
|
||||
|
||||
NoneBot.typing 模块
|
||||
===================
|
||||
|
||||
.. automodule:: nonebot.typing
|
||||
:members:
|
||||
:show-inheritance:
|
@ -1,10 +0,0 @@
|
||||
\-\-\-
|
||||
sidebar_position: 10
|
||||
\-\-\-
|
||||
|
||||
NoneBot.utils 模块
|
||||
==================
|
||||
|
||||
.. automodule:: nonebot.utils
|
||||
:members:
|
||||
:show-inheritance:
|
@ -1,31 +1,37 @@
|
||||
"""
|
||||
快捷导入
|
||||
========
|
||||
"""本模块主要定义了 NoneBot 启动所需函数,供 bot 入口文件调用。
|
||||
|
||||
为方便使用,``nonebot`` 模块从子模块导入了部分内容
|
||||
## 快捷导入
|
||||
|
||||
- ``on_message`` => ``nonebot.plugin.on_message``
|
||||
- ``on_notice`` => ``nonebot.plugin.on_notice``
|
||||
- ``on_request`` => ``nonebot.plugin.on_request``
|
||||
- ``on_metaevent`` => ``nonebot.plugin.on_metaevent``
|
||||
- ``on_startswith`` => ``nonebot.plugin.on_startswith``
|
||||
- ``on_endswith`` => ``nonebot.plugin.on_endswith``
|
||||
- ``on_keyword`` => ``nonebot.plugin.on_keyword``
|
||||
- ``on_command`` => ``nonebot.plugin.on_command``
|
||||
- ``on_shell_command`` => ``nonebot.plugin.on_shell_command``
|
||||
- ``on_regex`` => ``nonebot.plugin.on_regex``
|
||||
- ``CommandGroup`` => ``nonebot.plugin.CommandGroup``
|
||||
- ``Matchergroup`` => ``nonebot.plugin.MatcherGroup``
|
||||
- ``load_plugin`` => ``nonebot.plugin.load_plugin``
|
||||
- ``load_plugins`` => ``nonebot.plugin.load_plugins``
|
||||
- ``load_all_plugins`` => ``nonebot.plugin.load_all_plugins``
|
||||
- ``load_from_json`` => ``nonebot.plugin.load_from_json``
|
||||
- ``load_from_toml`` => ``nonebot.plugin.load_from_toml``
|
||||
- ``load_builtin_plugins`` => ``nonebot.plugin.load_builtin_plugins``
|
||||
- ``get_plugin`` => ``nonebot.plugin.get_plugin``
|
||||
- ``get_loaded_plugins`` => ``nonebot.plugin.get_loaded_plugins``
|
||||
- ``export`` => ``nonebot.plugin.export``
|
||||
- ``require`` => ``nonebot.plugin.require``
|
||||
为方便使用,本模块从子模块导入了部分内容,以下内容可以直接通过本模块导入:
|
||||
|
||||
- `on` => {ref}``on` <nonebot.plugin.on.on>`
|
||||
- `on_metaevent` => {ref}``on_metaevent` <nonebot.plugin.on.on_metaevent>`
|
||||
- `on_message` => {ref}``on_message` <nonebot.plugin.on.on_message>`
|
||||
- `on_notice` => {ref}``on_notice` <nonebot.plugin.on.on_notice>`
|
||||
- `on_request` => {ref}``on_request` <nonebot.plugin.on.on_request>`
|
||||
- `on_startswith` => {ref}``on_startswith` <nonebot.plugin.on.on_startswith>`
|
||||
- `on_endswith` => {ref}``on_endswith` <nonebot.plugin.on.on_endswith>`
|
||||
- `on_keyword` => {ref}``on_keyword` <nonebot.plugin.on.on_keyword>`
|
||||
- `on_command` => {ref}``on_command` <nonebot.plugin.on.on_command>`
|
||||
- `on_shell_command` => {ref}``on_shell_command` <nonebot.plugin.on.on_shell_command>`
|
||||
- `on_regex` => {ref}``on_regex` <nonebot.plugin.on.on_regex>`
|
||||
- `CommandGroup` => {ref}``CommandGroup` <nonebot.plugin.on.CommandGroup>`
|
||||
- `Matchergroup` => {ref}``MatcherGroup` <nonebot.plugin.on.MatcherGroup>`
|
||||
- `load_plugin` => {ref}``load_plugin` <nonebot.plugin.load.load_plugin>`
|
||||
- `load_plugins` => {ref}``load_plugins` <nonebot.plugin.load.load_plugins>`
|
||||
- `load_all_plugins` => {ref}``load_all_plugins` <nonebot.plugin.load.load_all_plugins>`
|
||||
- `load_from_json` => {ref}``load_from_json` <nonebot.plugin.load.load_from_json>`
|
||||
- `load_from_toml` => {ref}``load_from_toml` <nonebot.plugin.load.load_from_toml>`
|
||||
- `load_builtin_plugin` => {ref}``load_builtin_plugin` <nonebot.plugin.load.load_builtin_plugin>`
|
||||
- `load_builtin_plugins` => {ref}``load_builtin_plugins` <nonebot.plugin.load.load_builtin_plugins>`
|
||||
- `get_plugin` => {ref}``get_plugin` <nonebot.plugin.plugin.get_plugin>`
|
||||
- `get_loaded_plugins` => {ref}``get_loaded_plugins` <nonebot.plugin.plugin.get_loaded_plugins>`
|
||||
- `export` => {ref}``export` <nonebot.plugin.export.export>`
|
||||
- `require` => {ref}``require` <nonebot.plugin.load.require>`
|
||||
|
||||
FrontMatter:
|
||||
sidebar_position: 0
|
||||
description: nonebot 模块
|
||||
"""
|
||||
|
||||
import importlib
|
||||
@ -51,25 +57,20 @@ _driver: Optional[Driver] = None
|
||||
|
||||
|
||||
def get_driver() -> Driver:
|
||||
"""
|
||||
:说明:
|
||||
"""获取全局 {ref}`nonebot.drivers.Driver` 实例。
|
||||
|
||||
获取全局 Driver 对象。可用于在计划任务的回调中获取当前 Driver 对象。
|
||||
可用于在计划任务的回调等情形中获取当前 {ref}`nonebot.drivers.Driver` 实例。
|
||||
|
||||
:返回:
|
||||
返回:
|
||||
全局 {ref}`nonebot.drivers.Driver` 对象
|
||||
|
||||
* ``Driver``: 全局 Driver 对象
|
||||
|
||||
:异常:
|
||||
|
||||
* ``ValueError``: 全局 Driver 对象尚未初始化 (nonebot.init 尚未调用)
|
||||
|
||||
:用法:
|
||||
|
||||
.. code-block:: python
|
||||
异常:
|
||||
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化 ({ref}`nonebot.init <nonebot.init>` 尚未调用)
|
||||
|
||||
用法:
|
||||
```python
|
||||
driver = nonebot.get_driver()
|
||||
|
||||
```
|
||||
"""
|
||||
if _driver is None:
|
||||
raise ValueError("NoneBot has not been initialized.")
|
||||
@ -77,25 +78,19 @@ def get_driver() -> Driver:
|
||||
|
||||
|
||||
def get_app() -> Any:
|
||||
"""
|
||||
:说明:
|
||||
"""获取全局 {ref}`nonebot.drivers.ReverseDriver` 对应的 Server App 对象。
|
||||
|
||||
获取全局 Driver 对应 Server App 对象。
|
||||
返回:
|
||||
Server App 对象
|
||||
|
||||
:返回:
|
||||
|
||||
* ``Any``: Server App 对象
|
||||
|
||||
:异常:
|
||||
|
||||
* ``ValueError``: 全局 Driver 对象尚未初始化 (nonebot.init 尚未调用)
|
||||
|
||||
:用法:
|
||||
|
||||
.. code-block:: python
|
||||
异常:
|
||||
AssertionError: 全局 Driver 对象不是 {ref}`nonebot.drivers.ReverseDriver` 类型
|
||||
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化 ({ref}`nonebot.init <nonebot.init>` 尚未调用)
|
||||
|
||||
用法:
|
||||
```python
|
||||
app = nonebot.get_app()
|
||||
|
||||
```
|
||||
"""
|
||||
driver = get_driver()
|
||||
assert isinstance(
|
||||
@ -105,25 +100,19 @@ def get_app() -> Any:
|
||||
|
||||
|
||||
def get_asgi() -> Any:
|
||||
"""
|
||||
:说明:
|
||||
"""获取全局 {ref}`nonebot.drivers.ReverseDriver` 对应 [ASGI](https://asgi.readthedocs.io/) 对象。
|
||||
|
||||
获取全局 Driver 对应 Asgi 对象。
|
||||
返回:
|
||||
ASGI 对象
|
||||
|
||||
:返回:
|
||||
|
||||
* ``Any``: Asgi 对象
|
||||
|
||||
:异常:
|
||||
|
||||
* ``ValueError``: 全局 Driver 对象尚未初始化 (nonebot.init 尚未调用)
|
||||
|
||||
:用法:
|
||||
|
||||
.. code-block:: python
|
||||
异常:
|
||||
AssertionError: 全局 Driver 对象不是 {ref}`nonebot.drivers.ReverseDriver` 类型
|
||||
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化 ({ref}`nonebot.init <nonebot.init>` 尚未调用)
|
||||
|
||||
用法:
|
||||
```python
|
||||
asgi = nonebot.get_asgi()
|
||||
|
||||
```
|
||||
"""
|
||||
driver = get_driver()
|
||||
assert isinstance(
|
||||
@ -133,32 +122,28 @@ def get_asgi() -> Any:
|
||||
|
||||
|
||||
def get_bot(self_id: Optional[str] = None) -> Bot:
|
||||
"""
|
||||
:说明:
|
||||
"""获取一个连接到 NoneBot 的 {ref}`nonebot.adapters.Bot` 对象。
|
||||
|
||||
当提供 self_id 时,此函数是 get_bots()[self_id] 的简写;当不提供时,返回一个 Bot。
|
||||
当提供 `self_id` 时,此函数是 `get_bots()[self_id]` 的简写;
|
||||
当不提供时,返回一个 {ref}`nonebot.adapters.Bot`。
|
||||
|
||||
:参数:
|
||||
参数:
|
||||
self_id: 用来识别 {ref}`nonebot.adapters.Bot` 的 {ref}`nonebot.adapters.Bot.self_id` 属性
|
||||
|
||||
* ``self_id: Optional[str]``: 用来识别 Bot 的 ID
|
||||
返回:
|
||||
{ref}`nonebot.adapters.Bot` 对象
|
||||
|
||||
:返回:
|
||||
异常:
|
||||
KeyError: 对应 self_id 的 Bot 不存在
|
||||
ValueError: 没有传入 self_id 且没有 Bot 可用
|
||||
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化 ({ref}`nonebot.init <nonebot.init>` 尚未调用)
|
||||
|
||||
* ``Bot``: Bot 对象
|
||||
|
||||
:异常:
|
||||
|
||||
* ``KeyError``: 对应 ID 的 Bot 不存在
|
||||
* ``ValueError``: 全局 Driver 对象尚未初始化 (nonebot.init 尚未调用)
|
||||
* ``ValueError``: 没有传入 ID 且没有 Bot 可用
|
||||
|
||||
:用法:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
assert nonebot.get_bot('12345') == nonebot.get_bots()['12345']
|
||||
用法:
|
||||
```python
|
||||
assert nonebot.get_bot("12345") == nonebot.get_bots()["12345"]
|
||||
|
||||
another_unspecified_bot = nonebot.get_bot()
|
||||
```
|
||||
"""
|
||||
bots = get_bots()
|
||||
if self_id is not None:
|
||||
@ -171,25 +156,18 @@ def get_bot(self_id: Optional[str] = None) -> Bot:
|
||||
|
||||
|
||||
def get_bots() -> Dict[str, Bot]:
|
||||
"""
|
||||
:说明:
|
||||
"""获取所有连接到 NoneBot 的 {ref}`nonebot.adapters.Bot` 对象。
|
||||
|
||||
获取所有通过 ws 连接 NoneBot 的 Bot 对象。
|
||||
返回:
|
||||
一个以 {ref}`nonebot.adapters.Bot.self_id` 为键,{ref}`nonebot.adapters.Bot` 对象为值的字典
|
||||
|
||||
:返回:
|
||||
|
||||
* ``Dict[str, Bot]``: 一个以字符串 ID 为键,Bot 对象为值的字典
|
||||
|
||||
:异常:
|
||||
|
||||
* ``ValueError``: 全局 Driver 对象尚未初始化 (nonebot.init 尚未调用)
|
||||
|
||||
:用法:
|
||||
|
||||
.. code-block:: python
|
||||
异常:
|
||||
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化 ({ref}`nonebot.init <nonebot.init>` 尚未调用)
|
||||
|
||||
用法:
|
||||
```python
|
||||
bots = nonebot.get_bots()
|
||||
|
||||
```
|
||||
"""
|
||||
driver = get_driver()
|
||||
return driver.bots
|
||||
@ -226,31 +204,21 @@ def _resolve_combine_expr(obj_str: str) -> Type[Driver]:
|
||||
return combine_driver(DriverClass, *mixins)
|
||||
|
||||
|
||||
def init(*, _env_file: Optional[str] = None, **kwargs):
|
||||
"""
|
||||
:说明:
|
||||
def init(*, _env_file: Optional[str] = None, **kwargs: Any) -> None:
|
||||
"""初始化 NoneBot 以及 全局 {ref}`nonebot.drivers.Driver` 对象。
|
||||
|
||||
初始化 NoneBot 以及 全局 Driver 对象。
|
||||
NoneBot 将会从 .env 文件中读取环境信息,并使用相应的 env 文件配置。
|
||||
|
||||
NoneBot 将会从 .env 文件中读取环境信息,并使用相应的 env 文件配置。
|
||||
也可以传入自定义的 `_env_file` 来指定 NoneBot 从该文件读取配置。
|
||||
|
||||
你也可以传入自定义的 _env_file 来指定 NoneBot 从该文件读取配置。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``_env_file: Optional[str]``: 配置文件名,默认从 .env.{env_name} 中读取配置
|
||||
* ``**kwargs``: 任意变量,将会存储到 Config 对象里
|
||||
|
||||
:返回:
|
||||
|
||||
- ``None``
|
||||
|
||||
:用法:
|
||||
|
||||
.. code-block:: python
|
||||
参数:
|
||||
_env_file: 配置文件名,默认从 `.env.{env_name}` 中读取配置
|
||||
kwargs: 任意变量,将会存储到 {ref}`nonebot.drivers.Driver.config` 对象里
|
||||
|
||||
用法:
|
||||
```python
|
||||
nonebot.init(database=Database(...))
|
||||
|
||||
```
|
||||
"""
|
||||
global _driver
|
||||
if not _driver:
|
||||
@ -275,32 +243,22 @@ def init(*, _env_file: Optional[str] = None, **kwargs):
|
||||
|
||||
|
||||
def run(*args: Any, **kwargs: Any) -> None:
|
||||
"""
|
||||
:说明:
|
||||
"""启动 NoneBot,即运行全局 {ref}`nonebot.drivers.Driver` 对象。
|
||||
|
||||
启动 NoneBot,即运行全局 Driver 对象。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``*args``: 传入 Driver.run 的位置参数
|
||||
* ``**kwargs``: 传入 Driver.run 的命名参数
|
||||
|
||||
:返回:
|
||||
|
||||
- ``None``
|
||||
|
||||
:用法:
|
||||
|
||||
.. code-block:: python
|
||||
参数:
|
||||
args: 传入 {ref}`nonebot.drivers.Driver.run` 的位置参数
|
||||
kwargs: 传入 {ref}`nonebot.drivers.Driver.run` 的命名参数
|
||||
|
||||
用法:
|
||||
```python
|
||||
nonebot.run(host="127.0.0.1", port=8080)
|
||||
|
||||
```
|
||||
"""
|
||||
logger.success("Running NoneBot...")
|
||||
get_driver().run(*args, **kwargs)
|
||||
|
||||
|
||||
import nonebot.params as params
|
||||
from nonebot.plugin import on as on
|
||||
from nonebot.plugin import export as export
|
||||
from nonebot.plugin import require as require
|
||||
from nonebot.plugin import on_regex as on_regex
|
||||
@ -322,4 +280,7 @@ from nonebot.plugin import load_from_toml as load_from_toml
|
||||
from nonebot.plugin import load_all_plugins as load_all_plugins
|
||||
from nonebot.plugin import on_shell_command as on_shell_command
|
||||
from nonebot.plugin import get_loaded_plugins as get_loaded_plugins
|
||||
from nonebot.plugin import load_builtin_plugin as load_builtin_plugin
|
||||
from nonebot.plugin import load_builtin_plugins as load_builtin_plugins
|
||||
|
||||
__autodoc__ = {"internal": False}
|
||||
|
@ -1,8 +1,10 @@
|
||||
"""
|
||||
协议适配基类
|
||||
============
|
||||
"""本模块定义了协议适配基类,各协议请继承以下基类。
|
||||
|
||||
各协议请继承以下基类,并使用 ``driver.register_adapter`` 注册适配器
|
||||
使用 {ref}`nonebot.drivers.Driver.register_adapter` 注册适配器。
|
||||
|
||||
FrontMatter:
|
||||
sidebar_position: 0
|
||||
description: nonebot.adapters 模块
|
||||
"""
|
||||
|
||||
from typing import Iterable
|
||||
@ -20,9 +22,18 @@ except ImportError:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
from ._bot import Bot as Bot
|
||||
from ._event import Event as Event
|
||||
from ._adapter import Adapter as Adapter
|
||||
from ._message import Message as Message
|
||||
from ._message import MessageSegment as MessageSegment
|
||||
from ._template import MessageTemplate as MessageTemplate
|
||||
from nonebot.internal.adapter import Bot as Bot
|
||||
from nonebot.internal.adapter import Event as Event
|
||||
from nonebot.internal.adapter import Adapter as Adapter
|
||||
from nonebot.internal.adapter import Message as Message
|
||||
from nonebot.internal.adapter import MessageSegment as MessageSegment
|
||||
from nonebot.internal.adapter import MessageTemplate as MessageTemplate
|
||||
|
||||
__autodoc__ = {
|
||||
"Bot": True,
|
||||
"Event": True,
|
||||
"Adapter": True,
|
||||
"Message": True,
|
||||
"MessageSegment": True,
|
||||
"MessageTemplate": True,
|
||||
}
|
||||
|
@ -1,137 +0,0 @@
|
||||
import abc
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ._message import Message
|
||||
from nonebot.utils import DataclassEncoder
|
||||
|
||||
|
||||
class Event(abc.ABC, BaseModel):
|
||||
"""Event 基类。提供获取关键信息的方法,其余信息可直接获取。"""
|
||||
|
||||
class Config:
|
||||
extra = "allow"
|
||||
json_encoders = {Message: DataclassEncoder}
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_type(self) -> str:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
获取事件类型的方法,类型通常为 NoneBot 内置的四种类型。
|
||||
|
||||
:返回:
|
||||
|
||||
* ``Literal["message", "notice", "request", "meta_event"]``
|
||||
* 其他自定义 ``str``
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_event_name(self) -> str:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
获取事件名称的方法。
|
||||
|
||||
:返回:
|
||||
|
||||
* ``str``
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_event_description(self) -> str:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
获取事件描述的方法,通常为事件具体内容。
|
||||
|
||||
:返回:
|
||||
|
||||
* ``str``
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"[{self.get_event_name()}]: {self.get_event_description()}"
|
||||
|
||||
def get_log_string(self) -> str:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
获取事件日志信息的方法,通常你不需要修改这个方法,只有当希望 NoneBot 隐藏该事件日志时,可以抛出 ``NoLogException`` 异常。
|
||||
|
||||
:返回:
|
||||
|
||||
* ``str``
|
||||
|
||||
:异常:
|
||||
|
||||
- ``NoLogException``
|
||||
"""
|
||||
return f"[{self.get_event_name()}]: {self.get_event_description()}"
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_user_id(self) -> str:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
获取事件主体 id 的方法,通常是用户 id 。
|
||||
|
||||
:返回:
|
||||
|
||||
* ``str``
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_session_id(self) -> str:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
获取会话 id 的方法,用于判断当前事件属于哪一个会话,通常是用户 id、群组 id 组合。
|
||||
|
||||
:返回:
|
||||
|
||||
* ``str``
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_message(self) -> "Message":
|
||||
"""
|
||||
:说明:
|
||||
|
||||
获取事件消息内容的方法。
|
||||
|
||||
:返回:
|
||||
|
||||
* ``Message``
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_plaintext(self) -> str:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
获取消息纯文本的方法,通常不需要修改,默认通过 ``get_message().extract_plain_text`` 获取。
|
||||
|
||||
:返回:
|
||||
|
||||
* ``str``
|
||||
"""
|
||||
return self.get_message().extract_plain_text()
|
||||
|
||||
@abc.abstractmethod
|
||||
def is_tome(self) -> bool:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
获取事件是否与机器人有关的方法。
|
||||
|
||||
:返回:
|
||||
|
||||
* ``bool``
|
||||
"""
|
||||
raise NotImplementedError
|
@ -1,232 +0,0 @@
|
||||
import abc
|
||||
from copy import deepcopy
|
||||
from dataclasses import field, asdict, dataclass
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
Type,
|
||||
Union,
|
||||
Generic,
|
||||
Mapping,
|
||||
TypeVar,
|
||||
Iterable,
|
||||
)
|
||||
|
||||
from ._template import MessageTemplate
|
||||
|
||||
T = TypeVar("T")
|
||||
TMS = TypeVar("TMS", covariant=True)
|
||||
TM = TypeVar("TM", bound="Message")
|
||||
|
||||
|
||||
@dataclass
|
||||
class MessageSegment(Mapping, abc.ABC, Generic[TM]):
|
||||
"""消息段基类"""
|
||||
|
||||
type: str
|
||||
"""
|
||||
- 类型: ``str``
|
||||
- 说明: 消息段类型
|
||||
"""
|
||||
data: Dict[str, Any] = field(default_factory=lambda: {})
|
||||
"""
|
||||
- 类型: ``Dict[str, Union[str, list]]``
|
||||
- 说明: 消息段数据
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def get_message_class(cls) -> Type[TM]:
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def __str__(self) -> str:
|
||||
"""该消息段所代表的 str,在命令匹配部分使用"""
|
||||
raise NotImplementedError
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(str(self))
|
||||
|
||||
def __ne__(self: T, other: T) -> bool:
|
||||
return not self == other
|
||||
|
||||
def __add__(self, other: Union[str, Mapping, Iterable[Mapping]]) -> TM:
|
||||
return self.get_message_class()(self) + other # type: ignore
|
||||
|
||||
def __radd__(self, other: Union[str, Mapping, Iterable[Mapping]]) -> TM:
|
||||
return self.get_message_class()(other) + self # type: ignore
|
||||
|
||||
def __getitem__(self, key: str):
|
||||
return getattr(self, key)
|
||||
|
||||
def __setitem__(self, key: str, value: Any):
|
||||
return setattr(self, key, value)
|
||||
|
||||
def __iter__(self):
|
||||
yield from asdict(self).keys()
|
||||
|
||||
def __contains__(self, key: Any) -> bool:
|
||||
return key in asdict(self).keys()
|
||||
|
||||
def get(self, key: str, default: Any = None):
|
||||
return getattr(self, key, default)
|
||||
|
||||
def keys(self):
|
||||
return asdict(self).keys()
|
||||
|
||||
def values(self):
|
||||
return asdict(self).values()
|
||||
|
||||
def items(self):
|
||||
return asdict(self).items()
|
||||
|
||||
def copy(self: T) -> T:
|
||||
return deepcopy(self)
|
||||
|
||||
@abc.abstractmethod
|
||||
def is_text(self) -> bool:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Message(List[TMS], abc.ABC):
|
||||
"""消息数组"""
|
||||
|
||||
def __init__(
|
||||
self: TM,
|
||||
message: Union[str, None, Mapping, Iterable[Mapping], TMS, TM, Any] = None,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
:参数:
|
||||
|
||||
* ``message: Union[str, list, dict, MessageSegment, Message, Any]``: 消息内容
|
||||
"""
|
||||
super().__init__(*args, **kwargs)
|
||||
if message is None:
|
||||
return
|
||||
elif isinstance(message, Message):
|
||||
self.extend(message)
|
||||
elif isinstance(message, MessageSegment):
|
||||
self.append(message)
|
||||
else:
|
||||
self.extend(self._construct(message))
|
||||
|
||||
@classmethod
|
||||
def template(cls: Type[TM], format_string: Union[str, TM]) -> MessageTemplate[TM]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
根据创建消息模板, 用法和 ``str.format`` 大致相同, 但是可以输出消息对象, 并且支持以 ``Message`` 对象作为消息模板
|
||||
并且提供了拓展的格式化控制符, 可以用适用于该消息类型的 ``MessageSegment`` 的工厂方法创建消息
|
||||
|
||||
:示例:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> Message.template("{} {}").format("hello", "world") # 基础演示
|
||||
Message(MessageSegment(type='text', data={'text': 'hello world'}))
|
||||
>>> Message.template("{} {}").format(MessageSegment.image("file///..."), "world") # 支持消息段等对象
|
||||
Message(MessageSegment(type='image', data={'file': 'file///...'}), MessageSegment(type='text', data={'text': 'world'}))
|
||||
>>> Message.template( # 支持以Message对象作为消息模板
|
||||
... MessageSegment.text('test {event.user_id}') + MessageSegment.face(233) +
|
||||
... MessageSegment.text('test {event.message}')).format(event={'user_id':123456, 'message':'hello world'})
|
||||
Message(MessageSegment(type='text', data={'text': 'test 123456'}),
|
||||
MessageSegment(type='face', data={'face': 233}),
|
||||
MessageSegment(type='text', data={'text': 'test hello world'}))
|
||||
>>> Message.template("{link:image}").format(link='https://...') # 支持拓展格式化控制符
|
||||
Message(MessageSegment(type='image', data={'file': 'https://...'}))
|
||||
|
||||
:参数:
|
||||
|
||||
* ``format_string: str``: 格式化字符串
|
||||
|
||||
:返回:
|
||||
|
||||
- ``MessageFormatter[TM]``: 消息格式化器
|
||||
"""
|
||||
return MessageTemplate(format_string, cls)
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def get_segment_class(cls) -> Type[TMS]:
|
||||
raise NotImplementedError
|
||||
|
||||
def __str__(self):
|
||||
return "".join(str(seg) for seg in self)
|
||||
|
||||
@classmethod
|
||||
def __get_validators__(cls):
|
||||
yield cls._validate
|
||||
|
||||
@classmethod
|
||||
def _validate(cls, value):
|
||||
return cls(value)
|
||||
|
||||
@staticmethod
|
||||
@abc.abstractmethod
|
||||
def _construct(msg: Union[str, Mapping, Iterable[Mapping], Any]) -> Iterable[TMS]:
|
||||
raise NotImplementedError
|
||||
|
||||
def __add__(self: TM, other: Union[str, Mapping, Iterable[Mapping]]) -> TM:
|
||||
result = self.copy()
|
||||
result += other
|
||||
return result
|
||||
|
||||
def __radd__(self: TM, other: Union[str, Mapping, Iterable[Mapping]]) -> TM:
|
||||
result = self.__class__(other) # type: ignore
|
||||
return result + self
|
||||
|
||||
def __iadd__(self: TM, other: Union[str, Mapping, Iterable[Mapping]]) -> TM:
|
||||
if isinstance(other, MessageSegment):
|
||||
self.append(other)
|
||||
elif isinstance(other, Message):
|
||||
self.extend(other)
|
||||
else:
|
||||
self.extend(self._construct(other))
|
||||
return self
|
||||
|
||||
def append(self: TM, obj: Union[str, TMS]) -> TM:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
添加一个消息段到消息数组末尾
|
||||
|
||||
:参数:
|
||||
|
||||
* ``obj: Union[str, MessageSegment]``: 要添加的消息段
|
||||
"""
|
||||
if isinstance(obj, MessageSegment):
|
||||
super(Message, self).append(obj)
|
||||
elif isinstance(obj, str):
|
||||
self.extend(self._construct(obj))
|
||||
else:
|
||||
raise ValueError(f"Unexpected type: {type(obj)} {obj}")
|
||||
return self
|
||||
|
||||
def extend(self: TM, obj: Union[TM, Iterable[TMS]]) -> TM:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
拼接一个消息数组或多个消息段到消息数组末尾
|
||||
|
||||
:参数:
|
||||
|
||||
* ``obj: Union[Message, Iterable[MessageSegment]]``: 要添加的消息数组
|
||||
"""
|
||||
for segment in obj:
|
||||
self.append(segment)
|
||||
return self
|
||||
|
||||
def copy(self: TM) -> TM:
|
||||
return deepcopy(self)
|
||||
|
||||
def extract_plain_text(self: "Message[MessageSegment]") -> str:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
提取消息内纯文本消息
|
||||
"""
|
||||
|
||||
return "".join(str(seg) for seg in self if seg.is_text())
|
@ -1,23 +1,18 @@
|
||||
"""
|
||||
配置
|
||||
====
|
||||
"""本模块定义了 NoneBot 本身运行所需的配置项。
|
||||
|
||||
NoneBot 使用 `pydantic`_ 以及 `python-dotenv`_ 来读取配置。
|
||||
NoneBot 使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及 [`python-dotenv`](https://saurabh-kumar.com/python-dotenv/) 来读取配置。
|
||||
|
||||
配置项需符合特殊格式或 json 序列化格式。详情见 `pydantic Field Type`_ 文档。
|
||||
配置项需符合特殊格式或 json 序列化格式。详情见 [`pydantic Field Type`](https://pydantic-docs.helpmanual.io/usage/types/) 文档。
|
||||
|
||||
.. _pydantic:
|
||||
https://pydantic-docs.helpmanual.io/
|
||||
.. _python-dotenv:
|
||||
https://saurabh-kumar.com/python-dotenv/
|
||||
.. _pydantic Field Type:
|
||||
https://pydantic-docs.helpmanual.io/usage/types/
|
||||
FrontMatter:
|
||||
sidebar_position: 1
|
||||
description: nonebot.config 模块
|
||||
"""
|
||||
import os
|
||||
from pathlib import Path
|
||||
from datetime import timedelta
|
||||
from ipaddress import IPv4Address
|
||||
from typing import Any, Set, Dict, Tuple, Union, Mapping, Optional
|
||||
from typing import TYPE_CHECKING, Any, Set, Dict, Tuple, Union, Mapping, Optional
|
||||
|
||||
from pydantic import BaseSettings, IPvAnyAddress
|
||||
from pydantic.env_settings import (
|
||||
@ -105,9 +100,10 @@ class CustomEnvSettings(EnvSettingsSource):
|
||||
|
||||
|
||||
class BaseConfig(BaseSettings):
|
||||
# dummy getattr for pylance checking, actually not used
|
||||
def __getattr__(self, name: str) -> Any: # pragma: no cover
|
||||
return self.__dict__.get(name)
|
||||
if TYPE_CHECKING:
|
||||
# dummy getattr for pylance checking, actually not used
|
||||
def __getattr__(self, name: str) -> Any: # pragma: no cover
|
||||
return self.__dict__.get(name)
|
||||
|
||||
class Config:
|
||||
@classmethod
|
||||
@ -129,19 +125,15 @@ class BaseConfig(BaseSettings):
|
||||
|
||||
|
||||
class Env(BaseConfig):
|
||||
"""
|
||||
运行环境配置。大小写不敏感。
|
||||
"""运行环境配置。大小写不敏感。
|
||||
|
||||
将会从 ``nonebot.init 参数`` > ``环境变量`` > ``.env 环境配置文件`` 的优先级读取配置。
|
||||
将会从 `环境变量` > `.env 环境配置文件` 的优先级读取环境信息。
|
||||
"""
|
||||
|
||||
environment: str = "prod"
|
||||
"""
|
||||
- **类型**: ``str``
|
||||
- **默认值**: ``"prod"``
|
||||
"""当前环境名。
|
||||
|
||||
:说明:
|
||||
当前环境名。 NoneBot 将从 ``.env.{environment}`` 文件中加载配置。
|
||||
NoneBot 将从 `.env.{environment}` 文件中加载配置。
|
||||
"""
|
||||
|
||||
class Config:
|
||||
@ -150,138 +142,85 @@ class Env(BaseConfig):
|
||||
|
||||
|
||||
class Config(BaseConfig):
|
||||
"""
|
||||
NoneBot 主要配置。大小写不敏感。
|
||||
"""NoneBot 主要配置。大小写不敏感。
|
||||
|
||||
除了 NoneBot 的配置项外,还可以自行添加配置项到 ``.env.{environment}`` 文件中。
|
||||
这些配置将会在 json 反序列化后一起带入 ``Config`` 类中。
|
||||
除了 NoneBot 的配置项外,还可以自行添加配置项到 `.env.{environment}` 文件中。
|
||||
这些配置将会在 json 反序列化后一起带入 `Config` 类中。
|
||||
|
||||
配置方法参考: [配置](https://v2.nonebot.dev/docs/tutorial/configuration)
|
||||
"""
|
||||
|
||||
_common_config: dict
|
||||
_env_file: str
|
||||
_env_file: str = ".env"
|
||||
_common_config: Dict[str, Any] = {}
|
||||
|
||||
# nonebot configs
|
||||
driver: str = "~fastapi"
|
||||
"""
|
||||
- **类型**: ``str``
|
||||
- **默认值**: ``"~fastapi"``
|
||||
"""NoneBot 运行所使用的 `Driver` 。继承自 {ref}`nonebot.drivers.Driver` 。
|
||||
|
||||
:说明:
|
||||
配置格式为 `<module>[:<Driver>][+<module>[:<Mixin>]]*`。
|
||||
|
||||
NoneBot 运行所使用的 ``Driver`` 。继承自 ``nonebot.drivers.Driver`` 。
|
||||
|
||||
配置格式为 ``<module>[:<Driver>][+<module>[:<Mixin>]]*``。
|
||||
|
||||
``~`` 为 ``nonebot.drivers.`` 的缩写。
|
||||
`~` 为 `nonebot.drivers.` 的缩写。
|
||||
"""
|
||||
host: IPvAnyAddress = IPv4Address("127.0.0.1") # type: ignore
|
||||
"""
|
||||
- **类型**: ``IPvAnyAddress``
|
||||
- **默认值**: ``127.0.0.1``
|
||||
|
||||
:说明:
|
||||
|
||||
NoneBot 的 HTTP 和 WebSocket 服务端监听的 IP/主机名。
|
||||
"""
|
||||
"""NoneBot {ref}`nonebot.drivers.ReverseDriver` 服务端监听的 IP/主机名。"""
|
||||
port: int = 8080
|
||||
"""
|
||||
- **类型**: ``int``
|
||||
- **默认值**: ``8080``
|
||||
|
||||
:说明:
|
||||
|
||||
NoneBot 的 HTTP 和 WebSocket 服务端监听的端口。
|
||||
"""
|
||||
"""NoneBot {ref}`nonebot.drivers.ReverseDriver` 服务端监听的端口。"""
|
||||
log_level: Union[int, str] = "INFO"
|
||||
"""
|
||||
- **类型**: ``Union[int, str]``
|
||||
- **默认值**: ``INFO``
|
||||
"""NoneBot 日志输出等级,可以为 `int` 类型等级或等级名称
|
||||
|
||||
:说明:
|
||||
参考 [`loguru 日志等级`](https://loguru.readthedocs.io/en/stable/api/logger.html#levels)。
|
||||
|
||||
配置 NoneBot 日志输出等级,可以为 ``int`` 类型等级或等级名称,参考 `loguru 日志等级`_。
|
||||
|
||||
:示例:
|
||||
|
||||
.. code-block:: default
|
||||
:::tip 提示
|
||||
日志等级名称应为大写,如 `INFO`。
|
||||
:::
|
||||
|
||||
用法:
|
||||
```conf
|
||||
LOG_LEVEL=25
|
||||
LOG_LEVEL=INFO
|
||||
|
||||
.. _loguru 日志等级:
|
||||
https://loguru.readthedocs.io/en/stable/api/logger.html#levels
|
||||
```
|
||||
"""
|
||||
|
||||
# bot connection configs
|
||||
api_timeout: Optional[float] = 30.0
|
||||
"""
|
||||
- **类型**: ``Optional[float]``
|
||||
- **默认值**: ``30.``
|
||||
|
||||
:说明:
|
||||
|
||||
API 请求超时时间,单位: 秒。
|
||||
"""
|
||||
"""API 请求超时时间,单位: 秒。"""
|
||||
|
||||
# bot runtime configs
|
||||
superusers: Set[str] = set()
|
||||
"""
|
||||
- **类型**: ``Set[str]``
|
||||
- **默认值**: ``set()``
|
||||
|
||||
:说明:
|
||||
|
||||
机器人超级用户。
|
||||
|
||||
:示例:
|
||||
|
||||
.. code-block:: default
|
||||
"""机器人超级用户。
|
||||
|
||||
用法:
|
||||
```conf
|
||||
SUPERUSERS=["12345789"]
|
||||
```
|
||||
"""
|
||||
nickname: Set[str] = set()
|
||||
"""
|
||||
- **类型**: ``Set[str]``
|
||||
- **默认值**: ``set()``
|
||||
|
||||
:说明:
|
||||
|
||||
机器人昵称。
|
||||
"""
|
||||
"""机器人昵称。"""
|
||||
command_start: Set[str] = {"/"}
|
||||
"""
|
||||
- **类型**: ``Set[str]``
|
||||
- **默认值**: ``{"/"}``
|
||||
"""命令的起始标记,用于判断一条消息是不是命令。
|
||||
|
||||
:说明:
|
||||
|
||||
命令的起始标记,用于判断一条消息是不是命令。
|
||||
用法:
|
||||
```conf
|
||||
COMMAND_START=["/", ""]
|
||||
```
|
||||
"""
|
||||
command_sep: Set[str] = {"."}
|
||||
"""
|
||||
- **类型**: ``Set[str]``
|
||||
- **默认值**: ``{"."}``
|
||||
"""命令的分隔标记,用于将文本形式的命令切分为元组(实际的命令名)。
|
||||
|
||||
:说明:
|
||||
|
||||
命令的分隔标记,用于将文本形式的命令切分为元组(实际的命令名)。
|
||||
用法:
|
||||
```conf
|
||||
COMMAND_SEP=["."]
|
||||
```
|
||||
"""
|
||||
session_expire_timeout: timedelta = timedelta(minutes=2)
|
||||
"""
|
||||
- **类型**: ``timedelta``
|
||||
- **默认值**: ``timedelta(minutes=2)``
|
||||
|
||||
:说明:
|
||||
|
||||
等待用户回复的超时时间。
|
||||
|
||||
:示例:
|
||||
|
||||
.. code-block:: default
|
||||
"""等待用户回复的超时时间。
|
||||
|
||||
用法:
|
||||
```conf
|
||||
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
|
||||
```
|
||||
"""
|
||||
|
||||
# adapter configs
|
||||
@ -294,3 +233,9 @@ class Config(BaseConfig):
|
||||
class Config:
|
||||
extra = "allow"
|
||||
env_file = ".env.prod"
|
||||
|
||||
|
||||
__autodoc__ = {
|
||||
"CustomEnvSettings": False,
|
||||
"BaseConfig": False,
|
||||
}
|
||||
|
@ -1,20 +1,42 @@
|
||||
"""本模块包含了 NoneBot 事件处理过程中使用到的常量。
|
||||
|
||||
FrontMatter:
|
||||
sidebar_position: 9
|
||||
description: nonebot.consts 模块
|
||||
"""
|
||||
from typing_extensions import Literal
|
||||
|
||||
# used by Matcher
|
||||
RECEIVE_KEY = "_receive_{id}"
|
||||
LAST_RECEIVE_KEY = "_last_receive"
|
||||
ARG_KEY = "{key}"
|
||||
REJECT_TARGET = "_current_target"
|
||||
REJECT_CACHE_TARGET = "_next_target"
|
||||
RECEIVE_KEY: Literal["_receive_{id}"] = "_receive_{id}"
|
||||
"""`receive` 存储 key"""
|
||||
LAST_RECEIVE_KEY: Literal["_last_receive"] = "_last_receive"
|
||||
"""`last_receive` 存储 key"""
|
||||
ARG_KEY: Literal["{key}"] = "{key}"
|
||||
"""`arg` 存储 key"""
|
||||
REJECT_TARGET: Literal["_current_target"] = "_current_target"
|
||||
"""当前 `reject` 目标存储 key"""
|
||||
REJECT_CACHE_TARGET: Literal["_next_target"] = "_next_target"
|
||||
"""下一个 `reject` 目标存储 key"""
|
||||
|
||||
# used by Rule
|
||||
PREFIX_KEY = "_prefix"
|
||||
PREFIX_KEY: Literal["_prefix"] = "_prefix"
|
||||
"""命令前缀存储 key"""
|
||||
|
||||
CMD_KEY = "command"
|
||||
RAW_CMD_KEY = "raw_command"
|
||||
CMD_ARG_KEY = "command_arg"
|
||||
CMD_KEY: Literal["command"] = "command"
|
||||
"""命令元组存储 key"""
|
||||
RAW_CMD_KEY: Literal["raw_command"] = "raw_command"
|
||||
"""命令文本存储 key"""
|
||||
CMD_ARG_KEY: Literal["command_arg"] = "command_arg"
|
||||
"""命令参数存储 key"""
|
||||
|
||||
SHELL_ARGS = "_args"
|
||||
SHELL_ARGV = "_argv"
|
||||
SHELL_ARGS: Literal["_args"] = "_args"
|
||||
"""shell 命令 parse 后参数字典存储 key"""
|
||||
SHELL_ARGV: Literal["_argv"] = "_argv"
|
||||
"""shell 命令原始参数列表存储 key"""
|
||||
|
||||
REGEX_MATCHED = "_matched"
|
||||
REGEX_GROUP = "_matched_groups"
|
||||
REGEX_DICT = "_matched_dict"
|
||||
REGEX_MATCHED: Literal["_matched"] = "_matched"
|
||||
"""正则匹配结果存储 key"""
|
||||
REGEX_GROUP: Literal["_matched_groups"] = "_matched_groups"
|
||||
"""正则匹配 group 元组存储 key"""
|
||||
REGEX_DICT: Literal["_matched_dict"] = "_matched_dict"
|
||||
"""正则匹配 group 字典存储 key"""
|
||||
|
@ -1,8 +1,8 @@
|
||||
"""
|
||||
依赖注入处理模块
|
||||
================
|
||||
"""本模块模块实现了依赖注入的定义与处理。
|
||||
|
||||
该模块实现了依赖注入的定义与处理。
|
||||
FrontMatter:
|
||||
sidebar_position: 0
|
||||
description: nonebot.dependencies 模块
|
||||
"""
|
||||
|
||||
import abc
|
||||
@ -14,15 +14,21 @@ from pydantic.schema import get_annotation_from_field_info
|
||||
from pydantic.fields import Required, FieldInfo, Undefined, ModelField
|
||||
|
||||
from nonebot.log import logger
|
||||
from .utils import get_typed_signature
|
||||
from nonebot.exception import TypeMisMatch
|
||||
from nonebot.utils import run_sync, is_coroutine_callable
|
||||
|
||||
T = TypeVar("T", bound="Dependent")
|
||||
from .utils import check_field_type, get_typed_signature
|
||||
|
||||
R = TypeVar("R")
|
||||
T = TypeVar("T", bound="Dependent")
|
||||
|
||||
|
||||
class Param(abc.ABC, FieldInfo):
|
||||
"""依赖注入的基本单元 —— 参数。
|
||||
|
||||
继承自 `pydantic.fields.FieldInfo`,用于描述参数信息(不包括参数名)。
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls, dependent: "Dependent", name: str, param: inspect.Parameter
|
||||
@ -45,6 +51,16 @@ class CustomConfig(BaseConfig):
|
||||
|
||||
|
||||
class Dependent(Generic[R]):
|
||||
"""依赖注入容器
|
||||
|
||||
参数:
|
||||
call: 依赖注入的可调用对象,可以是任何 Callable 对象
|
||||
pre_checkers: 依赖注入解析前的参数检查
|
||||
params: 具名参数列表
|
||||
parameterless: 匿名参数列表
|
||||
allow_types: 允许的参数类型
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@ -180,15 +196,18 @@ class Dependent(Generic[R]):
|
||||
value = await field_info._solve(**params)
|
||||
if value == Undefined:
|
||||
value = field.get_default()
|
||||
_, errs_ = field.validate(value, values, loc=(str(field_info), field.alias))
|
||||
if errs_:
|
||||
|
||||
try:
|
||||
values[field.name] = check_field_type(field, value)
|
||||
except TypeMisMatch:
|
||||
logger.debug(
|
||||
f"{field_info} "
|
||||
f"type {type(value)} not match depends {self.call} "
|
||||
f"annotation {field._type_display()}, ignored"
|
||||
)
|
||||
raise TypeMisMatch(field, value)
|
||||
else:
|
||||
values[field.name] = value
|
||||
raise
|
||||
|
||||
return values
|
||||
|
||||
|
||||
__autodoc__ = {"CustomConfig": False}
|
||||
|
@ -1,11 +1,22 @@
|
||||
"""
|
||||
FrontMatter:
|
||||
sidebar_position: 1
|
||||
description: nonebot.dependencies.utils 模块
|
||||
"""
|
||||
import inspect
|
||||
from typing import Any, Dict, Callable
|
||||
from typing import Any, Dict, TypeVar, Callable
|
||||
|
||||
from loguru import logger
|
||||
from pydantic.fields import ModelField
|
||||
from pydantic.typing import ForwardRef, evaluate_forwardref
|
||||
|
||||
from nonebot.exception import TypeMisMatch
|
||||
|
||||
V = TypeVar("V")
|
||||
|
||||
|
||||
def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
|
||||
"""获取可调用对象签名"""
|
||||
signature = inspect.signature(call)
|
||||
globalns = getattr(call, "__globals__", {})
|
||||
typed_params = [
|
||||
@ -22,6 +33,7 @@ def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
|
||||
|
||||
|
||||
def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, Any]) -> Any:
|
||||
"""获取参数的类型注解"""
|
||||
annotation = param.annotation
|
||||
if isinstance(annotation, str):
|
||||
annotation = ForwardRef(annotation)
|
||||
@ -33,3 +45,10 @@ def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, Any]) ->
|
||||
)
|
||||
return inspect.Parameter.empty
|
||||
return annotation
|
||||
|
||||
|
||||
def check_field_type(field: ModelField, value: V) -> V:
|
||||
_, errs_ = field.validate(value, {}, loc=())
|
||||
if errs_:
|
||||
raise TypeMisMatch(field, value)
|
||||
return value
|
||||
|
@ -1,297 +1,38 @@
|
||||
"""
|
||||
后端驱动适配基类
|
||||
=================
|
||||
"""本模块定义了驱动适配器基类。
|
||||
|
||||
各驱动请继承以下基类
|
||||
各驱动请继承以下基类。
|
||||
|
||||
FrontMatter:
|
||||
sidebar_position: 0
|
||||
description: nonebot.drivers 模块
|
||||
"""
|
||||
|
||||
import abc
|
||||
import asyncio
|
||||
from dataclasses import dataclass
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Set,
|
||||
Dict,
|
||||
Type,
|
||||
Callable,
|
||||
Awaitable,
|
||||
AsyncGenerator,
|
||||
)
|
||||
from nonebot.internal.driver import URL as URL
|
||||
from nonebot.internal.driver import Driver as Driver
|
||||
from nonebot.internal.driver import Cookies as Cookies
|
||||
from nonebot.internal.driver import Request as Request
|
||||
from nonebot.internal.driver import Response as Response
|
||||
from nonebot.internal.driver import WebSocket as WebSocket
|
||||
from nonebot.internal.driver import HTTPVersion as HTTPVersion
|
||||
from nonebot.internal.driver import ForwardMixin as ForwardMixin
|
||||
from nonebot.internal.driver import ForwardDriver as ForwardDriver
|
||||
from nonebot.internal.driver import ReverseDriver as ReverseDriver
|
||||
from nonebot.internal.driver import combine_driver as combine_driver
|
||||
from nonebot.internal.driver import HTTPServerSetup as HTTPServerSetup
|
||||
from nonebot.internal.driver import WebSocketServerSetup as WebSocketServerSetup
|
||||
|
||||
from ._model import URL as URL
|
||||
from nonebot.log import logger
|
||||
from nonebot.utils import escape_tag
|
||||
from ._model import Request as Request
|
||||
from nonebot.config import Env, Config
|
||||
from ._model import Response as Response
|
||||
from ._model import WebSocket as WebSocket
|
||||
from ._model import HTTPVersion as HTTPVersion
|
||||
from nonebot.typing import T_BotConnectionHook, T_BotDisconnectionHook
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from nonebot.adapters import Bot, Adapter
|
||||
|
||||
|
||||
class Driver(abc.ABC):
|
||||
"""
|
||||
Driver 基类。
|
||||
"""
|
||||
|
||||
_adapters: Dict[str, "Adapter"] = {}
|
||||
"""
|
||||
:类型: ``Dict[str, Adapter]``
|
||||
:说明: 已注册的适配器列表
|
||||
"""
|
||||
_bot_connection_hook: Set[T_BotConnectionHook] = set()
|
||||
"""
|
||||
:类型: ``Set[T_BotConnectionHook]``
|
||||
:说明: Bot 连接建立时执行的函数
|
||||
"""
|
||||
_bot_disconnection_hook: Set[T_BotDisconnectionHook] = set()
|
||||
"""
|
||||
:类型: ``Set[T_BotDisconnectionHook]``
|
||||
:说明: Bot 连接断开时执行的函数
|
||||
"""
|
||||
|
||||
def __init__(self, env: Env, config: Config):
|
||||
"""
|
||||
:参数:
|
||||
|
||||
* ``env: Env``: 包含环境信息的 Env 对象
|
||||
* ``config: Config``: 包含配置信息的 Config 对象
|
||||
"""
|
||||
self.env: str = env.environment
|
||||
"""
|
||||
:类型: ``str``
|
||||
:说明: 环境名称
|
||||
"""
|
||||
self.config: Config = config
|
||||
"""
|
||||
:类型: ``Config``
|
||||
:说明: 配置对象
|
||||
"""
|
||||
self._clients: Dict[str, "Bot"] = {}
|
||||
"""
|
||||
:类型: ``Dict[str, Bot]``
|
||||
:说明: 已连接的 Bot
|
||||
"""
|
||||
|
||||
@property
|
||||
def bots(self) -> Dict[str, "Bot"]:
|
||||
"""
|
||||
:类型:
|
||||
|
||||
``Dict[str, Bot]``
|
||||
:说明:
|
||||
|
||||
获取当前所有已连接的 Bot
|
||||
"""
|
||||
return self._clients
|
||||
|
||||
def register_adapter(self, adapter: Type["Adapter"], **kwargs) -> None:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
注册一个协议适配器
|
||||
|
||||
:参数:
|
||||
|
||||
* ``name: str``: 适配器名称,用于在连接时进行识别
|
||||
* ``adapter: Type[Bot]``: 适配器 Class
|
||||
* ``**kwargs``: 其他传递给适配器的参数
|
||||
"""
|
||||
name = adapter.get_name()
|
||||
if name in self._adapters:
|
||||
logger.opt(colors=True).debug(
|
||||
f'Adapter "<y>{escape_tag(name)}</y>" already exists'
|
||||
)
|
||||
return
|
||||
self._adapters[name] = adapter(self, **kwargs)
|
||||
logger.opt(colors=True).debug(
|
||||
f'Succeeded to load adapter "<y>{escape_tag(name)}</y>"'
|
||||
)
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def type(self) -> str:
|
||||
"""驱动类型名称"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def logger(self):
|
||||
"""驱动专属 logger 日志记录器"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def run(self, *args, **kwargs):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
启动驱动框架
|
||||
|
||||
:参数:
|
||||
* ``*args``
|
||||
* ``**kwargs``
|
||||
"""
|
||||
logger.opt(colors=True).debug(
|
||||
f"<g>Loaded adapters: {escape_tag(', '.join(self._adapters))}</g>"
|
||||
)
|
||||
|
||||
@abc.abstractmethod
|
||||
def on_startup(self, func: Callable) -> Callable:
|
||||
"""注册一个在驱动启动时运行的函数"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def on_shutdown(self, func: Callable) -> Callable:
|
||||
"""注册一个在驱动停止时运行的函数"""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_bot_connect(self, func: T_BotConnectionHook) -> T_BotConnectionHook:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
装饰一个函数使他在 bot 通过 WebSocket 连接成功时执行。
|
||||
|
||||
:函数参数:
|
||||
|
||||
* ``bot: Bot``: 当前连接上的 Bot 对象
|
||||
"""
|
||||
self._bot_connection_hook.add(func)
|
||||
return func
|
||||
|
||||
def on_bot_disconnect(self, func: T_BotDisconnectionHook) -> T_BotDisconnectionHook:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
装饰一个函数使他在 bot 通过 WebSocket 连接断开时执行。
|
||||
|
||||
:函数参数:
|
||||
|
||||
* ``bot: Bot``: 当前连接上的 Bot 对象
|
||||
"""
|
||||
self._bot_disconnection_hook.add(func)
|
||||
return func
|
||||
|
||||
def _bot_connect(self, bot: "Bot") -> None:
|
||||
"""在 WebSocket 连接成功后,调用该函数来注册 bot 对象"""
|
||||
if bot.self_id in self._clients:
|
||||
raise RuntimeError(f"Duplicate bot connection with id {bot.self_id}")
|
||||
self._clients[bot.self_id] = bot
|
||||
|
||||
async def _run_hook(bot: "Bot") -> None:
|
||||
coros = list(map(lambda x: x(bot), self._bot_connection_hook))
|
||||
if coros:
|
||||
try:
|
||||
await asyncio.gather(*coros)
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running WebSocketConnection hook. "
|
||||
"Running cancelled!</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
asyncio.create_task(_run_hook(bot))
|
||||
|
||||
def _bot_disconnect(self, bot: "Bot") -> None:
|
||||
"""在 WebSocket 连接断开后,调用该函数来注销 bot 对象"""
|
||||
if bot.self_id in self._clients:
|
||||
del self._clients[bot.self_id]
|
||||
|
||||
async def _run_hook(bot: "Bot") -> None:
|
||||
coros = list(map(lambda x: x(bot), self._bot_disconnection_hook))
|
||||
if coros:
|
||||
try:
|
||||
await asyncio.gather(*coros)
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running WebSocketDisConnection hook. "
|
||||
"Running cancelled!</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
asyncio.create_task(_run_hook(bot))
|
||||
|
||||
|
||||
class ForwardMixin(abc.ABC):
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def type(self) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
async def request(self, setup: Request) -> Response:
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
@asynccontextmanager
|
||||
async def websocket(self, setup: Request) -> AsyncGenerator[WebSocket, None]:
|
||||
raise NotImplementedError
|
||||
yield # used for static type checking's generator detection
|
||||
|
||||
|
||||
class ForwardDriver(Driver, ForwardMixin):
|
||||
"""
|
||||
Forward Driver 基类。将客户端框架封装,以满足适配器使用。
|
||||
"""
|
||||
|
||||
|
||||
class ReverseDriver(Driver):
|
||||
"""
|
||||
Reverse Driver 基类。将后端框架封装,以满足适配器使用。
|
||||
"""
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def server_app(self) -> Any:
|
||||
"""驱动 APP 对象"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def asgi(self) -> Any:
|
||||
"""驱动 ASGI 对象"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def setup_http_server(self, setup: "HTTPServerSetup") -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def setup_websocket_server(self, setup: "WebSocketServerSetup") -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def combine_driver(driver: Type[Driver], *mixins: Type[ForwardMixin]) -> Type[Driver]:
|
||||
# check first
|
||||
assert issubclass(driver, Driver), "`driver` must be subclass of Driver"
|
||||
assert all(
|
||||
map(lambda m: issubclass(m, ForwardMixin), mixins)
|
||||
), "`mixins` must be subclass of ForwardMixin"
|
||||
|
||||
class CombinedDriver(*mixins, driver, ForwardDriver): # type: ignore
|
||||
@property
|
||||
def type(self) -> str:
|
||||
return (
|
||||
driver.type.__get__(self)
|
||||
+ "+"
|
||||
+ "+".join(map(lambda x: x.type.__get__(self), mixins))
|
||||
)
|
||||
|
||||
return CombinedDriver
|
||||
|
||||
|
||||
@dataclass
|
||||
class HTTPServerSetup:
|
||||
path: URL # path should not be absolute, check it by URL.is_absolute() == False
|
||||
method: str
|
||||
name: str
|
||||
handle_func: Callable[[Request], Awaitable[Response]]
|
||||
|
||||
|
||||
@dataclass
|
||||
class WebSocketServerSetup:
|
||||
path: URL # path should not be absolute, check it by URL.is_absolute() == False
|
||||
name: str
|
||||
handle_func: Callable[[WebSocket], Awaitable[Any]]
|
||||
__autodoc__ = {
|
||||
"URL": True,
|
||||
"Driver": True,
|
||||
"Cookies": True,
|
||||
"Request": True,
|
||||
"Response": True,
|
||||
"WebSocket": True,
|
||||
"HTTPVersion": True,
|
||||
"ForwardMixin": True,
|
||||
"ForwardDriver": True,
|
||||
"ReverseDriver": True,
|
||||
"combine_driver": True,
|
||||
"HTTPServerSetup": True,
|
||||
"WebSocketServerSetup": True,
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ class BlockDriver(Driver):
|
||||
@property
|
||||
@overrides(Driver)
|
||||
def type(self) -> str:
|
||||
"""驱动名称: ``block_driver``"""
|
||||
"""驱动名称: `block_driver`"""
|
||||
return "block_driver"
|
||||
|
||||
@property
|
||||
@ -39,13 +39,7 @@ class BlockDriver(Driver):
|
||||
@overrides(Driver)
|
||||
def on_startup(self, func: STARTUP_FUNC) -> STARTUP_FUNC:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
注册一个启动时执行的函数
|
||||
|
||||
:参数:
|
||||
|
||||
* ``func: Callable[[], Awaitable[None]]``
|
||||
注册一个启动时执行的函数
|
||||
"""
|
||||
self.startup_funcs.add(func)
|
||||
return func
|
||||
@ -53,13 +47,7 @@ class BlockDriver(Driver):
|
||||
@overrides(Driver)
|
||||
def on_shutdown(self, func: SHUTDOWN_FUNC) -> SHUTDOWN_FUNC:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
注册一个停止时执行的函数
|
||||
|
||||
:参数:
|
||||
|
||||
* ``func: Callable[[], Awaitable[None]]``
|
||||
注册一个停止时执行的函数
|
||||
"""
|
||||
self.shutdown_funcs.add(func)
|
||||
return func
|
||||
|
@ -1,11 +1,21 @@
|
||||
"""
|
||||
AIOHTTP 驱动适配
|
||||
================
|
||||
"""[AIOHTTP](https://aiohttp.readthedocs.io/en/stable/) 驱动适配器。
|
||||
|
||||
```bash
|
||||
nb driver install aiohttp
|
||||
# 或者
|
||||
pip install nonebot2[aiohttp]
|
||||
```
|
||||
|
||||
:::tip 提示
|
||||
本驱动仅支持客户端连接
|
||||
:::
|
||||
|
||||
FrontMatter:
|
||||
sidebar_position: 2
|
||||
description: nonebot.drivers.aiohttp 模块
|
||||
"""
|
||||
|
||||
from typing import AsyncGenerator
|
||||
from typing import Type, AsyncGenerator
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from nonebot.typing import overrides
|
||||
@ -13,7 +23,7 @@ from nonebot.drivers import Request, Response
|
||||
from nonebot.exception import WebSocketClosed
|
||||
from nonebot.drivers._block_driver import BlockDriver
|
||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||
from nonebot.drivers import HTTPVersion, ForwardMixin, combine_driver
|
||||
from nonebot.drivers import HTTPVersion, ForwardMixin, ForwardDriver, combine_driver
|
||||
|
||||
try:
|
||||
import aiohttp
|
||||
@ -24,6 +34,8 @@ except ImportError:
|
||||
|
||||
|
||||
class Mixin(ForwardMixin):
|
||||
"""AIOHTTP Mixin"""
|
||||
|
||||
@property
|
||||
@overrides(ForwardMixin)
|
||||
def type(self) -> str:
|
||||
@ -85,6 +97,8 @@ class Mixin(ForwardMixin):
|
||||
|
||||
|
||||
class WebSocket(BaseWebSocket):
|
||||
"""AIOHTTP Websocket Wrapper"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@ -139,4 +153,5 @@ class WebSocket(BaseWebSocket):
|
||||
await self.websocket.send_bytes(data)
|
||||
|
||||
|
||||
Driver = combine_driver(BlockDriver, Mixin)
|
||||
Driver: Type[ForwardDriver] = combine_driver(BlockDriver, Mixin) # type: ignore
|
||||
"""AIOHTTP Driver"""
|
||||
|
@ -1,13 +1,12 @@
|
||||
"""
|
||||
FastAPI 驱动适配
|
||||
================
|
||||
"""[FastAPI](https://fastapi.tiangolo.com/) 驱动适配
|
||||
|
||||
本驱动同时支持服务端以及客户端连接
|
||||
:::tip 提示
|
||||
本驱动仅支持服务端连接
|
||||
:::
|
||||
|
||||
后端使用方法请参考: `FastAPI 文档`_
|
||||
|
||||
.. _FastAPI 文档:
|
||||
https://fastapi.tiangolo.com/
|
||||
FrontMatter:
|
||||
sidebar_position: 1
|
||||
description: nonebot.drivers.fastapi 模块
|
||||
"""
|
||||
|
||||
import logging
|
||||
@ -20,10 +19,10 @@ from fastapi.responses import Response
|
||||
from fastapi import FastAPI, Request, UploadFile, status
|
||||
from starlette.websockets import WebSocket, WebSocketState, WebSocketDisconnect
|
||||
|
||||
from ._model import FileTypes
|
||||
from nonebot.config import Env
|
||||
from nonebot.typing import overrides
|
||||
from nonebot.exception import WebSocketClosed
|
||||
from nonebot.internal.driver import FileTypes
|
||||
from nonebot.config import Config as NoneBotConfig
|
||||
from nonebot.drivers import Request as BaseRequest
|
||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||
@ -42,97 +41,33 @@ def catch_closed(func):
|
||||
|
||||
|
||||
class Config(BaseSettings):
|
||||
"""
|
||||
FastAPI 驱动框架设置,详情参考 FastAPI 文档
|
||||
"""
|
||||
"""FastAPI 驱动框架设置,详情参考 FastAPI 文档"""
|
||||
|
||||
fastapi_openapi_url: Optional[str] = None
|
||||
"""
|
||||
:类型:
|
||||
|
||||
``Optional[str]``
|
||||
|
||||
:说明:
|
||||
|
||||
``openapi.json`` 地址,默认为 ``None`` 即关闭
|
||||
"""
|
||||
"""`openapi.json` 地址,默认为 `None` 即关闭"""
|
||||
fastapi_docs_url: Optional[str] = None
|
||||
"""
|
||||
:类型:
|
||||
|
||||
``Optional[str]``
|
||||
|
||||
:说明:
|
||||
|
||||
``swagger`` 地址,默认为 ``None`` 即关闭
|
||||
"""
|
||||
"""`swagger` 地址,默认为 `None` 即关闭"""
|
||||
fastapi_redoc_url: Optional[str] = None
|
||||
"""
|
||||
:类型:
|
||||
|
||||
``Optional[str]``
|
||||
|
||||
:说明:
|
||||
|
||||
``redoc`` 地址,默认为 ``None`` 即关闭
|
||||
"""
|
||||
"""`redoc` 地址,默认为 `None` 即关闭"""
|
||||
fastapi_include_adapter_schema: bool = True
|
||||
"""是否包含适配器路由的 schema,默认为 `True`"""
|
||||
fastapi_reload: bool = False
|
||||
"""
|
||||
:类型:
|
||||
|
||||
``bool``
|
||||
|
||||
:说明:
|
||||
|
||||
开启/关闭冷重载
|
||||
"""
|
||||
"""开启/关闭冷重载"""
|
||||
fastapi_reload_dirs: Optional[List[str]] = None
|
||||
"""
|
||||
:类型:
|
||||
|
||||
``Optional[List[str]]``
|
||||
|
||||
:说明:
|
||||
|
||||
重载监控文件夹列表,默认为 uvicorn 默认值
|
||||
"""
|
||||
"""重载监控文件夹列表,默认为 uvicorn 默认值"""
|
||||
fastapi_reload_delay: Optional[float] = None
|
||||
"""
|
||||
:类型:
|
||||
|
||||
``Optional[float]``
|
||||
|
||||
:说明:
|
||||
|
||||
重载延迟,默认为 uvicorn 默认值
|
||||
"""
|
||||
"""重载延迟,默认为 uvicorn 默认值"""
|
||||
fastapi_reload_includes: Optional[List[str]] = None
|
||||
"""
|
||||
:类型:
|
||||
|
||||
``Optional[List[str]]``
|
||||
|
||||
:说明:
|
||||
|
||||
要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值
|
||||
"""
|
||||
"""要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值"""
|
||||
fastapi_reload_excludes: Optional[List[str]] = None
|
||||
"""
|
||||
:类型:
|
||||
|
||||
``Optional[List[str]]``
|
||||
|
||||
:说明:
|
||||
|
||||
不要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值
|
||||
"""
|
||||
"""不要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值"""
|
||||
|
||||
class Config:
|
||||
extra = "ignore"
|
||||
|
||||
|
||||
class Driver(ReverseDriver):
|
||||
"""FastAPI 驱动框架。包含反向 Server 功能。"""
|
||||
"""FastAPI 驱动框架。"""
|
||||
|
||||
def __init__(self, env: Env, config: NoneBotConfig):
|
||||
super(Driver, self).__init__(env, config)
|
||||
@ -148,19 +83,19 @@ class Driver(ReverseDriver):
|
||||
@property
|
||||
@overrides(ReverseDriver)
|
||||
def type(self) -> str:
|
||||
"""驱动名称: ``fastapi``"""
|
||||
"""驱动名称: `fastapi`"""
|
||||
return "fastapi"
|
||||
|
||||
@property
|
||||
@overrides(ReverseDriver)
|
||||
def server_app(self) -> FastAPI:
|
||||
"""``FastAPI APP`` 对象"""
|
||||
"""`FastAPI APP` 对象"""
|
||||
return self._server_app
|
||||
|
||||
@property
|
||||
@overrides(ReverseDriver)
|
||||
def asgi(self) -> FastAPI:
|
||||
"""``FastAPI APP`` 对象"""
|
||||
"""`FastAPI APP` 对象"""
|
||||
return self._server_app
|
||||
|
||||
@property
|
||||
@ -179,6 +114,7 @@ class Driver(ReverseDriver):
|
||||
_handle,
|
||||
name=setup.name,
|
||||
methods=[setup.method],
|
||||
include_in_schema=self.fastapi_config.fastapi_include_adapter_schema,
|
||||
)
|
||||
|
||||
@overrides(ReverseDriver)
|
||||
@ -199,7 +135,7 @@ class Driver(ReverseDriver):
|
||||
|
||||
@overrides(ReverseDriver)
|
||||
def on_shutdown(self, func: Callable) -> Callable:
|
||||
"""参考文档: `Events <https://fastapi.tiangolo.com/advanced/events/#startup-event>`_"""
|
||||
"""参考文档: `Events <https://fastapi.tiangolo.com/advanced/events/#shutdown-event>`_"""
|
||||
return self.server_app.on_event("shutdown")(func)
|
||||
|
||||
@overrides(ReverseDriver)
|
||||
@ -211,7 +147,7 @@ class Driver(ReverseDriver):
|
||||
app: Optional[str] = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""使用 ``uvicorn`` 启动 FastAPI"""
|
||||
"""使用 `uvicorn` 启动 FastAPI"""
|
||||
super().run(host, port, app, **kwargs)
|
||||
LOGGING_CONFIG = {
|
||||
"version": 1,
|
||||
@ -300,6 +236,8 @@ class Driver(ReverseDriver):
|
||||
|
||||
|
||||
class FastAPIWebSocket(BaseWebSocket):
|
||||
"""FastAPI WebSocket Wrapper"""
|
||||
|
||||
@overrides(BaseWebSocket)
|
||||
def __init__(self, *, request: BaseRequest, websocket: WebSocket):
|
||||
super().__init__(request=request)
|
||||
@ -340,3 +278,6 @@ class FastAPIWebSocket(BaseWebSocket):
|
||||
@overrides(BaseWebSocket)
|
||||
async def send_bytes(self, data: bytes) -> None:
|
||||
await self.websocket.send({"type": "websocket.send", "bytes": data})
|
||||
|
||||
|
||||
__autodoc__ = {"catch_closed": False}
|
||||
|
@ -1,4 +1,20 @@
|
||||
from typing import AsyncGenerator
|
||||
"""[HTTPX](https://www.python-httpx.org/) 驱动适配
|
||||
|
||||
```bash
|
||||
nb driver install httpx
|
||||
# 或者
|
||||
pip install nonebot2[httpx]
|
||||
```
|
||||
|
||||
:::tip 提示
|
||||
本驱动仅支持客户端 HTTP 连接
|
||||
:::
|
||||
|
||||
FrontMatter:
|
||||
sidebar_position: 3
|
||||
description: nonebot.drivers.httpx 模块
|
||||
"""
|
||||
from typing import Type, AsyncGenerator
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from nonebot.typing import overrides
|
||||
@ -9,6 +25,7 @@ from nonebot.drivers import (
|
||||
WebSocket,
|
||||
HTTPVersion,
|
||||
ForwardMixin,
|
||||
ForwardDriver,
|
||||
combine_driver,
|
||||
)
|
||||
|
||||
@ -21,6 +38,8 @@ except ImportError:
|
||||
|
||||
|
||||
class Mixin(ForwardMixin):
|
||||
"""HTTPX Mixin"""
|
||||
|
||||
@property
|
||||
@overrides(ForwardMixin)
|
||||
def type(self) -> str:
|
||||
@ -57,4 +76,5 @@ class Mixin(ForwardMixin):
|
||||
yield ws
|
||||
|
||||
|
||||
Driver = combine_driver(BlockDriver, Mixin)
|
||||
Driver: Type[ForwardDriver] = combine_driver(BlockDriver, Mixin) # type: ignore
|
||||
"""HTTPX Driver"""
|
||||
|
@ -1,11 +1,18 @@
|
||||
"""
|
||||
Quart 驱动适配
|
||||
================
|
||||
"""[Quart](https://pgjones.gitlab.io/quart/index.html) 驱动适配
|
||||
|
||||
后端使用方法请参考: `Quart 文档`_
|
||||
```bash
|
||||
nb driver install quart
|
||||
# 或者
|
||||
pip install nonebot2[quart]
|
||||
```
|
||||
|
||||
.. _Quart 文档:
|
||||
https://pgjones.gitlab.io/quart/index.html
|
||||
:::tip 提示
|
||||
本驱动仅支持服务端连接
|
||||
:::
|
||||
|
||||
FrontMatter:
|
||||
sidebar_position: 5
|
||||
description: nonebot.drivers.quart 模块
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
@ -15,10 +22,10 @@ from typing import List, Tuple, TypeVar, Callable, Optional, Coroutine
|
||||
import uvicorn
|
||||
from pydantic import BaseSettings
|
||||
|
||||
from ._model import FileTypes
|
||||
from nonebot.config import Env
|
||||
from nonebot.typing import overrides
|
||||
from nonebot.exception import WebSocketClosed
|
||||
from nonebot.internal.driver import FileTypes
|
||||
from nonebot.config import Config as NoneBotConfig
|
||||
from nonebot.drivers import Request as BaseRequest
|
||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||
@ -50,69 +57,25 @@ def catch_closed(func):
|
||||
|
||||
|
||||
class Config(BaseSettings):
|
||||
"""
|
||||
Quart 驱动框架设置
|
||||
"""
|
||||
"""Quart 驱动框架设置"""
|
||||
|
||||
quart_reload: bool = False
|
||||
"""
|
||||
:类型:
|
||||
|
||||
``bool``
|
||||
|
||||
:说明:
|
||||
|
||||
开启/关闭冷重载
|
||||
"""
|
||||
"""开启/关闭冷重载"""
|
||||
quart_reload_dirs: Optional[List[str]] = None
|
||||
"""
|
||||
:类型:
|
||||
|
||||
``Optional[List[str]]``
|
||||
|
||||
:说明:
|
||||
|
||||
重载监控文件夹列表,默认为 uvicorn 默认值
|
||||
"""
|
||||
"""重载监控文件夹列表,默认为 uvicorn 默认值"""
|
||||
quart_reload_delay: Optional[float] = None
|
||||
"""
|
||||
:类型:
|
||||
|
||||
``Optional[float]``
|
||||
|
||||
:说明:
|
||||
|
||||
重载延迟,默认为 uvicorn 默认值
|
||||
"""
|
||||
"""重载延迟,默认为 uvicorn 默认值"""
|
||||
quart_reload_includes: Optional[List[str]] = None
|
||||
"""
|
||||
:类型:
|
||||
|
||||
``Optional[List[str]]``
|
||||
|
||||
:说明:
|
||||
|
||||
要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值
|
||||
"""
|
||||
"""要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值"""
|
||||
quart_reload_excludes: Optional[List[str]] = None
|
||||
"""
|
||||
:类型:
|
||||
|
||||
``Optional[List[str]]``
|
||||
|
||||
:说明:
|
||||
|
||||
不要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值
|
||||
"""
|
||||
"""不要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值"""
|
||||
|
||||
class Config:
|
||||
extra = "ignore"
|
||||
|
||||
|
||||
class Driver(ReverseDriver):
|
||||
"""
|
||||
Quart 驱动框架
|
||||
"""
|
||||
"""Quart 驱动框架"""
|
||||
|
||||
def __init__(self, env: Env, config: NoneBotConfig):
|
||||
super().__init__(env, config)
|
||||
@ -124,19 +87,19 @@ class Driver(ReverseDriver):
|
||||
@property
|
||||
@overrides(ReverseDriver)
|
||||
def type(self) -> str:
|
||||
"""驱动名称: ``quart``"""
|
||||
"""驱动名称: `quart`"""
|
||||
return "quart"
|
||||
|
||||
@property
|
||||
@overrides(ReverseDriver)
|
||||
def server_app(self) -> Quart:
|
||||
"""``Quart`` 对象"""
|
||||
"""`Quart` 对象"""
|
||||
return self._server_app
|
||||
|
||||
@property
|
||||
@overrides(ReverseDriver)
|
||||
def asgi(self):
|
||||
"""``Quart`` 对象"""
|
||||
"""`Quart` 对象"""
|
||||
return self._server_app
|
||||
|
||||
@property
|
||||
@ -170,16 +133,12 @@ class Driver(ReverseDriver):
|
||||
|
||||
@overrides(ReverseDriver)
|
||||
def on_startup(self, func: _AsyncCallable) -> _AsyncCallable:
|
||||
"""参考文档: `Startup and Shutdown`_
|
||||
|
||||
.. _Startup and Shutdown:
|
||||
https://pgjones.gitlab.io/quart/how_to_guides/startup_shutdown.html
|
||||
"""
|
||||
"""参考文档: [`Startup and Shutdown`](https://pgjones.gitlab.io/quart/how_to_guides/startup_shutdown.html)"""
|
||||
return self.server_app.before_serving(func) # type: ignore
|
||||
|
||||
@overrides(ReverseDriver)
|
||||
def on_shutdown(self, func: _AsyncCallable) -> _AsyncCallable:
|
||||
"""参考文档: `Startup and Shutdown`_"""
|
||||
"""参考文档: [`Startup and Shutdown`](https://pgjones.gitlab.io/quart/how_to_guides/startup_shutdown.html)"""
|
||||
return self.server_app.after_serving(func) # type: ignore
|
||||
|
||||
@overrides(ReverseDriver)
|
||||
@ -191,7 +150,7 @@ class Driver(ReverseDriver):
|
||||
app: Optional[str] = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""使用 ``uvicorn`` 启动 Quart"""
|
||||
"""使用 `uvicorn` 启动 Quart"""
|
||||
super().run(host, port, app, **kwargs)
|
||||
LOGGING_CONFIG = {
|
||||
"version": 1,
|
||||
@ -276,6 +235,8 @@ class Driver(ReverseDriver):
|
||||
|
||||
|
||||
class WebSocket(BaseWebSocket):
|
||||
"""Quart WebSocket Wrapper"""
|
||||
|
||||
def __init__(self, *, request: BaseRequest, websocket: QuartWebSocket):
|
||||
super().__init__(request=request)
|
||||
self.websocket = websocket
|
||||
@ -317,3 +278,6 @@ class WebSocket(BaseWebSocket):
|
||||
@overrides(BaseWebSocket)
|
||||
async def send_bytes(self, data: bytes):
|
||||
await self.websocket.send(data)
|
||||
|
||||
|
||||
__autodoc__ = {"catch_closed": False}
|
||||
|
@ -1,6 +1,22 @@
|
||||
"""[websockets](https://websockets.readthedocs.io/) 驱动适配
|
||||
|
||||
```bash
|
||||
nb driver install websockets
|
||||
# 或者
|
||||
pip install nonebot2[websockets]
|
||||
```
|
||||
|
||||
:::tip 提示
|
||||
本驱动仅支持客户端 WebSocket 连接
|
||||
:::
|
||||
|
||||
FrontMatter:
|
||||
sidebar_position: 4
|
||||
description: nonebot.drivers.websockets 模块
|
||||
"""
|
||||
import logging
|
||||
from functools import wraps
|
||||
from typing import AsyncGenerator
|
||||
from typing import Type, AsyncGenerator
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from nonebot.typing import overrides
|
||||
@ -9,7 +25,7 @@ from nonebot.drivers import Request, Response
|
||||
from nonebot.exception import WebSocketClosed
|
||||
from nonebot.drivers._block_driver import BlockDriver
|
||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||
from nonebot.drivers import ForwardMixin, combine_driver
|
||||
from nonebot.drivers import ForwardMixin, ForwardDriver, combine_driver
|
||||
|
||||
try:
|
||||
from websockets.exceptions import ConnectionClosed
|
||||
@ -38,6 +54,8 @@ def catch_closed(func):
|
||||
|
||||
|
||||
class Mixin(ForwardMixin):
|
||||
"""Websockets Mixin"""
|
||||
|
||||
@property
|
||||
@overrides(ForwardMixin)
|
||||
def type(self) -> str:
|
||||
@ -60,6 +78,8 @@ class Mixin(ForwardMixin):
|
||||
|
||||
|
||||
class WebSocket(BaseWebSocket):
|
||||
"""Websockets WebSocket Wrapper"""
|
||||
|
||||
@overrides(BaseWebSocket)
|
||||
def __init__(self, *, request: Request, websocket: WebSocketClientProtocol):
|
||||
super().__init__(request=request)
|
||||
@ -103,4 +123,5 @@ class WebSocket(BaseWebSocket):
|
||||
await self.websocket.send(data)
|
||||
|
||||
|
||||
Driver = combine_driver(BlockDriver, Mixin)
|
||||
Driver: Type[ForwardDriver] = combine_driver(BlockDriver, Mixin) # type: ignore
|
||||
"""Websockets Driver"""
|
||||
|
@ -1,9 +1,32 @@
|
||||
"""
|
||||
异常
|
||||
====
|
||||
"""本模块包含了所有 NoneBot 运行时可能会抛出的异常。
|
||||
|
||||
下列文档中的异常是所有 NoneBot 运行时可能会抛出的。
|
||||
这些异常并非所有需要用户处理,在 NoneBot 内部运行时被捕获,并进行对应操作。
|
||||
|
||||
```bash
|
||||
NoneBotException
|
||||
├── ParserExit
|
||||
├── ProcessException
|
||||
| ├── IgnoredException
|
||||
| ├── MockApiException
|
||||
| └── StopPropagation
|
||||
├── MatcherException
|
||||
| ├── SkippedException
|
||||
| | └── TypeMisMatch
|
||||
| ├── PausedException
|
||||
| ├── RejectedException
|
||||
| └── FinishedException
|
||||
├── AdapterException
|
||||
| ├── NoLogException
|
||||
| ├── ApiNotAvailable
|
||||
| ├── NetworkError
|
||||
| └── ActionFailed
|
||||
└── DriverException
|
||||
└── WebSocketClosed
|
||||
```
|
||||
|
||||
FrontMatter:
|
||||
sidebar_position: 10
|
||||
description: nonebot.exception 模块
|
||||
"""
|
||||
|
||||
from typing import Any, Optional
|
||||
@ -12,25 +35,12 @@ from pydantic.fields import ModelField
|
||||
|
||||
|
||||
class NoneBotException(Exception):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
所有 NoneBot 发生的异常基类。
|
||||
"""
|
||||
"""所有 NoneBot 发生的异常基类。"""
|
||||
|
||||
|
||||
# Rule Exception
|
||||
class ParserExit(NoneBotException):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
``shell command`` 处理消息失败时返回的异常
|
||||
|
||||
:参数:
|
||||
|
||||
* ``status``
|
||||
* ``message``
|
||||
"""
|
||||
"""{ref}`nonebot.rule.shell_command` 处理消息失败时返回的异常"""
|
||||
|
||||
def __init__(self, status: int = 0, message: Optional[str] = None):
|
||||
self.status = status
|
||||
@ -45,26 +55,18 @@ class ParserExit(NoneBotException):
|
||||
|
||||
# Processor Exception
|
||||
class ProcessException(NoneBotException):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
事件处理过程中发生的异常基类。
|
||||
"""
|
||||
"""事件处理过程中发生的异常基类。"""
|
||||
|
||||
|
||||
class IgnoredException(ProcessException):
|
||||
"""
|
||||
:说明:
|
||||
"""指示 NoneBot 应该忽略该事件。可由 PreProcessor 抛出。
|
||||
|
||||
指示 NoneBot 应该忽略该事件。可由 PreProcessor 抛出。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``reason``: 忽略事件的原因
|
||||
参数:
|
||||
reason: 忽略事件的原因
|
||||
"""
|
||||
|
||||
def __init__(self, reason):
|
||||
self.reason = reason
|
||||
def __init__(self, reason: Any):
|
||||
self.reason: Any = reason
|
||||
|
||||
def __repr__(self):
|
||||
return f"<IgnoredException, reason={self.reason}>"
|
||||
@ -74,14 +76,10 @@ class IgnoredException(ProcessException):
|
||||
|
||||
|
||||
class MockApiException(ProcessException):
|
||||
"""
|
||||
:说明:
|
||||
"""指示 NoneBot 阻止本次 API 调用或修改本次调用返回值,并返回自定义内容。可由 api hook 抛出。
|
||||
|
||||
指示 NoneBot 阻止本次 API 调用或修改本次调用返回值,并返回自定义内容。可由 api hook 抛出。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``result``: 返回的内容
|
||||
参数:
|
||||
result: 返回的内容
|
||||
"""
|
||||
|
||||
def __init__(self, result: Any):
|
||||
@ -95,44 +93,46 @@ class MockApiException(ProcessException):
|
||||
|
||||
|
||||
class StopPropagation(ProcessException):
|
||||
"""
|
||||
:说明:
|
||||
"""指示 NoneBot 终止事件向下层传播。
|
||||
|
||||
指示 NoneBot 终止事件向下层传播。
|
||||
在 {ref}`nonebot.matcher.Matcher.block` 为 `True`
|
||||
或使用 {ref}`nonebot.matcher.Matcher.stop_propagation` 方法时抛出。
|
||||
|
||||
:用法:
|
||||
|
||||
在 ``Matcher.block == True`` 时抛出。
|
||||
用法:
|
||||
```python
|
||||
matcher = on_notice(block=True)
|
||||
# 或者
|
||||
@matcher.handle()
|
||||
async def handler(matcher: Matcher):
|
||||
matcher.stop_propagation()
|
||||
```
|
||||
"""
|
||||
|
||||
|
||||
# Matcher Exceptions
|
||||
class MatcherException(NoneBotException):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
所有 Matcher 发生的异常基类。
|
||||
"""
|
||||
"""所有 Matcher 发生的异常基类。"""
|
||||
|
||||
|
||||
class SkippedException(MatcherException):
|
||||
"""
|
||||
:说明:
|
||||
"""指示 NoneBot 立即结束当前 `Handler` 的处理,继续处理下一个 `Handler`。
|
||||
|
||||
指示 NoneBot 立即结束当前 ``Handler`` 的处理,继续处理下一个 ``Handler``。
|
||||
可以在 `Handler` 中通过 {ref}`nonebot.matcher.Matcher.skip` 抛出。
|
||||
|
||||
:用法:
|
||||
用法:
|
||||
```python
|
||||
def always_skip():
|
||||
Matcher.skip()
|
||||
|
||||
可以在 ``Handler`` 中通过 ``Matcher.skip()`` 抛出。
|
||||
@matcher.handle()
|
||||
async def handler(dependency = Depends(always_skip)):
|
||||
...
|
||||
```
|
||||
"""
|
||||
|
||||
|
||||
class TypeMisMatch(SkippedException):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
当前 ``Handler`` 的参数类型不匹配。
|
||||
"""
|
||||
"""当前 `Handler` 的参数类型不匹配。"""
|
||||
|
||||
def __init__(self, param: ModelField, value: Any):
|
||||
self.param: ModelField = param
|
||||
@ -146,115 +146,85 @@ class TypeMisMatch(SkippedException):
|
||||
|
||||
|
||||
class PausedException(MatcherException):
|
||||
"""
|
||||
:说明:
|
||||
"""指示 NoneBot 结束当前 `Handler` 并等待下一条消息后继续下一个 `Handler`。可用于用户输入新信息。
|
||||
|
||||
指示 NoneBot 结束当前 ``Handler`` 并等待下一条消息后继续下一个 ``Handler``。
|
||||
可用于用户输入新信息。
|
||||
可以在 `Handler` 中通过 {ref}`nonebot.matcher.Matcher.pause` 抛出。
|
||||
|
||||
:用法:
|
||||
|
||||
可以在 ``Handler`` 中通过 ``Matcher.pause()`` 抛出。
|
||||
用法:
|
||||
```python
|
||||
@matcher.handle()
|
||||
async def handler():
|
||||
await matcher.pause("some message")
|
||||
```
|
||||
"""
|
||||
|
||||
|
||||
class RejectedException(MatcherException):
|
||||
"""
|
||||
:说明:
|
||||
"""指示 NoneBot 结束当前 `Handler` 并等待下一条消息后重新运行当前 `Handler`。可用于用户重新输入。
|
||||
|
||||
指示 NoneBot 结束当前 ``Handler`` 并等待下一条消息后重新运行当前 ``Handler``。
|
||||
可用于用户重新输入。
|
||||
可以在 `Handler` 中通过 {ref}`nonebot.matcher.Matcher.reject` 抛出。
|
||||
|
||||
:用法:
|
||||
|
||||
可以在 ``Handler`` 中通过 ``Matcher.reject()`` 抛出。
|
||||
用法:
|
||||
```python
|
||||
@matcher.handle()
|
||||
async def handler():
|
||||
await matcher.reject("some message")
|
||||
```
|
||||
"""
|
||||
|
||||
|
||||
class FinishedException(MatcherException):
|
||||
"""
|
||||
:说明:
|
||||
"""指示 NoneBot 结束当前 `Handler` 且后续 `Handler` 不再被运行。可用于结束用户会话。
|
||||
|
||||
指示 NoneBot 结束当前 ``Handler`` 且后续 ``Handler`` 不再被运行。
|
||||
可用于结束用户会话。
|
||||
可以在 `Handler` 中通过 {ref}`nonebot.matcher.Matcher.finish` 抛出。
|
||||
|
||||
:用法:
|
||||
|
||||
可以在 ``Handler`` 中通过 ``Matcher.finish()`` 抛出。
|
||||
用法:
|
||||
```python
|
||||
@matcher.handle()
|
||||
async def handler():
|
||||
await matcher.finish("some message")
|
||||
```
|
||||
"""
|
||||
|
||||
|
||||
# Adapter Exceptions
|
||||
class AdapterException(NoneBotException):
|
||||
"""
|
||||
:说明:
|
||||
"""代表 `Adapter` 抛出的异常,所有的 `Adapter` 都要在内部继承自这个 `Exception`
|
||||
|
||||
代表 ``Adapter`` 抛出的异常,所有的 ``Adapter`` 都要在内部继承自这个 ``Exception``
|
||||
|
||||
:参数:
|
||||
|
||||
* ``adapter_name: str``: 标识 adapter
|
||||
参数:
|
||||
adapter_name: 标识 adapter
|
||||
"""
|
||||
|
||||
def __init__(self, adapter_name: str) -> None:
|
||||
self.adapter_name = adapter_name
|
||||
self.adapter_name: str = adapter_name
|
||||
|
||||
|
||||
class NoLogException(AdapterException):
|
||||
"""
|
||||
:说明:
|
||||
"""指示 NoneBot 对当前 `Event` 进行处理但不显示 Log 信息。
|
||||
|
||||
指示 NoneBot 对当前 ``Event`` 进行处理但不显示 Log 信息,可在 ``get_log_string`` 时抛出
|
||||
可在 {ref}`nonebot.adapters.Event.get_log_string` 时抛出
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ApiNotAvailable(AdapterException):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
在 API 连接不可用时抛出。
|
||||
"""
|
||||
|
||||
pass
|
||||
"""在 API 连接不可用时抛出。"""
|
||||
|
||||
|
||||
class NetworkError(AdapterException):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
在网络出现问题时抛出,如: API 请求地址不正确, API 请求无返回或返回状态非正常等。
|
||||
"""
|
||||
|
||||
pass
|
||||
"""在网络出现问题时抛出,如: API 请求地址不正确, API 请求无返回或返回状态非正常等。"""
|
||||
|
||||
|
||||
class ActionFailed(AdapterException):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
API 请求成功返回数据,但 API 操作失败。
|
||||
"""
|
||||
|
||||
pass
|
||||
"""API 请求成功返回数据,但 API 操作失败。"""
|
||||
|
||||
|
||||
# Driver Exceptions
|
||||
class DriverException(NoneBotException):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
``Driver`` 抛出的异常基类
|
||||
"""
|
||||
"""`Driver` 抛出的异常基类"""
|
||||
|
||||
|
||||
class WebSocketClosed(DriverException):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
WebSocket 连接已关闭
|
||||
"""
|
||||
"""WebSocket 连接已关闭"""
|
||||
|
||||
def __init__(self, code: int, reason: Optional[str] = None):
|
||||
self.code = code
|
||||
|
6
nonebot/internal/adapter/__init__.py
Normal file
6
nonebot/internal/adapter/__init__.py
Normal file
@ -0,0 +1,6 @@
|
||||
from .bot import Bot as Bot
|
||||
from .event import Event as Event
|
||||
from .adapter import Adapter as Adapter
|
||||
from .message import Message as Message
|
||||
from .message import MessageSegment as MessageSegment
|
||||
from .template import MessageTemplate as MessageTemplate
|
@ -2,9 +2,8 @@ import abc
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Any, Dict, AsyncGenerator
|
||||
|
||||
from ._bot import Bot
|
||||
from nonebot.config import Config
|
||||
from nonebot.drivers import (
|
||||
from nonebot.internal.driver import (
|
||||
Driver,
|
||||
Request,
|
||||
Response,
|
||||
@ -15,61 +14,93 @@ from nonebot.drivers import (
|
||||
WebSocketServerSetup,
|
||||
)
|
||||
|
||||
from .bot import Bot
|
||||
|
||||
|
||||
class Adapter(abc.ABC):
|
||||
"""协议适配器基类。
|
||||
|
||||
通常,在 Adapter 中编写协议通信相关代码,如: 建立通信连接、处理接收与发送 data 等。
|
||||
|
||||
参数:
|
||||
driver: {ref}`nonebot.drivers.Driver` 实例
|
||||
kwargs: 其他由 {ref}`nonebot.drivers.Driver.register_adapter` 传入的额外参数
|
||||
"""
|
||||
|
||||
def __init__(self, driver: Driver, **kwargs: Any):
|
||||
self.driver: Driver = driver
|
||||
"""{ref}`nonebot.drivers.Driver` 实例"""
|
||||
self.bots: Dict[str, Bot] = {}
|
||||
"""本协议适配器已建立连接的 {ref}`nonebot.adapters.Bot` 实例"""
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def get_name(cls) -> str:
|
||||
"""当前协议适配器的名称"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def config(self) -> Config:
|
||||
"""全局 NoneBot 配置"""
|
||||
return self.driver.config
|
||||
|
||||
def bot_connect(self, bot: Bot) -> None:
|
||||
"""告知 NoneBot 建立了一个新的 {ref}`nonebot.adapters.Bot` 连接。
|
||||
|
||||
当有新的 {ref}`nonebot.adapters.Bot` 实例连接建立成功时调用。
|
||||
|
||||
参数:
|
||||
bot: {ref}`nonebot.adapters.Bot` 实例
|
||||
"""
|
||||
self.driver._bot_connect(bot)
|
||||
self.bots[bot.self_id] = bot
|
||||
|
||||
def bot_disconnect(self, bot: Bot) -> None:
|
||||
"""告知 NoneBot {ref}`nonebot.adapters.Bot` 连接已断开。
|
||||
|
||||
当有 {ref}`nonebot.adapters.Bot` 实例连接断开时调用。
|
||||
|
||||
参数:
|
||||
bot: {ref}`nonebot.adapters.Bot` 实例
|
||||
"""
|
||||
self.driver._bot_disconnect(bot)
|
||||
self.bots.pop(bot.self_id, None)
|
||||
|
||||
def setup_http_server(self, setup: HTTPServerSetup):
|
||||
"""设置一个 HTTP 服务器路由配置"""
|
||||
if not isinstance(self.driver, ReverseDriver):
|
||||
raise TypeError("Current driver does not support http server")
|
||||
self.driver.setup_http_server(setup)
|
||||
|
||||
def setup_websocket_server(self, setup: WebSocketServerSetup):
|
||||
"""设置一个 WebSocket 服务器路由配置"""
|
||||
if not isinstance(self.driver, ReverseDriver):
|
||||
raise TypeError("Current driver does not support websocket server")
|
||||
self.driver.setup_websocket_server(setup)
|
||||
|
||||
async def request(self, setup: Request) -> Response:
|
||||
"""进行一个 HTTP 客户端请求"""
|
||||
if not isinstance(self.driver, ForwardDriver):
|
||||
raise TypeError("Current driver does not support http client")
|
||||
return await self.driver.request(setup)
|
||||
|
||||
@asynccontextmanager
|
||||
async def websocket(self, setup: Request) -> AsyncGenerator[WebSocket, None]:
|
||||
"""建立一个 WebSocket 客户端连接请求"""
|
||||
if not isinstance(self.driver, ForwardDriver):
|
||||
raise TypeError("Current driver does not support websocket client")
|
||||
async with self.driver.websocket(setup) as ws:
|
||||
yield ws
|
||||
|
||||
@abc.abstractmethod
|
||||
async def _call_api(self, bot: Bot, api: str, **data) -> Any:
|
||||
"""
|
||||
:说明:
|
||||
async def _call_api(self, bot: Bot, api: str, **data: Any) -> Any:
|
||||
"""`Adapter` 实际调用 api 的逻辑实现函数,实现该方法以调用 api。
|
||||
|
||||
``adapter`` 实际调用 api 的逻辑实现函数,实现该方法以调用 api。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``api: str``: API 名称
|
||||
* ``**data``: API 数据
|
||||
参数:
|
||||
api: API 名称
|
||||
data: API 数据
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
__autodoc__ = {"Adapter._call_api": True}
|
@ -10,9 +10,9 @@ from nonebot.exception import MockApiException
|
||||
from nonebot.typing import T_CalledAPIHook, T_CallingAPIHook
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ._event import Event
|
||||
from ._adapter import Adapter
|
||||
from ._message import Message, MessageSegment
|
||||
from .event import Event
|
||||
from .adapter import Adapter
|
||||
from .message import Message, MessageSegment
|
||||
|
||||
|
||||
class _ApiCall(Protocol):
|
||||
@ -21,29 +21,23 @@ class _ApiCall(Protocol):
|
||||
|
||||
|
||||
class Bot(abc.ABC):
|
||||
"""
|
||||
Bot 基类。用于处理上报消息,并提供 API 调用接口。
|
||||
"""Bot 基类。
|
||||
|
||||
用于处理上报消息,并提供 API 调用接口。
|
||||
|
||||
参数:
|
||||
adapter: 协议适配器实例
|
||||
self_id: 机器人 ID
|
||||
"""
|
||||
|
||||
_calling_api_hook: Set[T_CallingAPIHook] = set()
|
||||
"""
|
||||
:类型: ``Set[T_CallingAPIHook]``
|
||||
:说明: call_api 时执行的函数
|
||||
"""
|
||||
"""call_api 时执行的函数"""
|
||||
_called_api_hook: Set[T_CalledAPIHook] = set()
|
||||
"""
|
||||
:类型: ``Set[T_CalledAPIHook]``
|
||||
:说明: call_api 后执行的函数
|
||||
"""
|
||||
"""call_api 后执行的函数"""
|
||||
|
||||
def __init__(self, adapter: "Adapter", self_id: str):
|
||||
"""
|
||||
:参数:
|
||||
|
||||
* ``self_id: str``: 机器人 ID
|
||||
* ``request: HTTPConnection``: request 连接对象
|
||||
"""
|
||||
self.adapter: "Adapter" = adapter
|
||||
"""协议适配器实例"""
|
||||
self.self_id: str = self_id
|
||||
"""机器人 ID"""
|
||||
|
||||
@ -52,29 +46,26 @@ class Bot(abc.ABC):
|
||||
|
||||
@property
|
||||
def type(self) -> str:
|
||||
"""协议适配器名称"""
|
||||
return self.adapter.get_name()
|
||||
|
||||
@property
|
||||
def config(self) -> Config:
|
||||
"""全局 NoneBot 配置"""
|
||||
return self.adapter.config
|
||||
|
||||
async def call_api(self, api: str, **data: Any) -> Any:
|
||||
"""
|
||||
:说明:
|
||||
"""调用机器人 API 接口,可以通过该函数或直接通过 bot 属性进行调用
|
||||
|
||||
调用机器人 API 接口,可以通过该函数或直接通过 bot 属性进行调用
|
||||
|
||||
:参数:
|
||||
|
||||
* ``api: str``: API 名称
|
||||
* ``**data``: API 数据
|
||||
|
||||
:示例:
|
||||
|
||||
.. code-block:: python
|
||||
参数:
|
||||
api: API 名称
|
||||
data: API 数据
|
||||
|
||||
用法:
|
||||
```python
|
||||
await bot.call_api("send_msg", message="hello world")
|
||||
await bot.send_msg(message="hello world")
|
||||
```
|
||||
"""
|
||||
|
||||
result: Any = None
|
||||
@ -128,51 +119,44 @@ class Bot(abc.ABC):
|
||||
|
||||
@abc.abstractmethod
|
||||
async def send(
|
||||
self, event: "Event", message: Union[str, "Message", "MessageSegment"], **kwargs
|
||||
self,
|
||||
event: "Event",
|
||||
message: Union[str, "Message", "MessageSegment"],
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
"""
|
||||
:说明:
|
||||
"""调用机器人基础发送消息接口
|
||||
|
||||
调用机器人基础发送消息接口
|
||||
|
||||
:参数:
|
||||
|
||||
* ``event: Event``: 上报事件
|
||||
* ``message: Union[str, Message, MessageSegment]``: 要发送的消息
|
||||
* ``**kwargs``
|
||||
参数:
|
||||
event: 上报事件
|
||||
message: 要发送的消息
|
||||
kwargs: 任意额外参数
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def on_calling_api(cls, func: T_CallingAPIHook) -> T_CallingAPIHook:
|
||||
"""
|
||||
:说明:
|
||||
"""调用 api 预处理。
|
||||
|
||||
调用 api 预处理。
|
||||
钩子函数参数:
|
||||
|
||||
:参数:
|
||||
|
||||
* ``bot: Bot``: 当前 bot 对象
|
||||
* ``api: str``: 调用的 api 名称
|
||||
* ``data: Dict[str, Any]``: api 调用的参数字典
|
||||
- bot: 当前 bot 对象
|
||||
- api: 调用的 api 名称
|
||||
- data: api 调用的参数字典
|
||||
"""
|
||||
cls._calling_api_hook.add(func)
|
||||
return func
|
||||
|
||||
@classmethod
|
||||
def on_called_api(cls, func: T_CalledAPIHook) -> T_CalledAPIHook:
|
||||
"""
|
||||
:说明:
|
||||
"""调用 api 后处理。
|
||||
|
||||
调用 api 后处理。
|
||||
钩子函数参数:
|
||||
|
||||
:参数:
|
||||
|
||||
* ``bot: Bot``: 当前 bot 对象
|
||||
* ``exception: Optional[Exception]``: 调用 api 时发生的错误
|
||||
* ``api: str``: 调用的 api 名称
|
||||
* ``data: Dict[str, Any]``: api 调用的参数字典
|
||||
* ``result: Any``: api 调用的返回
|
||||
- bot: 当前 bot 对象
|
||||
- exception: 调用 api 时发生的错误
|
||||
- api: 调用的 api 名称
|
||||
- data: api 调用的参数字典
|
||||
- result: api 调用的返回
|
||||
"""
|
||||
cls._called_api_hook.add(func)
|
||||
return func
|
70
nonebot/internal/adapter/event.py
Normal file
70
nonebot/internal/adapter/event.py
Normal file
@ -0,0 +1,70 @@
|
||||
import abc
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from nonebot.utils import DataclassEncoder
|
||||
|
||||
from .message import Message
|
||||
|
||||
|
||||
class Event(abc.ABC, BaseModel):
|
||||
"""Event 基类。提供获取关键信息的方法,其余信息可直接获取。"""
|
||||
|
||||
class Config:
|
||||
extra = "allow"
|
||||
json_encoders = {Message: DataclassEncoder}
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_type(self) -> str:
|
||||
"""获取事件类型的方法,类型通常为 NoneBot 内置的四种类型。"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_event_name(self) -> str:
|
||||
"""获取事件名称的方法。"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_event_description(self) -> str:
|
||||
"""获取事件描述的方法,通常为事件具体内容。"""
|
||||
raise NotImplementedError
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"[{self.get_event_name()}]: {self.get_event_description()}"
|
||||
|
||||
def get_log_string(self) -> str:
|
||||
"""获取事件日志信息的方法。
|
||||
|
||||
通常你不需要修改这个方法,只有当希望 NoneBot 隐藏该事件日志时,可以抛出 `NoLogException` 异常。
|
||||
|
||||
异常:
|
||||
NoLogException
|
||||
"""
|
||||
return f"[{self.get_event_name()}]: {self.get_event_description()}"
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_user_id(self) -> str:
|
||||
"""获取事件主体 id 的方法,通常是用户 id 。"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_session_id(self) -> str:
|
||||
"""获取会话 id 的方法,用于判断当前事件属于哪一个会话,通常是用户 id、群组 id 组合。"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_message(self) -> "Message":
|
||||
"""获取事件消息内容的方法。"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_plaintext(self) -> str:
|
||||
"""获取消息纯文本的方法。
|
||||
|
||||
通常不需要修改,默认通过 `get_message().extract_plain_text` 获取。
|
||||
"""
|
||||
return self.get_message().extract_plain_text()
|
||||
|
||||
@abc.abstractmethod
|
||||
def is_tome(self) -> bool:
|
||||
"""获取事件是否与机器人有关的方法。"""
|
||||
raise NotImplementedError
|
325
nonebot/internal/adapter/message.py
Normal file
325
nonebot/internal/adapter/message.py
Normal file
@ -0,0 +1,325 @@
|
||||
import abc
|
||||
from copy import deepcopy
|
||||
from dataclasses import field, asdict, dataclass
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
Type,
|
||||
Tuple,
|
||||
Union,
|
||||
Generic,
|
||||
TypeVar,
|
||||
Iterable,
|
||||
Optional,
|
||||
overload,
|
||||
)
|
||||
|
||||
from pydantic import parse_obj_as
|
||||
|
||||
from .template import MessageTemplate
|
||||
|
||||
T = TypeVar("T")
|
||||
TMS = TypeVar("TMS", bound="MessageSegment")
|
||||
TM = TypeVar("TM", bound="Message")
|
||||
|
||||
|
||||
@dataclass
|
||||
class MessageSegment(abc.ABC, Generic[TM]):
|
||||
"""消息段基类"""
|
||||
|
||||
type: str
|
||||
"""消息段类型"""
|
||||
data: Dict[str, Any] = field(default_factory=dict)
|
||||
"""消息段数据"""
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def get_message_class(cls) -> Type[TM]:
|
||||
"""获取消息数组类型"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def __str__(self) -> str:
|
||||
"""该消息段所代表的 str,在命令匹配部分使用"""
|
||||
raise NotImplementedError
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(str(self))
|
||||
|
||||
def __ne__(self: T, other: T) -> bool:
|
||||
return not self == other
|
||||
|
||||
def __add__(self: TMS, other: Union[str, TMS, Iterable[TMS]]) -> TM:
|
||||
return self.get_message_class()(self) + other
|
||||
|
||||
def __radd__(self: TMS, other: Union[str, TMS, Iterable[TMS]]) -> TM:
|
||||
return self.get_message_class()(other) + self
|
||||
|
||||
@classmethod
|
||||
def __get_validators__(cls):
|
||||
yield cls._validate
|
||||
|
||||
@classmethod
|
||||
def _validate(cls, value):
|
||||
if isinstance(value, cls):
|
||||
return value
|
||||
if not isinstance(value, dict):
|
||||
raise ValueError(f"Expected dict for MessageSegment, got {type(value)}")
|
||||
return cls(**value)
|
||||
|
||||
def get(self, key: str, default: Any = None):
|
||||
return asdict(self).get(key, default)
|
||||
|
||||
def keys(self):
|
||||
return asdict(self).keys()
|
||||
|
||||
def values(self):
|
||||
return asdict(self).values()
|
||||
|
||||
def items(self):
|
||||
return asdict(self).items()
|
||||
|
||||
def copy(self: T) -> T:
|
||||
return deepcopy(self)
|
||||
|
||||
@abc.abstractmethod
|
||||
def is_text(self) -> bool:
|
||||
"""当前消息段是否为纯文本"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Message(List[TMS], abc.ABC):
|
||||
"""消息数组
|
||||
|
||||
参数:
|
||||
message: 消息内容
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: Union[str, None, Iterable[TMS], TMS] = None,
|
||||
):
|
||||
super().__init__()
|
||||
if message is None:
|
||||
return
|
||||
elif isinstance(message, str):
|
||||
self.extend(self._construct(message))
|
||||
elif isinstance(message, MessageSegment):
|
||||
self.append(message)
|
||||
elif isinstance(message, Iterable):
|
||||
self.extend(message)
|
||||
else:
|
||||
self.extend(self._construct(message)) # pragma: no cover
|
||||
|
||||
@classmethod
|
||||
def template(cls: Type[TM], format_string: Union[str, TM]) -> MessageTemplate[TM]:
|
||||
"""创建消息模板。
|
||||
|
||||
用法和 `str.format` 大致相同, 但是可以输出消息对象, 并且支持以 `Message` 对象作为消息模板
|
||||
|
||||
并且提供了拓展的格式化控制符, 可以用适用于该消息类型的 `MessageSegment` 的工厂方法创建消息
|
||||
|
||||
参数:
|
||||
format_string: 格式化模板
|
||||
|
||||
返回:
|
||||
消息格式化器
|
||||
"""
|
||||
return MessageTemplate(format_string, cls)
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def get_segment_class(cls) -> Type[TMS]:
|
||||
"""获取消息段类型"""
|
||||
raise NotImplementedError
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "".join(str(seg) for seg in self)
|
||||
|
||||
@classmethod
|
||||
def __get_validators__(cls):
|
||||
yield cls._validate
|
||||
|
||||
@classmethod
|
||||
def _validate(cls, value):
|
||||
if isinstance(value, cls):
|
||||
return value
|
||||
elif isinstance(value, Message):
|
||||
raise ValueError(f"Type {type(value)} can not be converted to {cls}")
|
||||
elif isinstance(value, str):
|
||||
pass
|
||||
elif isinstance(value, dict):
|
||||
value = parse_obj_as(cls.get_segment_class(), value)
|
||||
elif isinstance(value, Iterable):
|
||||
value = [parse_obj_as(cls.get_segment_class(), v) for v in value]
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Expected str, dict or iterable for Message, got {type(value)}"
|
||||
)
|
||||
return cls(value)
|
||||
|
||||
@staticmethod
|
||||
@abc.abstractmethod
|
||||
def _construct(msg: str) -> Iterable[TMS]:
|
||||
"""构造消息数组"""
|
||||
raise NotImplementedError
|
||||
|
||||
def __add__(self: TM, other: Union[str, TMS, Iterable[TMS]]) -> TM:
|
||||
result = self.copy()
|
||||
result += other
|
||||
return result
|
||||
|
||||
def __radd__(self: TM, other: Union[str, TMS, Iterable[TMS]]) -> TM:
|
||||
result = self.__class__(other)
|
||||
return result + self
|
||||
|
||||
def __iadd__(self: TM, other: Union[str, TMS, Iterable[TMS]]) -> TM:
|
||||
if isinstance(other, str):
|
||||
self.extend(self._construct(other))
|
||||
elif isinstance(other, MessageSegment):
|
||||
self.append(other)
|
||||
elif isinstance(other, Iterable):
|
||||
self.extend(other)
|
||||
else:
|
||||
raise ValueError(f"Unsupported type: {type(other)}") # pragma: no cover
|
||||
return self
|
||||
|
||||
@overload
|
||||
def __getitem__(self: TM, __args: str) -> TM:
|
||||
"""
|
||||
参数:
|
||||
__args: 消息段类型
|
||||
|
||||
返回:
|
||||
所有类型为 `__args` 的消息段
|
||||
"""
|
||||
|
||||
@overload
|
||||
def __getitem__(self, __args: Tuple[str, int]) -> TMS:
|
||||
"""
|
||||
参数:
|
||||
__args: 消息段类型和索引
|
||||
|
||||
返回:
|
||||
类型为 `__args[0]` 的消息段第 `__args[1]` 个
|
||||
"""
|
||||
|
||||
@overload
|
||||
def __getitem__(self: TM, __args: Tuple[str, slice]) -> TM:
|
||||
"""
|
||||
参数:
|
||||
__args: 消息段类型和切片
|
||||
|
||||
返回:
|
||||
类型为 `__args[0]` 的消息段切片 `__args[1]`
|
||||
"""
|
||||
|
||||
@overload
|
||||
def __getitem__(self, __args: int) -> TMS:
|
||||
"""
|
||||
参数:
|
||||
__args: 索引
|
||||
|
||||
返回:
|
||||
第 `__args` 个消息段
|
||||
"""
|
||||
|
||||
@overload
|
||||
def __getitem__(self: TM, __args: slice) -> TM:
|
||||
"""
|
||||
参数:
|
||||
__args: 切片
|
||||
|
||||
返回:
|
||||
消息切片 `__args`
|
||||
"""
|
||||
|
||||
def __getitem__(
|
||||
self: TM,
|
||||
args: Union[
|
||||
str,
|
||||
Tuple[str, int],
|
||||
Tuple[str, slice],
|
||||
int,
|
||||
slice,
|
||||
],
|
||||
) -> Union[TMS, TM]:
|
||||
arg1, arg2 = args if isinstance(args, tuple) else (args, None)
|
||||
if isinstance(arg1, int) and arg2 is None:
|
||||
return super().__getitem__(arg1)
|
||||
elif isinstance(arg1, slice) and arg2 is None:
|
||||
return self.__class__(super().__getitem__(arg1))
|
||||
elif isinstance(arg1, str) and arg2 is None:
|
||||
return self.__class__(seg for seg in self if seg.type == arg1)
|
||||
elif isinstance(arg1, str) and isinstance(arg2, int):
|
||||
return [seg for seg in self if seg.type == arg1][arg2]
|
||||
elif isinstance(arg1, str) and isinstance(arg2, slice):
|
||||
return self.__class__([seg for seg in self if seg.type == arg1][arg2])
|
||||
else:
|
||||
raise ValueError("Incorrect arguments to slice") # pragma: no cover
|
||||
|
||||
def index(self, value: Union[TMS, str], *args) -> int:
|
||||
if isinstance(value, str):
|
||||
first_segment = next((seg for seg in self if seg.type == value), None)
|
||||
if first_segment is None:
|
||||
raise ValueError(f"Segment with type {value} is not in message")
|
||||
return super().index(first_segment, *args)
|
||||
return super().index(value, *args)
|
||||
|
||||
def get(self: TM, type_: str, count: Optional[int] = None) -> TM:
|
||||
if count is None:
|
||||
return self[type_]
|
||||
|
||||
iterator, filtered = (
|
||||
seg for seg in self if seg.type == type_
|
||||
), self.__class__()
|
||||
for _ in range(count):
|
||||
seg = next(iterator, None)
|
||||
if seg is None:
|
||||
break
|
||||
filtered.append(seg)
|
||||
return filtered
|
||||
|
||||
def count(self, value: Union[TMS, str]) -> int:
|
||||
return len(self[value]) if isinstance(value, str) else super().count(value)
|
||||
|
||||
def append(self: TM, obj: Union[str, TMS]) -> TM:
|
||||
"""添加一个消息段到消息数组末尾。
|
||||
|
||||
参数:
|
||||
obj: 要添加的消息段
|
||||
"""
|
||||
if isinstance(obj, MessageSegment):
|
||||
super().append(obj)
|
||||
elif isinstance(obj, str):
|
||||
self.extend(self._construct(obj))
|
||||
else:
|
||||
raise ValueError(f"Unexpected type: {type(obj)} {obj}") # pragma: no cover
|
||||
return self
|
||||
|
||||
def extend(self: TM, obj: Union[TM, Iterable[TMS]]) -> TM:
|
||||
"""拼接一个消息数组或多个消息段到消息数组末尾。
|
||||
|
||||
参数:
|
||||
obj: 要添加的消息数组
|
||||
"""
|
||||
for segment in obj:
|
||||
self.append(segment)
|
||||
return self
|
||||
|
||||
def copy(self: TM) -> TM:
|
||||
return deepcopy(self)
|
||||
|
||||
def extract_plain_text(self) -> str:
|
||||
"""提取消息内纯文本消息"""
|
||||
|
||||
return "".join(str(seg) for seg in self if seg.is_text())
|
||||
|
||||
|
||||
__autodoc__ = {
|
||||
"MessageSegment.__str__": True,
|
||||
"MessageSegment.__add__": True,
|
||||
"Message.__getitem__": True,
|
||||
"Message._construct": True,
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
import inspect
|
||||
import functools
|
||||
from string import Formatter
|
||||
from typing import (
|
||||
@ -21,7 +20,7 @@ from typing import (
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import Message, MessageSegment
|
||||
from .message import Message, MessageSegment
|
||||
|
||||
TM = TypeVar("TM", bound="Message")
|
||||
TF = TypeVar("TF", str, "Message")
|
||||
@ -31,7 +30,12 @@ FormatSpecFunc_T = TypeVar("FormatSpecFunc_T", bound=FormatSpecFunc)
|
||||
|
||||
|
||||
class MessageTemplate(Formatter, Generic[TF]):
|
||||
"""消息模板格式化实现类"""
|
||||
"""消息模板格式化实现类。
|
||||
|
||||
参数:
|
||||
template: 模板
|
||||
factory: 消息类型工厂,默认为 `str`
|
||||
"""
|
||||
|
||||
@overload
|
||||
def __init__(
|
||||
@ -46,16 +50,6 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
...
|
||||
|
||||
def __init__(self, template, factory=str) -> None:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
创建一个模板
|
||||
|
||||
:参数:
|
||||
|
||||
* ``template: Union[str, Message]``: 模板
|
||||
* ``factory: Union[str, Message]``: 消息构造类型,默认为 `str`
|
||||
"""
|
||||
self.template: TF = template
|
||||
self.factory: Type[TF] = factory
|
||||
self.format_specs: Dict[str, FormatSpecFunc] = {}
|
||||
@ -69,12 +63,15 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
self.format_specs[name] = spec
|
||||
return spec
|
||||
|
||||
def format(self, *args: Any, **kwargs: Any) -> TF:
|
||||
"""
|
||||
:说明:
|
||||
def format(self, *args, **kwargs):
|
||||
"""根据传入参数和模板生成消息对象"""
|
||||
return self._format(args, kwargs)
|
||||
|
||||
根据模板和参数生成消息对象
|
||||
"""
|
||||
def format_map(self, mapping: Mapping[str, Any]) -> TF:
|
||||
"""根据传入字典和模板生成消息对象, 在传入字段名不是有效标识符时有用"""
|
||||
return self._format([], mapping)
|
||||
|
||||
def _format(self, args: Sequence[Any], kwargs: Mapping[str, Any]) -> TF:
|
||||
msg = self.factory()
|
||||
if isinstance(self.template, str):
|
||||
msg += self.vformat(self.template, args, kwargs)
|
||||
@ -107,7 +104,7 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
if recursion_depth < 0:
|
||||
raise ValueError("Max string recursion exceeded")
|
||||
|
||||
results: List[Any] = []
|
||||
results: List[Any] = [self.factory()]
|
||||
|
||||
for (literal_text, field_name, format_spec, conversion) in self.parse(
|
||||
format_string
|
||||
@ -165,17 +162,14 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
formatted_text = self.format_field(obj, str(format_control))
|
||||
results.append(formatted_text)
|
||||
|
||||
return (
|
||||
self.factory(functools.reduce(self._add, results or [""])),
|
||||
auto_arg_index,
|
||||
)
|
||||
return functools.reduce(self._add, results), auto_arg_index
|
||||
|
||||
def format_field(self, value: Any, format_spec: str) -> Any:
|
||||
formatter: Optional[FormatSpecFunc] = self.format_specs.get(format_spec)
|
||||
if formatter is None and not issubclass(self.factory, str):
|
||||
segment_class: Type["MessageSegment"] = self.factory.get_segment_class()
|
||||
method = getattr(segment_class, format_spec, None)
|
||||
if inspect.ismethod(method):
|
||||
if callable(method) and not cast(str, method.__name__).startswith("_"):
|
||||
formatter = getattr(segment_class, format_spec)
|
||||
return (
|
||||
super().format_field(value, format_spec)
|
25
nonebot/internal/driver/__init__.py
Normal file
25
nonebot/internal/driver/__init__.py
Normal file
@ -0,0 +1,25 @@
|
||||
from .model import URL as URL
|
||||
from .model import RawURL as RawURL
|
||||
from .driver import Driver as Driver
|
||||
from .model import Cookies as Cookies
|
||||
from .model import Request as Request
|
||||
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 .model import FilesTypes as FilesTypes
|
||||
from .model import QueryTypes as QueryTypes
|
||||
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 .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 .model import HTTPServerSetup as HTTPServerSetup
|
||||
from .model import WebSocketServerSetup as WebSocketServerSetup
|
234
nonebot/internal/driver/driver.py
Normal file
234
nonebot/internal/driver/driver.py
Normal file
@ -0,0 +1,234 @@
|
||||
import abc
|
||||
import asyncio
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import TYPE_CHECKING, Any, Set, Dict, Type, Callable, AsyncGenerator
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.utils import escape_tag
|
||||
from nonebot.config import Env, Config
|
||||
from nonebot.dependencies import Dependent
|
||||
from nonebot.typing import T_BotConnectionHook, T_BotDisconnectionHook
|
||||
from nonebot.internal.params import BotParam, DependParam, DefaultParam
|
||||
|
||||
from .model import Request, Response, WebSocket, HTTPServerSetup, WebSocketServerSetup
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from nonebot.internal.adapter import Bot, Adapter
|
||||
|
||||
|
||||
BOT_HOOK_PARAMS = [DependParam, BotParam, DefaultParam]
|
||||
|
||||
|
||||
class Driver(abc.ABC):
|
||||
"""Driver 基类。
|
||||
|
||||
参数:
|
||||
env: 包含环境信息的 Env 对象
|
||||
config: 包含配置信息的 Config 对象
|
||||
"""
|
||||
|
||||
_adapters: Dict[str, "Adapter"] = {}
|
||||
"""已注册的适配器列表"""
|
||||
_bot_connection_hook: Set[Dependent[Any]] = set()
|
||||
"""Bot 连接建立时执行的函数"""
|
||||
_bot_disconnection_hook: Set[Dependent[Any]] = set()
|
||||
"""Bot 连接断开时执行的函数"""
|
||||
|
||||
def __init__(self, env: Env, config: Config):
|
||||
self.env: str = env.environment
|
||||
"""环境名称"""
|
||||
self.config: Config = config
|
||||
"""全局配置对象"""
|
||||
self._clients: Dict[str, "Bot"] = {}
|
||||
|
||||
@property
|
||||
def bots(self) -> Dict[str, "Bot"]:
|
||||
"""获取当前所有已连接的 Bot"""
|
||||
return self._clients
|
||||
|
||||
def register_adapter(self, adapter: Type["Adapter"], **kwargs) -> None:
|
||||
"""注册一个协议适配器
|
||||
|
||||
参数:
|
||||
adapter: 适配器类
|
||||
kwargs: 其他传递给适配器的参数
|
||||
"""
|
||||
name = adapter.get_name()
|
||||
if name in self._adapters:
|
||||
logger.opt(colors=True).debug(
|
||||
f'Adapter "<y>{escape_tag(name)}</y>" already exists'
|
||||
)
|
||||
return
|
||||
self._adapters[name] = adapter(self, **kwargs)
|
||||
logger.opt(colors=True).debug(
|
||||
f'Succeeded to load adapter "<y>{escape_tag(name)}</y>"'
|
||||
)
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def type(self) -> str:
|
||||
"""驱动类型名称"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def logger(self):
|
||||
"""驱动专属 logger 日志记录器"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def run(self, *args, **kwargs):
|
||||
"""
|
||||
启动驱动框架
|
||||
"""
|
||||
logger.opt(colors=True).debug(
|
||||
f"<g>Loaded adapters: {escape_tag(', '.join(self._adapters))}</g>"
|
||||
)
|
||||
|
||||
@abc.abstractmethod
|
||||
def on_startup(self, func: Callable) -> Callable:
|
||||
"""注册一个在驱动器启动时执行的函数"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def on_shutdown(self, func: Callable) -> Callable:
|
||||
"""注册一个在驱动器停止时执行的函数"""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def on_bot_connect(cls, func: T_BotConnectionHook) -> T_BotConnectionHook:
|
||||
"""装饰一个函数使他在 bot 连接成功时执行。
|
||||
|
||||
钩子函数参数:
|
||||
|
||||
- bot: 当前连接上的 Bot 对象
|
||||
"""
|
||||
cls._bot_connection_hook.add(
|
||||
Dependent[Any].parse(call=func, allow_types=BOT_HOOK_PARAMS)
|
||||
)
|
||||
return func
|
||||
|
||||
@classmethod
|
||||
def on_bot_disconnect(cls, func: T_BotDisconnectionHook) -> T_BotDisconnectionHook:
|
||||
"""装饰一个函数使他在 bot 连接断开时执行。
|
||||
|
||||
钩子函数参数:
|
||||
|
||||
- bot: 当前连接上的 Bot 对象
|
||||
"""
|
||||
cls._bot_disconnection_hook.add(
|
||||
Dependent[Any].parse(call=func, allow_types=BOT_HOOK_PARAMS)
|
||||
)
|
||||
return func
|
||||
|
||||
def _bot_connect(self, bot: "Bot") -> None:
|
||||
"""在连接成功后,调用该函数来注册 bot 对象"""
|
||||
if bot.self_id in self._clients:
|
||||
raise RuntimeError(f"Duplicate bot connection with id {bot.self_id}")
|
||||
self._clients[bot.self_id] = bot
|
||||
|
||||
async def _run_hook(bot: "Bot") -> None:
|
||||
coros = list(map(lambda x: x(bot=bot), self._bot_connection_hook))
|
||||
if coros:
|
||||
try:
|
||||
await asyncio.gather(*coros)
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running WebSocketConnection hook. "
|
||||
"Running cancelled!</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
asyncio.create_task(_run_hook(bot))
|
||||
|
||||
def _bot_disconnect(self, bot: "Bot") -> None:
|
||||
"""在连接断开后,调用该函数来注销 bot 对象"""
|
||||
if bot.self_id in self._clients:
|
||||
del self._clients[bot.self_id]
|
||||
|
||||
async def _run_hook(bot: "Bot") -> None:
|
||||
coros = list(map(lambda x: x(bot=bot), self._bot_disconnection_hook))
|
||||
if coros:
|
||||
try:
|
||||
await asyncio.gather(*coros)
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running WebSocketDisConnection hook. "
|
||||
"Running cancelled!</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
asyncio.create_task(_run_hook(bot))
|
||||
|
||||
|
||||
class ForwardMixin(abc.ABC):
|
||||
"""客户端混入基类。"""
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def type(self) -> str:
|
||||
"""客户端驱动类型名称"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
async def request(self, setup: Request) -> Response:
|
||||
"""发送一个 HTTP 请求"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
@asynccontextmanager
|
||||
async def websocket(self, setup: Request) -> AsyncGenerator[WebSocket, None]:
|
||||
"""发起一个 WebSocket 连接"""
|
||||
raise NotImplementedError
|
||||
yield # used for static type checking's generator detection
|
||||
|
||||
|
||||
class ForwardDriver(Driver, ForwardMixin):
|
||||
"""客户端基类。将客户端框架封装,以满足适配器使用。"""
|
||||
|
||||
|
||||
class ReverseDriver(Driver):
|
||||
"""服务端基类。将后端框架封装,以满足适配器使用。"""
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def server_app(self) -> Any:
|
||||
"""驱动 APP 对象"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def asgi(self) -> Any:
|
||||
"""驱动 ASGI 对象"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def setup_http_server(self, setup: "HTTPServerSetup") -> None:
|
||||
"""设置一个 HTTP 服务器路由配置"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def setup_websocket_server(self, setup: "WebSocketServerSetup") -> None:
|
||||
"""设置一个 WebSocket 服务器路由配置"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def combine_driver(driver: Type[Driver], *mixins: Type[ForwardMixin]) -> Type[Driver]:
|
||||
"""将一个驱动器和多个混入类合并。"""
|
||||
# check first
|
||||
assert issubclass(driver, Driver), "`driver` must be subclass of Driver"
|
||||
assert all(
|
||||
map(lambda m: issubclass(m, ForwardMixin), mixins)
|
||||
), "`mixins` must be subclass of ForwardMixin"
|
||||
|
||||
if not mixins:
|
||||
return driver
|
||||
|
||||
class CombinedDriver(*mixins, driver, ForwardDriver): # type: ignore
|
||||
@property
|
||||
def type(self) -> str:
|
||||
return (
|
||||
driver.type.__get__(self)
|
||||
+ "+"
|
||||
+ "+".join(map(lambda x: x.type.__get__(self), mixins))
|
||||
)
|
||||
|
||||
return CombinedDriver
|
@ -1,5 +1,6 @@
|
||||
import abc
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
from http.cookiejar import Cookie, CookieJar
|
||||
from typing import (
|
||||
IO,
|
||||
@ -9,8 +10,10 @@ from typing import (
|
||||
Tuple,
|
||||
Union,
|
||||
Mapping,
|
||||
Callable,
|
||||
Iterator,
|
||||
Optional,
|
||||
Awaitable,
|
||||
MutableMapping,
|
||||
)
|
||||
|
||||
@ -168,8 +171,7 @@ class WebSocket(abc.ABC):
|
||||
@abc.abstractmethod
|
||||
def closed(self) -> bool:
|
||||
"""
|
||||
:类型: ``bool``
|
||||
:说明: 连接是否已经关闭
|
||||
连接是否已经关闭
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@ -315,3 +317,22 @@ class Cookies(MutableMapping):
|
||||
)
|
||||
|
||||
return f"<Cookies [{cookies_repr}]>"
|
||||
|
||||
|
||||
@dataclass
|
||||
class HTTPServerSetup:
|
||||
"""HTTP 服务器路由配置。"""
|
||||
|
||||
path: URL # path should not be absolute, check it by URL.is_absolute() == False
|
||||
method: str
|
||||
name: str
|
||||
handle_func: Callable[[Request], Awaitable[Response]]
|
||||
|
||||
|
||||
@dataclass
|
||||
class WebSocketServerSetup:
|
||||
"""WebSocket 服务器路由配置。"""
|
||||
|
||||
path: URL # path should not be absolute, check it by URL.is_absolute() == False
|
||||
name: str
|
||||
handle_func: Callable[[WebSocket], Awaitable[Any]]
|
721
nonebot/internal/matcher.py
Normal file
721
nonebot/internal/matcher.py
Normal file
@ -0,0 +1,721 @@
|
||||
from types import ModuleType
|
||||
from datetime import datetime
|
||||
from contextvars import ContextVar
|
||||
from collections import defaultdict
|
||||
from contextlib import AsyncExitStack
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
Type,
|
||||
Union,
|
||||
TypeVar,
|
||||
Callable,
|
||||
NoReturn,
|
||||
Optional,
|
||||
)
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.dependencies import Dependent
|
||||
from nonebot.consts import (
|
||||
ARG_KEY,
|
||||
RECEIVE_KEY,
|
||||
REJECT_TARGET,
|
||||
LAST_RECEIVE_KEY,
|
||||
REJECT_CACHE_TARGET,
|
||||
)
|
||||
from nonebot.typing import (
|
||||
Any,
|
||||
T_State,
|
||||
T_Handler,
|
||||
T_TypeUpdater,
|
||||
T_DependencyCache,
|
||||
T_PermissionUpdater,
|
||||
)
|
||||
from nonebot.exception import (
|
||||
TypeMisMatch,
|
||||
PausedException,
|
||||
StopPropagation,
|
||||
SkippedException,
|
||||
FinishedException,
|
||||
RejectedException,
|
||||
)
|
||||
|
||||
from .rule import Rule
|
||||
from .permission import USER, Permission
|
||||
from .adapter import Bot, Event, Message, MessageSegment, MessageTemplate
|
||||
from .params import (
|
||||
Depends,
|
||||
ArgParam,
|
||||
BotParam,
|
||||
EventParam,
|
||||
StateParam,
|
||||
DependParam,
|
||||
DefaultParam,
|
||||
MatcherParam,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from nonebot.plugin import Plugin
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
matchers: Dict[int, List[Type["Matcher"]]] = defaultdict(list)
|
||||
"""用于存储当前所有的事件响应器"""
|
||||
current_bot: ContextVar[Bot] = ContextVar("current_bot")
|
||||
current_event: ContextVar[Event] = ContextVar("current_event")
|
||||
current_matcher: ContextVar["Matcher"] = ContextVar("current_matcher")
|
||||
current_handler: ContextVar[Dependent] = ContextVar("current_handler")
|
||||
|
||||
|
||||
class MatcherMeta(type):
|
||||
if TYPE_CHECKING:
|
||||
module: Optional[str]
|
||||
plugin_name: Optional[str]
|
||||
module_name: Optional[str]
|
||||
module_prefix: Optional[str]
|
||||
type: str
|
||||
rule: Rule
|
||||
permission: Permission
|
||||
handlers: List[T_Handler]
|
||||
priority: int
|
||||
block: bool
|
||||
temp: bool
|
||||
expire_time: Optional[datetime]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"<Matcher from {self.module_name or 'unknown'}, "
|
||||
f"type={self.type}, priority={self.priority}, "
|
||||
f"temp={self.temp}>"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return repr(self)
|
||||
|
||||
|
||||
class Matcher(metaclass=MatcherMeta):
|
||||
"""事件响应器类"""
|
||||
|
||||
plugin: Optional["Plugin"] = None
|
||||
"""事件响应器所在插件"""
|
||||
module: Optional[ModuleType] = None
|
||||
"""事件响应器所在插件模块"""
|
||||
plugin_name: Optional[str] = None
|
||||
"""事件响应器所在插件名"""
|
||||
module_name: Optional[str] = None
|
||||
"""事件响应器所在点分割插件模块路径"""
|
||||
|
||||
type: str = ""
|
||||
"""事件响应器类型"""
|
||||
rule: Rule = Rule()
|
||||
"""事件响应器匹配规则"""
|
||||
permission: Permission = Permission()
|
||||
"""事件响应器触发权限"""
|
||||
handlers: List[Dependent[Any]] = []
|
||||
"""事件响应器拥有的事件处理函数列表"""
|
||||
priority: int = 1
|
||||
"""事件响应器优先级"""
|
||||
block: bool = False
|
||||
"""事件响应器是否阻止事件传播"""
|
||||
temp: bool = False
|
||||
"""事件响应器是否为临时"""
|
||||
expire_time: Optional[datetime] = None
|
||||
"""事件响应器过期时间点"""
|
||||
|
||||
_default_state: T_State = {}
|
||||
"""事件响应器默认状态"""
|
||||
|
||||
_default_type_updater: Optional[Dependent[str]] = None
|
||||
"""事件响应器类型更新函数"""
|
||||
_default_permission_updater: Optional[Dependent[Permission]] = None
|
||||
"""事件响应器权限更新函数"""
|
||||
|
||||
HANDLER_PARAM_TYPES = [
|
||||
DependParam,
|
||||
BotParam,
|
||||
EventParam,
|
||||
StateParam,
|
||||
ArgParam,
|
||||
MatcherParam,
|
||||
DefaultParam,
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
self.handlers = self.handlers.copy()
|
||||
self.state = self._default_state.copy()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"<Matcher from {self.module_name or 'unknown'}, type={self.type}, "
|
||||
f"priority={self.priority}, temp={self.temp}>"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return repr(self)
|
||||
|
||||
@classmethod
|
||||
def new(
|
||||
cls,
|
||||
type_: str = "",
|
||||
rule: Optional[Rule] = None,
|
||||
permission: Optional[Permission] = None,
|
||||
handlers: Optional[List[Union[T_Handler, Dependent[Any]]]] = None,
|
||||
temp: bool = False,
|
||||
priority: int = 1,
|
||||
block: bool = False,
|
||||
*,
|
||||
plugin: Optional["Plugin"] = None,
|
||||
module: Optional[ModuleType] = None,
|
||||
expire_time: Optional[datetime] = None,
|
||||
default_state: Optional[T_State] = None,
|
||||
default_type_updater: Optional[Union[T_TypeUpdater, Dependent[str]]] = None,
|
||||
default_permission_updater: Optional[
|
||||
Union[T_PermissionUpdater, Dependent[Permission]]
|
||||
] = None,
|
||||
) -> Type["Matcher"]:
|
||||
"""
|
||||
创建一个新的事件响应器,并存储至 `matchers <#matchers>`_
|
||||
|
||||
参数:
|
||||
type_: 事件响应器类型,与 `event.get_type()` 一致时触发,空字符串表示任意
|
||||
rule: 匹配规则
|
||||
permission: 权限
|
||||
handlers: 事件处理函数列表
|
||||
temp: 是否为临时事件响应器,即触发一次后删除
|
||||
priority: 响应优先级
|
||||
block: 是否阻止事件向更低优先级的响应器传播
|
||||
plugin: 事件响应器所在插件
|
||||
module: 事件响应器所在模块
|
||||
default_state: 默认状态 `state`
|
||||
expire_time: 事件响应器最终有效时间点,过时即被删除
|
||||
|
||||
返回:
|
||||
Type[Matcher]: 新的事件响应器类
|
||||
"""
|
||||
NewMatcher = type(
|
||||
"Matcher",
|
||||
(Matcher,),
|
||||
{
|
||||
"plugin": plugin,
|
||||
"module": module,
|
||||
"plugin_name": plugin and plugin.name,
|
||||
"module_name": module and module.__name__,
|
||||
"type": type_,
|
||||
"rule": rule or Rule(),
|
||||
"permission": permission or Permission(),
|
||||
"handlers": [
|
||||
handler
|
||||
if isinstance(handler, Dependent)
|
||||
else Dependent[Any].parse(
|
||||
call=handler, allow_types=cls.HANDLER_PARAM_TYPES
|
||||
)
|
||||
for handler in handlers
|
||||
]
|
||||
if handlers
|
||||
else [],
|
||||
"temp": temp,
|
||||
"expire_time": expire_time,
|
||||
"priority": priority,
|
||||
"block": block,
|
||||
"_default_state": default_state or {},
|
||||
"_default_type_updater": (
|
||||
default_type_updater
|
||||
if isinstance(default_type_updater, Dependent)
|
||||
else default_type_updater
|
||||
and Dependent[str].parse(
|
||||
call=default_type_updater, allow_types=cls.HANDLER_PARAM_TYPES
|
||||
)
|
||||
),
|
||||
"_default_permission_updater": (
|
||||
default_permission_updater
|
||||
if isinstance(default_permission_updater, Dependent)
|
||||
else default_permission_updater
|
||||
and Dependent[Permission].parse(
|
||||
call=default_permission_updater,
|
||||
allow_types=cls.HANDLER_PARAM_TYPES,
|
||||
)
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
logger.trace(f"Define new matcher {NewMatcher}")
|
||||
|
||||
matchers[priority].append(NewMatcher)
|
||||
|
||||
return NewMatcher
|
||||
|
||||
@classmethod
|
||||
async def check_perm(
|
||||
cls,
|
||||
bot: Bot,
|
||||
event: Event,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
) -> bool:
|
||||
"""检查是否满足触发权限
|
||||
|
||||
参数:
|
||||
bot: Bot 对象
|
||||
event: 上报事件
|
||||
stack: 异步上下文栈
|
||||
dependency_cache: 依赖缓存
|
||||
|
||||
返回:
|
||||
是否满足权限
|
||||
"""
|
||||
event_type = event.get_type()
|
||||
return event_type == (cls.type or event_type) and await cls.permission(
|
||||
bot, event, stack, dependency_cache
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def check_rule(
|
||||
cls,
|
||||
bot: Bot,
|
||||
event: Event,
|
||||
state: T_State,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
) -> bool:
|
||||
"""检查是否满足匹配规则
|
||||
|
||||
参数:
|
||||
bot: Bot 对象
|
||||
event: 上报事件
|
||||
state: 当前状态
|
||||
stack: 异步上下文栈
|
||||
dependency_cache: 依赖缓存
|
||||
|
||||
返回:
|
||||
是否满足匹配规则
|
||||
"""
|
||||
event_type = event.get_type()
|
||||
return event_type == (cls.type or event_type) and await cls.rule(
|
||||
bot, event, state, stack, dependency_cache
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def type_updater(cls, func: T_TypeUpdater) -> T_TypeUpdater:
|
||||
"""装饰一个函数来更改当前事件响应器的默认响应事件类型更新函数
|
||||
|
||||
参数:
|
||||
func: 响应事件类型更新函数
|
||||
"""
|
||||
cls._default_type_updater = Dependent[str].parse(
|
||||
call=func, allow_types=cls.HANDLER_PARAM_TYPES
|
||||
)
|
||||
return func
|
||||
|
||||
@classmethod
|
||||
def permission_updater(cls, func: T_PermissionUpdater) -> T_PermissionUpdater:
|
||||
"""装饰一个函数来更改当前事件响应器的默认会话权限更新函数
|
||||
|
||||
参数:
|
||||
func: 会话权限更新函数
|
||||
"""
|
||||
cls._default_permission_updater = Dependent[Permission].parse(
|
||||
call=func, allow_types=cls.HANDLER_PARAM_TYPES
|
||||
)
|
||||
return func
|
||||
|
||||
@classmethod
|
||||
def append_handler(
|
||||
cls, handler: T_Handler, parameterless: Optional[List[Any]] = None
|
||||
) -> Dependent[Any]:
|
||||
handler_ = Dependent[Any].parse(
|
||||
call=handler,
|
||||
parameterless=parameterless,
|
||||
allow_types=cls.HANDLER_PARAM_TYPES,
|
||||
)
|
||||
cls.handlers.append(handler_)
|
||||
return handler_
|
||||
|
||||
@classmethod
|
||||
def handle(
|
||||
cls, parameterless: Optional[List[Any]] = None
|
||||
) -> Callable[[T_Handler], T_Handler]:
|
||||
"""装饰一个函数来向事件响应器直接添加一个处理函数
|
||||
|
||||
参数:
|
||||
parameterless: 非参数类型依赖列表
|
||||
"""
|
||||
|
||||
def _decorator(func: T_Handler) -> T_Handler:
|
||||
cls.append_handler(func, parameterless=parameterless)
|
||||
return func
|
||||
|
||||
return _decorator
|
||||
|
||||
@classmethod
|
||||
def receive(
|
||||
cls, id: str = "", parameterless: Optional[List[Any]] = None
|
||||
) -> Callable[[T_Handler], T_Handler]:
|
||||
"""装饰一个函数来指示 NoneBot 在接收用户新的一条消息后继续运行该函数
|
||||
|
||||
参数:
|
||||
id: 消息 ID
|
||||
parameterless: 非参数类型依赖列表
|
||||
"""
|
||||
|
||||
async def _receive(event: Event, matcher: "Matcher") -> Union[None, NoReturn]:
|
||||
matcher.set_target(RECEIVE_KEY.format(id=id))
|
||||
if matcher.get_target() == RECEIVE_KEY.format(id=id):
|
||||
matcher.set_receive(id, event)
|
||||
return
|
||||
if matcher.get_receive(id, ...) is not ...:
|
||||
return
|
||||
await matcher.reject()
|
||||
|
||||
_parameterless = [Depends(_receive), *(parameterless or [])]
|
||||
|
||||
def _decorator(func: T_Handler) -> T_Handler:
|
||||
|
||||
if cls.handlers and cls.handlers[-1].call is func:
|
||||
func_handler = cls.handlers[-1]
|
||||
for depend in reversed(_parameterless):
|
||||
func_handler.prepend_parameterless(depend)
|
||||
else:
|
||||
cls.append_handler(func, parameterless=_parameterless)
|
||||
|
||||
return func
|
||||
|
||||
return _decorator
|
||||
|
||||
@classmethod
|
||||
def got(
|
||||
cls,
|
||||
key: str,
|
||||
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
|
||||
parameterless: Optional[List[Any]] = None,
|
||||
) -> Callable[[T_Handler], T_Handler]:
|
||||
"""装饰一个函数来指示 NoneBot 获取一个参数 `key`
|
||||
|
||||
当要获取的 `key` 不存在时接收用户新的一条消息再运行该函数,如果 `key` 已存在则直接继续运行
|
||||
|
||||
参数:
|
||||
key: 参数名
|
||||
prompt: 在参数不存在时向用户发送的消息
|
||||
parameterless: 非参数类型依赖列表
|
||||
"""
|
||||
|
||||
async def _key_getter(event: Event, matcher: "Matcher"):
|
||||
matcher.set_target(ARG_KEY.format(key=key))
|
||||
if matcher.get_target() == ARG_KEY.format(key=key):
|
||||
matcher.set_arg(key, event.get_message())
|
||||
return
|
||||
if matcher.get_arg(key, ...) is not ...:
|
||||
return
|
||||
await matcher.reject(prompt)
|
||||
|
||||
_parameterless = [
|
||||
Depends(_key_getter),
|
||||
*(parameterless or []),
|
||||
]
|
||||
|
||||
def _decorator(func: T_Handler) -> T_Handler:
|
||||
|
||||
if cls.handlers and cls.handlers[-1].call is func:
|
||||
func_handler = cls.handlers[-1]
|
||||
for depend in reversed(_parameterless):
|
||||
func_handler.prepend_parameterless(depend)
|
||||
else:
|
||||
cls.append_handler(func, parameterless=_parameterless)
|
||||
|
||||
return func
|
||||
|
||||
return _decorator
|
||||
|
||||
@classmethod
|
||||
async def send(
|
||||
cls,
|
||||
message: Union[str, Message, MessageSegment, MessageTemplate],
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
"""发送一条消息给当前交互用户
|
||||
|
||||
参数:
|
||||
message: 消息内容
|
||||
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,请参考对应 adapter 的 bot 对象 api
|
||||
"""
|
||||
bot = current_bot.get()
|
||||
event = current_event.get()
|
||||
state = current_matcher.get().state
|
||||
if isinstance(message, MessageTemplate):
|
||||
_message = message.format(**state)
|
||||
else:
|
||||
_message = message
|
||||
return await bot.send(event=event, message=_message, **kwargs)
|
||||
|
||||
@classmethod
|
||||
async def finish(
|
||||
cls,
|
||||
message: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
|
||||
**kwargs,
|
||||
) -> NoReturn:
|
||||
"""发送一条消息给当前交互用户并结束当前事件响应器
|
||||
|
||||
参数:
|
||||
message: 消息内容
|
||||
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,请参考对应 adapter 的 bot 对象 api
|
||||
"""
|
||||
if message is not None:
|
||||
await cls.send(message, **kwargs)
|
||||
raise FinishedException
|
||||
|
||||
@classmethod
|
||||
async def pause(
|
||||
cls,
|
||||
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
|
||||
**kwargs,
|
||||
) -> NoReturn:
|
||||
"""发送一条消息给当前交互用户并暂停事件响应器,在接收用户新的一条消息后继续下一个处理函数
|
||||
|
||||
参数:
|
||||
prompt: 消息内容
|
||||
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,请参考对应 adapter 的 bot 对象 api
|
||||
"""
|
||||
if prompt is not None:
|
||||
await cls.send(prompt, **kwargs)
|
||||
raise PausedException
|
||||
|
||||
@classmethod
|
||||
async def reject(
|
||||
cls,
|
||||
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
|
||||
**kwargs,
|
||||
) -> NoReturn:
|
||||
"""最近使用 `got` / `receive` 接收的消息不符合预期,
|
||||
发送一条消息给当前交互用户并将当前事件处理流程中断在当前位置,在接收用户新的一个事件后从头开始执行当前处理函数
|
||||
|
||||
参数:
|
||||
prompt: 消息内容
|
||||
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,请参考对应 adapter 的 bot 对象 api
|
||||
"""
|
||||
if prompt is not None:
|
||||
await cls.send(prompt, **kwargs)
|
||||
raise RejectedException
|
||||
|
||||
@classmethod
|
||||
async def reject_arg(
|
||||
cls,
|
||||
key: str,
|
||||
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
|
||||
**kwargs,
|
||||
) -> NoReturn:
|
||||
"""最近使用 `got` 接收的消息不符合预期,
|
||||
发送一条消息给当前交互用户并将当前事件处理流程中断在当前位置,在接收用户新的一条消息后从头开始执行当前处理函数
|
||||
|
||||
参数:
|
||||
key: 参数名
|
||||
prompt: 消息内容
|
||||
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,请参考对应 adapter 的 bot 对象 api
|
||||
"""
|
||||
matcher = current_matcher.get()
|
||||
matcher.set_target(ARG_KEY.format(key=key))
|
||||
if prompt is not None:
|
||||
await cls.send(prompt, **kwargs)
|
||||
raise RejectedException
|
||||
|
||||
@classmethod
|
||||
async def reject_receive(
|
||||
cls,
|
||||
id: str = "",
|
||||
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
|
||||
**kwargs,
|
||||
) -> NoReturn:
|
||||
"""最近使用 `receive` 接收的消息不符合预期,
|
||||
发送一条消息给当前交互用户并将当前事件处理流程中断在当前位置,在接收用户新的一个事件后从头开始执行当前处理函数
|
||||
|
||||
参数:
|
||||
id: 消息 id
|
||||
prompt: 消息内容
|
||||
kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,请参考对应 adapter 的 bot 对象 api
|
||||
"""
|
||||
matcher = current_matcher.get()
|
||||
matcher.set_target(RECEIVE_KEY.format(id=id))
|
||||
if prompt is not None:
|
||||
await cls.send(prompt, **kwargs)
|
||||
raise RejectedException
|
||||
|
||||
@classmethod
|
||||
def skip(cls) -> NoReturn:
|
||||
"""跳过当前事件处理函数,继续下一个处理函数
|
||||
|
||||
通常在事件处理函数的依赖中使用。
|
||||
"""
|
||||
raise SkippedException
|
||||
|
||||
def get_receive(self, id: str, default: T = None) -> Union[Event, T]:
|
||||
"""获取一个 `receive` 事件
|
||||
|
||||
如果没有找到对应的事件,返回 `default` 值
|
||||
"""
|
||||
return self.state.get(RECEIVE_KEY.format(id=id), default)
|
||||
|
||||
def set_receive(self, id: str, event: Event) -> None:
|
||||
"""设置一个 `receive` 事件"""
|
||||
self.state[RECEIVE_KEY.format(id=id)] = event
|
||||
self.state[LAST_RECEIVE_KEY] = event
|
||||
|
||||
def get_last_receive(self, default: T = None) -> Union[Event, T]:
|
||||
"""获取最近一次 `receive` 事件
|
||||
|
||||
如果没有事件,返回 `default` 值
|
||||
"""
|
||||
return self.state.get(LAST_RECEIVE_KEY, default)
|
||||
|
||||
def get_arg(self, key: str, default: T = None) -> Union[Message, T]:
|
||||
"""获取一个 `got` 消息
|
||||
|
||||
如果没有找到对应的消息,返回 `default` 值
|
||||
"""
|
||||
return self.state.get(ARG_KEY.format(key=key), default)
|
||||
|
||||
def set_arg(self, key: str, message: Message) -> None:
|
||||
"""设置一个 `got` 消息"""
|
||||
self.state[ARG_KEY.format(key=key)] = message
|
||||
|
||||
def set_target(self, target: str, cache: bool = True) -> None:
|
||||
if cache:
|
||||
self.state[REJECT_CACHE_TARGET] = target
|
||||
else:
|
||||
self.state[REJECT_TARGET] = target
|
||||
|
||||
def get_target(self, default: T = None) -> Union[str, T]:
|
||||
return self.state.get(REJECT_TARGET, default)
|
||||
|
||||
def stop_propagation(self):
|
||||
"""阻止事件传播"""
|
||||
self.block = True
|
||||
|
||||
async def update_type(self, bot: Bot, event: Event) -> str:
|
||||
updater = self.__class__._default_type_updater
|
||||
if not updater:
|
||||
return "message"
|
||||
return await updater(bot=bot, event=event, state=self.state, matcher=self)
|
||||
|
||||
async def update_permission(self, bot: Bot, event: Event) -> Permission:
|
||||
updater = self.__class__._default_permission_updater
|
||||
if not updater:
|
||||
return USER(event.get_session_id(), perm=self.permission)
|
||||
return await updater(bot=bot, event=event, state=self.state, matcher=self)
|
||||
|
||||
async def resolve_reject(self):
|
||||
handler = current_handler.get()
|
||||
self.handlers.insert(0, handler)
|
||||
if REJECT_CACHE_TARGET in self.state:
|
||||
self.state[REJECT_TARGET] = self.state[REJECT_CACHE_TARGET]
|
||||
|
||||
async def simple_run(
|
||||
self,
|
||||
bot: Bot,
|
||||
event: Event,
|
||||
state: T_State,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
):
|
||||
logger.trace(
|
||||
f"Matcher {self} run with incoming args: "
|
||||
f"bot={bot}, event={event}, state={state}"
|
||||
)
|
||||
b_t = current_bot.set(bot)
|
||||
e_t = current_event.set(event)
|
||||
m_t = current_matcher.set(self)
|
||||
try:
|
||||
# Refresh preprocess state
|
||||
self.state.update(state)
|
||||
|
||||
while self.handlers:
|
||||
handler = self.handlers.pop(0)
|
||||
current_handler.set(handler)
|
||||
logger.debug(f"Running handler {handler}")
|
||||
try:
|
||||
await handler(
|
||||
matcher=self,
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=self.state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
)
|
||||
except TypeMisMatch as e:
|
||||
logger.debug(
|
||||
f"Handler {handler} param {e.param.name} value {e.value} "
|
||||
f"mismatch type {e.param._type_display()}, skipped"
|
||||
)
|
||||
except SkippedException as e:
|
||||
logger.debug(f"Handler {handler} skipped")
|
||||
except StopPropagation:
|
||||
self.block = True
|
||||
finally:
|
||||
logger.info(f"Matcher {self} running complete")
|
||||
current_bot.reset(b_t)
|
||||
current_event.reset(e_t)
|
||||
current_matcher.reset(m_t)
|
||||
|
||||
# 运行handlers
|
||||
async def run(
|
||||
self,
|
||||
bot: Bot,
|
||||
event: Event,
|
||||
state: T_State,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
):
|
||||
try:
|
||||
await self.simple_run(bot, event, state, stack, dependency_cache)
|
||||
|
||||
except RejectedException:
|
||||
await self.resolve_reject()
|
||||
type_ = await self.update_type(bot, event)
|
||||
permission = await self.update_permission(bot, event)
|
||||
|
||||
Matcher.new(
|
||||
type_,
|
||||
Rule(),
|
||||
permission,
|
||||
self.handlers,
|
||||
temp=True,
|
||||
priority=0,
|
||||
block=True,
|
||||
plugin=self.plugin,
|
||||
module=self.module,
|
||||
expire_time=datetime.now() + bot.config.session_expire_timeout,
|
||||
default_state=self.state,
|
||||
default_type_updater=self.__class__._default_type_updater,
|
||||
default_permission_updater=self.__class__._default_permission_updater,
|
||||
)
|
||||
except PausedException:
|
||||
type_ = await self.update_type(bot, event)
|
||||
permission = await self.update_permission(bot, event)
|
||||
|
||||
Matcher.new(
|
||||
type_,
|
||||
Rule(),
|
||||
permission,
|
||||
self.handlers,
|
||||
temp=True,
|
||||
priority=0,
|
||||
block=True,
|
||||
plugin=self.plugin,
|
||||
module=self.module,
|
||||
expire_time=datetime.now() + bot.config.session_expire_timeout,
|
||||
default_state=self.state,
|
||||
default_type_updater=self.__class__._default_type_updater,
|
||||
default_permission_updater=self.__class__._default_permission_updater,
|
||||
)
|
||||
except FinishedException:
|
||||
pass
|
||||
|
||||
|
||||
__autodoc__ = {
|
||||
"MatcherMeta": False,
|
||||
"Matcher.get_target": False,
|
||||
"Matcher.set_target": False,
|
||||
"Matcher.update_type": False,
|
||||
"Matcher.update_permission": False,
|
||||
"Matcher.resolve_reject": False,
|
||||
"Matcher.simple_run": False,
|
||||
}
|
378
nonebot/internal/params.py
Normal file
378
nonebot/internal/params.py
Normal file
@ -0,0 +1,378 @@
|
||||
import asyncio
|
||||
import inspect
|
||||
import warnings
|
||||
from typing_extensions import Literal
|
||||
from typing import TYPE_CHECKING, Any, Callable, Optional, cast
|
||||
from contextlib import AsyncExitStack, contextmanager, asynccontextmanager
|
||||
|
||||
from pydantic.fields import Required, Undefined, ModelField
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.exception import TypeMisMatch
|
||||
from nonebot.dependencies.utils import check_field_type
|
||||
from nonebot.dependencies import Param, Dependent, CustomConfig
|
||||
from nonebot.typing import T_State, T_Handler, T_DependencyCache
|
||||
from nonebot.utils import (
|
||||
get_name,
|
||||
run_sync,
|
||||
is_gen_callable,
|
||||
run_sync_ctx_manager,
|
||||
is_async_gen_callable,
|
||||
is_coroutine_callable,
|
||||
generic_check_issubclass,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.adapters import Bot, Event
|
||||
|
||||
|
||||
class DependsInner:
|
||||
def __init__(
|
||||
self,
|
||||
dependency: Optional[T_Handler] = None,
|
||||
*,
|
||||
use_cache: bool = True,
|
||||
) -> None:
|
||||
self.dependency = dependency
|
||||
self.use_cache = use_cache
|
||||
|
||||
def __repr__(self) -> str:
|
||||
dep = get_name(self.dependency)
|
||||
cache = "" if self.use_cache else ", use_cache=False"
|
||||
return f"{self.__class__.__name__}({dep}{cache})"
|
||||
|
||||
|
||||
def Depends(
|
||||
dependency: Optional[T_Handler] = None,
|
||||
*,
|
||||
use_cache: bool = True,
|
||||
) -> Any:
|
||||
"""子依赖装饰器
|
||||
|
||||
参数:
|
||||
dependency: 依赖函数。默认为参数的类型注释。
|
||||
use_cache: 是否使用缓存。默认为 `True`。
|
||||
|
||||
用法:
|
||||
```python
|
||||
def depend_func() -> Any:
|
||||
return ...
|
||||
|
||||
def depend_gen_func():
|
||||
try:
|
||||
yield ...
|
||||
finally:
|
||||
...
|
||||
|
||||
async def handler(param_name: Any = Depends(depend_func), gen: Any = Depends(depend_gen_func)):
|
||||
...
|
||||
```
|
||||
"""
|
||||
return DependsInner(dependency, use_cache=use_cache)
|
||||
|
||||
|
||||
class DependParam(Param):
|
||||
"""子依赖参数"""
|
||||
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls,
|
||||
dependent: Dependent,
|
||||
name: str,
|
||||
param: inspect.Parameter,
|
||||
) -> Optional["DependParam"]:
|
||||
if isinstance(param.default, DependsInner):
|
||||
dependency: T_Handler
|
||||
if param.default.dependency is None:
|
||||
assert param.annotation is not param.empty, "Dependency cannot be empty"
|
||||
dependency = param.annotation
|
||||
else:
|
||||
dependency = param.default.dependency
|
||||
sub_dependent = Dependent[Any].parse(
|
||||
call=dependency,
|
||||
allow_types=dependent.allow_types,
|
||||
)
|
||||
dependent.pre_checkers.extend(sub_dependent.pre_checkers)
|
||||
sub_dependent.pre_checkers.clear()
|
||||
return cls(
|
||||
Required, use_cache=param.default.use_cache, dependent=sub_dependent
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _check_parameterless(
|
||||
cls, dependent: "Dependent", value: Any
|
||||
) -> Optional["Param"]:
|
||||
if isinstance(value, DependsInner):
|
||||
assert value.dependency, "Dependency cannot be empty"
|
||||
dependent = Dependent[Any].parse(
|
||||
call=value.dependency, allow_types=dependent.allow_types
|
||||
)
|
||||
return cls(Required, use_cache=value.use_cache, dependent=dependent)
|
||||
|
||||
async def _solve(
|
||||
self,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
use_cache: bool = self.extra["use_cache"]
|
||||
dependency_cache = {} if dependency_cache is None else dependency_cache
|
||||
|
||||
sub_dependent: Dependent = self.extra["dependent"]
|
||||
sub_dependent.call = cast(Callable[..., Any], sub_dependent.call)
|
||||
call = sub_dependent.call
|
||||
|
||||
# solve sub dependency with current cache
|
||||
sub_values = await sub_dependent.solve(
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
# run dependency function
|
||||
task: asyncio.Task[Any]
|
||||
if use_cache and call in dependency_cache:
|
||||
solved = await dependency_cache[call]
|
||||
elif is_gen_callable(call) or is_async_gen_callable(call):
|
||||
assert isinstance(
|
||||
stack, AsyncExitStack
|
||||
), "Generator dependency should be called in context"
|
||||
if is_gen_callable(call):
|
||||
cm = run_sync_ctx_manager(contextmanager(call)(**sub_values))
|
||||
else:
|
||||
cm = asynccontextmanager(call)(**sub_values)
|
||||
task = asyncio.create_task(stack.enter_async_context(cm))
|
||||
dependency_cache[call] = task
|
||||
solved = await task
|
||||
elif is_coroutine_callable(call):
|
||||
task = asyncio.create_task(call(**sub_values))
|
||||
dependency_cache[call] = task
|
||||
solved = await task
|
||||
else:
|
||||
task = asyncio.create_task(run_sync(call)(**sub_values))
|
||||
dependency_cache[call] = task
|
||||
solved = await task
|
||||
|
||||
return solved
|
||||
|
||||
|
||||
class _BotChecker(Param):
|
||||
async def _solve(self, bot: "Bot", **kwargs: Any) -> Any:
|
||||
field: ModelField = self.extra["field"]
|
||||
try:
|
||||
return check_field_type(field, bot)
|
||||
except TypeMisMatch:
|
||||
logger.debug(
|
||||
f"Bot type {type(bot)} not match "
|
||||
f"annotation {field._type_display()}, ignored"
|
||||
)
|
||||
raise
|
||||
|
||||
|
||||
class BotParam(Param):
|
||||
"""{ref}`nonebot.adapters.Bot` 参数"""
|
||||
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
||||
) -> Optional["BotParam"]:
|
||||
from nonebot.adapters import Bot
|
||||
|
||||
if param.default == param.empty:
|
||||
if generic_check_issubclass(param.annotation, Bot):
|
||||
if param.annotation is not Bot:
|
||||
dependent.pre_checkers.append(
|
||||
_BotChecker(
|
||||
Required,
|
||||
field=ModelField(
|
||||
name=name,
|
||||
type_=param.annotation,
|
||||
class_validators=None,
|
||||
model_config=CustomConfig,
|
||||
default=None,
|
||||
required=True,
|
||||
),
|
||||
)
|
||||
)
|
||||
return cls(Required)
|
||||
elif param.annotation == param.empty and name == "bot":
|
||||
return cls(Required)
|
||||
|
||||
async def _solve(self, bot: "Bot", **kwargs: Any) -> Any:
|
||||
return bot
|
||||
|
||||
|
||||
class _EventChecker(Param):
|
||||
async def _solve(self, event: "Event", **kwargs: Any) -> Any:
|
||||
field: ModelField = self.extra["field"]
|
||||
try:
|
||||
return check_field_type(field, event)
|
||||
except TypeMisMatch:
|
||||
logger.debug(
|
||||
f"Event type {type(event)} not match "
|
||||
f"annotation {field._type_display()}, ignored"
|
||||
)
|
||||
raise
|
||||
|
||||
|
||||
class EventParam(Param):
|
||||
"""{ref}`nonebot.adapters.Event` 参数"""
|
||||
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
||||
) -> Optional["EventParam"]:
|
||||
from nonebot.adapters import Event
|
||||
|
||||
if param.default == param.empty:
|
||||
if generic_check_issubclass(param.annotation, Event):
|
||||
if param.annotation is not Event:
|
||||
dependent.pre_checkers.append(
|
||||
_EventChecker(
|
||||
Required,
|
||||
field=ModelField(
|
||||
name=name,
|
||||
type_=param.annotation,
|
||||
class_validators=None,
|
||||
model_config=CustomConfig,
|
||||
default=None,
|
||||
required=True,
|
||||
),
|
||||
)
|
||||
)
|
||||
return cls(Required)
|
||||
elif param.annotation == param.empty and name == "event":
|
||||
return cls(Required)
|
||||
|
||||
async def _solve(self, event: "Event", **kwargs: Any) -> Any:
|
||||
return event
|
||||
|
||||
|
||||
class StateInner(T_State):
|
||||
...
|
||||
|
||||
|
||||
def State() -> T_State:
|
||||
"""**Deprecated**: 事件处理状态参数,请直接使用 {ref}`nonebot.typing.T_State`"""
|
||||
warnings.warn("State() is deprecated, use `T_State` instead", DeprecationWarning)
|
||||
return StateInner()
|
||||
|
||||
|
||||
class StateParam(Param):
|
||||
"""事件处理状态参数"""
|
||||
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
||||
) -> Optional["StateParam"]:
|
||||
if isinstance(param.default, StateInner):
|
||||
return cls(Required)
|
||||
elif param.default == param.empty:
|
||||
if param.annotation is T_State:
|
||||
return cls(Required)
|
||||
elif param.annotation == param.empty and name == "state":
|
||||
return cls(Required)
|
||||
|
||||
async def _solve(self, state: T_State, **kwargs: Any) -> Any:
|
||||
return state
|
||||
|
||||
|
||||
class MatcherParam(Param):
|
||||
"""事件响应器实例参数"""
|
||||
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
||||
) -> Optional["MatcherParam"]:
|
||||
from nonebot.matcher import Matcher
|
||||
|
||||
if generic_check_issubclass(param.annotation, Matcher) or (
|
||||
param.annotation == param.empty and name == "matcher"
|
||||
):
|
||||
return cls(Required)
|
||||
|
||||
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
||||
return matcher
|
||||
|
||||
|
||||
class ArgInner:
|
||||
def __init__(
|
||||
self, key: Optional[str], type: Literal["message", "str", "plaintext"]
|
||||
) -> None:
|
||||
self.key = key
|
||||
self.type = type
|
||||
|
||||
|
||||
def Arg(key: Optional[str] = None) -> Any:
|
||||
"""`got` 的 Arg 参数消息"""
|
||||
return ArgInner(key, "message")
|
||||
|
||||
|
||||
def ArgStr(key: Optional[str] = None) -> str:
|
||||
"""`got` 的 Arg 参数消息文本"""
|
||||
return ArgInner(key, "str") # type: ignore
|
||||
|
||||
|
||||
def ArgPlainText(key: Optional[str] = None) -> str:
|
||||
"""`got` 的 Arg 参数消息纯文本"""
|
||||
return ArgInner(key, "plaintext") # type: ignore
|
||||
|
||||
|
||||
class ArgParam(Param):
|
||||
"""`got` 的 Arg 参数"""
|
||||
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
||||
) -> Optional["ArgParam"]:
|
||||
if isinstance(param.default, ArgInner):
|
||||
return cls(Required, key=param.default.key or name, type=param.default.type)
|
||||
|
||||
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
||||
message = matcher.get_arg(self.extra["key"])
|
||||
if message is None:
|
||||
return message
|
||||
if self.extra["type"] == "message":
|
||||
return message
|
||||
elif self.extra["type"] == "str":
|
||||
return str(message)
|
||||
else:
|
||||
return message.extract_plain_text()
|
||||
|
||||
|
||||
class ExceptionParam(Param):
|
||||
"""`run_postprocessor` 的异常参数"""
|
||||
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
||||
) -> Optional["ExceptionParam"]:
|
||||
if generic_check_issubclass(param.annotation, Exception) or (
|
||||
param.annotation == param.empty and name == "exception"
|
||||
):
|
||||
return cls(Required)
|
||||
|
||||
async def _solve(self, exception: Optional[Exception] = None, **kwargs: Any) -> Any:
|
||||
return exception
|
||||
|
||||
|
||||
class DefaultParam(Param):
|
||||
"""默认值参数"""
|
||||
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
||||
) -> Optional["DefaultParam"]:
|
||||
if param.default != param.empty:
|
||||
return cls(param.default)
|
||||
|
||||
async def _solve(self, **kwargs: Any) -> Any:
|
||||
return Undefined
|
||||
|
||||
|
||||
__autodoc__ = {
|
||||
"DependsInner": False,
|
||||
"StateInner": False,
|
||||
"ArgInner": False,
|
||||
}
|
133
nonebot/internal/permission.py
Normal file
133
nonebot/internal/permission.py
Normal file
@ -0,0 +1,133 @@
|
||||
import asyncio
|
||||
from contextlib import AsyncExitStack
|
||||
from typing import Any, Set, Tuple, Union, NoReturn, Optional, Coroutine
|
||||
|
||||
from nonebot.dependencies import Dependent
|
||||
from nonebot.exception import SkippedException
|
||||
from nonebot.typing import T_DependencyCache, T_PermissionChecker
|
||||
|
||||
from .adapter import Bot, Event
|
||||
from .params import BotParam, EventParam, DependParam, DefaultParam
|
||||
|
||||
|
||||
async def _run_coro_with_catch(coro: Coroutine[Any, Any, Any]):
|
||||
try:
|
||||
return await coro
|
||||
except SkippedException:
|
||||
return False
|
||||
|
||||
|
||||
class Permission:
|
||||
"""{ref}`nonebot.matcher.Matcher` 权限类。
|
||||
|
||||
当事件传递时,在 {ref}`nonebot.matcher.Matcher` 运行前进行检查。
|
||||
|
||||
参数:
|
||||
checkers: PermissionChecker
|
||||
|
||||
用法:
|
||||
```python
|
||||
Permission(async_function) | sync_function
|
||||
# 等价于
|
||||
Permission(async_function, sync_function)
|
||||
```
|
||||
"""
|
||||
|
||||
__slots__ = ("checkers",)
|
||||
|
||||
HANDLER_PARAM_TYPES = [
|
||||
DependParam,
|
||||
BotParam,
|
||||
EventParam,
|
||||
DefaultParam,
|
||||
]
|
||||
|
||||
def __init__(self, *checkers: Union[T_PermissionChecker, Dependent[bool]]) -> None:
|
||||
self.checkers: Set[Dependent[bool]] = set(
|
||||
checker
|
||||
if isinstance(checker, Dependent)
|
||||
else Dependent[bool].parse(
|
||||
call=checker, allow_types=self.HANDLER_PARAM_TYPES
|
||||
)
|
||||
for checker in checkers
|
||||
)
|
||||
"""存储 `PermissionChecker`"""
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
bot: Bot,
|
||||
event: Event,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
) -> bool:
|
||||
"""检查是否满足某个权限
|
||||
|
||||
参数:
|
||||
bot: Bot 对象
|
||||
event: Event 对象
|
||||
stack: 异步上下文栈
|
||||
dependency_cache: 依赖缓存
|
||||
"""
|
||||
if not self.checkers:
|
||||
return True
|
||||
results = await asyncio.gather(
|
||||
*(
|
||||
_run_coro_with_catch(
|
||||
checker(
|
||||
bot=bot,
|
||||
event=event,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
)
|
||||
)
|
||||
for checker in self.checkers
|
||||
),
|
||||
)
|
||||
return any(results)
|
||||
|
||||
def __and__(self, other) -> NoReturn:
|
||||
raise RuntimeError("And operation between Permissions is not allowed.")
|
||||
|
||||
def __or__(
|
||||
self, other: Optional[Union["Permission", T_PermissionChecker]]
|
||||
) -> "Permission":
|
||||
if other is None:
|
||||
return self
|
||||
elif isinstance(other, Permission):
|
||||
return Permission(*self.checkers, *other.checkers)
|
||||
else:
|
||||
return Permission(*self.checkers, other)
|
||||
|
||||
|
||||
class User:
|
||||
"""检查当前事件是否属于指定会话
|
||||
|
||||
参数:
|
||||
users: 会话 ID 元组
|
||||
perm: 需同时满足的权限
|
||||
"""
|
||||
|
||||
__slots__ = ("users", "perm")
|
||||
|
||||
def __init__(
|
||||
self, users: Tuple[str, ...], perm: Optional[Permission] = None
|
||||
) -> None:
|
||||
self.users = users
|
||||
self.perm = perm
|
||||
|
||||
async def __call__(self, bot: Bot, event: Event) -> bool:
|
||||
return bool(
|
||||
event.get_session_id() in self.users
|
||||
and (self.perm is None or await self.perm(bot, event))
|
||||
)
|
||||
|
||||
|
||||
def USER(*users: str, perm: Optional[Permission] = None):
|
||||
"""匹配当前事件属于指定会话
|
||||
|
||||
参数:
|
||||
user: 会话白名单
|
||||
perm: 需要同时满足的权限
|
||||
"""
|
||||
|
||||
return Permission(User(users, perm))
|
95
nonebot/internal/rule.py
Normal file
95
nonebot/internal/rule.py
Normal file
@ -0,0 +1,95 @@
|
||||
import asyncio
|
||||
from contextlib import AsyncExitStack
|
||||
from typing import Set, Union, NoReturn, Optional
|
||||
|
||||
from nonebot.dependencies import Dependent
|
||||
from nonebot.exception import SkippedException
|
||||
from nonebot.typing import T_State, T_RuleChecker, T_DependencyCache
|
||||
|
||||
from .adapter import Bot, Event
|
||||
from .params import BotParam, EventParam, StateParam, DependParam, DefaultParam
|
||||
|
||||
|
||||
class Rule:
|
||||
"""{ref}`nonebot.matcher.Matcher` 规则类。
|
||||
|
||||
当事件传递时,在 {ref}`nonebot.matcher.Matcher` 运行前进行检查。
|
||||
|
||||
参数:
|
||||
*checkers: RuleChecker
|
||||
|
||||
用法:
|
||||
```python
|
||||
Rule(async_function) & sync_function
|
||||
# 等价于
|
||||
Rule(async_function, sync_function)
|
||||
```
|
||||
"""
|
||||
|
||||
__slots__ = ("checkers",)
|
||||
|
||||
HANDLER_PARAM_TYPES = [
|
||||
DependParam,
|
||||
BotParam,
|
||||
EventParam,
|
||||
StateParam,
|
||||
DefaultParam,
|
||||
]
|
||||
|
||||
def __init__(self, *checkers: Union[T_RuleChecker, Dependent[bool]]) -> None:
|
||||
self.checkers: Set[Dependent[bool]] = set(
|
||||
checker
|
||||
if isinstance(checker, Dependent)
|
||||
else Dependent[bool].parse(
|
||||
call=checker, allow_types=self.HANDLER_PARAM_TYPES
|
||||
)
|
||||
for checker in checkers
|
||||
)
|
||||
"""存储 `RuleChecker`"""
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
bot: Bot,
|
||||
event: Event,
|
||||
state: T_State,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
) -> bool:
|
||||
"""检查是否符合所有规则
|
||||
|
||||
参数:
|
||||
bot: Bot 对象
|
||||
event: Event 对象
|
||||
state: 当前 State
|
||||
stack: 异步上下文栈
|
||||
dependency_cache: 依赖缓存
|
||||
"""
|
||||
if not self.checkers:
|
||||
return True
|
||||
try:
|
||||
results = await asyncio.gather(
|
||||
*(
|
||||
checker(
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
)
|
||||
for checker in self.checkers
|
||||
)
|
||||
)
|
||||
except SkippedException:
|
||||
return False
|
||||
return all(results)
|
||||
|
||||
def __and__(self, other: Optional[Union["Rule", T_RuleChecker]]) -> "Rule":
|
||||
if other is None:
|
||||
return self
|
||||
elif isinstance(other, Rule):
|
||||
return Rule(*self.checkers, *other.checkers)
|
||||
else:
|
||||
return Rule(*self.checkers, other)
|
||||
|
||||
def __or__(self, other) -> NoReturn:
|
||||
raise RuntimeError("Or operation between rules is not allowed.")
|
@ -1,13 +1,15 @@
|
||||
"""
|
||||
日志
|
||||
====
|
||||
"""本模块定义了 NoneBot 的日志记录 Logger。
|
||||
|
||||
NoneBot 使用 `loguru`_ 来记录日志信息。
|
||||
NoneBot 使用 [`loguru`][loguru] 来记录日志信息。
|
||||
|
||||
自定义 logger 请参考 `loguru`_ 文档。
|
||||
自定义 logger 请参考 [自定义日志](https://v2.nonebot.dev/docs/tutorial/custom-logger)
|
||||
以及 [`loguru`][loguru] 文档。
|
||||
|
||||
.. _loguru:
|
||||
https://github.com/Delgan/loguru
|
||||
[loguru]: https://github.com/Delgan/loguru
|
||||
|
||||
FrontMatter:
|
||||
sidebar_position: 7
|
||||
description: nonebot.log 模块
|
||||
"""
|
||||
|
||||
import sys
|
||||
@ -21,24 +23,22 @@ if TYPE_CHECKING:
|
||||
# because loguru module do not have `Logger` class actually
|
||||
from loguru import Logger
|
||||
|
||||
from nonebot.plugin import Plugin
|
||||
|
||||
# logger = logging.getLogger("nonebot")
|
||||
logger: "Logger" = loguru.logger
|
||||
"""
|
||||
:说明:
|
||||
"""NoneBot 日志记录器对象。
|
||||
|
||||
NoneBot 日志记录器对象。
|
||||
默认信息:
|
||||
|
||||
:默认信息:
|
||||
|
||||
* 格式: ``[%(asctime)s %(name)s] %(levelname)s: %(message)s``
|
||||
* 等级: ``INFO`` ,根据 ``config.log_level`` 配置改变
|
||||
* 输出: 输出至 stdout
|
||||
|
||||
:用法:
|
||||
|
||||
.. code-block:: python
|
||||
- 格式: `[%(asctime)s %(name)s] %(levelname)s: %(message)s`
|
||||
- 等级: `INFO` ,根据 `config.log_level` 配置改变
|
||||
- 输出: 输出至 stdout
|
||||
|
||||
用法:
|
||||
```python
|
||||
from nonebot.log import logger
|
||||
```
|
||||
"""
|
||||
|
||||
# default_handler = logging.StreamHandler(sys.stdout)
|
||||
@ -53,9 +53,11 @@ class Filter:
|
||||
|
||||
def __call__(self, record):
|
||||
module_name: str = record["name"]
|
||||
module = sys.modules.get(module_name)
|
||||
if module:
|
||||
module_name = getattr(module, "__module_name__", module_name)
|
||||
# TODO: get plugin name instead of module name
|
||||
# module = sys.modules.get(module_name)
|
||||
# if module and hasattr(module, "__plugin__"):
|
||||
# plugin: "Plugin" = getattr(module, "__plugin__")
|
||||
# module_name = plugin.module_name
|
||||
record["name"] = module_name.split(".")[0]
|
||||
levelno = (
|
||||
logger.level(self.level).no if isinstance(self.level, str) else self.level
|
||||
@ -81,14 +83,16 @@ class LoguruHandler(logging.Handler): # pragma: no cover
|
||||
|
||||
|
||||
logger.remove()
|
||||
default_filter = Filter()
|
||||
default_format = (
|
||||
default_filter: Filter = Filter()
|
||||
"""默认日志等级过滤器"""
|
||||
default_format: str = (
|
||||
"<g>{time:MM-DD HH:mm:ss}</g> "
|
||||
"[<lvl>{level}</lvl>] "
|
||||
"<c><u>{name}</u></c> | "
|
||||
# "<c>{function}:{line}</c>| "
|
||||
"{message}"
|
||||
)
|
||||
"""默认日志格式"""
|
||||
logger_id = logger.add(
|
||||
sys.stdout,
|
||||
level=0,
|
||||
@ -97,3 +101,5 @@ logger_id = logger.add(
|
||||
filter=default_filter,
|
||||
format=default_format,
|
||||
)
|
||||
|
||||
__autodoc__ = {"Filter": False, "LoguruHandler": False}
|
||||
|
@ -1,784 +1,18 @@
|
||||
"""
|
||||
事件响应器
|
||||
==========
|
||||
"""本模块实现事件响应器的创建与运行,并提供一些快捷方法来帮助用户更好的与机器人进行对话。
|
||||
|
||||
该模块实现事件响应器的创建与运行,并提供一些快捷方法来帮助用户更好的与机器人进行对话 。
|
||||
FrontMatter:
|
||||
sidebar_position: 3
|
||||
description: nonebot.matcher 模块
|
||||
"""
|
||||
|
||||
from types import ModuleType
|
||||
from datetime import datetime
|
||||
from contextvars import ContextVar
|
||||
from collections import defaultdict
|
||||
from contextlib import AsyncExitStack
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
Type,
|
||||
Union,
|
||||
TypeVar,
|
||||
Callable,
|
||||
NoReturn,
|
||||
Optional,
|
||||
)
|
||||
|
||||
from nonebot import params
|
||||
from nonebot.rule import Rule
|
||||
from nonebot.log import logger
|
||||
from nonebot.dependencies import Dependent
|
||||
from nonebot.permission import USER, Permission
|
||||
from nonebot.adapters import (
|
||||
Bot,
|
||||
Event,
|
||||
Message,
|
||||
MessageSegment,
|
||||
MessageTemplate,
|
||||
)
|
||||
from nonebot.consts import (
|
||||
ARG_KEY,
|
||||
RECEIVE_KEY,
|
||||
REJECT_TARGET,
|
||||
LAST_RECEIVE_KEY,
|
||||
REJECT_CACHE_TARGET,
|
||||
)
|
||||
from nonebot.typing import (
|
||||
Any,
|
||||
T_State,
|
||||
T_Handler,
|
||||
T_TypeUpdater,
|
||||
T_DependencyCache,
|
||||
T_PermissionUpdater,
|
||||
)
|
||||
from nonebot.exception import (
|
||||
TypeMisMatch,
|
||||
PausedException,
|
||||
StopPropagation,
|
||||
SkippedException,
|
||||
FinishedException,
|
||||
RejectedException,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from nonebot.plugin import Plugin
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
matchers: Dict[int, List[Type["Matcher"]]] = defaultdict(list)
|
||||
"""
|
||||
:类型: ``Dict[int, List[Type[Matcher]]]``
|
||||
:说明: 用于存储当前所有的事件响应器
|
||||
"""
|
||||
current_bot: ContextVar[Bot] = ContextVar("current_bot")
|
||||
current_event: ContextVar[Event] = ContextVar("current_event")
|
||||
current_matcher: ContextVar["Matcher"] = ContextVar("current_matcher")
|
||||
current_handler: ContextVar[Dependent] = ContextVar("current_handler")
|
||||
|
||||
|
||||
class MatcherMeta(type):
|
||||
if TYPE_CHECKING:
|
||||
module: Optional[str]
|
||||
plugin_name: Optional[str]
|
||||
module_name: Optional[str]
|
||||
module_prefix: Optional[str]
|
||||
type: str
|
||||
rule: Rule
|
||||
permission: Permission
|
||||
handlers: List[T_Handler]
|
||||
priority: int
|
||||
block: bool
|
||||
temp: bool
|
||||
expire_time: Optional[datetime]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"<Matcher from {self.module_name or 'unknown'}, "
|
||||
f"type={self.type}, priority={self.priority}, "
|
||||
f"temp={self.temp}>"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return repr(self)
|
||||
|
||||
|
||||
class Matcher(metaclass=MatcherMeta):
|
||||
"""事件响应器类"""
|
||||
|
||||
plugin: Optional["Plugin"] = None
|
||||
"""
|
||||
:类型: ``Optional[Plugin]``
|
||||
:说明: 事件响应器所在插件
|
||||
"""
|
||||
module: Optional[ModuleType] = None
|
||||
"""
|
||||
:类型: ``Optional[ModuleType]``
|
||||
:说明: 事件响应器所在插件模块
|
||||
"""
|
||||
plugin_name: Optional[str] = None
|
||||
"""
|
||||
:类型: ``Optional[str]``
|
||||
:说明: 事件响应器所在插件名
|
||||
"""
|
||||
module_name: Optional[str] = None
|
||||
"""
|
||||
:类型: ``Optional[str]``
|
||||
:说明: 事件响应器所在点分割插件模块路径
|
||||
"""
|
||||
|
||||
type: str = ""
|
||||
"""
|
||||
:类型: ``str``
|
||||
:说明: 事件响应器类型
|
||||
"""
|
||||
rule: Rule = Rule()
|
||||
"""
|
||||
:类型: ``Rule``
|
||||
:说明: 事件响应器匹配规则
|
||||
"""
|
||||
permission: Permission = Permission()
|
||||
"""
|
||||
:类型: ``Permission``
|
||||
:说明: 事件响应器触发权限
|
||||
"""
|
||||
handlers: List[Dependent[Any]] = []
|
||||
"""
|
||||
:类型: ``List[Handler]``
|
||||
:说明: 事件响应器拥有的事件处理函数列表
|
||||
"""
|
||||
priority: int = 1
|
||||
"""
|
||||
:类型: ``int``
|
||||
:说明: 事件响应器优先级
|
||||
"""
|
||||
block: bool = False
|
||||
"""
|
||||
:类型: ``bool``
|
||||
:说明: 事件响应器是否阻止事件传播
|
||||
"""
|
||||
temp: bool = False
|
||||
"""
|
||||
:类型: ``bool``
|
||||
:说明: 事件响应器是否为临时
|
||||
"""
|
||||
expire_time: Optional[datetime] = None
|
||||
"""
|
||||
:类型: ``Optional[datetime]``
|
||||
:说明: 事件响应器过期时间点
|
||||
"""
|
||||
|
||||
_default_state: T_State = {}
|
||||
"""
|
||||
:类型: ``T_State``
|
||||
:说明: 事件响应器默认状态
|
||||
"""
|
||||
|
||||
_default_type_updater: Optional[Dependent[str]] = None
|
||||
"""
|
||||
:类型: ``Optional[Dependent]``
|
||||
:说明: 事件响应器类型更新函数
|
||||
"""
|
||||
_default_permission_updater: Optional[Dependent[Permission]] = None
|
||||
"""
|
||||
:类型: ``Optional[Dependent]``
|
||||
:说明: 事件响应器权限更新函数
|
||||
"""
|
||||
|
||||
HANDLER_PARAM_TYPES = [
|
||||
params.DependParam,
|
||||
params.BotParam,
|
||||
params.EventParam,
|
||||
params.StateParam,
|
||||
params.ArgParam,
|
||||
params.MatcherParam,
|
||||
params.DefaultParam,
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
"""实例化 Matcher 以便运行"""
|
||||
self.handlers = self.handlers.copy()
|
||||
self.state = self._default_state.copy()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"<Matcher from {self.module_name or 'unknown'}, type={self.type}, "
|
||||
f"priority={self.priority}, temp={self.temp}>"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return repr(self)
|
||||
|
||||
@classmethod
|
||||
def new(
|
||||
cls,
|
||||
type_: str = "",
|
||||
rule: Optional[Rule] = None,
|
||||
permission: Optional[Permission] = None,
|
||||
handlers: Optional[List[Union[T_Handler, Dependent[Any]]]] = None,
|
||||
temp: bool = False,
|
||||
priority: int = 1,
|
||||
block: bool = False,
|
||||
*,
|
||||
plugin: Optional["Plugin"] = None,
|
||||
module: Optional[ModuleType] = None,
|
||||
expire_time: Optional[datetime] = None,
|
||||
default_state: Optional[T_State] = None,
|
||||
default_type_updater: Optional[T_TypeUpdater] = None,
|
||||
default_permission_updater: Optional[T_PermissionUpdater] = None,
|
||||
) -> Type["Matcher"]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
创建一个新的事件响应器,并存储至 `matchers <#matchers>`_
|
||||
|
||||
:参数:
|
||||
|
||||
* ``type_: str``: 事件响应器类型,与 ``event.get_type()`` 一致时触发,空字符串表示任意
|
||||
* ``rule: Optional[Rule]``: 匹配规则
|
||||
* ``permission: Optional[Permission]``: 权限
|
||||
* ``handlers: Optional[List[T_Handler]]``: 事件处理函数列表
|
||||
* ``temp: bool``: 是否为临时事件响应器,即触发一次后删除
|
||||
* ``priority: int``: 响应优先级
|
||||
* ``block: bool``: 是否阻止事件向更低优先级的响应器传播
|
||||
* ``plugin: Optional[Plugin]``: 事件响应器所在插件
|
||||
* ``module: Optional[ModuleType]``: 事件响应器所在模块
|
||||
* ``default_state: Optional[T_State]``: 默认状态 ``state``
|
||||
* ``expire_time: Optional[datetime]``: 事件响应器最终有效时间点,过时即被删除
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Type[Matcher]``: 新的事件响应器类
|
||||
"""
|
||||
NewMatcher = type(
|
||||
"Matcher",
|
||||
(Matcher,),
|
||||
{
|
||||
"plugin": plugin,
|
||||
"module": module,
|
||||
"plugin_name": plugin and plugin.name,
|
||||
"module_name": module and module.__name__,
|
||||
"type": type_,
|
||||
"rule": rule or Rule(),
|
||||
"permission": permission or Permission(),
|
||||
"handlers": [
|
||||
handler
|
||||
if isinstance(handler, Dependent)
|
||||
else Dependent[Any].parse(
|
||||
call=handler, allow_types=cls.HANDLER_PARAM_TYPES
|
||||
)
|
||||
for handler in handlers
|
||||
]
|
||||
if handlers
|
||||
else [],
|
||||
"temp": temp,
|
||||
"expire_time": expire_time,
|
||||
"priority": priority,
|
||||
"block": block,
|
||||
"_default_state": default_state or {},
|
||||
"_default_type_updater": default_type_updater,
|
||||
"_default_permission_updater": default_permission_updater,
|
||||
},
|
||||
)
|
||||
|
||||
logger.trace(f"Define new matcher {NewMatcher}")
|
||||
|
||||
matchers[priority].append(NewMatcher)
|
||||
|
||||
return NewMatcher
|
||||
|
||||
@classmethod
|
||||
async def check_perm(
|
||||
cls,
|
||||
bot: Bot,
|
||||
event: Event,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
检查是否满足触发权限
|
||||
|
||||
:参数:
|
||||
|
||||
* ``bot: Bot``: Bot 对象
|
||||
* ``event: Event``: 上报事件
|
||||
|
||||
:返回:
|
||||
|
||||
- ``bool``: 是否满足权限
|
||||
"""
|
||||
event_type = event.get_type()
|
||||
return event_type == (cls.type or event_type) and await cls.permission(
|
||||
bot, event, stack, dependency_cache
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def check_rule(
|
||||
cls,
|
||||
bot: Bot,
|
||||
event: Event,
|
||||
state: T_State,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
检查是否满足匹配规则
|
||||
|
||||
:参数:
|
||||
|
||||
* ``bot: Bot``: Bot 对象
|
||||
* ``event: Event``: 上报事件
|
||||
* ``state: T_State``: 当前状态
|
||||
|
||||
:返回:
|
||||
|
||||
- ``bool``: 是否满足匹配规则
|
||||
"""
|
||||
event_type = event.get_type()
|
||||
return event_type == (cls.type or event_type) and await cls.rule(
|
||||
bot, event, state, stack, dependency_cache
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def type_updater(cls, func: T_TypeUpdater) -> T_TypeUpdater:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
装饰一个函数来更改当前事件响应器的默认响应事件类型更新函数
|
||||
|
||||
:参数:
|
||||
|
||||
* ``func: T_TypeUpdater``: 响应事件类型更新函数
|
||||
"""
|
||||
cls._default_type_updater = Dependent[str].parse(
|
||||
call=func, allow_types=cls.HANDLER_PARAM_TYPES
|
||||
)
|
||||
return func
|
||||
|
||||
@classmethod
|
||||
def permission_updater(cls, func: T_PermissionUpdater) -> T_PermissionUpdater:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
装饰一个函数来更改当前事件响应器的默认会话权限更新函数
|
||||
|
||||
:参数:
|
||||
|
||||
* ``func: T_PermissionUpdater``: 会话权限更新函数
|
||||
"""
|
||||
cls._default_permission_updater = Dependent[Permission].parse(
|
||||
call=func, allow_types=cls.HANDLER_PARAM_TYPES
|
||||
)
|
||||
return func
|
||||
|
||||
@classmethod
|
||||
def append_handler(
|
||||
cls, handler: T_Handler, parameterless: Optional[List[Any]] = None
|
||||
) -> Dependent[Any]:
|
||||
handler_ = Dependent[Any].parse(
|
||||
call=handler,
|
||||
parameterless=parameterless,
|
||||
allow_types=cls.HANDLER_PARAM_TYPES,
|
||||
)
|
||||
cls.handlers.append(handler_)
|
||||
return handler_
|
||||
|
||||
@classmethod
|
||||
def handle(
|
||||
cls, parameterless: Optional[List[Any]] = None
|
||||
) -> Callable[[T_Handler], T_Handler]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
装饰一个函数来向事件响应器直接添加一个处理函数
|
||||
|
||||
:参数:
|
||||
|
||||
* ``parameterless: Optional[List[Any]]``: 非参数类型依赖列表
|
||||
"""
|
||||
|
||||
def _decorator(func: T_Handler) -> T_Handler:
|
||||
cls.append_handler(func, parameterless=parameterless)
|
||||
return func
|
||||
|
||||
return _decorator
|
||||
|
||||
@classmethod
|
||||
def receive(
|
||||
cls, id: str = "", parameterless: Optional[List[Any]] = None
|
||||
) -> Callable[[T_Handler], T_Handler]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
装饰一个函数来指示 NoneBot 在接收用户新的一条消息后继续运行该函数
|
||||
|
||||
:参数:
|
||||
|
||||
* ``id: str``: 消息 ID
|
||||
* ``parameterless: Optional[List[Any]]``: 非参数类型依赖列表
|
||||
"""
|
||||
|
||||
async def _receive(event: Event, matcher: "Matcher") -> Union[None, NoReturn]:
|
||||
matcher.set_target(RECEIVE_KEY.format(id=id))
|
||||
if matcher.get_target() == RECEIVE_KEY.format(id=id):
|
||||
matcher.set_receive(id, event)
|
||||
return
|
||||
if matcher.get_receive(id):
|
||||
return
|
||||
await matcher.reject()
|
||||
|
||||
_parameterless = [params.Depends(_receive), *(parameterless or [])]
|
||||
|
||||
def _decorator(func: T_Handler) -> T_Handler:
|
||||
|
||||
if cls.handlers and cls.handlers[-1].call is func:
|
||||
func_handler = cls.handlers[-1]
|
||||
for depend in reversed(_parameterless):
|
||||
func_handler.prepend_parameterless(depend)
|
||||
else:
|
||||
cls.append_handler(func, parameterless=_parameterless)
|
||||
|
||||
return func
|
||||
|
||||
return _decorator
|
||||
|
||||
@classmethod
|
||||
def got(
|
||||
cls,
|
||||
key: str,
|
||||
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
|
||||
parameterless: Optional[List[Any]] = None,
|
||||
) -> Callable[[T_Handler], T_Handler]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
装饰一个函数来指示 NoneBot 当要获取的 ``key`` 不存在时接收用户新的一条消息并经过 ``ArgsParser`` 处理后再运行该函数,如果 ``key`` 已存在则直接继续运行
|
||||
|
||||
:参数:
|
||||
|
||||
* ``key: str``: 参数名
|
||||
* ``prompt: Optional[Union[str, Message, MessageSegment, MessageFormatter]]``: 在参数不存在时向用户发送的消息
|
||||
* ``args_parser: Optional[T_ArgsParser]``: 可选参数解析函数,空则使用默认解析函数
|
||||
* ``parameterless: Optional[List[Any]]``: 非参数类型依赖列表
|
||||
"""
|
||||
|
||||
async def _key_getter(event: Event, matcher: "Matcher"):
|
||||
matcher.set_target(ARG_KEY.format(key=key))
|
||||
if matcher.get_target() == ARG_KEY.format(key=key):
|
||||
matcher.set_arg(key, event.get_message())
|
||||
return
|
||||
if matcher.get_arg(key):
|
||||
return
|
||||
await matcher.reject(prompt)
|
||||
|
||||
_parameterless = [
|
||||
params.Depends(_key_getter),
|
||||
*(parameterless or []),
|
||||
]
|
||||
|
||||
def _decorator(func: T_Handler) -> T_Handler:
|
||||
|
||||
if cls.handlers and cls.handlers[-1].call is func:
|
||||
func_handler = cls.handlers[-1]
|
||||
for depend in reversed(_parameterless):
|
||||
func_handler.prepend_parameterless(depend)
|
||||
else:
|
||||
cls.append_handler(func, parameterless=_parameterless)
|
||||
|
||||
return func
|
||||
|
||||
return _decorator
|
||||
|
||||
@classmethod
|
||||
async def send(
|
||||
cls,
|
||||
message: Union[str, Message, MessageSegment, MessageTemplate],
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
发送一条消息给当前交互用户
|
||||
|
||||
:参数:
|
||||
|
||||
* ``message: Union[str, Message, MessageSegment]``: 消息内容
|
||||
* ``**kwargs``: 其他传递给 ``bot.send`` 的参数,请参考对应 adapter 的 bot 对象 api
|
||||
"""
|
||||
bot = current_bot.get()
|
||||
event = current_event.get()
|
||||
state = current_matcher.get().state
|
||||
if isinstance(message, MessageTemplate):
|
||||
_message = message.format(**state)
|
||||
else:
|
||||
_message = message
|
||||
return await bot.send(event=event, message=_message, **kwargs)
|
||||
|
||||
@classmethod
|
||||
async def finish(
|
||||
cls,
|
||||
message: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
|
||||
**kwargs,
|
||||
) -> NoReturn:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
发送一条消息给当前交互用户并结束当前事件响应器
|
||||
|
||||
:参数:
|
||||
|
||||
* ``message: Union[str, Message, MessageSegment, MessageTemplate]``: 消息内容
|
||||
* ``**kwargs``: 其他传递给 ``bot.send`` 的参数,请参考对应 adapter 的 bot 对象 api
|
||||
"""
|
||||
if message is not None:
|
||||
await cls.send(message, **kwargs)
|
||||
raise FinishedException
|
||||
|
||||
@classmethod
|
||||
async def pause(
|
||||
cls,
|
||||
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
|
||||
**kwargs,
|
||||
) -> NoReturn:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
发送一条消息给当前交互用户并暂停事件响应器,在接收用户新的一条消息后继续下一个处理函数
|
||||
|
||||
:参数:
|
||||
|
||||
* ``prompt: Union[str, Message, MessageSegment, MessageTemplate]``: 消息内容
|
||||
* ``**kwargs``: 其他传递给 ``bot.send`` 的参数,请参考对应 adapter 的 bot 对象 api
|
||||
"""
|
||||
if prompt is not None:
|
||||
await cls.send(prompt, **kwargs)
|
||||
raise PausedException
|
||||
|
||||
@classmethod
|
||||
async def reject(
|
||||
cls,
|
||||
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
|
||||
**kwargs,
|
||||
) -> NoReturn:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
最近使用 ``got`` / ``receive`` 接收的消息不符合预期,发送一条消息给当前交互用户并暂停事件响应器,
|
||||
在接收用户新的一条消息后继续当前处理函数
|
||||
|
||||
:参数:
|
||||
|
||||
* ``prompt: Union[str, Message, MessageSegment, MessageTemplate]``: 消息内容
|
||||
* ``**kwargs``: 其他传递给 ``bot.send`` 的参数,请参考对应 adapter 的 bot 对象 api
|
||||
"""
|
||||
if prompt is not None:
|
||||
await cls.send(prompt, **kwargs)
|
||||
raise RejectedException
|
||||
|
||||
@classmethod
|
||||
async def reject_arg(
|
||||
cls,
|
||||
key: str,
|
||||
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
|
||||
**kwargs,
|
||||
) -> NoReturn:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
最近使用 ``got`` 接收的消息不符合预期,发送一条消息给当前交互用户并暂停事件响应器,
|
||||
在接收用户新的一条消息后继续当前处理函数
|
||||
|
||||
:参数:
|
||||
|
||||
* ``key: str``: 参数名
|
||||
* ``prompt: Union[str, Message, MessageSegment, MessageTemplate]``: 消息内容
|
||||
* ``**kwargs``: 其他传递给 ``bot.send`` 的参数,请参考对应 adapter 的 bot 对象 api
|
||||
"""
|
||||
matcher = current_matcher.get()
|
||||
matcher.set_target(ARG_KEY.format(key=key))
|
||||
if prompt is not None:
|
||||
await cls.send(prompt, **kwargs)
|
||||
raise RejectedException
|
||||
|
||||
@classmethod
|
||||
async def reject_receive(
|
||||
cls,
|
||||
id: str = "",
|
||||
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
|
||||
**kwargs,
|
||||
) -> NoReturn:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
最近使用 ``got`` 接收的消息不符合预期,发送一条消息给当前交互用户并暂停事件响应器,
|
||||
在接收用户新的一条消息后继续当前处理函数
|
||||
|
||||
:参数:
|
||||
|
||||
* ``id: str``: 消息 id
|
||||
* ``prompt: Union[str, Message, MessageSegment, MessageTemplate]``: 消息内容
|
||||
* ``**kwargs``: 其他传递给 ``bot.send`` 的参数,请参考对应 adapter 的 bot 对象 api
|
||||
"""
|
||||
matcher = current_matcher.get()
|
||||
matcher.set_target(RECEIVE_KEY.format(id=id))
|
||||
if prompt is not None:
|
||||
await cls.send(prompt, **kwargs)
|
||||
raise RejectedException
|
||||
|
||||
@classmethod
|
||||
def skip(cls) -> NoReturn:
|
||||
raise SkippedException
|
||||
|
||||
def get_receive(self, id: str, default: T = None) -> Union[Event, T]:
|
||||
return self.state.get(RECEIVE_KEY.format(id=id), default)
|
||||
|
||||
def set_receive(self, id: str, event: Event) -> None:
|
||||
self.state[RECEIVE_KEY.format(id=id)] = event
|
||||
self.state[LAST_RECEIVE_KEY] = event
|
||||
|
||||
def get_last_receive(self, default: T = None) -> Union[Event, T]:
|
||||
return self.state.get(LAST_RECEIVE_KEY, default)
|
||||
|
||||
def get_arg(self, key: str, default: T = None) -> Union[Message, T]:
|
||||
return self.state.get(ARG_KEY.format(key=key), default)
|
||||
|
||||
def set_arg(self, key: str, message: Message) -> None:
|
||||
self.state[ARG_KEY.format(key=key)] = message
|
||||
|
||||
def set_target(self, target: str, cache: bool = True) -> None:
|
||||
if cache:
|
||||
self.state[REJECT_CACHE_TARGET] = target
|
||||
else:
|
||||
self.state[REJECT_TARGET] = target
|
||||
|
||||
def get_target(self, default: T = None) -> Union[str, T]:
|
||||
return self.state.get(REJECT_TARGET, default)
|
||||
|
||||
def stop_propagation(self):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
阻止事件传播
|
||||
"""
|
||||
self.block = True
|
||||
|
||||
async def update_type(self, bot: Bot, event: Event) -> str:
|
||||
updater = self.__class__._default_type_updater
|
||||
if not updater:
|
||||
return "message"
|
||||
return await updater(bot=bot, event=event, state=self.state, matcher=self)
|
||||
|
||||
async def update_permission(self, bot: Bot, event: Event) -> Permission:
|
||||
updater = self.__class__._default_permission_updater
|
||||
if not updater:
|
||||
return USER(event.get_session_id(), perm=self.permission)
|
||||
return await updater(bot=bot, event=event, state=self.state, matcher=self)
|
||||
|
||||
async def resolve_reject(self):
|
||||
handler = current_handler.get()
|
||||
self.handlers.insert(0, handler)
|
||||
if REJECT_CACHE_TARGET in self.state:
|
||||
self.state[REJECT_TARGET] = self.state[REJECT_CACHE_TARGET]
|
||||
|
||||
async def simple_run(
|
||||
self,
|
||||
bot: Bot,
|
||||
event: Event,
|
||||
state: T_State,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
):
|
||||
logger.trace(
|
||||
f"Matcher {self} run with incoming args: "
|
||||
f"bot={bot}, event={event}, state={state}"
|
||||
)
|
||||
b_t = current_bot.set(bot)
|
||||
e_t = current_event.set(event)
|
||||
m_t = current_matcher.set(self)
|
||||
try:
|
||||
# Refresh preprocess state
|
||||
self.state.update(state)
|
||||
|
||||
while self.handlers:
|
||||
handler = self.handlers.pop(0)
|
||||
current_handler.set(handler)
|
||||
logger.debug(f"Running handler {handler}")
|
||||
try:
|
||||
await handler(
|
||||
matcher=self,
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=self.state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
)
|
||||
except TypeMisMatch as e:
|
||||
logger.debug(
|
||||
f"Handler {handler} param {e.param.name} value {e.value} "
|
||||
f"mismatch type {e.param._type_display()}, skipped"
|
||||
)
|
||||
except SkippedException as e:
|
||||
logger.debug(f"Handler {handler} skipped")
|
||||
except StopPropagation:
|
||||
self.block = True
|
||||
finally:
|
||||
logger.info(f"Matcher {self} running complete")
|
||||
current_bot.reset(b_t)
|
||||
current_event.reset(e_t)
|
||||
current_matcher.reset(m_t)
|
||||
|
||||
# 运行handlers
|
||||
async def run(
|
||||
self,
|
||||
bot: Bot,
|
||||
event: Event,
|
||||
state: T_State,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
):
|
||||
try:
|
||||
await self.simple_run(bot, event, state, stack, dependency_cache)
|
||||
|
||||
except RejectedException:
|
||||
await self.resolve_reject()
|
||||
type_ = await self.update_type(bot, event)
|
||||
permission = await self.update_permission(bot, event)
|
||||
|
||||
Matcher.new(
|
||||
type_,
|
||||
Rule(),
|
||||
permission,
|
||||
self.handlers,
|
||||
temp=True,
|
||||
priority=0,
|
||||
block=True,
|
||||
plugin=self.plugin,
|
||||
module=self.module,
|
||||
expire_time=datetime.now() + bot.config.session_expire_timeout,
|
||||
default_state=self.state,
|
||||
default_type_updater=self.__class__._default_type_updater,
|
||||
default_permission_updater=self.__class__._default_permission_updater,
|
||||
)
|
||||
except PausedException:
|
||||
type_ = await self.update_type(bot, event)
|
||||
permission = await self.update_permission(bot, event)
|
||||
|
||||
Matcher.new(
|
||||
type_,
|
||||
Rule(),
|
||||
permission,
|
||||
self.handlers,
|
||||
temp=True,
|
||||
priority=0,
|
||||
block=True,
|
||||
plugin=self.plugin,
|
||||
module=self.module,
|
||||
expire_time=datetime.now() + bot.config.session_expire_timeout,
|
||||
default_state=self.state,
|
||||
default_type_updater=self.__class__._default_type_updater,
|
||||
default_permission_updater=self.__class__._default_permission_updater,
|
||||
)
|
||||
except FinishedException:
|
||||
pass
|
||||
from nonebot.internal.matcher import Matcher as Matcher
|
||||
from nonebot.internal.matcher import matchers as matchers
|
||||
from nonebot.internal.matcher import current_bot as current_bot
|
||||
from nonebot.internal.matcher import current_event as current_event
|
||||
from nonebot.internal.matcher import current_handler as current_handler
|
||||
from nonebot.internal.matcher import current_matcher as current_matcher
|
||||
|
||||
__autodoc__ = {
|
||||
"Matcher": True,
|
||||
"matchers": True,
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
"""
|
||||
事件处理
|
||||
========
|
||||
"""本模块定义了事件处理主要流程。
|
||||
|
||||
NoneBot 内部处理并按优先级分发事件给所有事件响应器,提供了多个插槽以进行事件的预处理等。
|
||||
|
||||
FrontMatter:
|
||||
sidebar_position: 2
|
||||
description: nonebot.message 模块
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
@ -10,7 +12,6 @@ from datetime import datetime
|
||||
from contextlib import AsyncExitStack
|
||||
from typing import TYPE_CHECKING, Any, Set, Dict, Type, Optional, Coroutine
|
||||
|
||||
from nonebot import params
|
||||
from nonebot.log import logger
|
||||
from nonebot.rule import TrieRule
|
||||
from nonebot.utils import escape_tag
|
||||
@ -30,87 +31,81 @@ from nonebot.typing import (
|
||||
T_EventPreProcessor,
|
||||
T_EventPostProcessor,
|
||||
)
|
||||
from nonebot.internal.params import (
|
||||
ArgParam,
|
||||
BotParam,
|
||||
EventParam,
|
||||
StateParam,
|
||||
DependParam,
|
||||
DefaultParam,
|
||||
MatcherParam,
|
||||
ExceptionParam,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from nonebot.adapters import Bot, Event
|
||||
|
||||
_event_preprocessors: Set[Dependent[None]] = set()
|
||||
_event_postprocessors: Set[Dependent[None]] = set()
|
||||
_run_preprocessors: Set[Dependent[None]] = set()
|
||||
_run_postprocessors: Set[Dependent[None]] = set()
|
||||
_event_preprocessors: Set[Dependent[Any]] = set()
|
||||
_event_postprocessors: Set[Dependent[Any]] = set()
|
||||
_run_preprocessors: Set[Dependent[Any]] = set()
|
||||
_run_postprocessors: Set[Dependent[Any]] = set()
|
||||
|
||||
EVENT_PCS_PARAMS = [
|
||||
params.DependParam,
|
||||
params.BotParam,
|
||||
params.EventParam,
|
||||
params.StateParam,
|
||||
params.DefaultParam,
|
||||
DependParam,
|
||||
BotParam,
|
||||
EventParam,
|
||||
StateParam,
|
||||
DefaultParam,
|
||||
]
|
||||
RUN_PREPCS_PARAMS = [
|
||||
params.DependParam,
|
||||
params.BotParam,
|
||||
params.EventParam,
|
||||
params.StateParam,
|
||||
params.ArgParam,
|
||||
params.MatcherParam,
|
||||
params.DefaultParam,
|
||||
DependParam,
|
||||
BotParam,
|
||||
EventParam,
|
||||
StateParam,
|
||||
ArgParam,
|
||||
MatcherParam,
|
||||
DefaultParam,
|
||||
]
|
||||
RUN_POSTPCS_PARAMS = [
|
||||
params.DependParam,
|
||||
params.ExceptionParam,
|
||||
params.BotParam,
|
||||
params.EventParam,
|
||||
params.StateParam,
|
||||
params.ArgParam,
|
||||
params.MatcherParam,
|
||||
params.DefaultParam,
|
||||
DependParam,
|
||||
ExceptionParam,
|
||||
BotParam,
|
||||
EventParam,
|
||||
StateParam,
|
||||
ArgParam,
|
||||
MatcherParam,
|
||||
DefaultParam,
|
||||
]
|
||||
|
||||
|
||||
def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
事件预处理。装饰一个函数,使它在每次接收到事件并分发给各响应器之前执行。
|
||||
"""
|
||||
"""事件预处理。装饰一个函数,使它在每次接收到事件并分发给各响应器之前执行。"""
|
||||
_event_preprocessors.add(
|
||||
Dependent[None].parse(call=func, allow_types=EVENT_PCS_PARAMS)
|
||||
Dependent[Any].parse(call=func, allow_types=EVENT_PCS_PARAMS)
|
||||
)
|
||||
return func
|
||||
|
||||
|
||||
def event_postprocessor(func: T_EventPostProcessor) -> T_EventPostProcessor:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
事件后处理。装饰一个函数,使它在每次接收到事件并分发给各响应器之后执行。
|
||||
"""
|
||||
"""事件后处理。装饰一个函数,使它在每次接收到事件并分发给各响应器之后执行。"""
|
||||
_event_postprocessors.add(
|
||||
Dependent[None].parse(call=func, allow_types=EVENT_PCS_PARAMS)
|
||||
Dependent[Any].parse(call=func, allow_types=EVENT_PCS_PARAMS)
|
||||
)
|
||||
return func
|
||||
|
||||
|
||||
def run_preprocessor(func: T_RunPreProcessor) -> T_RunPreProcessor:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
运行预处理。装饰一个函数,使它在每次事件响应器运行前执行。
|
||||
"""
|
||||
"""运行预处理。装饰一个函数,使它在每次事件响应器运行前执行。"""
|
||||
_run_preprocessors.add(
|
||||
Dependent[None].parse(call=func, allow_types=RUN_PREPCS_PARAMS)
|
||||
Dependent[Any].parse(call=func, allow_types=RUN_PREPCS_PARAMS)
|
||||
)
|
||||
return func
|
||||
|
||||
|
||||
def run_postprocessor(func: T_RunPostProcessor) -> T_RunPostProcessor:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
运行后处理。装饰一个函数,使它在每次事件响应器运行后执行。
|
||||
"""
|
||||
"""运行后处理。装饰一个函数,使它在每次事件响应器运行后执行。"""
|
||||
_run_postprocessors.add(
|
||||
Dependent[None].parse(call=func, allow_types=RUN_POSTPCS_PARAMS)
|
||||
Dependent[Any].parse(call=func, allow_types=RUN_POSTPCS_PARAMS)
|
||||
)
|
||||
return func
|
||||
|
||||
@ -241,22 +236,17 @@ async def _run_matcher(
|
||||
|
||||
|
||||
async def handle_event(bot: "Bot", event: "Event") -> None:
|
||||
"""
|
||||
:说明:
|
||||
"""处理一个事件。调用该函数以实现分发事件。
|
||||
|
||||
处理一个事件。调用该函数以实现分发事件。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``bot: Bot``: Bot 对象
|
||||
* ``event: Event``: Event 对象
|
||||
|
||||
:示例:
|
||||
|
||||
.. code-block:: python
|
||||
参数:
|
||||
bot: Bot 对象
|
||||
event: Event 对象
|
||||
|
||||
用法:
|
||||
```python
|
||||
import asyncio
|
||||
asyncio.create_task(handle_event(bot, event))
|
||||
```
|
||||
"""
|
||||
show_log = True
|
||||
log_msg = f"<m>{escape_tag(bot.type.upper())} {escape_tag(bot.self_id)}</m> | "
|
||||
|
@ -1,16 +1,28 @@
|
||||
import asyncio
|
||||
import inspect
|
||||
from typing_extensions import Literal
|
||||
from typing import Any, Dict, List, Tuple, Callable, Optional, cast
|
||||
from contextlib import AsyncExitStack, contextmanager, asynccontextmanager
|
||||
"""本模块定义了依赖注入的各类参数。
|
||||
|
||||
from pydantic.fields import Required, Undefined, ModelField
|
||||
FrontMatter:
|
||||
sidebar_position: 4
|
||||
description: nonebot.params 模块
|
||||
"""
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.exception import TypeMisMatch
|
||||
from nonebot.adapters import Bot, Event, Message
|
||||
from nonebot.dependencies import Param, Dependent, CustomConfig
|
||||
from nonebot.typing import T_State, T_Handler, T_DependencyCache
|
||||
from typing import Any, Dict, List, Tuple, Optional
|
||||
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.adapters import Event, Message
|
||||
from nonebot.internal.params import Arg as Arg
|
||||
from nonebot.internal.params import State as State
|
||||
from nonebot.internal.params import ArgStr as ArgStr
|
||||
from nonebot.internal.params import Depends as Depends
|
||||
from nonebot.internal.params import ArgParam as ArgParam
|
||||
from nonebot.internal.params import BotParam as BotParam
|
||||
from nonebot.internal.params import EventParam as EventParam
|
||||
from nonebot.internal.params import StateParam as StateParam
|
||||
from nonebot.internal.params import DependParam as DependParam
|
||||
from nonebot.internal.params import ArgPlainText as ArgPlainText
|
||||
from nonebot.internal.params import DefaultParam as DefaultParam
|
||||
from nonebot.internal.params import MatcherParam as MatcherParam
|
||||
from nonebot.internal.params import ExceptionParam as ExceptionParam
|
||||
from nonebot.consts import (
|
||||
CMD_KEY,
|
||||
PREFIX_KEY,
|
||||
@ -22,228 +34,6 @@ from nonebot.consts import (
|
||||
REGEX_GROUP,
|
||||
REGEX_MATCHED,
|
||||
)
|
||||
from nonebot.utils import (
|
||||
get_name,
|
||||
run_sync,
|
||||
is_gen_callable,
|
||||
run_sync_ctx_manager,
|
||||
is_async_gen_callable,
|
||||
is_coroutine_callable,
|
||||
generic_check_issubclass,
|
||||
)
|
||||
|
||||
|
||||
class DependsInner:
|
||||
def __init__(
|
||||
self,
|
||||
dependency: Optional[T_Handler] = None,
|
||||
*,
|
||||
use_cache: bool = True,
|
||||
) -> None:
|
||||
self.dependency = dependency
|
||||
self.use_cache = use_cache
|
||||
|
||||
def __repr__(self) -> str:
|
||||
dep = get_name(self.dependency)
|
||||
cache = "" if self.use_cache else ", use_cache=False"
|
||||
return f"{self.__class__.__name__}({dep}{cache})"
|
||||
|
||||
|
||||
def Depends(
|
||||
dependency: Optional[T_Handler] = None,
|
||||
*,
|
||||
use_cache: bool = True,
|
||||
) -> Any:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
参数依赖注入装饰器
|
||||
|
||||
:参数:
|
||||
|
||||
* ``dependency: Optional[Callable[..., Any]] = None``: 依赖函数。默认为参数的类型注释。
|
||||
* ``use_cache: bool = True``: 是否使用缓存。默认为 ``True``。
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def depend_func() -> Any:
|
||||
return ...
|
||||
|
||||
def depend_gen_func():
|
||||
try:
|
||||
yield ...
|
||||
finally:
|
||||
...
|
||||
|
||||
async def handler(param_name: Any = Depends(depend_func), gen: Any = Depends(depend_gen_func)):
|
||||
...
|
||||
"""
|
||||
return DependsInner(dependency, use_cache=use_cache)
|
||||
|
||||
|
||||
class DependParam(Param):
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls,
|
||||
dependent: Dependent,
|
||||
name: str,
|
||||
param: inspect.Parameter,
|
||||
) -> Optional["DependParam"]:
|
||||
if isinstance(param.default, DependsInner):
|
||||
dependency: T_Handler
|
||||
if param.default.dependency is None:
|
||||
assert param.annotation is not param.empty, "Dependency cannot be empty"
|
||||
dependency = param.annotation
|
||||
else:
|
||||
dependency = param.default.dependency
|
||||
sub_dependent = Dependent[Any].parse(
|
||||
call=dependency,
|
||||
allow_types=dependent.allow_types,
|
||||
)
|
||||
dependent.pre_checkers.extend(sub_dependent.pre_checkers)
|
||||
sub_dependent.pre_checkers.clear()
|
||||
return cls(
|
||||
Required, use_cache=param.default.use_cache, dependent=sub_dependent
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _check_parameterless(
|
||||
cls, dependent: "Dependent", value: Any
|
||||
) -> Optional["Param"]:
|
||||
if isinstance(value, DependsInner):
|
||||
assert value.dependency, "Dependency cannot be empty"
|
||||
dependent = Dependent[Any].parse(
|
||||
call=value.dependency, allow_types=dependent.allow_types
|
||||
)
|
||||
return cls(Required, use_cache=value.use_cache, dependent=dependent)
|
||||
|
||||
async def _solve(
|
||||
self,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
use_cache: bool = self.extra["use_cache"]
|
||||
dependency_cache = {} if dependency_cache is None else dependency_cache
|
||||
|
||||
sub_dependent: Dependent = self.extra["dependent"]
|
||||
sub_dependent.call = cast(Callable[..., Any], sub_dependent.call)
|
||||
call = sub_dependent.call
|
||||
|
||||
# solve sub dependency with current cache
|
||||
sub_values = await sub_dependent.solve(
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
# run dependency function
|
||||
task: asyncio.Task[Any]
|
||||
if use_cache and call in dependency_cache:
|
||||
solved = await dependency_cache[call]
|
||||
elif is_gen_callable(call) or is_async_gen_callable(call):
|
||||
assert isinstance(
|
||||
stack, AsyncExitStack
|
||||
), "Generator dependency should be called in context"
|
||||
if is_gen_callable(call):
|
||||
cm = run_sync_ctx_manager(contextmanager(call)(**sub_values))
|
||||
else:
|
||||
cm = asynccontextmanager(call)(**sub_values)
|
||||
task = asyncio.create_task(stack.enter_async_context(cm))
|
||||
dependency_cache[call] = task
|
||||
solved = await task
|
||||
elif is_coroutine_callable(call):
|
||||
task = asyncio.create_task(call(**sub_values))
|
||||
dependency_cache[call] = task
|
||||
solved = await task
|
||||
else:
|
||||
task = asyncio.create_task(run_sync(call)(**sub_values))
|
||||
dependency_cache[call] = task
|
||||
solved = await task
|
||||
|
||||
return solved
|
||||
|
||||
|
||||
class _BotChecker(Param):
|
||||
async def _solve(self, bot: Bot, **kwargs: Any) -> Any:
|
||||
field: ModelField = self.extra["field"]
|
||||
_, errs_ = field.validate(bot, {}, loc=("bot",))
|
||||
if errs_:
|
||||
logger.debug(
|
||||
f"Bot type {type(bot)} not match "
|
||||
f"annotation {field._type_display()}, ignored"
|
||||
)
|
||||
raise TypeMisMatch(field, bot)
|
||||
|
||||
|
||||
class BotParam(Param):
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
||||
) -> Optional["BotParam"]:
|
||||
if param.default == param.empty:
|
||||
if generic_check_issubclass(param.annotation, Bot):
|
||||
if param.annotation is not Bot:
|
||||
dependent.pre_checkers.append(
|
||||
_BotChecker(
|
||||
Required,
|
||||
field=ModelField(
|
||||
name=name,
|
||||
type_=param.annotation,
|
||||
class_validators=None,
|
||||
model_config=CustomConfig,
|
||||
default=None,
|
||||
required=True,
|
||||
),
|
||||
)
|
||||
)
|
||||
return cls(Required)
|
||||
elif param.annotation == param.empty and name == "bot":
|
||||
return cls(Required)
|
||||
|
||||
async def _solve(self, bot: Bot, **kwargs: Any) -> Any:
|
||||
return bot
|
||||
|
||||
|
||||
class _EventChecker(Param):
|
||||
async def _solve(self, event: Event, **kwargs: Any) -> Any:
|
||||
field: ModelField = self.extra["field"]
|
||||
_, errs_ = field.validate(event, {}, loc=("event",))
|
||||
if errs_:
|
||||
logger.debug(
|
||||
f"Event type {type(event)} not match "
|
||||
f"annotation {field._type_display()}, ignored"
|
||||
)
|
||||
raise TypeMisMatch(field, event)
|
||||
|
||||
|
||||
class EventParam(Param):
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
||||
) -> Optional["EventParam"]:
|
||||
if param.default == param.empty:
|
||||
if generic_check_issubclass(param.annotation, Event):
|
||||
if param.annotation is not Event:
|
||||
dependent.pre_checkers.append(
|
||||
_EventChecker(
|
||||
Required,
|
||||
field=ModelField(
|
||||
name=name,
|
||||
type_=param.annotation,
|
||||
class_validators=None,
|
||||
model_config=CustomConfig,
|
||||
default=None,
|
||||
required=True,
|
||||
),
|
||||
)
|
||||
)
|
||||
return cls(Required)
|
||||
elif param.annotation == param.empty and name == "event":
|
||||
return cls(Required)
|
||||
|
||||
async def _solve(self, event: Event, **kwargs: Any) -> Any:
|
||||
return event
|
||||
|
||||
|
||||
async def _event_type(event: Event) -> str:
|
||||
@ -251,6 +41,7 @@ async def _event_type(event: Event) -> str:
|
||||
|
||||
|
||||
def EventType() -> str:
|
||||
"""{ref}`nonebot.adapters.Event` 类型参数"""
|
||||
return Depends(_event_type)
|
||||
|
||||
|
||||
@ -259,6 +50,7 @@ async def _event_message(event: Event) -> Message:
|
||||
|
||||
|
||||
def EventMessage() -> Any:
|
||||
"""{ref}`nonebot.adapters.Event` 消息参数"""
|
||||
return Depends(_event_message)
|
||||
|
||||
|
||||
@ -267,6 +59,7 @@ async def _event_plain_text(event: Event) -> str:
|
||||
|
||||
|
||||
def EventPlainText() -> str:
|
||||
"""{ref}`nonebot.adapters.Event` 纯文本消息参数"""
|
||||
return Depends(_event_plain_text)
|
||||
|
||||
|
||||
@ -275,108 +68,85 @@ async def _event_to_me(event: Event) -> bool:
|
||||
|
||||
|
||||
def EventToMe() -> bool:
|
||||
"""{ref}`nonebot.adapters.Event` `to_me` 参数"""
|
||||
return Depends(_event_to_me)
|
||||
|
||||
|
||||
class StateInner:
|
||||
...
|
||||
|
||||
|
||||
def State() -> T_State:
|
||||
return StateInner() # type: ignore
|
||||
|
||||
|
||||
class StateParam(Param):
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
||||
) -> Optional["StateParam"]:
|
||||
if isinstance(param.default, StateInner):
|
||||
return cls(Required)
|
||||
|
||||
async def _solve(self, state: T_State, **kwargs: Any) -> Any:
|
||||
return state
|
||||
|
||||
|
||||
def _command(state=State()) -> Message:
|
||||
def _command(state: T_State) -> Message:
|
||||
return state[PREFIX_KEY][CMD_KEY]
|
||||
|
||||
|
||||
def Command() -> Tuple[str, ...]:
|
||||
return Depends(_command, use_cache=False)
|
||||
"""消息命令元组"""
|
||||
return Depends(_command)
|
||||
|
||||
|
||||
def _raw_command(state=State()) -> Message:
|
||||
def _raw_command(state: T_State) -> Message:
|
||||
return state[PREFIX_KEY][RAW_CMD_KEY]
|
||||
|
||||
|
||||
def RawCommand() -> str:
|
||||
return Depends(_raw_command, use_cache=False)
|
||||
"""消息命令文本"""
|
||||
return Depends(_raw_command)
|
||||
|
||||
|
||||
def _command_arg(state=State()) -> Message:
|
||||
def _command_arg(state: T_State) -> Message:
|
||||
return state[PREFIX_KEY][CMD_ARG_KEY]
|
||||
|
||||
|
||||
def CommandArg() -> Any:
|
||||
return Depends(_command_arg, use_cache=False)
|
||||
"""消息命令参数"""
|
||||
return Depends(_command_arg)
|
||||
|
||||
|
||||
def _shell_command_args(state=State()) -> Any:
|
||||
def _shell_command_args(state: T_State) -> Any:
|
||||
return state[SHELL_ARGS]
|
||||
|
||||
|
||||
def ShellCommandArgs():
|
||||
"""shell 命令解析后的参数字典"""
|
||||
return Depends(_shell_command_args, use_cache=False)
|
||||
|
||||
|
||||
def _shell_command_argv(state=State()) -> List[str]:
|
||||
def _shell_command_argv(state: T_State) -> List[str]:
|
||||
return state[SHELL_ARGV]
|
||||
|
||||
|
||||
def ShellCommandArgv() -> Any:
|
||||
"""shell 命令原始参数列表"""
|
||||
return Depends(_shell_command_argv, use_cache=False)
|
||||
|
||||
|
||||
def _regex_matched(state=State()) -> str:
|
||||
def _regex_matched(state: T_State) -> str:
|
||||
return state[REGEX_MATCHED]
|
||||
|
||||
|
||||
def RegexMatched() -> str:
|
||||
"""正则匹配结果"""
|
||||
return Depends(_regex_matched, use_cache=False)
|
||||
|
||||
|
||||
def _regex_group(state=State()):
|
||||
def _regex_group(state: T_State):
|
||||
return state[REGEX_GROUP]
|
||||
|
||||
|
||||
def RegexGroup() -> Tuple[Any, ...]:
|
||||
"""正则匹配结果 group 元组"""
|
||||
return Depends(_regex_group, use_cache=False)
|
||||
|
||||
|
||||
def _regex_dict(state=State()):
|
||||
def _regex_dict(state: T_State):
|
||||
return state[REGEX_DICT]
|
||||
|
||||
|
||||
def RegexDict() -> Dict[str, Any]:
|
||||
"""正则匹配结果 group 字典"""
|
||||
return Depends(_regex_dict, use_cache=False)
|
||||
|
||||
|
||||
class MatcherParam(Param):
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
||||
) -> Optional["MatcherParam"]:
|
||||
if generic_check_issubclass(param.annotation, Matcher) or (
|
||||
param.annotation == param.empty and name == "matcher"
|
||||
):
|
||||
return cls(Required)
|
||||
|
||||
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
||||
return matcher
|
||||
|
||||
|
||||
def Received(id: Optional[str] = None, default: Any = None) -> Any:
|
||||
"""`receive` 事件参数"""
|
||||
|
||||
def _received(matcher: "Matcher"):
|
||||
return matcher.get_receive(id or "", default)
|
||||
|
||||
@ -384,76 +154,26 @@ def Received(id: Optional[str] = None, default: Any = None) -> Any:
|
||||
|
||||
|
||||
def LastReceived(default: Any = None) -> Any:
|
||||
"""`last_receive` 事件参数"""
|
||||
|
||||
def _last_received(matcher: "Matcher") -> Any:
|
||||
return matcher.get_last_receive(default)
|
||||
|
||||
return Depends(_last_received, use_cache=False)
|
||||
|
||||
|
||||
class ArgInner:
|
||||
def __init__(
|
||||
self, key: Optional[str], type: Literal["message", "str", "plaintext"]
|
||||
) -> None:
|
||||
self.key = key
|
||||
self.type = type
|
||||
|
||||
|
||||
def Arg(key: Optional[str] = None) -> Any:
|
||||
return ArgInner(key, "message")
|
||||
|
||||
|
||||
def ArgStr(key: Optional[str] = None) -> str:
|
||||
return ArgInner(key, "str") # type: ignore
|
||||
|
||||
|
||||
def ArgPlainText(key: Optional[str] = None) -> str:
|
||||
return ArgInner(key, "plaintext") # type: ignore
|
||||
|
||||
|
||||
class ArgParam(Param):
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
||||
) -> Optional["ArgParam"]:
|
||||
if isinstance(param.default, ArgInner):
|
||||
return cls(Required, key=param.default.key or name, type=param.default.type)
|
||||
|
||||
async def _solve(self, matcher: "Matcher", **kwargs: Any) -> Any:
|
||||
message = matcher.get_arg(self.extra["key"])
|
||||
if message is None:
|
||||
return message
|
||||
if self.extra["type"] == "message":
|
||||
return message
|
||||
elif self.extra["type"] == "str":
|
||||
return str(message)
|
||||
else:
|
||||
return message.extract_plain_text()
|
||||
|
||||
|
||||
class ExceptionParam(Param):
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
||||
) -> Optional["ExceptionParam"]:
|
||||
if generic_check_issubclass(param.annotation, Exception) or (
|
||||
param.annotation == param.empty and name == "exception"
|
||||
):
|
||||
return cls(Required)
|
||||
|
||||
async def _solve(self, exception: Optional[Exception] = None, **kwargs: Any) -> Any:
|
||||
return exception
|
||||
|
||||
|
||||
class DefaultParam(Param):
|
||||
@classmethod
|
||||
def _check_param(
|
||||
cls, dependent: Dependent, name: str, param: inspect.Parameter
|
||||
) -> Optional["DefaultParam"]:
|
||||
if param.default != param.empty:
|
||||
return cls(param.default)
|
||||
|
||||
async def _solve(self, **kwargs: Any) -> Any:
|
||||
return Undefined
|
||||
|
||||
|
||||
from nonebot.matcher import Matcher
|
||||
__autodoc__ = {
|
||||
"Arg": True,
|
||||
"State": True,
|
||||
"ArgStr": True,
|
||||
"Depends": True,
|
||||
"ArgParam": True,
|
||||
"BotParam": True,
|
||||
"EventParam": True,
|
||||
"StateParam": True,
|
||||
"DependParam": True,
|
||||
"ArgPlainText": True,
|
||||
"DefaultParam": True,
|
||||
"MatcherParam": True,
|
||||
"ExceptionParam": True,
|
||||
}
|
||||
|
@ -1,210 +1,83 @@
|
||||
r"""
|
||||
权限
|
||||
====
|
||||
"""本模块是 {ref}`nonebot.matcher.Matcher.permission` 的类型定义。
|
||||
|
||||
每个 ``Matcher`` 拥有一个 ``Permission`` ,其中是 ``PermissionChecker`` 的集合,只要有一个 ``PermissionChecker`` 检查结果为 ``True`` 时就会继续运行。
|
||||
每个 {ref}`nonebot.matcher.Matcher` 拥有一个 {ref}`nonebot.permission.Permission` ,
|
||||
其中是 `PermissionChecker` 的集合,只要有一个 `PermissionChecker` 检查结果为 `True` 时就会继续运行。
|
||||
|
||||
\:\:\:tip 提示
|
||||
``PermissionChecker`` 既可以是 async function 也可以是 sync function
|
||||
\:\:\:
|
||||
FrontMatter:
|
||||
sidebar_position: 6
|
||||
description: nonebot.permission 模块
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from contextlib import AsyncExitStack
|
||||
from typing import Any, Set, Tuple, Union, NoReturn, Optional, Coroutine
|
||||
|
||||
from nonebot.params import EventType
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot.dependencies import Dependent
|
||||
from nonebot.exception import SkippedException
|
||||
from nonebot.typing import T_Handler, T_DependencyCache, T_PermissionChecker
|
||||
from nonebot.params import (
|
||||
BotParam,
|
||||
EventType,
|
||||
EventParam,
|
||||
DependParam,
|
||||
DefaultParam,
|
||||
)
|
||||
|
||||
|
||||
async def _run_coro_with_catch(coro: Coroutine[Any, Any, Any]):
|
||||
try:
|
||||
return await coro
|
||||
except SkippedException:
|
||||
return False
|
||||
|
||||
|
||||
class Permission:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
``Matcher`` 规则类,当事件传递时,在 ``Matcher`` 运行前进行检查。
|
||||
|
||||
:示例:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
Permission(async_function) | sync_function
|
||||
# 等价于
|
||||
from nonebot.utils import run_sync
|
||||
Permission(async_function, run_sync(sync_function))
|
||||
"""
|
||||
|
||||
__slots__ = ("checkers",)
|
||||
|
||||
HANDLER_PARAM_TYPES = [
|
||||
DependParam,
|
||||
BotParam,
|
||||
EventParam,
|
||||
DefaultParam,
|
||||
]
|
||||
|
||||
def __init__(self, *checkers: Union[T_PermissionChecker, Dependent[bool]]) -> None:
|
||||
"""
|
||||
:参数:
|
||||
|
||||
* ``*checkers: Union[T_PermissionChecker, Dependent[bool]``: PermissionChecker
|
||||
"""
|
||||
|
||||
self.checkers: Set[Dependent[bool]] = set(
|
||||
checker
|
||||
if isinstance(checker, Dependent)
|
||||
else Dependent[bool].parse(
|
||||
call=checker, allow_types=self.HANDLER_PARAM_TYPES
|
||||
)
|
||||
for checker in checkers
|
||||
)
|
||||
"""
|
||||
:说明:
|
||||
|
||||
存储 ``PermissionChecker``
|
||||
|
||||
:类型:
|
||||
|
||||
* ``Set[Dependent[bool]]``
|
||||
"""
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
bot: Bot,
|
||||
event: Event,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
检查是否满足某个权限
|
||||
|
||||
:参数:
|
||||
|
||||
* ``bot: Bot``: Bot 对象
|
||||
* ``event: Event``: Event 对象
|
||||
* ``stack: Optional[AsyncExitStack]``: 异步上下文栈
|
||||
* ``dependency_cache: Optional[CacheDict[T_Handler, Any]]``: 依赖缓存
|
||||
|
||||
:返回:
|
||||
|
||||
- ``bool``
|
||||
"""
|
||||
if not self.checkers:
|
||||
return True
|
||||
results = await asyncio.gather(
|
||||
*(
|
||||
_run_coro_with_catch(
|
||||
checker(
|
||||
bot=bot,
|
||||
event=event,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
)
|
||||
)
|
||||
for checker in self.checkers
|
||||
),
|
||||
)
|
||||
return any(results)
|
||||
|
||||
def __and__(self, other) -> NoReturn:
|
||||
raise RuntimeError("And operation between Permissions is not allowed.")
|
||||
|
||||
def __or__(
|
||||
self, other: Optional[Union["Permission", T_PermissionChecker]]
|
||||
) -> "Permission":
|
||||
if other is None:
|
||||
return self
|
||||
elif isinstance(other, Permission):
|
||||
return Permission(*self.checkers, *other.checkers)
|
||||
else:
|
||||
return Permission(*self.checkers, other)
|
||||
from nonebot.internal.permission import USER as USER
|
||||
from nonebot.internal.permission import User as User
|
||||
from nonebot.internal.permission import Permission as Permission
|
||||
|
||||
|
||||
class Message:
|
||||
"""检查是否为消息事件"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
async def __call__(self, type: str = EventType()) -> bool:
|
||||
return type == "message"
|
||||
|
||||
|
||||
class Notice:
|
||||
"""检查是否为通知事件"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
async def __call__(self, type: str = EventType()) -> bool:
|
||||
return type == "notice"
|
||||
|
||||
|
||||
class Request:
|
||||
"""检查是否为请求事件"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
async def __call__(self, type: str = EventType()) -> bool:
|
||||
return type == "request"
|
||||
|
||||
|
||||
class MetaEvent:
|
||||
"""检查是否为元事件"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
async def __call__(self, type: str = EventType()) -> bool:
|
||||
return type == "meta_event"
|
||||
|
||||
|
||||
MESSAGE = Permission(Message())
|
||||
MESSAGE: Permission = Permission(Message())
|
||||
"""匹配任意 `message` 类型事件
|
||||
|
||||
仅在需要同时捕获不同类型事件时使用,优先使用 message type 的 Matcher。
|
||||
"""
|
||||
- **说明**: 匹配任意 ``message`` 类型事件,仅在需要同时捕获不同类型事件时使用。优先使用 message type 的 Matcher。
|
||||
NOTICE: Permission = Permission(Notice())
|
||||
"""匹配任意 `notice` 类型事件
|
||||
|
||||
仅在需要同时捕获不同类型事件时使用,优先使用 notice type 的 Matcher。
|
||||
"""
|
||||
NOTICE = Permission(Notice())
|
||||
REQUEST: Permission = Permission(Request())
|
||||
"""匹配任意 `request` 类型事件
|
||||
|
||||
仅在需要同时捕获不同类型事件时使用,优先使用 request type 的 Matcher。
|
||||
"""
|
||||
- **说明**: 匹配任意 ``notice`` 类型事件,仅在需要同时捕获不同类型事件时使用。优先使用 notice type 的 Matcher。
|
||||
METAEVENT: Permission = Permission(MetaEvent())
|
||||
"""匹配任意 `meta_event` 类型事件
|
||||
|
||||
仅在需要同时捕获不同类型事件时使用,优先使用 meta_event type 的 Matcher。
|
||||
"""
|
||||
REQUEST = Permission(Request())
|
||||
"""
|
||||
- **说明**: 匹配任意 ``request`` 类型事件,仅在需要同时捕获不同类型事件时使用。优先使用 request type 的 Matcher。
|
||||
"""
|
||||
METAEVENT = Permission(MetaEvent())
|
||||
"""
|
||||
- **说明**: 匹配任意 ``meta_event`` 类型事件,仅在需要同时捕获不同类型事件时使用。优先使用 meta_event type 的 Matcher。
|
||||
"""
|
||||
|
||||
|
||||
class User:
|
||||
def __init__(
|
||||
self, users: Tuple[str, ...], perm: Optional[Permission] = None
|
||||
) -> None:
|
||||
self.users = users
|
||||
self.perm = perm
|
||||
|
||||
async def __call__(self, bot: Bot, event: Event) -> bool:
|
||||
return bool(
|
||||
event.get_session_id() in self.users
|
||||
and (self.perm is None or await self.perm(bot, event))
|
||||
)
|
||||
|
||||
|
||||
def USER(*users: str, perm: Optional[Permission] = None):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
``event`` 的 ``session_id`` 在白名单内且满足 perm
|
||||
|
||||
:参数:
|
||||
|
||||
* ``*user: str``: 白名单
|
||||
* ``perm: Optional[Permission]``: 需要同时满足的权限
|
||||
"""
|
||||
|
||||
return Permission(User(users, perm))
|
||||
|
||||
|
||||
class SuperUser:
|
||||
"""检查当前事件是否是消息事件且属于超级管理员"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
async def __call__(self, bot: Bot, event: Event) -> bool:
|
||||
return event.get_type() == "message" and (
|
||||
f"{bot.adapter.get_name().split(maxsplit=1)[0].lower()}:{event.get_user_id()}"
|
||||
@ -213,7 +86,12 @@ class SuperUser:
|
||||
)
|
||||
|
||||
|
||||
SUPERUSER = Permission(SuperUser())
|
||||
"""
|
||||
- **说明**: 匹配任意超级用户消息类型事件
|
||||
"""
|
||||
SUPERUSER: Permission = Permission(SuperUser())
|
||||
"""匹配任意超级用户消息类型事件"""
|
||||
|
||||
__autodoc__ = {
|
||||
"Permission": True,
|
||||
"Permission.__call__": True,
|
||||
"User": True,
|
||||
"USER": True,
|
||||
}
|
||||
|
@ -1,8 +1,37 @@
|
||||
"""
|
||||
插件
|
||||
====
|
||||
"""本模块为 NoneBot 插件开发提供便携的定义函数。
|
||||
|
||||
为 NoneBot 插件开发提供便携的定义函数。
|
||||
## 快捷导入
|
||||
|
||||
为方便使用,本模块从子模块导入了部分内容,以下内容可以直接通过本模块导入:
|
||||
|
||||
- `on` => {ref}``on` <nonebot.plugin.on.on>`
|
||||
- `on_metaevent` => {ref}``on_metaevent` <nonebot.plugin.on.on_metaevent>`
|
||||
- `on_message` => {ref}``on_message` <nonebot.plugin.on.on_message>`
|
||||
- `on_notice` => {ref}``on_notice` <nonebot.plugin.on.on_notice>`
|
||||
- `on_request` => {ref}``on_request` <nonebot.plugin.on.on_request>`
|
||||
- `on_startswith` => {ref}``on_startswith` <nonebot.plugin.on.on_startswith>`
|
||||
- `on_endswith` => {ref}``on_endswith` <nonebot.plugin.on.on_endswith>`
|
||||
- `on_keyword` => {ref}``on_keyword` <nonebot.plugin.on.on_keyword>`
|
||||
- `on_command` => {ref}``on_command` <nonebot.plugin.on.on_command>`
|
||||
- `on_shell_command` => {ref}``on_shell_command` <nonebot.plugin.on.on_shell_command>`
|
||||
- `on_regex` => {ref}``on_regex` <nonebot.plugin.on.on_regex>`
|
||||
- `CommandGroup` => {ref}``CommandGroup` <nonebot.plugin.on.CommandGroup>`
|
||||
- `Matchergroup` => {ref}``MatcherGroup` <nonebot.plugin.on.MatcherGroup>`
|
||||
- `load_plugin` => {ref}``load_plugin` <nonebot.plugin.load.load_plugin>`
|
||||
- `load_plugins` => {ref}``load_plugins` <nonebot.plugin.load.load_plugins>`
|
||||
- `load_all_plugins` => {ref}``load_all_plugins` <nonebot.plugin.load.load_all_plugins>`
|
||||
- `load_from_json` => {ref}``load_from_json` <nonebot.plugin.load.load_from_json>`
|
||||
- `load_from_toml` => {ref}``load_from_toml` <nonebot.plugin.load.load_from_toml>`
|
||||
- `load_builtin_plugin` => {ref}``load_builtin_plugin` <nonebot.plugin.load.load_builtin_plugin>`
|
||||
- `load_builtin_plugins` => {ref}``load_builtin_plugins` <nonebot.plugin.load.load_builtin_plugins>`
|
||||
- `get_plugin` => {ref}``get_plugin` <nonebot.plugin.plugin.get_plugin>`
|
||||
- `get_loaded_plugins` => {ref}``get_loaded_plugins` <nonebot.plugin.plugin.get_loaded_plugins>`
|
||||
- `export` => {ref}``export` <nonebot.plugin.export.export>`
|
||||
- `require` => {ref}``require` <nonebot.plugin.load.require>`
|
||||
|
||||
FrontMatter:
|
||||
sidebar_position: 0
|
||||
description: nonebot.plugin 模块
|
||||
"""
|
||||
|
||||
from typing import List, Optional
|
||||
@ -37,5 +66,6 @@ from .load import load_from_json as load_from_json
|
||||
from .load import load_from_toml as load_from_toml
|
||||
from .on import on_shell_command as on_shell_command
|
||||
from .load import load_all_plugins as load_all_plugins
|
||||
from .load import load_builtin_plugin as load_builtin_plugin
|
||||
from .plugin import get_loaded_plugins as get_loaded_plugins
|
||||
from .load import load_builtin_plugins as load_builtin_plugins
|
||||
|
@ -1,16 +1,20 @@
|
||||
"""本模块定义了插件导出的内容对象。
|
||||
|
||||
在新版插件系统中,推荐优先使用直接 import 所需要的插件内容。
|
||||
|
||||
FrontMatter:
|
||||
sidebar_position: 4
|
||||
description: nonebot.plugin.export 模块
|
||||
"""
|
||||
|
||||
from . import _current_plugin
|
||||
|
||||
|
||||
class Export(dict):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
插件导出内容以使得其他插件可以获得。
|
||||
|
||||
:示例:
|
||||
|
||||
.. code-block:: python
|
||||
"""插件导出内容以使得其他插件可以获得。
|
||||
|
||||
用法:
|
||||
```python
|
||||
nonebot.export().default = "bar"
|
||||
|
||||
@nonebot.export()
|
||||
@ -25,6 +29,7 @@ class Export(dict):
|
||||
@nonebot.export().sub
|
||||
def something_else():
|
||||
pass
|
||||
```
|
||||
"""
|
||||
|
||||
def __call__(self, func, **kwargs):
|
||||
@ -45,15 +50,7 @@ class Export(dict):
|
||||
|
||||
|
||||
def export() -> Export:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
获取插件的导出内容对象
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Export``
|
||||
"""
|
||||
"""获取当前插件的导出内容对象"""
|
||||
plugin = _current_plugin.get()
|
||||
if not plugin:
|
||||
raise RuntimeError("Export outside of the plugin!")
|
||||
|
@ -1,3 +1,9 @@
|
||||
"""本模块定义插件加载接口。
|
||||
|
||||
FrontMatter:
|
||||
sidebar_position: 1
|
||||
description: nonebot.plugin.load 模块
|
||||
"""
|
||||
import json
|
||||
import warnings
|
||||
from typing import Set, Iterable, Optional
|
||||
@ -11,18 +17,10 @@ from .plugin import Plugin, get_plugin
|
||||
|
||||
|
||||
def load_plugin(module_path: str) -> Optional[Plugin]:
|
||||
"""
|
||||
:说明:
|
||||
"""加载单个插件,可以是本地插件或是通过 `pip` 安装的插件。
|
||||
|
||||
使用 ``PluginManager`` 加载单个插件,可以是本地插件或是通过 ``pip`` 安装的插件。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``module_path: str``: 插件名称 ``path.to.your.plugin``
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Optional[Plugin]``
|
||||
参数:
|
||||
module_path: 插件名称 `path.to.your.plugin`
|
||||
"""
|
||||
|
||||
manager = PluginManager([module_path])
|
||||
@ -31,18 +29,10 @@ def load_plugin(module_path: str) -> Optional[Plugin]:
|
||||
|
||||
|
||||
def load_plugins(*plugin_dir: str) -> Set[Plugin]:
|
||||
"""
|
||||
:说明:
|
||||
"""导入文件夹下多个插件,以 `_` 开头的插件不会被导入!
|
||||
|
||||
导入目录下多个插件,以 ``_`` 开头的插件不会被导入!
|
||||
|
||||
:参数:
|
||||
|
||||
- ``*plugin_dir: str``: 插件路径
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Set[Plugin]``
|
||||
参数:
|
||||
plugin_dir: 文件夹路径
|
||||
"""
|
||||
manager = PluginManager(search_path=plugin_dir)
|
||||
_managers.append(manager)
|
||||
@ -52,19 +42,11 @@ def load_plugins(*plugin_dir: str) -> Set[Plugin]:
|
||||
def load_all_plugins(
|
||||
module_path: Iterable[str], plugin_dir: Iterable[str]
|
||||
) -> Set[Plugin]:
|
||||
"""
|
||||
:说明:
|
||||
"""导入指定列表中的插件以及指定目录下多个插件,以 `_` 开头的插件不会被导入!
|
||||
|
||||
导入指定列表中的插件以及指定目录下多个插件,以 ``_`` 开头的插件不会被导入!
|
||||
|
||||
:参数:
|
||||
|
||||
- ``module_path: Iterable[str]``: 指定插件集合
|
||||
- ``plugin_dir: Iterable[str]``: 指定插件路径集合
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Set[Plugin]``
|
||||
参数:
|
||||
module_path: 指定插件集合
|
||||
plugin_dir: 指定文件夹路径集合
|
||||
"""
|
||||
manager = PluginManager(module_path, plugin_dir)
|
||||
_managers.append(manager)
|
||||
@ -72,19 +54,23 @@ def load_all_plugins(
|
||||
|
||||
|
||||
def load_from_json(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
|
||||
"""
|
||||
:说明:
|
||||
"""导入指定 json 文件中的 `plugins` 以及 `plugin_dirs` 下多个插件,以 `_` 开头的插件不会被导入!
|
||||
|
||||
导入指定 json 文件中的 ``plugins`` 以及 ``plugin_dirs`` 下多个插件,以 ``_`` 开头的插件不会被导入!
|
||||
参数:
|
||||
file_path: 指定 json 文件路径
|
||||
encoding: 指定 json 文件编码
|
||||
|
||||
:参数:
|
||||
用法:
|
||||
```json title=plugins.json
|
||||
{
|
||||
"plugins": ["some_plugin"],
|
||||
"plugin_dirs": ["some_dir"]
|
||||
}
|
||||
```
|
||||
|
||||
- ``file_path: str``: 指定 json 文件路径
|
||||
- ``encoding: str``: 指定 json 文件编码
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Set[Plugin]``
|
||||
```python
|
||||
nonebot.load_from_json("plugins.json")
|
||||
```
|
||||
"""
|
||||
with open(file_path, "r", encoding=encoding) as f:
|
||||
data = json.load(f)
|
||||
@ -96,20 +82,22 @@ def load_from_json(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
|
||||
|
||||
|
||||
def load_from_toml(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
|
||||
"""
|
||||
:说明:
|
||||
"""导入指定 toml 文件 `[tool.nonebot]` 中的 `plugins` 以及 `plugin_dirs` 下多个插件,以 `_` 开头的插件不会被导入!
|
||||
|
||||
导入指定 toml 文件 ``[tool.nonebot]`` 中的 ``plugins`` 以及 ``plugin_dirs`` 下多个插件,
|
||||
以 ``_`` 开头的插件不会被导入!
|
||||
参数:
|
||||
file_path: 指定 toml 文件路径
|
||||
encoding: 指定 toml 文件编码
|
||||
|
||||
:参数:
|
||||
用法:
|
||||
```toml title=pyproject.toml
|
||||
[tool.nonebot]
|
||||
plugins = ["some_plugin"]
|
||||
plugin_dirs = ["some_dir"]
|
||||
```
|
||||
|
||||
- ``file_path: str``: 指定 toml 文件路径
|
||||
- ``encoding: str``: 指定 toml 文件编码
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Set[Plugin]``
|
||||
```python
|
||||
nonebot.load_from_toml("pyproject.toml")
|
||||
```
|
||||
"""
|
||||
with open(file_path, "r", encoding=encoding) as f:
|
||||
data = tomlkit.parse(f.read()) # type: ignore
|
||||
@ -131,37 +119,48 @@ def load_from_toml(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
|
||||
return load_all_plugins(plugins, plugin_dirs)
|
||||
|
||||
|
||||
def load_builtin_plugins(name: str) -> Optional[Plugin]:
|
||||
"""
|
||||
:说明:
|
||||
def load_builtin_plugin(name: str) -> Optional[Plugin]:
|
||||
"""导入 NoneBot 内置插件。
|
||||
|
||||
导入 NoneBot 内置插件
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Plugin``
|
||||
参数:
|
||||
name: 插件名称
|
||||
"""
|
||||
return load_plugin(f"nonebot.plugins.{name}")
|
||||
|
||||
|
||||
def load_builtin_plugins(*plugins) -> Set[Plugin]:
|
||||
"""导入多个 NoneBot 内置插件。
|
||||
|
||||
参数:
|
||||
plugins: 插件名称列表
|
||||
"""
|
||||
return load_all_plugins([f"nonebot.plugins.{p}" for p in plugins], [])
|
||||
|
||||
|
||||
def _find_manager_by_name(name: str) -> Optional[PluginManager]:
|
||||
for manager in reversed(_managers):
|
||||
if name in manager.plugins or name in manager.searched_plugins:
|
||||
return manager
|
||||
|
||||
|
||||
def require(name: str) -> Export:
|
||||
"""获取一个插件的导出内容。
|
||||
|
||||
如果为 `load_plugins` 文件夹导入的插件,则为文件(夹)名。
|
||||
|
||||
参数:
|
||||
name: 插件名,即 {ref}`nonebot.plugin.plugin.Plugin.name`。
|
||||
|
||||
异常:
|
||||
RuntimeError: 插件无法加载
|
||||
"""
|
||||
:说明:
|
||||
|
||||
获取一个插件的导出内容
|
||||
|
||||
:参数:
|
||||
|
||||
* ``name: str``: 插件名,与 ``load_plugin`` 参数一致。如果为 ``load_plugins`` 导入的插件,则为文件(夹)名。
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Export``
|
||||
|
||||
:异常:
|
||||
- ``RuntimeError``: 插件无法加载
|
||||
"""
|
||||
plugin = get_plugin(name) or load_plugin(name)
|
||||
plugin = get_plugin(name.rsplit(".", 1)[-1])
|
||||
if not plugin:
|
||||
raise RuntimeError(f'Cannot load plugin "{name}"!')
|
||||
manager = _find_manager_by_name(name)
|
||||
if manager:
|
||||
plugin = manager.load_plugin(name)
|
||||
else:
|
||||
plugin = load_plugin(name)
|
||||
if not plugin:
|
||||
raise RuntimeError(f'Cannot load plugin "{name}"!')
|
||||
return plugin.export
|
||||
|
@ -1,3 +1,11 @@
|
||||
"""本模块实现插件加载流程。
|
||||
|
||||
参考: [import hooks](https://docs.python.org/3/reference/import.html#import-hooks), [PEP302](https://www.python.org/dev/peps/pep-0302/)
|
||||
|
||||
FrontMatter:
|
||||
sidebar_position: 5
|
||||
description: nonebot.plugin.manager 模块
|
||||
"""
|
||||
import sys
|
||||
import pkgutil
|
||||
import importlib
|
||||
@ -10,8 +18,9 @@ from typing import Set, Dict, List, Union, Iterable, Optional, Sequence
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.utils import escape_tag
|
||||
from .plugin import Plugin, _new_plugin
|
||||
|
||||
from . import _managers, _current_plugin
|
||||
from .plugin import Plugin, _new_plugin, _confirm_plugin
|
||||
|
||||
|
||||
class PluginManager:
|
||||
@ -44,7 +53,10 @@ class PluginManager:
|
||||
|
||||
return [
|
||||
*chain.from_iterable(
|
||||
[*manager.plugins, *manager.searched_plugins.keys()]
|
||||
[
|
||||
*map(lambda x: x.rsplit(".", 1)[-1], manager.plugins),
|
||||
*manager.searched_plugins.keys(),
|
||||
]
|
||||
for manager in _pre_managers
|
||||
)
|
||||
]
|
||||
@ -56,7 +68,7 @@ class PluginManager:
|
||||
third_party_plugins: Set[str] = set()
|
||||
|
||||
for plugin in self.plugins:
|
||||
name = plugin.rsplit(".", 1)[-1] if "." in plugin else plugin
|
||||
name = plugin.rsplit(".", 1)[-1]
|
||||
if name in third_party_plugins or name in previous_plugins:
|
||||
raise RuntimeError(
|
||||
f"Plugin already exists: {name}! Check your plugin name"
|
||||
@ -86,21 +98,27 @@ class PluginManager:
|
||||
|
||||
return third_party_plugins | set(self.searched_plugins.keys())
|
||||
|
||||
def load_plugin(self, name) -> Optional[Plugin]:
|
||||
def load_plugin(self, name: str) -> Optional[Plugin]:
|
||||
try:
|
||||
if name in self.plugins:
|
||||
module = importlib.import_module(name)
|
||||
elif name not in self.searched_plugins:
|
||||
raise RuntimeError(f"Plugin not found: {name}! Check your plugin name")
|
||||
else:
|
||||
elif name in self.searched_plugins:
|
||||
module = importlib.import_module(
|
||||
self._path_to_module_name(self.searched_plugins[name])
|
||||
)
|
||||
else:
|
||||
raise RuntimeError(f"Plugin not found: {name}! Check your plugin name")
|
||||
|
||||
logger.opt(colors=True).success(
|
||||
f'Succeeded to import "<y>{escape_tag(name)}</y>"'
|
||||
)
|
||||
return getattr(module, "__plugin__", None)
|
||||
plugin = getattr(module, "__plugin__", None)
|
||||
if plugin is None:
|
||||
raise RuntimeError(
|
||||
f"Module {module.__name__} is not loaded as a plugin! "
|
||||
"Make sure not to import it before loading."
|
||||
)
|
||||
return plugin
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
f'<r><bg #f8bbd0>Failed to import "{escape_tag(name)}"</bg #f8bbd0></r>'
|
||||
@ -120,7 +138,6 @@ class PluginFinder(MetaPathFinder):
|
||||
target: Optional[ModuleType] = None,
|
||||
):
|
||||
if _managers:
|
||||
index = -1
|
||||
module_spec = PathFinder.find_spec(fullname, path, target)
|
||||
if not module_spec:
|
||||
return
|
||||
@ -129,17 +146,13 @@ class PluginFinder(MetaPathFinder):
|
||||
return
|
||||
module_path = Path(module_origin).resolve()
|
||||
|
||||
while -index <= len(_managers):
|
||||
manager = _managers[index]
|
||||
|
||||
for manager in reversed(_managers):
|
||||
if (
|
||||
fullname in manager.plugins
|
||||
or module_path in manager.searched_plugins.values()
|
||||
):
|
||||
module_spec.loader = PluginLoader(manager, fullname, module_origin)
|
||||
return module_spec
|
||||
|
||||
index -= 1
|
||||
return
|
||||
|
||||
|
||||
@ -180,6 +193,8 @@ class PluginLoader(SourceFileLoader):
|
||||
# ) from e
|
||||
super().exec_module(module)
|
||||
|
||||
_confirm_plugin(plugin)
|
||||
|
||||
_current_plugin.reset(_plugin_token)
|
||||
return
|
||||
|
||||
|
@ -1,3 +1,9 @@
|
||||
"""本模块定义事件响应器便携定义函数。
|
||||
|
||||
FrontMatter:
|
||||
sidebar_position: 2
|
||||
description: nonebot.plugin.on 模块
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
import inspect
|
||||
@ -5,15 +11,9 @@ from types import ModuleType
|
||||
from typing import Any, Set, Dict, List, Type, Tuple, Union, Optional
|
||||
|
||||
from nonebot.matcher import Matcher
|
||||
from .manager import _current_plugin
|
||||
from nonebot.permission import Permission
|
||||
from nonebot.dependencies import Dependent
|
||||
from nonebot.typing import (
|
||||
T_State,
|
||||
T_Handler,
|
||||
T_RuleChecker,
|
||||
T_PermissionChecker,
|
||||
)
|
||||
from nonebot.typing import T_State, T_Handler, T_RuleChecker, T_PermissionChecker
|
||||
from nonebot.rule import (
|
||||
Rule,
|
||||
ArgumentParser,
|
||||
@ -25,6 +25,8 @@ from nonebot.rule import (
|
||||
shell_command,
|
||||
)
|
||||
|
||||
from .manager import _current_plugin
|
||||
|
||||
|
||||
def _store_matcher(matcher: Type[Matcher]) -> None:
|
||||
plugin = _current_plugin.get()
|
||||
@ -55,24 +57,17 @@ def on(
|
||||
_depth: int = 0,
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
注册一个基础事件响应器,可自定义类型。
|
||||
|
||||
注册一个基础事件响应器,可自定义类型。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``type: str``: 事件响应器类型
|
||||
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
|
||||
* ``permission: Optional[Union[Permission, T_PermissionChecker]] =]]``: 事件响应权限
|
||||
* ``handlers: Optional[List[Union[T_Handler, Dependent]]]``: 事件处理函数列表
|
||||
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
|
||||
* ``priority: int``: 事件响应器优先级
|
||||
* ``block: bool``: 是否阻止事件向更低优先级传递
|
||||
* ``state: Optional[T_State]``: 默认 state
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Type[Matcher]``
|
||||
参数:
|
||||
type: 事件响应器类型
|
||||
rule: 事件响应规则
|
||||
permission: 事件响应权限
|
||||
handlers: 事件处理函数列表
|
||||
temp: 是否为临时事件响应器(仅执行一次)
|
||||
priority: 事件响应器优先级
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
matcher = Matcher.new(
|
||||
type,
|
||||
@ -101,22 +96,15 @@ def on_metaevent(
|
||||
_depth: int = 0,
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
注册一个元事件响应器。
|
||||
|
||||
注册一个元事件响应器。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
|
||||
* ``handlers: Optional[List[Union[T_Handler, Dependent]]]``: 事件处理函数列表
|
||||
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
|
||||
* ``priority: int``: 事件响应器优先级
|
||||
* ``block: bool``: 是否阻止事件向更低优先级传递
|
||||
* ``state: Optional[T_State]``: 默认 state
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Type[Matcher]``
|
||||
参数:
|
||||
rule: 事件响应规则
|
||||
handlers: 事件处理函数列表
|
||||
temp: 是否为临时事件响应器(仅执行一次)
|
||||
priority: 事件响应器优先级
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
matcher = Matcher.new(
|
||||
"meta_event",
|
||||
@ -146,23 +134,16 @@ def on_message(
|
||||
_depth: int = 0,
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
注册一个消息事件响应器。
|
||||
|
||||
注册一个消息事件响应器。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
|
||||
* ``permission: Optional[Union[Permission, T_PermissionChecker]] =]]``: 事件响应权限
|
||||
* ``handlers: Optional[List[Union[T_Handler, Dependent]]]``: 事件处理函数列表
|
||||
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
|
||||
* ``priority: int``: 事件响应器优先级
|
||||
* ``block: bool``: 是否阻止事件向更低优先级传递
|
||||
* ``state: Optional[T_State]``: 默认 state
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Type[Matcher]``
|
||||
参数:
|
||||
rule: 事件响应规则
|
||||
permission: 事件响应权限
|
||||
handlers: 事件处理函数列表
|
||||
temp: 是否为临时事件响应器(仅执行一次)
|
||||
priority: 事件响应器优先级
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
matcher = Matcher.new(
|
||||
"message",
|
||||
@ -191,22 +172,15 @@ def on_notice(
|
||||
_depth: int = 0,
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
注册一个通知事件响应器。
|
||||
|
||||
注册一个通知事件响应器。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
|
||||
* ``handlers: Optional[List[Union[T_Handler, Dependent]]]``: 事件处理函数列表
|
||||
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
|
||||
* ``priority: int``: 事件响应器优先级
|
||||
* ``block: bool``: 是否阻止事件向更低优先级传递
|
||||
* ``state: Optional[T_State]``: 默认 state
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Type[Matcher]``
|
||||
参数:
|
||||
rule: 事件响应规则
|
||||
handlers: 事件处理函数列表
|
||||
temp: 是否为临时事件响应器(仅执行一次)
|
||||
priority: 事件响应器优先级
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
matcher = Matcher.new(
|
||||
"notice",
|
||||
@ -235,22 +209,15 @@ def on_request(
|
||||
_depth: int = 0,
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
注册一个请求事件响应器。
|
||||
|
||||
注册一个请求事件响应器。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
|
||||
* ``handlers: Optional[List[Union[T_Handler, Dependent]]]``: 事件处理函数列表
|
||||
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
|
||||
* ``priority: int``: 事件响应器优先级
|
||||
* ``block: bool``: 是否阻止事件向更低优先级传递
|
||||
* ``state: Optional[T_State]``: 默认 state
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Type[Matcher]``
|
||||
参数:
|
||||
rule: 事件响应规则
|
||||
handlers: 事件处理函数列表
|
||||
temp: 是否为临时事件响应器(仅执行一次)
|
||||
priority: 事件响应器优先级
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
matcher = Matcher.new(
|
||||
"request",
|
||||
@ -276,25 +243,18 @@ def on_startswith(
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
注册一个消息事件响应器,并且当消息的**文本部分**以指定内容开头时响应。
|
||||
|
||||
注册一个消息事件响应器,并且当消息的**文本部分**以指定内容开头时响应。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``msg: Union[str, Tuple[str, ...]]``: 指定消息开头内容
|
||||
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
|
||||
* ``ignorecase: bool``: 是否忽略大小写
|
||||
* ``permission: Optional[Union[Permission, T_PermissionChecker]] =]]``: 事件响应权限
|
||||
* ``handlers: Optional[List[Union[T_Handler, Dependent]]]``: 事件处理函数列表
|
||||
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
|
||||
* ``priority: int``: 事件响应器优先级
|
||||
* ``block: bool``: 是否阻止事件向更低优先级传递
|
||||
* ``state: Optional[T_State]``: 默认 state
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Type[Matcher]``
|
||||
参数:
|
||||
msg: 指定消息开头内容
|
||||
rule: 事件响应规则
|
||||
ignorecase: 是否忽略大小写
|
||||
permission: 事件响应权限
|
||||
handlers: 事件处理函数列表
|
||||
temp: 是否为临时事件响应器(仅执行一次)
|
||||
priority: 事件响应器优先级
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
return on_message(startswith(msg, ignorecase) & rule, **kwargs, _depth=_depth + 1)
|
||||
|
||||
@ -307,25 +267,18 @@ def on_endswith(
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
注册一个消息事件响应器,并且当消息的**文本部分**以指定内容结尾时响应。
|
||||
|
||||
注册一个消息事件响应器,并且当消息的**文本部分**以指定内容结尾时响应。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``msg: Union[str, Tuple[str, ...]]``: 指定消息结尾内容
|
||||
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
|
||||
* ``ignorecase: bool``: 是否忽略大小写
|
||||
* ``permission: Optional[Union[Permission, T_PermissionChecker]] =]]``: 事件响应权限
|
||||
* ``handlers: Optional[List[Union[T_Handler, Dependent]]]``: 事件处理函数列表
|
||||
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
|
||||
* ``priority: int``: 事件响应器优先级
|
||||
* ``block: bool``: 是否阻止事件向更低优先级传递
|
||||
* ``state: Optional[T_State]``: 默认 state
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Type[Matcher]``
|
||||
参数:
|
||||
msg: 指定消息结尾内容
|
||||
rule: 事件响应规则
|
||||
ignorecase: 是否忽略大小写
|
||||
permission: 事件响应权限
|
||||
handlers: 事件处理函数列表
|
||||
temp: 是否为临时事件响应器(仅执行一次)
|
||||
priority: 事件响应器优先级
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
return on_message(endswith(msg, ignorecase) & rule, **kwargs, _depth=_depth + 1)
|
||||
|
||||
@ -337,24 +290,17 @@ def on_keyword(
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
注册一个消息事件响应器,并且当消息纯文本部分包含关键词时响应。
|
||||
|
||||
注册一个消息事件响应器,并且当消息纯文本部分包含关键词时响应。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``keywords: Set[str]``: 关键词列表
|
||||
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
|
||||
* ``permission: Optional[Union[Permission, T_PermissionChecker]] =]]``: 事件响应权限
|
||||
* ``handlers: Optional[List[Union[T_Handler, Dependent]]]``: 事件处理函数列表
|
||||
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
|
||||
* ``priority: int``: 事件响应器优先级
|
||||
* ``block: bool``: 是否阻止事件向更低优先级传递
|
||||
* ``state: Optional[T_State]``: 默认 state
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Type[Matcher]``
|
||||
参数:
|
||||
keywords: 关键词列表
|
||||
rule: 事件响应规则
|
||||
permission: 事件响应权限
|
||||
handlers: 事件处理函数列表
|
||||
temp: 是否为临时事件响应器(仅执行一次)
|
||||
priority: 事件响应器优先级
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
return on_message(keyword(*keywords) & rule, **kwargs, _depth=_depth + 1)
|
||||
|
||||
@ -367,27 +313,20 @@ def on_command(
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
注册一个消息事件响应器,并且当消息以指定命令开头时响应。
|
||||
|
||||
注册一个消息事件响应器,并且当消息以指定命令开头时响应。
|
||||
命令匹配规则参考: `命令形式匹配 <rule.md#command-command>`_
|
||||
|
||||
命令匹配规则参考: `命令形式匹配 <rule.md#command-command>`_
|
||||
|
||||
:参数:
|
||||
|
||||
* ``cmd: Union[str, Tuple[str, ...]]``: 指定命令内容
|
||||
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
|
||||
* ``aliases: Optional[Set[Union[str, Tuple[str, ...]]]]``: 命令别名
|
||||
* ``permission: Optional[Union[Permission, T_PermissionChecker]] =]]``: 事件响应权限
|
||||
* ``handlers: Optional[List[Union[T_Handler, Dependent]]]``: 事件处理函数列表
|
||||
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
|
||||
* ``priority: int``: 事件响应器优先级
|
||||
* ``block: bool``: 是否阻止事件向更低优先级传递
|
||||
* ``state: Optional[T_State]``: 默认 state
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Type[Matcher]``
|
||||
参数:
|
||||
cmd: 指定命令内容
|
||||
rule: 事件响应规则
|
||||
aliases: 命令别名
|
||||
permission: 事件响应权限
|
||||
handlers: 事件处理函数列表
|
||||
temp: 是否为临时事件响应器(仅执行一次)
|
||||
priority: 事件响应器优先级
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
|
||||
commands = set([cmd]) | (aliases or set())
|
||||
@ -406,30 +345,23 @@ def on_shell_command(
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
注册一个支持 `shell_like` 解析参数的命令消息事件响应器。
|
||||
|
||||
注册一个支持 ``shell_like`` 解析参数的命令消息事件响应器。
|
||||
与普通的 `on_command` 不同的是,在添加 `parser` 参数时, 响应器会自动处理消息。
|
||||
|
||||
与普通的 ``on_command`` 不同的是,在添加 ``parser`` 参数时, 响应器会自动处理消息。
|
||||
并将用户输入的原始参数列表保存在 `state["argv"]`, `parser` 处理的参数保存在 `state["args"]` 中
|
||||
|
||||
并将用户输入的原始参数列表保存在 ``state["argv"]``, ``parser`` 处理的参数保存在 ``state["args"]`` 中
|
||||
|
||||
:参数:
|
||||
|
||||
* ``cmd: Union[str, Tuple[str, ...]]``: 指定命令内容
|
||||
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
|
||||
* ``aliases: Optional[Set[Union[str, Tuple[str, ...]]]]``: 命令别名
|
||||
* ``parser: Optional[ArgumentParser]``: ``nonebot.rule.ArgumentParser`` 对象
|
||||
* ``permission: Optional[Union[Permission, T_PermissionChecker]] =]]``: 事件响应权限
|
||||
* ``handlers: Optional[List[Union[T_Handler, Dependent]]]``: 事件处理函数列表
|
||||
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
|
||||
* ``priority: int``: 事件响应器优先级
|
||||
* ``block: bool``: 是否阻止事件向更低优先级传递
|
||||
* ``state: Optional[T_State]``: 默认 state
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Type[Matcher]``
|
||||
参数:
|
||||
cmd: 指定命令内容
|
||||
rule: 事件响应规则
|
||||
aliases: 命令别名
|
||||
parser: `nonebot.rule.ArgumentParser` 对象
|
||||
permission: 事件响应权限
|
||||
handlers: 事件处理函数列表
|
||||
temp: 是否为临时事件响应器(仅执行一次)
|
||||
priority: 事件响应器优先级
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
|
||||
commands = set([cmd]) | (aliases or set())
|
||||
@ -448,27 +380,20 @@ def on_regex(
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
注册一个消息事件响应器,并且当消息匹配正则表达式时响应。
|
||||
|
||||
注册一个消息事件响应器,并且当消息匹配正则表达式时响应。
|
||||
命令匹配规则参考: `正则匹配 <rule.md#regex-regex-flags-0>`_
|
||||
|
||||
命令匹配规则参考: `正则匹配 <rule.md#regex-regex-flags-0>`_
|
||||
|
||||
:参数:
|
||||
|
||||
* ``pattern: str``: 正则表达式
|
||||
* ``flags: Union[int, re.RegexFlag]``: 正则匹配标志
|
||||
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
|
||||
* ``permission: Optional[Union[Permission, T_PermissionChecker]] =]]``: 事件响应权限
|
||||
* ``handlers: Optional[List[Union[T_Handler, Dependent]]]``: 事件处理函数列表
|
||||
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
|
||||
* ``priority: int``: 事件响应器优先级
|
||||
* ``block: bool``: 是否阻止事件向更低优先级传递
|
||||
* ``state: Optional[T_State]``: 默认 state
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Type[Matcher]``
|
||||
参数:
|
||||
pattern: 正则表达式
|
||||
flags: 正则匹配标志
|
||||
rule: 事件响应规则
|
||||
permission: 事件响应权限
|
||||
handlers: 事件处理函数列表
|
||||
temp: 是否为临时事件响应器(仅执行一次)
|
||||
priority: 事件响应器优先级
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
return on_message(regex(pattern, flags) & rule, **kwargs, _depth=_depth + 1)
|
||||
|
||||
@ -478,38 +403,28 @@ class CommandGroup:
|
||||
|
||||
def __init__(self, cmd: Union[str, Tuple[str, ...]], **kwargs):
|
||||
"""
|
||||
:参数:
|
||||
|
||||
* ``cmd: Union[str, Tuple[str, ...]]``: 命令前缀
|
||||
* ``**kwargs``: 其他传递给 ``on_command`` 的参数默认值,参考 `on_command <#on-command-cmd-rule-none-aliases-none-kwargs>`_
|
||||
参数:
|
||||
cmd: 命令前缀
|
||||
**kwargs: `on_command` 的参数默认值,参考 `on_command <#on-command-cmd-rule-none-aliases-none-kwargs>`_
|
||||
"""
|
||||
self.basecmd: Tuple[str, ...] = (cmd,) if isinstance(cmd, str) else cmd
|
||||
"""
|
||||
- **类型**: ``Tuple[str, ...]``
|
||||
- **说明**: 命令前缀
|
||||
命令前缀
|
||||
"""
|
||||
if "aliases" in kwargs:
|
||||
del kwargs["aliases"]
|
||||
self.base_kwargs: Dict[str, Any] = kwargs
|
||||
"""
|
||||
- **类型**: ``Dict[str, Any]``
|
||||
- **说明**: 其他传递给 ``on_command`` 的参数默认值
|
||||
其他传递给 `on_command` 的参数默认值
|
||||
"""
|
||||
|
||||
def command(self, cmd: Union[str, Tuple[str, ...]], **kwargs) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
注册一个新的命令。
|
||||
|
||||
注册一个新的命令。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``cmd: Union[str, Tuple[str, ...]]``: 命令前缀
|
||||
* ``**kwargs``: 其他传递给 ``on_command`` 的参数,将会覆盖命令组默认值
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Type[Matcher]``
|
||||
参数:
|
||||
cmd: 命令前缀
|
||||
**kwargs: `on_command` 的参数,将会覆盖命令组默认值
|
||||
"""
|
||||
sub_cmd = (cmd,) if isinstance(cmd, str) else cmd
|
||||
cmd = self.basecmd + sub_cmd
|
||||
@ -522,18 +437,11 @@ class CommandGroup:
|
||||
self, cmd: Union[str, Tuple[str, ...]], **kwargs
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
注册一个新的命令。
|
||||
|
||||
注册一个新的命令。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``cmd: Union[str, Tuple[str, ...]]``: 命令前缀
|
||||
* ``**kwargs``: 其他传递给 ``on_shell_command`` 的参数,将会覆盖命令组默认值
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Type[Matcher]``
|
||||
参数:
|
||||
cmd: 命令前缀
|
||||
**kwargs: `on_shell_command` 的参数,将会覆盖命令组默认值
|
||||
"""
|
||||
sub_cmd = (cmd,) if isinstance(cmd, str) else cmd
|
||||
cmd = self.basecmd + sub_cmd
|
||||
@ -544,45 +452,34 @@ class CommandGroup:
|
||||
|
||||
|
||||
class MatcherGroup:
|
||||
"""事件响应器组合,统一管理。为 ``Matcher`` 创建提供默认属性。"""
|
||||
"""事件响应器组合,统一管理。为 `Matcher` 创建提供默认属性。"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
创建一个事件响应器组合,参数为默认值,与 ``on`` 一致
|
||||
创建一个事件响应器组合,参数为默认值,与 `on` 一致
|
||||
"""
|
||||
self.matchers: List[Type[Matcher]] = []
|
||||
"""
|
||||
:类型: ``List[Type[Matcher]]``
|
||||
:说明: 组内事件响应器列表
|
||||
组内事件响应器列表
|
||||
"""
|
||||
self.base_kwargs: Dict[str, Any] = kwargs
|
||||
"""
|
||||
- **类型**: ``Dict[str, Any]``
|
||||
- **说明**: 其他传递给 ``on`` 的参数默认值
|
||||
其他传递给 `on` 的参数默认值
|
||||
"""
|
||||
|
||||
def on(self, **kwargs) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
注册一个基础事件响应器,可自定义类型。
|
||||
|
||||
注册一个基础事件响应器,可自定义类型。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``type: str``: 事件响应器类型
|
||||
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
|
||||
* ``permission: Optional[Union[Permission, T_PermissionChecker]] =]]``: 事件响应权限
|
||||
* ``handlers: Optional[List[Union[T_Handler, Dependent]]]``: 事件处理函数列表
|
||||
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
|
||||
* ``priority: int``: 事件响应器优先级
|
||||
* ``block: bool``: 是否阻止事件向更低优先级传递
|
||||
* ``state: Optional[T_State]``: 默认 state
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Type[Matcher]``
|
||||
参数:
|
||||
type: 事件响应器类型
|
||||
rule: 事件响应规则
|
||||
permission: 事件响应权限
|
||||
handlers: 事件处理函数列表
|
||||
temp: 是否为临时事件响应器(仅执行一次)
|
||||
priority: 事件响应器优先级
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
@ -592,22 +489,15 @@ class MatcherGroup:
|
||||
|
||||
def on_metaevent(self, **kwargs) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
注册一个元事件响应器。
|
||||
|
||||
注册一个元事件响应器。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
|
||||
* ``handlers: Optional[List[Union[T_Handler, Dependent]]]``: 事件处理函数列表
|
||||
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
|
||||
* ``priority: int``: 事件响应器优先级
|
||||
* ``block: bool``: 是否阻止事件向更低优先级传递
|
||||
* ``state: Optional[T_State]``: 默认 state
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Type[Matcher]``
|
||||
参数:
|
||||
rule: 事件响应规则
|
||||
handlers: 事件处理函数列表
|
||||
temp: 是否为临时事件响应器(仅执行一次)
|
||||
priority: 事件响应器优先级
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
@ -619,23 +509,16 @@ class MatcherGroup:
|
||||
|
||||
def on_message(self, **kwargs) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
注册一个消息事件响应器。
|
||||
|
||||
注册一个消息事件响应器。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
|
||||
* ``permission: Optional[Union[Permission, T_PermissionChecker]] =]]``: 事件响应权限
|
||||
* ``handlers: Optional[List[Union[T_Handler, Dependent]]]``: 事件处理函数列表
|
||||
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
|
||||
* ``priority: int``: 事件响应器优先级
|
||||
* ``block: bool``: 是否阻止事件向更低优先级传递
|
||||
* ``state: Optional[T_State]``: 默认 state
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Type[Matcher]``
|
||||
参数:
|
||||
rule: 事件响应规则
|
||||
permission: 事件响应权限
|
||||
handlers: 事件处理函数列表
|
||||
temp: 是否为临时事件响应器(仅执行一次)
|
||||
priority: 事件响应器优先级
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
@ -646,22 +529,15 @@ class MatcherGroup:
|
||||
|
||||
def on_notice(self, **kwargs) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
注册一个通知事件响应器。
|
||||
|
||||
注册一个通知事件响应器。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
|
||||
* ``handlers: Optional[List[Union[T_Handler, Dependent]]]``: 事件处理函数列表
|
||||
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
|
||||
* ``priority: int``: 事件响应器优先级
|
||||
* ``block: bool``: 是否阻止事件向更低优先级传递
|
||||
* ``state: Optional[T_State]``: 默认 state
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Type[Matcher]``
|
||||
参数:
|
||||
rule: 事件响应规则
|
||||
handlers: 事件处理函数列表
|
||||
temp: 是否为临时事件响应器(仅执行一次)
|
||||
priority: 事件响应器优先级
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
@ -672,22 +548,15 @@ class MatcherGroup:
|
||||
|
||||
def on_request(self, **kwargs) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
注册一个请求事件响应器。
|
||||
|
||||
注册一个请求事件响应器。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
|
||||
* ``handlers: Optional[List[Union[T_Handler, Dependent]]]``: 事件处理函数列表
|
||||
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
|
||||
* ``priority: int``: 事件响应器优先级
|
||||
* ``block: bool``: 是否阻止事件向更低优先级传递
|
||||
* ``state: Optional[T_State]``: 默认 state
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Type[Matcher]``
|
||||
参数:
|
||||
rule: 事件响应规则
|
||||
handlers: 事件处理函数列表
|
||||
temp: 是否为临时事件响应器(仅执行一次)
|
||||
priority: 事件响应器优先级
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
@ -700,25 +569,18 @@ class MatcherGroup:
|
||||
self, msg: Union[str, Tuple[str, ...]], **kwargs
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
注册一个消息事件响应器,并且当消息的**文本部分**以指定内容开头时响应。
|
||||
|
||||
注册一个消息事件响应器,并且当消息的**文本部分**以指定内容开头时响应。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``msg: Union[str, Tuple[str, ...]]``: 指定消息开头内容
|
||||
* ``ignorecase: bool``: 是否忽略大小写
|
||||
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
|
||||
* ``permission: Optional[Union[Permission, T_PermissionChecker]] =]]``: 事件响应权限
|
||||
* ``handlers: Optional[List[Union[T_Handler, Dependent]]]``: 事件处理函数列表
|
||||
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
|
||||
* ``priority: int``: 事件响应器优先级
|
||||
* ``block: bool``: 是否阻止事件向更低优先级传递
|
||||
* ``state: Optional[T_State]``: 默认 state
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Type[Matcher]``
|
||||
参数:
|
||||
msg: 指定消息开头内容
|
||||
ignorecase: 是否忽略大小写
|
||||
rule: 事件响应规则
|
||||
permission: 事件响应权限
|
||||
handlers: 事件处理函数列表
|
||||
temp: 是否为临时事件响应器(仅执行一次)
|
||||
priority: 事件响应器优先级
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
@ -729,25 +591,18 @@ class MatcherGroup:
|
||||
|
||||
def on_endswith(self, msg: Union[str, Tuple[str, ...]], **kwargs) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
注册一个消息事件响应器,并且当消息的**文本部分**以指定内容结尾时响应。
|
||||
|
||||
注册一个消息事件响应器,并且当消息的**文本部分**以指定内容结尾时响应。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``msg: Union[str, Tuple[str, ...]]``: 指定消息结尾内容
|
||||
* ``ignorecase: bool``: 是否忽略大小写
|
||||
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
|
||||
* ``permission: Optional[Union[Permission, T_PermissionChecker]] =]]``: 事件响应权限
|
||||
* ``handlers: Optional[List[Union[T_Handler, Dependent]]]``: 事件处理函数列表
|
||||
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
|
||||
* ``priority: int``: 事件响应器优先级
|
||||
* ``block: bool``: 是否阻止事件向更低优先级传递
|
||||
* ``state: Optional[T_State]``: 默认 state
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Type[Matcher]``
|
||||
参数:
|
||||
msg: 指定消息结尾内容
|
||||
ignorecase: 是否忽略大小写
|
||||
rule: 事件响应规则
|
||||
permission: 事件响应权限
|
||||
handlers: 事件处理函数列表
|
||||
temp: 是否为临时事件响应器(仅执行一次)
|
||||
priority: 事件响应器优先级
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
@ -758,24 +613,17 @@ class MatcherGroup:
|
||||
|
||||
def on_keyword(self, keywords: Set[str], **kwargs) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
注册一个消息事件响应器,并且当消息纯文本部分包含关键词时响应。
|
||||
|
||||
注册一个消息事件响应器,并且当消息纯文本部分包含关键词时响应。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``keywords: Set[str]``: 关键词列表
|
||||
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
|
||||
* ``permission: Optional[Union[Permission, T_PermissionChecker]] =]]``: 事件响应权限
|
||||
* ``handlers: Optional[List[Union[T_Handler, Dependent]]]``: 事件处理函数列表
|
||||
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
|
||||
* ``priority: int``: 事件响应器优先级
|
||||
* ``block: bool``: 是否阻止事件向更低优先级传递
|
||||
* ``state: Optional[T_State]``: 默认 state
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Type[Matcher]``
|
||||
参数:
|
||||
keywords: 关键词列表
|
||||
rule: 事件响应规则
|
||||
permission: 事件响应权限
|
||||
handlers: 事件处理函数列表
|
||||
temp: 是否为临时事件响应器(仅执行一次)
|
||||
priority: 事件响应器优先级
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
@ -791,27 +639,20 @@ class MatcherGroup:
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
注册一个消息事件响应器,并且当消息以指定命令开头时响应。
|
||||
|
||||
注册一个消息事件响应器,并且当消息以指定命令开头时响应。
|
||||
命令匹配规则参考: `命令形式匹配 <rule.md#command-command>`_
|
||||
|
||||
命令匹配规则参考: `命令形式匹配 <rule.md#command-command>`_
|
||||
|
||||
:参数:
|
||||
|
||||
* ``cmd: Union[str, Tuple[str, ...]]``: 指定命令内容
|
||||
* ``aliases: Optional[Set[Union[str, Tuple[str, ...]]]]``: 命令别名
|
||||
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
|
||||
* ``permission: Optional[Union[Permission, T_PermissionChecker]] =]]``: 事件响应权限
|
||||
* ``handlers: Optional[List[Union[T_Handler, Dependent]]]``: 事件处理函数列表
|
||||
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
|
||||
* ``priority: int``: 事件响应器优先级
|
||||
* ``block: bool``: 是否阻止事件向更低优先级传递
|
||||
* ``state: Optional[T_State]``: 默认 state
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Type[Matcher]``
|
||||
参数:
|
||||
cmd: 指定命令内容
|
||||
aliases: 命令别名
|
||||
rule: 事件响应规则
|
||||
permission: 事件响应权限
|
||||
handlers: 事件处理函数列表
|
||||
temp: 是否为临时事件响应器(仅执行一次)
|
||||
priority: 事件响应器优先级
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
@ -828,30 +669,23 @@ class MatcherGroup:
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
注册一个支持 `shell_like` 解析参数的命令消息事件响应器。
|
||||
|
||||
注册一个支持 ``shell_like`` 解析参数的命令消息事件响应器。
|
||||
与普通的 `on_command` 不同的是,在添加 `parser` 参数时, 响应器会自动处理消息。
|
||||
|
||||
与普通的 ``on_command`` 不同的是,在添加 ``parser`` 参数时, 响应器会自动处理消息。
|
||||
并将用户输入的原始参数列表保存在 `state["argv"]`, `parser` 处理的参数保存在 `state["args"]` 中
|
||||
|
||||
并将用户输入的原始参数列表保存在 ``state["argv"]``, ``parser`` 处理的参数保存在 ``state["args"]`` 中
|
||||
|
||||
:参数:
|
||||
|
||||
* ``cmd: Union[str, Tuple[str, ...]]``: 指定命令内容
|
||||
* ``aliases: Optional[Set[Union[str, Tuple[str, ...]]]]``: 命令别名
|
||||
* ``parser: Optional[ArgumentParser]``: ``nonebot.rule.ArgumentParser`` 对象
|
||||
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
|
||||
* ``permission: Optional[Union[Permission, T_PermissionChecker]] =]]``: 事件响应权限
|
||||
* ``handlers: Optional[List[Union[T_Handler, Dependent]]]``: 事件处理函数列表
|
||||
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
|
||||
* ``priority: int``: 事件响应器优先级
|
||||
* ``block: bool``: 是否阻止事件向更低优先级传递
|
||||
* ``state: Optional[T_State]``: 默认 state
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Type[Matcher]``
|
||||
参数:
|
||||
cmd: 指定命令内容
|
||||
aliases: 命令别名
|
||||
parser: `nonebot.rule.ArgumentParser` 对象
|
||||
rule: 事件响应规则
|
||||
permission: 事件响应权限
|
||||
handlers: 事件处理函数列表
|
||||
temp: 是否为临时事件响应器(仅执行一次)
|
||||
priority: 事件响应器优先级
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
@ -866,27 +700,20 @@ class MatcherGroup:
|
||||
self, pattern: str, flags: Union[int, re.RegexFlag] = 0, **kwargs
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
注册一个消息事件响应器,并且当消息匹配正则表达式时响应。
|
||||
|
||||
注册一个消息事件响应器,并且当消息匹配正则表达式时响应。
|
||||
命令匹配规则参考: `正则匹配 <rule.md#regex-regex-flags-0>`_
|
||||
|
||||
命令匹配规则参考: `正则匹配 <rule.md#regex-regex-flags-0>`_
|
||||
|
||||
:参数:
|
||||
|
||||
* ``pattern: str``: 正则表达式
|
||||
* ``flags: Union[int, re.RegexFlag]``: 正则匹配标志
|
||||
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
|
||||
* ``permission: Optional[Union[Permission, T_PermissionChecker]] =]]``: 事件响应权限
|
||||
* ``handlers: Optional[List[Union[T_Handler, Dependent]]]``: 事件处理函数列表
|
||||
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
|
||||
* ``priority: int``: 事件响应器优先级
|
||||
* ``block: bool``: 是否阻止事件向更低优先级传递
|
||||
* ``state: Optional[T_State]``: 默认 state
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Type[Matcher]``
|
||||
参数:
|
||||
pattern: 正则表达式
|
||||
flags: 正则匹配标志
|
||||
rule: 事件响应规则
|
||||
permission: 事件响应权限
|
||||
handlers: 事件处理函数列表
|
||||
temp: 是否为临时事件响应器(仅执行一次)
|
||||
priority: 事件响应器优先级
|
||||
block: 是否阻止事件向更低优先级传递
|
||||
state: 默认 state
|
||||
"""
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
|
@ -5,12 +5,7 @@ from nonebot.matcher import Matcher
|
||||
from nonebot.permission import Permission
|
||||
from nonebot.dependencies import Dependent
|
||||
from nonebot.rule import Rule, ArgumentParser
|
||||
from nonebot.typing import (
|
||||
T_State,
|
||||
T_Handler,
|
||||
T_RuleChecker,
|
||||
T_PermissionChecker,
|
||||
)
|
||||
from nonebot.typing import T_State, T_Handler, T_RuleChecker, T_PermissionChecker
|
||||
|
||||
def on(
|
||||
type: str = "",
|
||||
|
@ -1,18 +1,22 @@
|
||||
"""本模块定义插件对象。
|
||||
|
||||
FrontMatter:
|
||||
sidebar_position: 3
|
||||
description: nonebot.plugin.plugin 模块
|
||||
"""
|
||||
from types import ModuleType
|
||||
from dataclasses import field, dataclass
|
||||
from typing import TYPE_CHECKING, Set, Dict, Type, Optional
|
||||
|
||||
from .export import Export
|
||||
from nonebot.matcher import Matcher
|
||||
|
||||
from .export import Export
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .manager import PluginManager
|
||||
|
||||
plugins: Dict[str, "Plugin"] = {}
|
||||
"""
|
||||
:类型: ``Dict[str, Plugin]``
|
||||
:说明: 已加载的插件
|
||||
"""
|
||||
"""已加载的插件"""
|
||||
|
||||
|
||||
@dataclass(eq=False)
|
||||
@ -20,74 +24,36 @@ class Plugin(object):
|
||||
"""存储插件信息"""
|
||||
|
||||
name: str
|
||||
"""
|
||||
- **类型**: ``str``
|
||||
- **说明**: 插件名称,使用 文件/文件夹 名称作为插件名
|
||||
"""
|
||||
"""插件名称,使用 文件/文件夹 名称作为插件名"""
|
||||
module: ModuleType
|
||||
"""
|
||||
- **类型**: ``ModuleType``
|
||||
- **说明**: 插件模块对象
|
||||
"""
|
||||
"""插件模块对象"""
|
||||
module_name: str
|
||||
"""
|
||||
- **类型**: ``str``
|
||||
- **说明**: 点分割模块路径
|
||||
"""
|
||||
"""点分割模块路径"""
|
||||
manager: "PluginManager"
|
||||
"""
|
||||
- **类型**: ``PluginManager``
|
||||
- **说明**: 导入该插件的插件管理器
|
||||
"""
|
||||
"""导入该插件的插件管理器"""
|
||||
export: Export = field(default_factory=Export)
|
||||
"""
|
||||
- **类型**: ``Export``
|
||||
- **说明**: 插件内定义的导出内容
|
||||
"""
|
||||
"""插件内定义的导出内容"""
|
||||
matcher: Set[Type[Matcher]] = field(default_factory=set)
|
||||
"""
|
||||
- **类型**: ``Set[Type[Matcher]]``
|
||||
- **说明**: 插件内定义的 ``Matcher``
|
||||
"""
|
||||
"""插件内定义的 `Matcher`"""
|
||||
parent_plugin: Optional["Plugin"] = None
|
||||
"""
|
||||
- **类型**: ``Optional[Plugin]``
|
||||
- **说明**: 父插件
|
||||
"""
|
||||
"""父插件"""
|
||||
sub_plugins: Set["Plugin"] = field(default_factory=set)
|
||||
"""
|
||||
- **类型**: ``Set[Plugin]``
|
||||
- **说明**: 子插件集合
|
||||
"""
|
||||
"""子插件集合"""
|
||||
|
||||
|
||||
def get_plugin(name: str) -> Optional[Plugin]:
|
||||
"""
|
||||
:说明:
|
||||
"""获取已经导入的某个插件。
|
||||
|
||||
获取当前导入的某个插件。
|
||||
如果为 `load_plugins` 文件夹导入的插件,则为文件(夹)名。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``name: str``: 插件名,与 ``load_plugin`` 参数一致。如果为 ``load_plugins`` 导入的插件,则为文件(夹)名。
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Optional[Plugin]``
|
||||
参数:
|
||||
name: 插件名,即 {ref}`nonebot.plugin.plugin.Plugin.name`。
|
||||
"""
|
||||
return plugins.get(name)
|
||||
|
||||
|
||||
def get_loaded_plugins() -> Set[Plugin]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
获取当前已导入的所有插件。
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Set[Plugin]``
|
||||
"""
|
||||
"""获取当前已导入的所有插件。"""
|
||||
return set(plugins.values())
|
||||
|
||||
|
||||
@ -96,5 +62,10 @@ def _new_plugin(fullname: str, module: ModuleType, manager: "PluginManager") ->
|
||||
if name in plugins:
|
||||
raise RuntimeError("Plugin already exists! Check your plugin name.")
|
||||
plugin = Plugin(name, module, fullname, manager)
|
||||
plugins[name] = plugin
|
||||
return plugin
|
||||
|
||||
|
||||
def _confirm_plugin(plugin: Plugin) -> None:
|
||||
if plugin.name in plugins:
|
||||
raise RuntimeError("Plugin already exists! Check your plugin name.")
|
||||
plugins[plugin.name] = plugin
|
||||
|
@ -1,34 +1,31 @@
|
||||
from typing import Dict
|
||||
from typing import Dict, AsyncGenerator
|
||||
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.message import (
|
||||
IgnoredException,
|
||||
run_preprocessor,
|
||||
run_postprocessor,
|
||||
)
|
||||
from nonebot.params import Depends
|
||||
from nonebot.message import IgnoredException, event_preprocessor
|
||||
|
||||
_running_matcher: Dict[str, int] = {}
|
||||
|
||||
|
||||
@run_preprocessor
|
||||
async def preprocess(event: Event):
|
||||
async def matcher_mutex(event: Event) -> AsyncGenerator[bool, None]:
|
||||
result = False
|
||||
try:
|
||||
session_id = event.get_session_id()
|
||||
except Exception:
|
||||
return
|
||||
current_event_id = id(event)
|
||||
event_id = _running_matcher.get(session_id, None)
|
||||
if event_id and event_id != current_event_id:
|
||||
yield result
|
||||
else:
|
||||
current_event_id = id(event)
|
||||
event_id = _running_matcher.get(session_id, None)
|
||||
if event_id:
|
||||
result = event_id != current_event_id
|
||||
else:
|
||||
_running_matcher[session_id] = current_event_id
|
||||
yield result
|
||||
if not result:
|
||||
del _running_matcher[session_id]
|
||||
|
||||
|
||||
@event_preprocessor
|
||||
async def preprocess(mutex: bool = Depends(matcher_mutex)):
|
||||
if mutex:
|
||||
raise IgnoredException("Another matcher running")
|
||||
|
||||
_running_matcher[session_id] = current_event_id
|
||||
|
||||
|
||||
@run_postprocessor
|
||||
async def postprocess(event: Event):
|
||||
try:
|
||||
session_id = event.get_session_id()
|
||||
except Exception:
|
||||
return
|
||||
if session_id in _running_matcher:
|
||||
del _running_matcher[session_id]
|
||||
|
371
nonebot/rule.py
371
nonebot/rule.py
@ -1,32 +1,37 @@
|
||||
r"""
|
||||
规则
|
||||
====
|
||||
"""本模块是 {ref}`nonebot.matcher.Matcher.rule` 的类型定义。
|
||||
|
||||
每个事件响应器 ``Matcher`` 拥有一个匹配规则 ``Rule`` ,其中是 ``RuleChecker`` 的集合,只有当所有 ``RuleChecker`` 检查结果为 ``True`` 时继续运行。
|
||||
每个事件响应器 {ref}`nonebot.matcher.Matcher` 拥有一个匹配规则 {ref}`nonebot.rule.Rule`
|
||||
其中是 `RuleChecker` 的集合,只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。
|
||||
|
||||
\:\:\:tip 提示
|
||||
``RuleChecker`` 既可以是 async function 也可以是 sync function
|
||||
\:\:\:
|
||||
FrontMatter:
|
||||
sidebar_position: 5
|
||||
description: nonebot.rule 模块
|
||||
"""
|
||||
|
||||
import re
|
||||
import shlex
|
||||
import asyncio
|
||||
from itertools import product
|
||||
from argparse import Namespace
|
||||
from contextlib import AsyncExitStack
|
||||
from typing_extensions import TypedDict
|
||||
from argparse import ArgumentParser as ArgParser
|
||||
from typing import Any, Set, List, Tuple, Union, NoReturn, Optional, Sequence
|
||||
from typing import Any, List, Tuple, Union, Optional, Sequence
|
||||
|
||||
from pygtrie import CharTrie
|
||||
|
||||
from nonebot import get_driver
|
||||
from nonebot.log import logger
|
||||
from nonebot.dependencies import Dependent
|
||||
from nonebot.exception import ParserExit, SkippedException
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.exception import ParserExit
|
||||
from nonebot.internal.rule import Rule as Rule
|
||||
from nonebot.adapters import Bot, Event, Message, MessageSegment
|
||||
from nonebot.typing import T_State, T_RuleChecker, T_DependencyCache
|
||||
from nonebot.params import (
|
||||
Command,
|
||||
EventToMe,
|
||||
EventType,
|
||||
CommandArg,
|
||||
EventMessage,
|
||||
EventPlainText,
|
||||
)
|
||||
from nonebot.consts import (
|
||||
CMD_KEY,
|
||||
PREFIX_KEY,
|
||||
@ -38,19 +43,6 @@ from nonebot.consts import (
|
||||
REGEX_GROUP,
|
||||
REGEX_MATCHED,
|
||||
)
|
||||
from nonebot.params import (
|
||||
State,
|
||||
Command,
|
||||
BotParam,
|
||||
EventToMe,
|
||||
EventType,
|
||||
EventParam,
|
||||
StateParam,
|
||||
DependParam,
|
||||
DefaultParam,
|
||||
EventMessage,
|
||||
EventPlainText,
|
||||
)
|
||||
|
||||
CMD_RESULT = TypedDict(
|
||||
"CMD_RESULT",
|
||||
@ -62,113 +54,6 @@ CMD_RESULT = TypedDict(
|
||||
)
|
||||
|
||||
|
||||
class Rule:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
``Matcher`` 规则类,当事件传递时,在 ``Matcher`` 运行前进行检查。
|
||||
|
||||
:示例:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
Rule(async_function) & sync_function
|
||||
# 等价于
|
||||
from nonebot.utils import run_sync
|
||||
Rule(async_function, run_sync(sync_function))
|
||||
"""
|
||||
|
||||
__slots__ = ("checkers",)
|
||||
|
||||
HANDLER_PARAM_TYPES = [
|
||||
DependParam,
|
||||
BotParam,
|
||||
EventParam,
|
||||
StateParam,
|
||||
DefaultParam,
|
||||
]
|
||||
|
||||
def __init__(self, *checkers: Union[T_RuleChecker, Dependent[bool]]) -> None:
|
||||
"""
|
||||
:参数:
|
||||
|
||||
* ``*checkers: Union[T_RuleChecker, Dependent[bool]]``: RuleChecker
|
||||
|
||||
"""
|
||||
self.checkers: Set[Dependent[bool]] = set(
|
||||
checker
|
||||
if isinstance(checker, Dependent)
|
||||
else Dependent[bool].parse(
|
||||
call=checker, allow_types=self.HANDLER_PARAM_TYPES
|
||||
)
|
||||
for checker in checkers
|
||||
)
|
||||
"""
|
||||
:说明:
|
||||
|
||||
存储 ``RuleChecker``
|
||||
|
||||
:类型:
|
||||
|
||||
* ``Set[Dependent[bool]]``
|
||||
"""
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
bot: Bot,
|
||||
event: Event,
|
||||
state: T_State,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
检查是否符合所有规则
|
||||
|
||||
:参数:
|
||||
|
||||
* ``bot: Bot``: Bot 对象
|
||||
* ``event: Event``: Event 对象
|
||||
* ``state: T_State``: 当前 State
|
||||
* ``stack: Optional[AsyncExitStack]``: 异步上下文栈
|
||||
* ``dependency_cache: Optional[CacheDict[T_Handler, Any]]``: 依赖缓存
|
||||
|
||||
:返回:
|
||||
|
||||
- ``bool``
|
||||
"""
|
||||
if not self.checkers:
|
||||
return True
|
||||
try:
|
||||
results = await asyncio.gather(
|
||||
*(
|
||||
checker(
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
)
|
||||
for checker in self.checkers
|
||||
)
|
||||
)
|
||||
except SkippedException:
|
||||
return False
|
||||
return all(results)
|
||||
|
||||
def __and__(self, other: Optional[Union["Rule", T_RuleChecker]]) -> "Rule":
|
||||
if other is None:
|
||||
return self
|
||||
elif isinstance(other, Rule):
|
||||
return Rule(*self.checkers, *other.checkers)
|
||||
else:
|
||||
return Rule(*self.checkers, other)
|
||||
|
||||
def __or__(self, other) -> NoReturn:
|
||||
raise RuntimeError("Or operation between rules is not allowed.")
|
||||
|
||||
|
||||
class TrieRule:
|
||||
prefix: CharTrie = CharTrie()
|
||||
|
||||
@ -205,6 +90,15 @@ class TrieRule:
|
||||
|
||||
|
||||
class StartswithRule:
|
||||
"""检查消息纯文本是否以指定字符串开头。
|
||||
|
||||
参数:
|
||||
msg: 指定消息开头字符串元组
|
||||
ignorecase: 是否忽略大小写
|
||||
"""
|
||||
|
||||
__slots__ = ("msg", "ignorecase")
|
||||
|
||||
def __init__(self, msg: Tuple[str, ...], ignorecase: bool = False):
|
||||
self.msg = msg
|
||||
self.ignorecase = ignorecase
|
||||
@ -224,14 +118,11 @@ class StartswithRule:
|
||||
|
||||
|
||||
def startswith(msg: Union[str, Tuple[str, ...]], ignorecase: bool = False) -> Rule:
|
||||
"""
|
||||
:说明:
|
||||
"""匹配消息纯文本开头。
|
||||
|
||||
匹配消息开头
|
||||
|
||||
:参数:
|
||||
|
||||
* ``msg: str``: 消息开头字符串
|
||||
参数:
|
||||
msg: 指定消息开头字符串元组
|
||||
ignorecase: 是否忽略大小写
|
||||
"""
|
||||
if isinstance(msg, str):
|
||||
msg = (msg,)
|
||||
@ -240,6 +131,15 @@ def startswith(msg: Union[str, Tuple[str, ...]], ignorecase: bool = False) -> Ru
|
||||
|
||||
|
||||
class EndswithRule:
|
||||
"""检查消息纯文本是否以指定字符串结尾。
|
||||
|
||||
参数:
|
||||
msg: 指定消息结尾字符串元组
|
||||
ignorecase: 是否忽略大小写
|
||||
"""
|
||||
|
||||
__slots__ = ("msg", "ignorecase")
|
||||
|
||||
def __init__(self, msg: Tuple[str, ...], ignorecase: bool = False):
|
||||
self.msg = msg
|
||||
self.ignorecase = ignorecase
|
||||
@ -259,14 +159,11 @@ class EndswithRule:
|
||||
|
||||
|
||||
def endswith(msg: Union[str, Tuple[str, ...]], ignorecase: bool = False) -> Rule:
|
||||
"""
|
||||
:说明:
|
||||
"""匹配消息纯文本结尾。
|
||||
|
||||
匹配消息结尾
|
||||
|
||||
:参数:
|
||||
|
||||
* ``msg: str``: 消息结尾字符串
|
||||
参数:
|
||||
msg: 指定消息开头字符串元组
|
||||
ignorecase: 是否忽略大小写
|
||||
"""
|
||||
if isinstance(msg, str):
|
||||
msg = (msg,)
|
||||
@ -275,6 +172,14 @@ def endswith(msg: Union[str, Tuple[str, ...]], ignorecase: bool = False) -> Rule
|
||||
|
||||
|
||||
class KeywordsRule:
|
||||
"""检查消息纯文本是否包含指定关键字。
|
||||
|
||||
参数:
|
||||
keywords: 指定关键字元组
|
||||
"""
|
||||
|
||||
__slots__ = ("keywords",)
|
||||
|
||||
def __init__(self, *keywords: str):
|
||||
self.keywords = keywords
|
||||
|
||||
@ -287,20 +192,24 @@ class KeywordsRule:
|
||||
|
||||
|
||||
def keyword(*keywords: str) -> Rule:
|
||||
"""
|
||||
:说明:
|
||||
"""匹配消息纯文本关键词。
|
||||
|
||||
匹配消息关键词
|
||||
|
||||
:参数:
|
||||
|
||||
* ``*keywords: str``: 关键词
|
||||
参数:
|
||||
keywords: 指定关键字元组
|
||||
"""
|
||||
|
||||
return Rule(KeywordsRule(*keywords))
|
||||
|
||||
|
||||
class CommandRule:
|
||||
"""检查消息是否为指定命令。
|
||||
|
||||
参数:
|
||||
cmds: 指定命令元组列表
|
||||
"""
|
||||
|
||||
__slots__ = ("cmds",)
|
||||
|
||||
def __init__(self, cmds: List[Tuple[str, ...]]):
|
||||
self.cmds = cmds
|
||||
|
||||
@ -312,27 +221,27 @@ class CommandRule:
|
||||
|
||||
|
||||
def command(*cmds: Union[str, Tuple[str, ...]]) -> Rule:
|
||||
r"""
|
||||
:说明:
|
||||
"""匹配消息命令。
|
||||
|
||||
命令形式匹配,根据配置里提供的 ``command_start``, ``command_sep`` 判断消息是否为命令。
|
||||
根据配置里提供的 {ref}``command_start` <nonebot.config.Config.command_start>`,
|
||||
{ref}``command_sep` <nonebot.config.Config.command_sep>` 判断消息是否为命令。
|
||||
|
||||
可以通过 ``state["_prefix"]["command"]`` 获取匹配成功的命令(例:``("test",)``),通过 ``state["_prefix"]["raw_command"]`` 获取匹配成功的原始命令文本(例:``"/test"``)。
|
||||
可以通过 {ref}`nonebot.params.Command` 获取匹配成功的命令(例: `("test",)`),
|
||||
通过 {ref}`nonebot.params.RawCommand` 获取匹配成功的原始命令文本(例: `"/test"`),
|
||||
通过 {ref}`nonebot.params.CommandArg` 获取匹配成功的命令参数。
|
||||
|
||||
:参数:
|
||||
参数:
|
||||
cmds: 命令文本或命令元组
|
||||
|
||||
* ``*cmds: Union[str, Tuple[str, ...]]``: 命令内容
|
||||
用法:
|
||||
使用默认 `command_start`, `command_sep` 配置
|
||||
|
||||
:示例:
|
||||
命令 `("test",)` 可以匹配: `/test` 开头的消息
|
||||
命令 `("test", "sub")` 可以匹配: `/test.sub` 开头的消息
|
||||
|
||||
使用默认 ``command_start``, ``command_sep`` 配置
|
||||
|
||||
命令 ``("test",)`` 可以匹配:``/test`` 开头的消息
|
||||
命令 ``("test", "sub")`` 可以匹配”``/test.sub`` 开头的消息
|
||||
|
||||
\:\:\:tip 提示
|
||||
命令内容与后续消息间无需空格!
|
||||
\:\:\:
|
||||
:::tip 提示
|
||||
命令内容与后续消息间无需空格!
|
||||
:::
|
||||
"""
|
||||
|
||||
config = get_driver().config
|
||||
@ -356,10 +265,11 @@ def command(*cmds: Union[str, Tuple[str, ...]]) -> Rule:
|
||||
|
||||
|
||||
class ArgumentParser(ArgParser):
|
||||
"""
|
||||
:说明:
|
||||
"""`shell_like` 命令参数解析器,解析出错时不会退出程序。
|
||||
|
||||
``shell_like`` 命令参数解析器,解析出错时不会退出程序。
|
||||
用法:
|
||||
用法与 `argparse.ArgumentParser` 相同,
|
||||
参考文档: [argparse](https://docs.python.org/3/library/argparse.html)
|
||||
"""
|
||||
|
||||
def _print_message(self, message, file=None):
|
||||
@ -384,20 +294,28 @@ class ArgumentParser(ArgParser):
|
||||
|
||||
|
||||
class ShellCommandRule:
|
||||
"""检查消息是否为指定 shell 命令。
|
||||
|
||||
参数:
|
||||
cmds: 指定命令元组列表
|
||||
parser: 可选参数解析器
|
||||
"""
|
||||
|
||||
__slots__ = ("cmds", "parser")
|
||||
|
||||
def __init__(self, cmds: List[Tuple[str, ...]], parser: Optional[ArgumentParser]):
|
||||
self.cmds = cmds
|
||||
self.parser = parser
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
state: T_State,
|
||||
cmd: Optional[Tuple[str, ...]] = Command(),
|
||||
msg: Message = EventMessage(),
|
||||
state: T_State = State(),
|
||||
msg: Optional[Message] = CommandArg(),
|
||||
) -> bool:
|
||||
if cmd in self.cmds:
|
||||
if cmd in self.cmds and msg is not None:
|
||||
message = str(msg)
|
||||
strip_message = message[len(state[PREFIX_KEY][RAW_CMD_KEY]) :].lstrip()
|
||||
state[SHELL_ARGV] = shlex.split(strip_message)
|
||||
state[SHELL_ARGV] = shlex.split(message)
|
||||
if self.parser:
|
||||
try:
|
||||
args = self.parser.parse_args(state[SHELL_ARGV])
|
||||
@ -412,38 +330,40 @@ class ShellCommandRule:
|
||||
def shell_command(
|
||||
*cmds: Union[str, Tuple[str, ...]], parser: Optional[ArgumentParser] = None
|
||||
) -> Rule:
|
||||
r"""
|
||||
:说明:
|
||||
"""匹配 `shell_like` 形式的消息命令。
|
||||
|
||||
支持 ``shell_like`` 解析参数的命令形式匹配,根据配置里提供的 ``command_start``, ``command_sep`` 判断消息是否为命令。
|
||||
根据配置里提供的 {ref}``command_start` <nonebot.config.Config.command_start>`,
|
||||
{ref}``command_sep` <nonebot.config.Config.command_sep>` 判断消息是否为命令。
|
||||
|
||||
可以通过 ``state["_prefix"]["command"]`` 获取匹配成功的命令(例:``("test",)``),通过 ``state["_prefix"]["raw_command"]`` 获取匹配成功的原始命令文本(例:``"/test"``)。
|
||||
可以通过 {ref}`nonebot.params.Command` 获取匹配成功的命令(例: `("test",)`),
|
||||
通过 {ref}`nonebot.params.RawCommand` 获取匹配成功的原始命令文本(例: `"/test"`),
|
||||
通过 {ref}`nonebot.params.ShellCommandArgv` 获取解析前的参数列表(例: `["arg", "-h"]`),
|
||||
通过 {ref}`nonebot.params.ShellCommandArgs` 获取解析后的参数字典(例: `{"arg": "arg", "h": True}`)。
|
||||
|
||||
可以通过 ``state["argv"]`` 获取用户输入的原始参数列表
|
||||
:::warning 警告
|
||||
如果参数解析失败,则通过 {ref}`nonebot.params.ShellCommandArgs`
|
||||
获取的将是 {ref}`nonebot.exception.ParserExit` 异常。
|
||||
:::
|
||||
|
||||
添加 ``parser`` 参数后, 可以自动处理消息并将结果保存在 ``state["args"]`` 中。
|
||||
参数:
|
||||
cmds: 命令文本或命令元组
|
||||
parser: {ref}`nonebot.rule.ArgumentParser` 对象
|
||||
|
||||
:参数:
|
||||
|
||||
* ``*cmds: Union[str, Tuple[str, ...]]``: 命令内容
|
||||
* ``parser: Optional[ArgumentParser]``: ``nonebot.rule.ArgumentParser`` 对象
|
||||
|
||||
:示例:
|
||||
|
||||
使用默认 ``command_start``, ``command_sep`` 配置,更多示例参考 ``argparse`` 标准库文档。
|
||||
|
||||
.. code-block:: python
|
||||
用法:
|
||||
使用默认 `command_start`, `command_sep` 配置,更多示例参考 `argparse` 标准库文档。
|
||||
|
||||
```python
|
||||
from nonebot.rule import ArgumentParser
|
||||
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument("-a", action="store_true")
|
||||
|
||||
rule = shell_command("ls", parser=parser)
|
||||
```
|
||||
|
||||
\:\:\:tip 提示
|
||||
命令内容与后续消息间无需空格!
|
||||
\:\:\:
|
||||
:::tip 提示
|
||||
命令内容与后续消息间无需空格!
|
||||
:::
|
||||
"""
|
||||
if parser is not None and not isinstance(parser, ArgumentParser):
|
||||
raise TypeError("`parser` must be an instance of nonebot.rule.ArgumentParser")
|
||||
@ -469,15 +389,24 @@ def shell_command(
|
||||
|
||||
|
||||
class RegexRule:
|
||||
"""检查消息字符串是否符合指定正则表达式。
|
||||
|
||||
参数:
|
||||
regex: 正则表达式
|
||||
flags: 正则表达式标记
|
||||
"""
|
||||
|
||||
__slots__ = ("regex", "flags")
|
||||
|
||||
def __init__(self, regex: str, flags: int = 0):
|
||||
self.regex = regex
|
||||
self.flags = flags
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
state: T_State,
|
||||
type: str = EventType(),
|
||||
msg: Message = EventMessage(),
|
||||
state: T_State = State(),
|
||||
) -> bool:
|
||||
if type != "message":
|
||||
return False
|
||||
@ -492,41 +421,47 @@ class RegexRule:
|
||||
|
||||
|
||||
def regex(regex: str, flags: Union[int, re.RegexFlag] = 0) -> Rule:
|
||||
r"""
|
||||
:说明:
|
||||
"""匹配符合正则表达式的消息字符串。
|
||||
|
||||
根据正则表达式进行匹配。
|
||||
可以通过 {ref}`nonebot.params.RegexMatched` 获取匹配成功的字符串,
|
||||
通过 {ref}`nonebot.params.RegexGroup` 获取匹配成功的 group 元组,
|
||||
通过 {ref}`nonebot.params.RegexDict` 获取匹配成功的 group 字典。
|
||||
|
||||
可以通过 ``state["_matched"]`` ``state["_matched_groups"]`` ``state["_matched_dict"]``
|
||||
获取正则表达式匹配成功的文本。
|
||||
参数:
|
||||
regex: 正则表达式
|
||||
flags: 正则表达式标记
|
||||
|
||||
:参数:
|
||||
:::tip 提示
|
||||
正则表达式匹配使用 search 而非 match,如需从头匹配请使用 `r"^xxx"` 来确保匹配开头
|
||||
:::
|
||||
|
||||
* ``regex: str``: 正则表达式
|
||||
* ``flags: Union[int, re.RegexFlag]``: 正则标志
|
||||
|
||||
\:\:\:tip 提示
|
||||
正则表达式匹配使用 search 而非 match,如需从头匹配请使用 ``r"^xxx"`` 来确保匹配开头
|
||||
\:\:\:
|
||||
:::tip 提示
|
||||
正则表达式匹配使用 `EventMessage` 的 `str` 字符串,而非 `EventMessage` 的 `PlainText` 纯文本字符串
|
||||
:::
|
||||
"""
|
||||
|
||||
return Rule(RegexRule(regex, flags))
|
||||
|
||||
|
||||
class ToMeRule:
|
||||
"""检查事件是否与机器人有关。"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
async def __call__(self, to_me: bool = EventToMe()) -> bool:
|
||||
return to_me
|
||||
|
||||
|
||||
def to_me() -> Rule:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
通过 ``event.is_tome()`` 判断事件是否与机器人有关
|
||||
|
||||
:参数:
|
||||
|
||||
* 无
|
||||
"""
|
||||
"""匹配与机器人有关的事件。"""
|
||||
|
||||
return Rule(ToMeRule())
|
||||
|
||||
|
||||
__autodoc__ = {
|
||||
"Rule": True,
|
||||
"Rule.__call__": True,
|
||||
"TrieRule": False,
|
||||
"ArgumentParser.exit": False,
|
||||
"ArgumentParser.parse_args": False,
|
||||
}
|
||||
|
@ -1,21 +1,15 @@
|
||||
"""
|
||||
类型
|
||||
====
|
||||
"""本模块定义了 NoneBot 模块中共享的一些类型。
|
||||
|
||||
下面的文档中,「类型」部分使用 Python 的 Type Hint 语法,见 `PEP 484`_、`PEP 526`_ 和 `typing`_。
|
||||
下面的文档中,「类型」部分使用 Python 的 Type Hint 语法,
|
||||
参考 [`PEP 484`](https://www.python.org/dev/peps/pep-0484/),
|
||||
[`PEP 526`](https://www.python.org/dev/peps/pep-0526/) 和
|
||||
[`typing`](https://docs.python.org/3/library/typing.html)。
|
||||
|
||||
除了 Python 内置的类型,下面还出现了如下 NoneBot 自定类型,实际上它们是 Python 内置类型的别名。
|
||||
|
||||
以下类型均可从 nonebot.typing 模块导入。
|
||||
|
||||
.. _PEP 484:
|
||||
https://www.python.org/dev/peps/pep-0484/
|
||||
|
||||
.. _PEP 526:
|
||||
https://www.python.org/dev/peps/pep-0526/
|
||||
|
||||
.. _typing:
|
||||
https://docs.python.org/3/library/typing.html
|
||||
FrontMatter:
|
||||
sidebar_position: 11
|
||||
description: nonebot.typing 模块
|
||||
"""
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
@ -31,13 +25,15 @@ from typing import (
|
||||
if TYPE_CHECKING:
|
||||
from asyncio import Task
|
||||
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.permission import Permission
|
||||
|
||||
T_Wrapped = TypeVar("T_Wrapped", bound=Callable)
|
||||
|
||||
|
||||
def overrides(InterfaceClass: object):
|
||||
def overrides(InterfaceClass: object) -> Callable[[T_Wrapped], T_Wrapped]:
|
||||
"""标记一个方法为父类 interface 的 implement"""
|
||||
|
||||
def overrider(func: T_Wrapped) -> T_Wrapped:
|
||||
assert func.__name__ in dir(InterfaceClass), f"Error method: {func.__name__}"
|
||||
return func
|
||||
@ -46,194 +42,114 @@ def overrides(InterfaceClass: object):
|
||||
|
||||
|
||||
T_State = Dict[Any, Any]
|
||||
"""
|
||||
:类型: ``Dict[Any, Any]``
|
||||
"""事件处理状态 State 类型"""
|
||||
|
||||
:说明:
|
||||
|
||||
事件处理状态 State 类型
|
||||
"""
|
||||
|
||||
T_BotConnectionHook = Callable[["Bot"], Awaitable[None]]
|
||||
"""
|
||||
:类型: ``Callable[[Bot], Awaitable[None]]``
|
||||
|
||||
:说明:
|
||||
|
||||
Bot 连接建立时执行的函数
|
||||
"""
|
||||
T_BotDisconnectionHook = Callable[["Bot"], Awaitable[None]]
|
||||
"""
|
||||
:类型: ``Callable[[Bot], Awaitable[None]]``
|
||||
|
||||
:说明:
|
||||
|
||||
Bot 连接断开时执行的函数
|
||||
"""
|
||||
T_CallingAPIHook = Callable[["Bot", str, Dict[str, Any]], Awaitable[None]]
|
||||
"""
|
||||
:类型: ``Callable[[Bot, str, Dict[str, Any]], Awaitable[None]]``
|
||||
|
||||
:说明:
|
||||
|
||||
``bot.call_api`` 时执行的函数
|
||||
"""
|
||||
T_BotConnectionHook = Callable[..., Awaitable[Any]]
|
||||
"""Bot 连接建立时钩子函数"""
|
||||
T_BotDisconnectionHook = Callable[..., Awaitable[Any]]
|
||||
"""Bot 连接断开时钩子函数"""
|
||||
T_CallingAPIHook = Callable[["Bot", str, Dict[str, Any]], Awaitable[Any]]
|
||||
"""`bot.call_api` 钩子函数"""
|
||||
T_CalledAPIHook = Callable[
|
||||
["Bot", Optional[Exception], str, Dict[str, Any], Any], Awaitable[None]
|
||||
["Bot", Optional[Exception], str, Dict[str, Any], Any], Awaitable[Any]
|
||||
]
|
||||
"""`bot.call_api` 后执行的函数,参数分别为 bot, exception, api, data, result"""
|
||||
|
||||
T_EventPreProcessor = Callable[..., Union[Any, Awaitable[Any]]]
|
||||
"""事件预处理函数 EventPreProcessor 类型
|
||||
|
||||
依赖参数:
|
||||
|
||||
- DependParam: 子依赖参数
|
||||
- BotParam: Bot 对象
|
||||
- EventParam: Event 对象
|
||||
- StateParam: State 对象
|
||||
- DefaultParam: 带有默认值的参数
|
||||
"""
|
||||
:类型: ``Callable[[Bot, Optional[Exception], str, Dict[str, Any], Any], Awaitable[None]]``
|
||||
T_EventPostProcessor = Callable[..., Union[Any, Awaitable[Any]]]
|
||||
"""事件预处理函数 EventPostProcessor 类型
|
||||
|
||||
:说明:
|
||||
依赖参数:
|
||||
|
||||
``bot.call_api`` 后执行的函数,参数分别为 bot, exception, api, data, result
|
||||
- DependParam: 子依赖参数
|
||||
- BotParam: Bot 对象
|
||||
- EventParam: Event 对象
|
||||
- StateParam: State 对象
|
||||
- DefaultParam: 带有默认值的参数
|
||||
"""
|
||||
T_RunPreProcessor = Callable[..., Union[Any, Awaitable[Any]]]
|
||||
"""事件响应器运行前预处理函数 RunPreProcessor 类型
|
||||
|
||||
T_EventPreProcessor = Callable[..., Union[None, Awaitable[None]]]
|
||||
依赖参数:
|
||||
|
||||
- DependParam: 子依赖参数
|
||||
- BotParam: Bot 对象
|
||||
- EventParam: Event 对象
|
||||
- StateParam: State 对象
|
||||
- MatcherParam: Matcher 对象
|
||||
- DefaultParam: 带有默认值的参数
|
||||
"""
|
||||
:类型: ``Callable[..., Union[None, Awaitable[None]]]``
|
||||
T_RunPostProcessor = Callable[..., Union[Any, Awaitable[Any]]]
|
||||
"""事件响应器运行前预处理函数 RunPostProcessor 类型
|
||||
|
||||
:依赖参数:
|
||||
依赖参数:
|
||||
|
||||
* ``DependParam``: 子依赖参数
|
||||
* ``BotParam``: Bot 对象
|
||||
* ``EventParam``: Event 对象
|
||||
* ``StateParam``: State 对象
|
||||
* ``DefaultParam``: 带有默认值的参数
|
||||
|
||||
:说明:
|
||||
|
||||
事件预处理函数 EventPreProcessor 类型
|
||||
"""
|
||||
T_EventPostProcessor = Callable[..., Union[None, Awaitable[None]]]
|
||||
"""
|
||||
:类型: ``Callable[..., Union[None, Awaitable[None]]]``
|
||||
|
||||
:依赖参数:
|
||||
|
||||
* ``DependParam``: 子依赖参数
|
||||
* ``BotParam``: Bot 对象
|
||||
* ``EventParam``: Event 对象
|
||||
* ``StateParam``: State 对象
|
||||
* ``DefaultParam``: 带有默认值的参数
|
||||
|
||||
:说明:
|
||||
|
||||
事件预处理函数 EventPostProcessor 类型
|
||||
"""
|
||||
T_RunPreProcessor = Callable[..., Union[None, Awaitable[None]]]
|
||||
"""
|
||||
:类型: ``Callable[..., Union[None, Awaitable[None]]]``
|
||||
|
||||
:依赖参数:
|
||||
|
||||
* ``DependParam``: 子依赖参数
|
||||
* ``BotParam``: Bot 对象
|
||||
* ``EventParam``: Event 对象
|
||||
* ``StateParam``: State 对象
|
||||
* ``MatcherParam``: Matcher 对象
|
||||
* ``DefaultParam``: 带有默认值的参数
|
||||
|
||||
:说明:
|
||||
|
||||
事件响应器运行前预处理函数 RunPreProcessor 类型
|
||||
"""
|
||||
T_RunPostProcessor = Callable[..., Union[None, Awaitable[None]]]
|
||||
"""
|
||||
:类型: ``Callable[..., Union[None, Awaitable[None]]]``
|
||||
|
||||
:依赖参数:
|
||||
|
||||
* ``DependParam``: 子依赖参数
|
||||
* ``BotParam``: Bot 对象
|
||||
* ``EventParam``: Event 对象
|
||||
* ``StateParam``: State 对象
|
||||
* ``MatcherParam``: Matcher 对象
|
||||
* ``ExceptionParam``: 异常对象(可能为 None)
|
||||
* ``DefaultParam``: 带有默认值的参数
|
||||
|
||||
:说明:
|
||||
|
||||
事件响应器运行前预处理函数 RunPostProcessor 类型,第二个参数为运行时产生的错误(如果存在)
|
||||
- DependParam: 子依赖参数
|
||||
- BotParam: Bot 对象
|
||||
- EventParam: Event 对象
|
||||
- StateParam: State 对象
|
||||
- MatcherParam: Matcher 对象
|
||||
- ExceptionParam: 异常对象(可能为 None)
|
||||
- DefaultParam: 带有默认值的参数
|
||||
"""
|
||||
|
||||
T_RuleChecker = Callable[..., Union[bool, Awaitable[bool]]]
|
||||
"""
|
||||
:类型: ``Callable[..., Union[bool, Awaitable[bool]]]``
|
||||
"""RuleChecker 即判断是否响应事件的处理函数。
|
||||
|
||||
:依赖参数:
|
||||
依赖参数:
|
||||
|
||||
* ``DependParam``: 子依赖参数
|
||||
* ``BotParam``: Bot 对象
|
||||
* ``EventParam``: Event 对象
|
||||
* ``StateParam``: State 对象
|
||||
* ``DefaultParam``: 带有默认值的参数
|
||||
|
||||
:说明:
|
||||
|
||||
RuleChecker 即判断是否响应事件的处理函数。
|
||||
- DependParam: 子依赖参数
|
||||
- BotParam: Bot 对象
|
||||
- EventParam: Event 对象
|
||||
- StateParam: State 对象
|
||||
- DefaultParam: 带有默认值的参数
|
||||
"""
|
||||
T_PermissionChecker = Callable[..., Union[bool, Awaitable[bool]]]
|
||||
"""
|
||||
:类型: ``Callable[..., Union[bool, Awaitable[bool]]]``
|
||||
"""PermissionChecker 即判断事件是否满足权限的处理函数。
|
||||
|
||||
:依赖参数:
|
||||
依赖参数:
|
||||
|
||||
* ``DependParam``: 子依赖参数
|
||||
* ``BotParam``: Bot 对象
|
||||
* ``EventParam``: Event 对象
|
||||
* ``DefaultParam``: 带有默认值的参数
|
||||
|
||||
:说明:
|
||||
|
||||
RuleChecker 即判断是否响应消息的处理函数。
|
||||
- DependParam: 子依赖参数
|
||||
- BotParam: Bot 对象
|
||||
- EventParam: Event 对象
|
||||
- DefaultParam: 带有默认值的参数
|
||||
"""
|
||||
|
||||
T_Handler = Callable[..., Any]
|
||||
"""
|
||||
:类型: ``Callable[..., Any]``
|
||||
|
||||
:说明:
|
||||
|
||||
Handler 处理函数。
|
||||
"""
|
||||
"""Handler 处理函数。"""
|
||||
T_TypeUpdater = Callable[..., Union[str, Awaitable[str]]]
|
||||
"""
|
||||
:类型: ``Callable[..., Union[None, Awaitable[None]]]``
|
||||
"""TypeUpdater 在 Matcher.pause, Matcher.reject 时被运行,用于更新响应的事件类型。默认会更新为 `message`。
|
||||
|
||||
:依赖参数:
|
||||
依赖参数:
|
||||
|
||||
* ``DependParam``: 子依赖参数
|
||||
* ``BotParam``: Bot 对象
|
||||
* ``EventParam``: Event 对象
|
||||
* ``StateParam``: State 对象
|
||||
* ``MatcherParam``: Matcher 对象
|
||||
* ``DefaultParam``: 带有默认值的参数
|
||||
|
||||
:说明:
|
||||
|
||||
TypeUpdater 在 Matcher.pause, Matcher.reject 时被运行,用于更新响应的事件类型。默认会更新为 ``message``。
|
||||
- DependParam: 子依赖参数
|
||||
- BotParam: Bot 对象
|
||||
- EventParam: Event 对象
|
||||
- StateParam: State 对象
|
||||
- MatcherParam: Matcher 对象
|
||||
- DefaultParam: 带有默认值的参数
|
||||
"""
|
||||
T_PermissionUpdater = Callable[..., Union["Permission", Awaitable["Permission"]]]
|
||||
"""
|
||||
:类型: ``Callable[..., Union[Permission, Awaitable[Permission]]]``
|
||||
"""PermissionUpdater 在 Matcher.pause, Matcher.reject 时被运行,用于更新会话对象权限。默认会更新为当前事件的触发对象。
|
||||
|
||||
:依赖参数:
|
||||
依赖参数:
|
||||
|
||||
* ``DependParam``: 子依赖参数
|
||||
* ``BotParam``: Bot 对象
|
||||
* ``EventParam``: Event 对象
|
||||
* ``StateParam``: State 对象
|
||||
* ``MatcherParam``: Matcher 对象
|
||||
* ``DefaultParam``: 带有默认值的参数
|
||||
|
||||
:说明:
|
||||
|
||||
PermissionUpdater 在 Matcher.pause, Matcher.reject 时被运行,用于更新会话对象权限。默认会更新为当前事件的触发对象。
|
||||
- DependParam: 子依赖参数
|
||||
- BotParam: Bot 对象
|
||||
- EventParam: Event 对象
|
||||
- StateParam: State 对象
|
||||
- MatcherParam: Matcher 对象
|
||||
- DefaultParam: 带有默认值的参数
|
||||
"""
|
||||
T_DependencyCache = Dict[Callable[..., Any], "Task[Any]"]
|
||||
"""
|
||||
:类型: ``Dict[Callable[..., Any], Task[Any]]``
|
||||
:说明:
|
||||
依赖缓存, 用于存储依赖函数的返回值
|
||||
"""
|
||||
"""依赖缓存, 用于存储依赖函数的返回值"""
|
||||
|
@ -1,3 +1,10 @@
|
||||
"""本模块包含了 NoneBot 的一些工具函数
|
||||
|
||||
FrontMatter:
|
||||
sidebar_position: 8
|
||||
description: nonebot.utils 模块
|
||||
"""
|
||||
|
||||
import re
|
||||
import json
|
||||
import asyncio
|
||||
@ -15,10 +22,13 @@ from typing import (
|
||||
Callable,
|
||||
Optional,
|
||||
Awaitable,
|
||||
Coroutine,
|
||||
AsyncGenerator,
|
||||
ContextManager,
|
||||
)
|
||||
|
||||
from pydantic.typing import is_union, is_none_type
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.typing import overrides
|
||||
|
||||
@ -30,18 +40,12 @@ V = TypeVar("V")
|
||||
|
||||
|
||||
def escape_tag(s: str) -> str:
|
||||
"""
|
||||
:说明:
|
||||
"""用于记录带颜色日志时转义 `<tag>` 类型特殊标签
|
||||
|
||||
用于记录带颜色日志时转义 ``<tag>`` 类型特殊标签
|
||||
参考: [loguru color 标签](https://loguru.readthedocs.io/en/stable/api/logger.html#color)
|
||||
|
||||
:参数:
|
||||
|
||||
* ``s: str``: 需要转义的字符串
|
||||
|
||||
:返回:
|
||||
|
||||
- ``str``
|
||||
参数:
|
||||
s: 需要转义的字符串
|
||||
"""
|
||||
return re.sub(r"</?((?:[fb]g\s)?[^<>\s]*)>", r"\\\g<0>", s)
|
||||
|
||||
@ -49,13 +53,18 @@ def escape_tag(s: str) -> str:
|
||||
def generic_check_issubclass(
|
||||
cls: Any, class_or_tuple: Union[Type[Any], Tuple[Type[Any], ...]]
|
||||
) -> bool:
|
||||
"""检查 cls 是否是 class_or_tuple 中的一个类型子类。
|
||||
|
||||
特别的,如果 cls 是 `typing.Union` 或 `types.UnionType` 类型,
|
||||
则会检查其中的类型是否是 class_or_tuple 中的一个类型子类。(None 会被忽略)
|
||||
"""
|
||||
try:
|
||||
return issubclass(cls, class_or_tuple)
|
||||
except TypeError:
|
||||
origin = get_origin(cls)
|
||||
if origin is Union:
|
||||
if is_union(origin):
|
||||
for type_ in get_args(cls):
|
||||
if type_ is not type(None) and not generic_check_issubclass(
|
||||
if not is_none_type(type_) and not generic_check_issubclass(
|
||||
type_, class_or_tuple
|
||||
):
|
||||
return False
|
||||
@ -66,6 +75,7 @@ def generic_check_issubclass(
|
||||
|
||||
|
||||
def is_coroutine_callable(call: Callable[..., Any]) -> bool:
|
||||
"""检查 call 是否是一个 callable 协程函数"""
|
||||
if inspect.isroutine(call):
|
||||
return inspect.iscoroutinefunction(call)
|
||||
if inspect.isclass(call):
|
||||
@ -75,6 +85,7 @@ def is_coroutine_callable(call: Callable[..., Any]) -> bool:
|
||||
|
||||
|
||||
def is_gen_callable(call: Callable[..., Any]) -> bool:
|
||||
"""检查 call 是否是一个生成器函数"""
|
||||
if inspect.isgeneratorfunction(call):
|
||||
return True
|
||||
func_ = getattr(call, "__call__", None)
|
||||
@ -82,25 +93,18 @@ def is_gen_callable(call: Callable[..., Any]) -> bool:
|
||||
|
||||
|
||||
def is_async_gen_callable(call: Callable[..., Any]) -> bool:
|
||||
"""检查 call 是否是一个异步生成器函数"""
|
||||
if inspect.isasyncgenfunction(call):
|
||||
return True
|
||||
func_ = getattr(call, "__call__", None)
|
||||
return inspect.isasyncgenfunction(func_)
|
||||
|
||||
|
||||
def run_sync(call: Callable[P, R]) -> Callable[P, Awaitable[R]]:
|
||||
"""
|
||||
:说明:
|
||||
def run_sync(call: Callable[P, R]) -> Callable[P, Coroutine[None, None, R]]:
|
||||
"""一个用于包装 sync function 为 async function 的装饰器
|
||||
|
||||
一个用于包装 sync function 为 async function 的装饰器
|
||||
|
||||
:参数:
|
||||
|
||||
* ``call: Callable[P, R]``: 被装饰的同步函数
|
||||
|
||||
:返回:
|
||||
|
||||
- ``Callable[P, Awaitable[R]]``
|
||||
参数:
|
||||
call: 被装饰的同步函数
|
||||
"""
|
||||
|
||||
@wraps(call)
|
||||
@ -117,6 +121,7 @@ def run_sync(call: Callable[P, R]) -> Callable[P, Awaitable[R]]:
|
||||
async def run_sync_ctx_manager(
|
||||
cm: ContextManager[T],
|
||||
) -> AsyncGenerator[T, None]:
|
||||
"""一个用于包装 sync context manager 为 async context manager 的执行函数"""
|
||||
try:
|
||||
yield await run_sync(cm.__enter__)()
|
||||
except Exception as e:
|
||||
@ -128,17 +133,14 @@ async def run_sync_ctx_manager(
|
||||
|
||||
|
||||
def get_name(obj: Any) -> str:
|
||||
"""获取对象的名称"""
|
||||
if inspect.isfunction(obj) or inspect.isclass(obj):
|
||||
return obj.__name__
|
||||
return obj.__class__.__name__
|
||||
|
||||
|
||||
class DataclassEncoder(json.JSONEncoder):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
在JSON序列化 ``Message`` (List[Dataclass]) 时使用的 ``JSONEncoder``
|
||||
"""
|
||||
"""在JSON序列化 {re}`nonebot.adapters._message.Message` (List[Dataclass]) 时使用的 `JSONEncoder`"""
|
||||
|
||||
@overrides(json.JSONEncoder)
|
||||
def default(self, o):
|
||||
@ -148,16 +150,17 @@ class DataclassEncoder(json.JSONEncoder):
|
||||
|
||||
|
||||
def logger_wrapper(logger_name: str):
|
||||
"""
|
||||
:说明:
|
||||
"""用于打印 adapter 的日志。
|
||||
|
||||
用于打印 adapter 的日志。
|
||||
参数:
|
||||
logger_name: adapter 的名称
|
||||
|
||||
:log 参数:
|
||||
返回:
|
||||
日志记录函数
|
||||
|
||||
* ``level: Literal["CRITICAL", "WARNING", "INFO", "DEBUG", "TRACE"]``: 日志等级
|
||||
* ``message: str``: 日志信息
|
||||
* ``exception: Optional[Exception]``: 异常信息
|
||||
- level: 日志等级
|
||||
- message: 日志信息
|
||||
- exception: 异常信息
|
||||
"""
|
||||
|
||||
def log(level: str, message: str, exception: Optional[Exception] = None):
|
||||
|
@ -1,8 +1,9 @@
|
||||
from pathlib import Path
|
||||
|
||||
from nonebot.drivers.fastapi import Driver
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from nonebot.drivers.fastapi import Driver
|
||||
|
||||
|
||||
def register_route(driver: Driver):
|
||||
app = driver.server_app
|
||||
|
@ -17,21 +17,6 @@ nonebot2 = "^2.0.0-beta.1"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
|
||||
[tool.black]
|
||||
line-length = 88
|
||||
target-version = ["py37", "py38", "py39"]
|
||||
include = '\.pyi?$'
|
||||
extend-exclude = '''
|
||||
'''
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
line_length = 80
|
||||
length_sort = true
|
||||
skip_gitignore = true
|
||||
force_sort_within_sections = true
|
||||
extra_standard_library = ["typing_extensions"]
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
1027
poetry.lock
generated
1027
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -24,25 +24,27 @@ include = ["nonebot/py.typed"]
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.7.3"
|
||||
yarl = "^1.7.2"
|
||||
loguru = "^0.5.1"
|
||||
loguru = "^0.6.0"
|
||||
pygtrie = "^2.4.1"
|
||||
tomlkit = "^0.7.0"
|
||||
fastapi = "^0.70.0"
|
||||
tomlkit = "^0.9.0"
|
||||
fastapi = "^0.73.0"
|
||||
typing-extensions = ">=3.10.0,<5.0.0"
|
||||
Quart = { version = "^0.16.0", optional = true }
|
||||
websockets = { version=">=9.1", optional = true }
|
||||
websockets = { version="^10.0", optional = true }
|
||||
pydantic = { version = "~1.9.0", extras = ["dotenv"] }
|
||||
uvicorn = { version = "^0.15.0", extras = ["standard"] }
|
||||
uvicorn = { version = "^0.17.0", extras = ["standard"] }
|
||||
aiohttp = { version = "^3.7.4", extras = ["speedups"], optional = true }
|
||||
httpx = { version = ">=0.20.0, <1.0.0", extras = ["http2"], optional = true }
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
sphinx = "^4.1.1"
|
||||
isort = "^5.10.1"
|
||||
black = "^21.11b1"
|
||||
black = "^22.1.0"
|
||||
pytest-cov = "^3.0.0"
|
||||
pre-commit = "^2.16.0"
|
||||
pytest-xdist = "^2.5.0"
|
||||
pytest-asyncio = "^0.18.1"
|
||||
nonebug = { git = "https://github.com/nonebot/nonebug.git" }
|
||||
sphinx-markdown-builder = { git = "https://github.com/nonebot/sphinx-markdown-builder.git" }
|
||||
nb-autodoc = { git = "https://github.com/nonebot/nb-autodoc.git" }
|
||||
|
||||
[tool.poetry.extras]
|
||||
quart = ["quart"]
|
||||
@ -57,6 +59,7 @@ all = ["quart", "aiohttp", "httpx", "websockets"]
|
||||
# default = true
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
asyncio_mode = "auto"
|
||||
addopts = "--cov=nonebot --cov-report=term-missing"
|
||||
|
||||
[tool.black]
|
||||
@ -68,11 +71,11 @@ extend-exclude = '''
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
line_length = 80
|
||||
line_length = 88
|
||||
length_sort = true
|
||||
skip_gitignore = true
|
||||
force_sort_within_sections = true
|
||||
known_local_folder = ["nonebot"]
|
||||
src_paths = ["nonebot", "tests"]
|
||||
extra_standard_library = ["typing_extensions"]
|
||||
|
||||
[build-system]
|
||||
|
@ -1,8 +0,0 @@
|
||||
[settings]
|
||||
profile=black
|
||||
line_length=80
|
||||
length_sort=true
|
||||
skip_gitignore=true
|
||||
force_sort_within_sections=true
|
||||
known_local_folder=plugins
|
||||
extra_standard_library=typing_extensions
|
@ -3,7 +3,6 @@ from typing import List, Tuple
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.adapters import Message
|
||||
from nonebot.params import (
|
||||
State,
|
||||
Command,
|
||||
RegexDict,
|
||||
CommandArg,
|
||||
@ -15,7 +14,7 @@ from nonebot.params import (
|
||||
)
|
||||
|
||||
|
||||
async def state(x: T_State = State()) -> T_State:
|
||||
async def state(x: T_State) -> T_State:
|
||||
return x
|
||||
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
from nonebot import require
|
||||
|
||||
from plugins.export import test
|
||||
|
||||
from .export import test as test_related
|
||||
|
||||
test_require = require("export").test
|
||||
|
153
tests/test_adapters/test_message.py
Normal file
153
tests/test_adapters/test_message.py
Normal file
@ -0,0 +1,153 @@
|
||||
from pydantic import ValidationError, parse_obj_as
|
||||
|
||||
from utils import make_fake_message
|
||||
|
||||
|
||||
def test_segment_add():
|
||||
Message = make_fake_message()
|
||||
MessageSegment = Message.get_segment_class()
|
||||
|
||||
assert MessageSegment.text("text") + MessageSegment.text("text") == Message(
|
||||
[MessageSegment.text("text"), MessageSegment.text("text")]
|
||||
)
|
||||
|
||||
assert MessageSegment.text("text") + "text" == Message(
|
||||
[MessageSegment.text("text"), MessageSegment.text("text")]
|
||||
)
|
||||
|
||||
assert (
|
||||
MessageSegment.text("text") + Message([MessageSegment.text("text")])
|
||||
) == Message([MessageSegment.text("text"), MessageSegment.text("text")])
|
||||
|
||||
assert "text" + MessageSegment.text("text") == Message(
|
||||
[MessageSegment.text("text"), MessageSegment.text("text")]
|
||||
)
|
||||
|
||||
|
||||
def test_segment_validate():
|
||||
Message = make_fake_message()
|
||||
MessageSegment = Message.get_segment_class()
|
||||
|
||||
assert parse_obj_as(
|
||||
MessageSegment, {"type": "text", "data": {"text": "text"}}
|
||||
) == MessageSegment.text("text")
|
||||
|
||||
try:
|
||||
parse_obj_as(MessageSegment, "some str")
|
||||
assert False
|
||||
except ValidationError:
|
||||
assert True
|
||||
|
||||
|
||||
def test_segment():
|
||||
Message = make_fake_message()
|
||||
MessageSegment = Message.get_segment_class()
|
||||
|
||||
assert len(MessageSegment.text("text")) == 4
|
||||
assert MessageSegment.text("text") != MessageSegment.text("other")
|
||||
assert MessageSegment.text("text").get("data") == {"text": "text"}
|
||||
assert list(MessageSegment.text("text").keys()) == ["type", "data"]
|
||||
assert list(MessageSegment.text("text").values()) == ["text", {"text": "text"}]
|
||||
assert list(MessageSegment.text("text").items()) == [
|
||||
("type", "text"),
|
||||
("data", {"text": "text"}),
|
||||
]
|
||||
|
||||
origin = MessageSegment.text("text")
|
||||
copy = origin.copy()
|
||||
assert origin is not copy
|
||||
assert origin == copy
|
||||
|
||||
|
||||
def test_message_add():
|
||||
Message = make_fake_message()
|
||||
MessageSegment = Message.get_segment_class()
|
||||
|
||||
assert (
|
||||
Message([MessageSegment.text("text")]) + MessageSegment.text("text")
|
||||
) == Message([MessageSegment.text("text"), MessageSegment.text("text")])
|
||||
|
||||
assert Message([MessageSegment.text("text")]) + "text" == Message(
|
||||
[MessageSegment.text("text"), MessageSegment.text("text")]
|
||||
)
|
||||
|
||||
assert (
|
||||
Message([MessageSegment.text("text")]) + Message([MessageSegment.text("text")])
|
||||
) == Message([MessageSegment.text("text"), MessageSegment.text("text")])
|
||||
|
||||
assert "text" + Message([MessageSegment.text("text")]) == Message(
|
||||
[MessageSegment.text("text"), MessageSegment.text("text")]
|
||||
)
|
||||
|
||||
msg = Message([MessageSegment.text("text")])
|
||||
msg += MessageSegment.text("text")
|
||||
assert msg == Message([MessageSegment.text("text"), MessageSegment.text("text")])
|
||||
|
||||
|
||||
def test_message_getitem():
|
||||
|
||||
Message = make_fake_message()
|
||||
MessageSegment = Message.get_segment_class()
|
||||
|
||||
message = Message(
|
||||
[
|
||||
MessageSegment.text("test"),
|
||||
MessageSegment.image("test2"),
|
||||
MessageSegment.image("test3"),
|
||||
MessageSegment.text("test4"),
|
||||
]
|
||||
)
|
||||
|
||||
assert message[0] == MessageSegment.text("test")
|
||||
|
||||
assert message[0:2] == Message(
|
||||
[MessageSegment.text("test"), MessageSegment.image("test2")]
|
||||
)
|
||||
|
||||
assert message["image"] == Message(
|
||||
[MessageSegment.image("test2"), MessageSegment.image("test3")]
|
||||
)
|
||||
|
||||
assert message["image", 0] == MessageSegment.image("test2")
|
||||
assert message["image", 0:2] == message["image"]
|
||||
|
||||
assert message.index(message[0]) == 0
|
||||
assert message.index("image") == 1
|
||||
|
||||
assert message.get("image") == message["image"]
|
||||
assert message.get("image", 114514) == message["image"]
|
||||
assert message.get("image", 1) == Message([message["image", 0]])
|
||||
|
||||
assert message.count("image") == 2
|
||||
|
||||
|
||||
def test_message_validate():
|
||||
Message = make_fake_message()
|
||||
MessageSegment = Message.get_segment_class()
|
||||
|
||||
Message_ = make_fake_message()
|
||||
|
||||
assert parse_obj_as(Message, Message([])) == Message([])
|
||||
|
||||
try:
|
||||
parse_obj_as(Message, Message_([]))
|
||||
assert False
|
||||
except ValidationError:
|
||||
assert True
|
||||
|
||||
assert parse_obj_as(Message, "text") == Message([MessageSegment.text("text")])
|
||||
|
||||
assert parse_obj_as(Message, {"type": "text", "data": {"text": "text"}}) == Message(
|
||||
[MessageSegment.text("text")]
|
||||
)
|
||||
|
||||
assert parse_obj_as(
|
||||
Message,
|
||||
[MessageSegment.text("text"), {"type": "text", "data": {"text": "text"}}],
|
||||
) == Message([MessageSegment.text("text"), MessageSegment.text("text")])
|
||||
|
||||
try:
|
||||
parse_obj_as(Message, object())
|
||||
assert False
|
||||
except ValidationError:
|
||||
assert True
|
@ -1,17 +1,41 @@
|
||||
from utils import make_fake_message
|
||||
from utils import escape_text, make_fake_message
|
||||
|
||||
|
||||
def test_message_template():
|
||||
def test_template_basis():
|
||||
from nonebot.adapters import MessageTemplate
|
||||
|
||||
Message = make_fake_message()
|
||||
template = MessageTemplate("{key:.3%}")
|
||||
formatted = template.format(key=0.123456789)
|
||||
assert formatted == "12.346%"
|
||||
|
||||
template = MessageTemplate("{a:custom}{b:text}{c:image}", Message)
|
||||
|
||||
def test_template_message():
|
||||
Message = make_fake_message()
|
||||
template = Message.template("{a:custom}{b:text}{c:image}")
|
||||
|
||||
@template.add_format_spec
|
||||
def custom(input: str) -> str:
|
||||
return input + "-custom!"
|
||||
|
||||
formatted = template.format(a="test", b="test", c="https://example.com/test")
|
||||
assert formatted.extract_plain_text() == "test-custom!test"
|
||||
assert str(formatted) == "test-custom!test[fake:image]"
|
||||
try:
|
||||
template.add_format_spec(custom)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
raise AssertionError("Should raise ValueError")
|
||||
|
||||
format_args = {"a": "custom", "b": "text", "c": "https://example.com/test"}
|
||||
formatted = template.format(**format_args)
|
||||
|
||||
assert template.format_map(format_args) == formatted
|
||||
assert formatted.extract_plain_text() == "custom-custom!text"
|
||||
assert str(formatted) == "custom-custom!text[fake:image]"
|
||||
|
||||
|
||||
def test_message_injection():
|
||||
Message = make_fake_message()
|
||||
|
||||
template = Message.template("{name}Is Bad")
|
||||
message = template.format(name="[fake:image]")
|
||||
|
||||
assert message.extract_plain_text() == escape_text("[fake:image]Is Bad")
|
||||
|
@ -29,7 +29,11 @@ async def test_weather(app: App):
|
||||
event = make_fake_event(_message=msg, _to_me=True)()
|
||||
|
||||
ctx.receive_event(bot, event)
|
||||
ctx.should_call_send(event, Message("你想查询的城市 南京 暂不支持,请重新输入!"), True)
|
||||
ctx.should_call_send(
|
||||
event,
|
||||
Message.template("你想查询的城市 {} 暂不支持,请重新输入!").format("南京"),
|
||||
True,
|
||||
)
|
||||
ctx.should_rejected()
|
||||
|
||||
msg = Message("北京")
|
||||
@ -53,7 +57,11 @@ async def test_weather(app: App):
|
||||
event = make_fake_event(_message=msg)()
|
||||
|
||||
ctx.receive_event(bot, event)
|
||||
ctx.should_call_send(event, Message("你想查询的城市 杭州 暂不支持,请重新输入!"), True)
|
||||
ctx.should_call_send(
|
||||
event,
|
||||
Message.template("你想查询的城市 {} 暂不支持,请重新输入!").format("杭州"),
|
||||
True,
|
||||
)
|
||||
ctx.should_rejected()
|
||||
|
||||
msg = Message("北京")
|
||||
|
@ -1,12 +1,7 @@
|
||||
import os
|
||||
import sys
|
||||
from typing import TYPE_CHECKING, Set
|
||||
|
||||
import pytest
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from nonebot.plugin import Plugin
|
||||
|
||||
os.environ["CONFIG_FROM_ENV"] = '{"test": "test"}'
|
||||
|
||||
|
||||
@ -74,23 +69,3 @@ async def test_get(monkeypatch: pytest.MonkeyPatch, nonebug_clear):
|
||||
assert get_bot() == "test"
|
||||
assert get_bot("test") == "test"
|
||||
assert get_bots() == {"test": "test"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_plugin(load_plugin: Set["Plugin"]):
|
||||
import nonebot
|
||||
|
||||
loaded_plugins = set(
|
||||
plugin for plugin in nonebot.get_loaded_plugins() if not plugin.parent_plugin
|
||||
)
|
||||
assert loaded_plugins == load_plugin
|
||||
plugin = nonebot.get_plugin("export")
|
||||
assert plugin
|
||||
assert plugin.module_name == "plugins.export"
|
||||
assert "plugins.export" in sys.modules
|
||||
|
||||
try:
|
||||
nonebot.load_plugin("plugins.export")
|
||||
assert False
|
||||
except RuntimeError:
|
||||
assert True
|
||||
|
@ -78,10 +78,7 @@ async def test_matcher(app: App, load_plugin):
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_type_updater(app: App, load_plugin):
|
||||
from plugins.matcher.matcher_type import (
|
||||
test_type_updater,
|
||||
test_custom_updater,
|
||||
)
|
||||
from plugins.matcher.matcher_type import test_type_updater, test_custom_updater
|
||||
|
||||
event = make_fake_event()()
|
||||
|
||||
@ -103,7 +100,6 @@ async def test_type_updater(app: App, load_plugin):
|
||||
@pytest.mark.asyncio
|
||||
async def test_permission_updater(app: App, load_plugin):
|
||||
from nonebot.permission import User
|
||||
|
||||
from plugins.matcher.matcher_permission import (
|
||||
default_permission,
|
||||
test_custom_updater,
|
||||
|
@ -7,7 +7,6 @@ from utils import make_fake_event, make_fake_message
|
||||
@pytest.mark.asyncio
|
||||
async def test_depend(app: App, load_plugin):
|
||||
from nonebot.params import DependParam
|
||||
|
||||
from plugins.param.param_depend import (
|
||||
ClassDependency,
|
||||
runned,
|
||||
@ -37,7 +36,6 @@ async def test_depend(app: App, load_plugin):
|
||||
@pytest.mark.asyncio
|
||||
async def test_bot(app: App, load_plugin):
|
||||
from nonebot.params import BotParam
|
||||
|
||||
from plugins.param.param_bot import get_bot
|
||||
|
||||
async with app.test_dependent(get_bot, allow_types=[BotParam]) as ctx:
|
||||
@ -49,7 +47,6 @@ async def test_bot(app: App, load_plugin):
|
||||
@pytest.mark.asyncio
|
||||
async def test_event(app: App, load_plugin):
|
||||
from nonebot.params import EventParam, DependParam
|
||||
|
||||
from plugins.param.param_event import (
|
||||
event,
|
||||
event_type,
|
||||
@ -104,7 +101,6 @@ async def test_state(app: App, load_plugin):
|
||||
REGEX_GROUP,
|
||||
REGEX_MATCHED,
|
||||
)
|
||||
|
||||
from plugins.param.param_state import (
|
||||
state,
|
||||
command,
|
||||
@ -184,7 +180,6 @@ async def test_state(app: App, load_plugin):
|
||||
async def test_matcher(app: App, load_plugin):
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.params import DependParam, MatcherParam
|
||||
|
||||
from plugins.param.param_matcher import matcher, receive, last_receive
|
||||
|
||||
fake_matcher = Matcher()
|
||||
@ -215,7 +210,6 @@ async def test_matcher(app: App, load_plugin):
|
||||
async def test_arg(app: App, load_plugin):
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.params import ArgParam
|
||||
|
||||
from plugins.param.param_arg import arg, arg_str, arg_plain_text
|
||||
|
||||
matcher = Matcher()
|
||||
@ -238,7 +232,6 @@ async def test_arg(app: App, load_plugin):
|
||||
@pytest.mark.asyncio
|
||||
async def test_exception(app: App, load_plugin):
|
||||
from nonebot.params import ExceptionParam
|
||||
|
||||
from plugins.param.param_exception import exc
|
||||
|
||||
exception = ValueError("test")
|
||||
@ -250,7 +243,6 @@ async def test_exception(app: App, load_plugin):
|
||||
@pytest.mark.asyncio
|
||||
async def test_default(app: App, load_plugin):
|
||||
from nonebot.params import DefaultParam
|
||||
|
||||
from plugins.param.param_default import default
|
||||
|
||||
async with app.test_dependent(default, allow_types=[DefaultParam]) as ctx:
|
||||
|
89
tests/test_plugin/test_load.py
Normal file
89
tests/test_plugin/test_load.py
Normal file
@ -0,0 +1,89 @@
|
||||
import sys
|
||||
from typing import TYPE_CHECKING, Set
|
||||
|
||||
import pytest
|
||||
from nonebug import App
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from nonebot.plugin import Plugin
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_plugin(load_plugin: Set["Plugin"]):
|
||||
import nonebot
|
||||
|
||||
loaded_plugins = set(
|
||||
plugin for plugin in nonebot.get_loaded_plugins() if not plugin.parent_plugin
|
||||
)
|
||||
assert loaded_plugins == load_plugin
|
||||
plugin = nonebot.get_plugin("export")
|
||||
assert plugin
|
||||
assert plugin.module_name == "plugins.export"
|
||||
assert "plugins.export" in sys.modules
|
||||
|
||||
try:
|
||||
nonebot.load_plugin("plugins.export")
|
||||
assert False
|
||||
except RuntimeError:
|
||||
assert True
|
||||
|
||||
assert nonebot.load_plugin("some_plugin_not_exist") is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_require_loaded(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
import nonebot
|
||||
|
||||
def _patched_find(name: str):
|
||||
assert False
|
||||
|
||||
monkeypatch.setattr("nonebot.plugin.load._find_manager_by_name", _patched_find)
|
||||
|
||||
nonebot.load_plugin("plugins.export")
|
||||
|
||||
nonebot.require("plugins.export")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_require_not_loaded(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
import nonebot
|
||||
from nonebot.plugin import _managers
|
||||
from nonebot.plugin.manager import PluginManager
|
||||
|
||||
m = PluginManager(["plugins.export"])
|
||||
_managers.append(m)
|
||||
|
||||
origin_load = PluginManager.load_plugin
|
||||
|
||||
def _patched_load(self: PluginManager, name: str):
|
||||
assert self is m
|
||||
return origin_load(self, name)
|
||||
|
||||
monkeypatch.setattr(PluginManager, "load_plugin", _patched_load)
|
||||
|
||||
nonebot.require("plugins.export")
|
||||
|
||||
assert len(_managers) == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_require_not_declared(app: App):
|
||||
import nonebot
|
||||
from nonebot.plugin import _managers
|
||||
|
||||
nonebot.require("plugins.export")
|
||||
|
||||
assert len(_managers) == 1
|
||||
assert _managers[-1].plugins == {"plugins.export"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_require_not_found(app: App):
|
||||
import nonebot
|
||||
from nonebot.plugin import _managers
|
||||
|
||||
try:
|
||||
nonebot.require("some_plugin_not_exist")
|
||||
assert False
|
||||
except RuntimeError:
|
||||
assert True
|
36
tests/test_single_session.py
Normal file
36
tests/test_single_session.py
Normal file
@ -0,0 +1,36 @@
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
import pytest
|
||||
|
||||
from utils import make_fake_event
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_matcher_mutex():
|
||||
from nonebot.plugins.single_session import matcher_mutex, _running_matcher
|
||||
|
||||
am = asynccontextmanager(matcher_mutex)
|
||||
event = make_fake_event()()
|
||||
event_1 = make_fake_event()()
|
||||
event_2 = make_fake_event(_session_id="test1")()
|
||||
event_3 = make_fake_event(_session_id=None)()
|
||||
|
||||
async with am(event) as ctx:
|
||||
assert ctx == False
|
||||
assert not _running_matcher
|
||||
|
||||
async with am(event) as ctx:
|
||||
async with am(event_1) as ctx_1:
|
||||
assert ctx == False
|
||||
assert ctx_1 == True
|
||||
assert not _running_matcher
|
||||
|
||||
async with am(event) as ctx:
|
||||
async with am(event_2) as ctx_2:
|
||||
assert ctx == False
|
||||
assert ctx_2 == False
|
||||
assert not _running_matcher
|
||||
|
||||
async with am(event_3) as ctx_3:
|
||||
assert ctx_3 == False
|
||||
assert not _running_matcher
|
@ -1,4 +1,4 @@
|
||||
from typing import TYPE_CHECKING, Type, Optional
|
||||
from typing import TYPE_CHECKING, Type, Union, Mapping, Iterable, Optional
|
||||
|
||||
from pydantic import create_model
|
||||
|
||||
@ -6,7 +6,14 @@ if TYPE_CHECKING:
|
||||
from nonebot.adapters import Event, Message
|
||||
|
||||
|
||||
def make_fake_message() -> Type["Message"]:
|
||||
def escape_text(s: str, *, escape_comma: bool = True) -> str:
|
||||
s = s.replace("&", "&").replace("[", "[").replace("]", "]")
|
||||
if escape_comma:
|
||||
s = s.replace(",", ",")
|
||||
return s
|
||||
|
||||
|
||||
def make_fake_message():
|
||||
from nonebot.adapters import Message, MessageSegment
|
||||
|
||||
class FakeMessageSegment(MessageSegment):
|
||||
@ -21,9 +28,9 @@ def make_fake_message() -> Type["Message"]:
|
||||
def text(cls, text: str):
|
||||
return cls("text", {"text": text})
|
||||
|
||||
@classmethod
|
||||
def image(cls, url: str):
|
||||
return cls("image", {"url": url})
|
||||
@staticmethod
|
||||
def image(url: str):
|
||||
return FakeMessageSegment("image", {"url": url})
|
||||
|
||||
def is_text(self) -> bool:
|
||||
return self.type == "text"
|
||||
@ -34,8 +41,17 @@ def make_fake_message() -> Type["Message"]:
|
||||
return FakeMessageSegment
|
||||
|
||||
@staticmethod
|
||||
def _construct(msg: str):
|
||||
yield FakeMessageSegment.text(msg)
|
||||
def _construct(msg: Union[str, Iterable[Mapping]]):
|
||||
if isinstance(msg, str):
|
||||
yield FakeMessageSegment.text(msg)
|
||||
else:
|
||||
for seg in msg:
|
||||
yield FakeMessageSegment(**seg)
|
||||
return
|
||||
|
||||
def __add__(self, other):
|
||||
other = escape_text(other) if isinstance(other, str) else other
|
||||
return super().__add__(other)
|
||||
|
||||
return FakeMessage
|
||||
|
||||
@ -45,7 +61,7 @@ def make_fake_event(
|
||||
_name: str = "test",
|
||||
_description: str = "test",
|
||||
_user_id: str = "test",
|
||||
_session_id: str = "test",
|
||||
_session_id: Optional[str] = "test",
|
||||
_message: Optional["Message"] = None,
|
||||
_to_me: bool = True,
|
||||
**fields,
|
||||
@ -68,7 +84,9 @@ def make_fake_event(
|
||||
return _user_id
|
||||
|
||||
def get_session_id(self) -> str:
|
||||
return _session_id
|
||||
if _session_id is not None:
|
||||
return _session_id
|
||||
raise NotImplementedError
|
||||
|
||||
def get_message(self) -> "Message":
|
||||
if _message is not None:
|
||||
|
@ -1,3 +1,3 @@
|
||||
module.exports = {
|
||||
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
|
||||
presets: [require.resolve("@docusaurus/core/lib/babel/preset")],
|
||||
};
|
||||
|
@ -8,7 +8,7 @@ slug: /
|
||||
|
||||
NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架,它基于 Python 的类型注解和异步特性,能够为你的需求实现提供便捷灵活的支持。
|
||||
|
||||
需要注意的是,NoneBot 仅支持 **Python 3.7.3 以上版本**
|
||||
需要注意的是,NoneBot2 仅支持 **Python 3.7.3 以上版本**
|
||||
|
||||
## 特色
|
||||
|
||||
@ -18,15 +18,15 @@ NoneBot2 基于 Python [asyncio](https://docs.python.org/3/library/asyncio.html)
|
||||
|
||||
### 完整的类型注解
|
||||
|
||||
NoneBot2 参考 [PEP 484](https://www.python.org/dev/peps/pep-0484/) 等 PEP 完整实现了类型注解,通过 `pyright`/`pylance` 检查。配合编辑器的类型推导功能,能将绝大多数的 Bug 杜绝在编辑器中 ([编辑器支持](./start/editor-support))。
|
||||
NoneBot2 参考 [PEP 484](https://www.python.org/dev/peps/pep-0484/) 等 PEP 完整实现了类型注解,通过 `pyright`/`pylance` 检查。配合编辑器的类型推导功能,能将绝大多数的 Bug 杜绝在编辑器中([编辑器支持](./start/editor-support))。
|
||||
|
||||
### 开箱即用
|
||||
|
||||
NoneBot2 提供了使用便捷、具有交互式功能的命令行工具 -- `nb-cli`,使得初次接触 NoneBot 时更容易上手。详细使用方法请参考各文档章节以及 [使用脚手架](./start/nb-cli)。
|
||||
NoneBot2 提供了使用便捷、具有交互式功能的命令行工具--`nb-cli`,使得初次接触 NoneBot2 时更容易上手。详细使用方法请参考各文档章节以及[使用脚手架](./start/nb-cli)。
|
||||
|
||||
### 插件系统
|
||||
|
||||
插件系统是 NoneBot 的核心,通过它可以实现机器人的模块化以及功能扩展,便于维护和管理。
|
||||
插件系统是 NoneBot2 的核心,通过它可以实现机器人的模块化以及功能扩展,便于维护和管理。
|
||||
|
||||
### 依赖注入系统
|
||||
|
||||
@ -34,15 +34,15 @@ NoneBot2 采用了一套自行定义的依赖注入系统,可以让事件的
|
||||
|
||||
#### 什么是依赖注入
|
||||
|
||||
[**"依赖注入"**](https://zh.m.wikipedia.org/wiki/%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%AC)意思是,在编程中,有一种方法可以让你的代码声明它工作和使用所需要的东西, 即"**依赖**"。
|
||||
[**“依赖注入”**](https://zh.m.wikipedia.org/wiki/%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%AC)意思是,在编程中,有一种方法可以让你的代码声明它工作和使用所需要的东西,即**“依赖”**。
|
||||
|
||||
系统 (在这里是指 `NoneBot` ) 将负责做任何需要的事情,为你的代码提供这些必要依赖 (即"**注入**"依赖性)
|
||||
系统(在这里是指 NoneBot2)将负责做任何需要的事情,为你的代码提供这些必要依赖(即**“注入”**依赖性)
|
||||
|
||||
这在你有以下情形的需求时非常有用:
|
||||
这在你有以下情形的需求时非常有用:
|
||||
|
||||
- 这部分代码拥有共享的逻辑(同样的代码逻辑多次重复)
|
||||
- 这部分代码拥有共享的逻辑(同样的代码逻辑多次重复)
|
||||
- 共享数据库以及网络请求连接会话
|
||||
- 比如 `httpx.AsyncClient`, `aiohttp.ClientSession` 和 `sqlalchemy.Session`
|
||||
- 比如 `httpx.AsyncClient`、`aiohttp.ClientSession` 和 `sqlalchemy.Session`
|
||||
- 用户权限检查以及认证
|
||||
- 还有更多...
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user