mirror of
https://github.com/nonebot/nonebot2.git
synced 2024-11-24 00:55:07 +08:00
Merge branch 'dev' into richardchien-patch-1
This commit is contained in:
commit
4795f01583
7
.github/ISSUE_TEMPLATE/bug-report.md
vendored
7
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@ -28,6 +28,11 @@ A clear and concise description of what you expected to happen.
|
||||
- Python Version: [e.g. 3.8]
|
||||
- Nonebot Version: [e.g. 2.0.0]
|
||||
|
||||
**截图**
|
||||
**协议端信息:**
|
||||
|
||||
- 协议端: [e.g. go-cqhttp]
|
||||
- 协议端版本: [e.g. 1.0.0]
|
||||
|
||||
**截图或日志**
|
||||
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,5 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Question
|
||||
url: https://github.com/nonebot/discussions/discussions/new
|
||||
about: Ask questions about nonebot
|
||||
- name: Plugin Publish
|
||||
url: https://v2.nonebot.dev/store.html
|
||||
about: Publish your plugin to nonebot homepage and nb-cli
|
||||
|
17
.github/ISSUE_TEMPLATE/document.md
vendored
Normal file
17
.github/ISSUE_TEMPLATE/document.md
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
name: Document improvement
|
||||
about: Feedback on documentation, including errors and ideas
|
||||
title: 'Docs: some description'
|
||||
labels: documentation
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**描述问题或主题:**
|
||||
|
||||
|
||||
**需做出的修改:**
|
||||
|
||||
* [ ] 一些修改
|
||||
* [ ] 一些修改
|
||||
* [ ] 一些修改
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -189,3 +189,5 @@ dev
|
||||
docs_build/_build
|
||||
!tests/.env
|
||||
*.xmind
|
||||
yarn.lock
|
||||
.DS_Store
|
||||
|
9
.prettierrc
Normal file
9
.prettierrc
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"endOfLine": "lf",
|
||||
"arrowParens": "always",
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"semi": true
|
||||
}
|
14
README.md
14
README.md
@ -17,7 +17,7 @@ _✨ Python 异步机器人框架 ✨_
|
||||
<a href="https://pypi.python.org/pypi/nonebot2">
|
||||
<img src="https://img.shields.io/pypi/v/nonebot2" alt="pypi">
|
||||
</a>
|
||||
<img src="https://img.shields.io/badge/python-3.7+-blue" alt="python"><br />
|
||||
<img src="https://img.shields.io/badge/python-3.7.3+-blue" alt="python"><br />
|
||||
<a href="https://github.com/howmanybots/onebot/blob/master/README.md">
|
||||
<img src="https://img.shields.io/badge/OneBot-v11-black?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABABAMAAABYR2ztAAAAIVBMVEUAAAAAAAADAwMHBwceHh4UFBQNDQ0ZGRkoKCgvLy8iIiLWSdWYAAAAAXRSTlMAQObYZgAAAQVJREFUSMftlM0RgjAQhV+0ATYK6i1Xb+iMd0qgBEqgBEuwBOxU2QDKsjvojQPvkJ/ZL5sXkgWrFirK4MibYUdE3OR2nEpuKz1/q8CdNxNQgthZCXYVLjyoDQftaKuniHHWRnPh2GCUetR2/9HsMAXyUT4/3UHwtQT2AggSCGKeSAsFnxBIOuAggdh3AKTL7pDuCyABcMb0aQP7aM4AnAbc/wHwA5D2wDHTTe56gIIOUA/4YYV2e1sg713PXdZJAuncdZMAGkAukU9OAn40O849+0ornPwT93rphWF0mgAbauUrEOthlX8Zu7P5A6kZyKCJy75hhw1Mgr9RAUvX7A3csGqZegEdniCx30c3agAAAABJRU5ErkJggg==" alt="cqhttp">
|
||||
</a>
|
||||
@ -97,7 +97,13 @@ NoneBot2 的驱动框架 `Driver` 以及通信协议 `Adapter` 均可**自定义
|
||||
nb create
|
||||
```
|
||||
|
||||
## 插件
|
||||
## 社区资源
|
||||
|
||||
### 教程/实际项目/经验分享
|
||||
|
||||
- [awesome-nonebot](https://github.com/nonebot/awesome-nonebot)
|
||||
|
||||
### 插件
|
||||
|
||||
此外,NoneBot2 还有丰富的官方以及第三方现成的插件供大家使用:
|
||||
|
||||
@ -107,7 +113,9 @@ NoneBot2 的驱动框架 `Driver` 以及通信协议 `Adapter` 均可**自定义
|
||||
nb plugin install nonebot_plugin_docs
|
||||
```
|
||||
|
||||
或者尝试 [文档镜像](https://nonebot2-vercel-mirror.vercel.app)
|
||||
或者尝试以下镜像:
|
||||
- [文档镜像(中国境内)](https://nb2.baka.icu)
|
||||
- [文档镜像(vercel)](https://nonebot2-vercel-mirror.vercel.app)
|
||||
|
||||
- 其他插件请查看 [商店](https://v2.nonebot.dev/store.html)
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
## 从 NoneBot v1 迁移
|
||||
|
||||
`APScheduler` 作为 `nonebot` v1 的可选依赖,为众多 bot 提供了方便的定时任务功能。`nonebot2` 已将 `APScheduler` 独立为 `nonebot_plugin_apscheduler` 插件,你可以在 [插件广场](https://v2.nonebot.dev/plugin-store.html) 中找到它。
|
||||
`APScheduler` 作为 `nonebot` v1 的可选依赖,为众多 bot 提供了方便的定时任务功能。`nonebot2` 已将 `APScheduler` 独立为 `nonebot_plugin_apscheduler` 插件,你可以在 [插件广场](https://v2.nonebot.dev/store.html) 中找到它。
|
||||
|
||||
相比于 `nonebot` v1 ,只需要安装插件并修改 `scheduler` 的导入方式即可完成迁移。
|
||||
|
||||
|
@ -6,9 +6,9 @@
|
||||
|
||||
## 从 NoneBot v1 迁移
|
||||
|
||||
`APScheduler` 作为 `nonebot` v1 的可选依赖,为众多 bot 提供了方便的定时任务功能。`nonebot2` 已将 `APScheduler` 独立为 `nonebot_plugin_apscheduler` 插件,你可以在 [插件广场](https://v2.nonebot.dev/plugin-store.html) 中找到它。
|
||||
`APScheduler` 作为 `nonebot` v1 的可选依赖,为众多 bot 提供了方便的定时任务功能。`nonebot2` 已将 `APScheduler` 独立为 `nonebot_plugin_apscheduler` 插件,你可以在 [插件广场](https://v2.nonebot.dev/store.html) 中找到它。
|
||||
|
||||
相比于 `nonebot` v1,`nonebot` v2只需要安装插件并修改 `scheduler` 的导入方式即可完成迁移。
|
||||
相比于 `nonebot` v1,`nonebot` v2 只需要安装插件并修改 `scheduler` 的导入方式即可完成迁移。
|
||||
|
||||
## 安装插件
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
## 从 v1 迁移
|
||||
|
||||
`APScheduler` 作为 `nonebot` v1 的可选依赖,为众多 bot 提供了方便的定时任务功能。`nonebot2` 已将 `APScheduler` 独立为 `nonebot_plugin_apscheduler` 插件,你可以在 [插件广场](https://v2.nonebot.dev/plugin-store.html) 中找到它。
|
||||
`APScheduler` 作为 `nonebot` v1 的可选依赖,为众多 bot 提供了方便的定时任务功能。`nonebot2` 已将 `APScheduler` 独立为 `nonebot_plugin_apscheduler` 插件,你可以在 [插件广场](https://v2.nonebot.dev/store.html) 中找到它。
|
||||
|
||||
相比于 `nonebot` v1 ,只需要安装插件并修改 `scheduler` 的导入方式即可完成迁移。
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<v-card flat class="adapters">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="4">
|
||||
<v-row class="justify-center">
|
||||
<v-col cols="12" sm="6">
|
||||
<v-text-field
|
||||
v-model="filterText"
|
||||
dense
|
||||
@ -9,7 +9,7 @@
|
||||
outlined
|
||||
clearable
|
||||
hide-details
|
||||
label="Filter Adapter"
|
||||
label="搜索适配器"
|
||||
>
|
||||
<template v-slot:prepend-inner>
|
||||
<div class="v-input__icon v-input__icon--prepend-inner">
|
||||
@ -18,16 +18,16 @@
|
||||
</template>
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="4">
|
||||
<v-col cols="12" sm="6">
|
||||
<v-dialog v-model="dialog" max-width="600px">
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<v-btn dark block color="primary" v-bind="attrs" v-on="on"
|
||||
>Publish Your Adapter
|
||||
>发布适配器
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<span class="headline">Adapter Information</span>
|
||||
<span class="headline">适配器信息</span>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form ref="newAdapterForm" v-model="valid" lazy-validation>
|
||||
@ -49,14 +49,14 @@
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="newAdapter.id"
|
||||
v-model="newAdapter.link"
|
||||
label="PyPI 项目名"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="newAdapter.link"
|
||||
v-model="newAdapter.id"
|
||||
label="协议 import 包名"
|
||||
required
|
||||
></v-text-field>
|
||||
@ -75,7 +75,7 @@
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="blue darken-1" text @click="dialog = false">
|
||||
Close
|
||||
关闭
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:disabled="!valid"
|
||||
@ -86,22 +86,23 @@
|
||||
publishAdapter();
|
||||
"
|
||||
>
|
||||
Publish
|
||||
发布
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="4">
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-pagination
|
||||
v-model="page"
|
||||
:length="pageNum"
|
||||
prev-icon="fa-caret-left"
|
||||
next-icon="fa-caret-right"
|
||||
></v-pagination>
|
||||
</v-col>
|
||||
></v-pagination
|
||||
></v-col>
|
||||
</v-row>
|
||||
<hr />
|
||||
<v-row>
|
||||
<v-col
|
||||
cols="12"
|
||||
@ -138,7 +139,7 @@ import adapters from "../public/adapters.json";
|
||||
export default {
|
||||
name: "Adapters",
|
||||
components: {
|
||||
PublishCard
|
||||
PublishCard,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -152,8 +153,8 @@ export default {
|
||||
desc: null,
|
||||
id: null,
|
||||
link: null,
|
||||
repo: null
|
||||
}
|
||||
repo: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -161,7 +162,7 @@ export default {
|
||||
return Math.ceil(this.filteredAdapters.length / 10);
|
||||
},
|
||||
filteredAdapters() {
|
||||
return this.adapters.filter(adapter => {
|
||||
return this.adapters.filter((adapter) => {
|
||||
return (
|
||||
adapter.id.indexOf(this.filterText || "") != -1 ||
|
||||
adapter.name.indexOf(this.filterText || "") != -1 ||
|
||||
@ -173,7 +174,7 @@ export default {
|
||||
displayAdapters() {
|
||||
return this.filteredAdapters.slice((this.page - 1) * 10, this.page * 10);
|
||||
},
|
||||
publishPlugin() {
|
||||
publishAdapter() {
|
||||
if (!this.$refs.newAdapterForm.validate()) {
|
||||
return;
|
||||
}
|
||||
@ -215,7 +216,7 @@ ${this.newAdapter.repo}
|
||||
window.open(
|
||||
`https://github.com/nonebot/nonebot2/issues/new?title=${title}&body=${body}&labels=Adapter`
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<v-card flat class="bots">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="4">
|
||||
<v-col cols="12" sm="6">
|
||||
<v-text-field
|
||||
v-model="filterText"
|
||||
dense
|
||||
@ -9,7 +9,7 @@
|
||||
outlined
|
||||
clearable
|
||||
hide-details
|
||||
label="Filter Bot"
|
||||
label="搜索机器人"
|
||||
>
|
||||
<template v-slot:prepend-inner>
|
||||
<div class="v-input__icon v-input__icon--prepend-inner">
|
||||
@ -18,16 +18,16 @@
|
||||
</template>
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="4">
|
||||
<v-col cols="12" sm="6">
|
||||
<v-dialog v-model="dialog" max-width="600px">
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<v-btn dark block color="primary" v-bind="attrs" v-on="on"
|
||||
>Publish Your Bot
|
||||
>发布机器人
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<span class="headline">Bot Information</span>
|
||||
<span class="headline">机器人信息</span>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form ref="newBotForm" v-model="valid" lazy-validation>
|
||||
@ -61,7 +61,7 @@
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="blue darken-1" text @click="dialog = false">
|
||||
Close
|
||||
关闭
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:disabled="!valid"
|
||||
@ -72,13 +72,15 @@
|
||||
publishBot();
|
||||
"
|
||||
>
|
||||
Publish
|
||||
发布
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="4">
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-pagination
|
||||
v-model="page"
|
||||
:length="pageNum"
|
||||
@ -87,7 +89,6 @@
|
||||
></v-pagination>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<hr />
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" v-for="(bot, index) in displayBots" :key="index">
|
||||
<PublishCard
|
||||
@ -118,7 +119,7 @@ import bots from "../public/bots.json";
|
||||
export default {
|
||||
name: "Bots",
|
||||
components: {
|
||||
PublishCard
|
||||
PublishCard,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -130,8 +131,8 @@ export default {
|
||||
newBot: {
|
||||
name: null,
|
||||
desc: null,
|
||||
repo: null
|
||||
}
|
||||
repo: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -139,7 +140,7 @@ export default {
|
||||
return Math.ceil(this.filteredBots.length / 10);
|
||||
},
|
||||
filteredBots() {
|
||||
return this.bots.filter(bot => {
|
||||
return this.bots.filter((bot) => {
|
||||
return (
|
||||
bot.name.indexOf(this.filterText || "") != -1 ||
|
||||
bot.desc.indexOf(this.filterText || "") != -1 ||
|
||||
@ -183,7 +184,7 @@ ${this.newBot.repo}
|
||||
window.open(
|
||||
`https://github.com/nonebot/nonebot2/issues/new?title=${title}&body=${body}&labels=Bot`
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -136,8 +136,8 @@ export default {
|
||||
props: {
|
||||
messages: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
initWOW: function() {
|
||||
@ -146,13 +146,13 @@ export default {
|
||||
animateClass: "animate__animated",
|
||||
offset: 0,
|
||||
mobile: true,
|
||||
live: true
|
||||
live: true,
|
||||
}).init();
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.initWOW();
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<v-card flat class="plugins">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="4">
|
||||
<v-col cols="12" sm="6">
|
||||
<v-text-field
|
||||
v-model="filterText"
|
||||
dense
|
||||
@ -9,7 +9,7 @@
|
||||
outlined
|
||||
clearable
|
||||
hide-details
|
||||
label="Filter Plugin"
|
||||
label="搜索插件"
|
||||
>
|
||||
<template v-slot:prepend-inner>
|
||||
<div class="v-input__icon v-input__icon--prepend-inner">
|
||||
@ -18,16 +18,16 @@
|
||||
</template>
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="4">
|
||||
<v-col cols="12" sm="6">
|
||||
<v-dialog v-model="dialog" max-width="600px">
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<v-btn dark block color="primary" v-bind="attrs" v-on="on"
|
||||
>Publish Your Plugin
|
||||
>发布插件
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<span class="headline">Plugin Information</span>
|
||||
<span class="headline">插件信息</span>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form ref="newPluginForm" v-model="valid" lazy-validation>
|
||||
@ -75,7 +75,7 @@
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="blue darken-1" text @click="dialog = false">
|
||||
Close
|
||||
关闭
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:disabled="!valid"
|
||||
@ -86,13 +86,15 @@
|
||||
publishPlugin();
|
||||
"
|
||||
>
|
||||
Publish
|
||||
发布
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="4">
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-pagination
|
||||
v-model="page"
|
||||
:length="pageNum"
|
||||
@ -101,7 +103,6 @@
|
||||
></v-pagination>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<hr />
|
||||
<v-row>
|
||||
<v-col
|
||||
cols="12"
|
||||
@ -115,7 +116,7 @@
|
||||
:id="plugin.id"
|
||||
:author="plugin.author"
|
||||
:link="plugin.repo"
|
||||
text="copy nb install command"
|
||||
text="点此复制安装命令"
|
||||
:command="`nb plugin install ${plugin.id}`"
|
||||
></PublishCard>
|
||||
</v-col>
|
||||
@ -140,7 +141,7 @@ import plugins from "../public/plugins.json";
|
||||
export default {
|
||||
name: "Plugins",
|
||||
components: {
|
||||
PublishCard
|
||||
PublishCard,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -154,8 +155,8 @@ export default {
|
||||
desc: null,
|
||||
id: null,
|
||||
link: null,
|
||||
repo: null
|
||||
}
|
||||
repo: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -163,7 +164,7 @@ export default {
|
||||
return Math.ceil(this.filteredPlugins.length / 10);
|
||||
},
|
||||
filteredPlugins() {
|
||||
return this.plugins.filter(plugin => {
|
||||
return this.plugins.filter((plugin) => {
|
||||
return (
|
||||
plugin.id.indexOf(this.filterText || "") != -1 ||
|
||||
plugin.name.indexOf(this.filterText || "") != -1 ||
|
||||
@ -217,7 +218,7 @@ ${this.newPlugin.repo}
|
||||
window.open(
|
||||
`https://github.com/nonebot/nonebot2/issues/new?title=${title}&body=${body}&labels=Plugin`
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -28,7 +28,7 @@
|
||||
{{ text }}
|
||||
<v-icon right small>fa-copy</v-icon>
|
||||
</v-btn>
|
||||
<v-snackbar v-model="snackbar">Copied!</v-snackbar>
|
||||
<v-snackbar v-model="snackbar">复制成功!</v-snackbar>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
@ -44,17 +44,17 @@ export default {
|
||||
author: String,
|
||||
link: String,
|
||||
text: String,
|
||||
command: String
|
||||
command: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
snackbar: false
|
||||
snackbar: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
showCommand() {
|
||||
return this.text && this.command;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
repoLink(repo) {
|
||||
@ -65,11 +65,11 @@ export default {
|
||||
},
|
||||
copyCommand() {
|
||||
copy(this.command, {
|
||||
format: "text/plain"
|
||||
format: "text/plain",
|
||||
});
|
||||
this.snackbar = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
}}</v-tab>
|
||||
</v-tabs>
|
||||
</v-toolbar>
|
||||
<v-tabs-items class="sub-item" v-model="tab">
|
||||
<v-tabs-items class="sub-item pt-1" v-model="tab">
|
||||
<v-tab-item>
|
||||
<Adapter></Adapter>
|
||||
</v-tab-item>
|
||||
@ -37,7 +37,7 @@ export default {
|
||||
components: {
|
||||
Adapter,
|
||||
Plugin,
|
||||
Bot
|
||||
Bot,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -45,12 +45,12 @@ export default {
|
||||
tabs: {
|
||||
0: "协议",
|
||||
1: "插件",
|
||||
2: "机器人"
|
||||
}
|
||||
2: "机器人",
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {},
|
||||
methods: {}
|
||||
methods: {},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -1 +1,64 @@
|
||||
# 事件处理函数重载
|
||||
|
||||
当我们在编写 `nonebot2` 应用时,常常会遇到这样一个问题:该怎么让同一类型的不同事件执行不同的响应逻辑?又或者如何让不同的 `adapter` 针对同一类型的事件作出不同响应?
|
||||
|
||||
针对这个问题, `nonebot2` 提供一个便捷而高效的解决方案:事件处理函数重载机制。简单地说,`handler` (事件处理函数) 会根据其参数的 `type hints` ([PEP484 类型标注](https://www.python.org/dev/peps/pep-0484/)) 来对相对应的 `adapter` 和 `Event` 进行响应,并且会忽略不符合其参数类型标注的情况。
|
||||
|
||||
必须要注意的是,该机制利用了 `inspect` 标准库获取到了事件处理函数的 `singnature` (签名) ,进一步获取到参数名称和类型标注。故而,我们在编写 `handler` 时,参数的名称和类型标注必须要符合 `T_Handler` 规定,详情可以参看 **指南** 中的[事件处理](../guide/creating-a-handler)。
|
||||
|
||||
::: tip 提示
|
||||
|
||||
如果想了解更多关于 `inspect` 标准库的信息,可以查看[官方文档](https://docs.python.org/zh-cn/3.9/library/inspect.html)。
|
||||
|
||||
:::
|
||||
|
||||
下面,我们会以 `CQHTTP` 中的 `群聊消息事件` 和 `私聊消息事件` 为例,对该机制的应用进行简单的介绍。
|
||||
|
||||
## 一个例子
|
||||
|
||||
首先,我们需要导入需要的方法、类型。
|
||||
|
||||
```python
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters.cqhttp import Bot, GroupMessageEvent, PrivateMessageEvent
|
||||
```
|
||||
|
||||
之后,我们可以注册一个 `Matcher` 来响应 `消息事件` 。
|
||||
|
||||
```python
|
||||
matcher = on_command("testoverload")
|
||||
```
|
||||
|
||||
最后, 我们编写不同的 `handler` 并编写不同的类型标注来实现事件处理函数重载:
|
||||
|
||||
```python
|
||||
@matcher.handle()
|
||||
async def _(bot: Bot, event: GroupMessageEvent):
|
||||
await matcher.send("群聊消息事件响应成功!")
|
||||
|
||||
|
||||
@matcher.handle()
|
||||
async def _(bot: Bot, event: PrivateMessageEvent):
|
||||
await matcher.send("私聊消息事件响应成功!")
|
||||
```
|
||||
|
||||
此时,我们可以在群聊或私聊中对我们的机器人发送 `testoverload` ,它会在不同的场景做出不同的应答。
|
||||
|
||||
这样一个简单的事件处理函数重载就完成了。
|
||||
|
||||
## 进阶
|
||||
|
||||
事件处理函数重载机制同样支持被 `matcher.got` 等装饰器装饰的函数。 例如:
|
||||
|
||||
```python
|
||||
@matcher.got("key1", prompt="群事件提问")
|
||||
async def _(bot: Bot, event: GroupMessageEvent):
|
||||
await matcher.send("群聊消息事件响应成功!")
|
||||
|
||||
|
||||
@matcher.got("key2", prompt="私聊事件提问")
|
||||
async def _(bot: Bot, event: PrivateMessageEvent):
|
||||
await matcher.send("私聊消息事件响应成功!")
|
||||
```
|
||||
|
||||
只有触发事件符合的函数才会触发装饰器。
|
||||
|
@ -1,2 +1,90 @@
|
||||
# 权限控制
|
||||
|
||||
**权限控制**是机器人在实际应用中需要解决的重点问题之一,`Nonebot` 提供了十分完善且灵活的权限控制机制—— `Permission` 机制。接下来我们将对这个机制进行简单的说明。
|
||||
|
||||
## 应用
|
||||
|
||||
如同 `Rule` 一样, `Permission` 可以在[注册事件响应器](../guide/creating-a-matcher)时添加 `permission` 参数来加以应用,这样 `Nonebot` 会在事件响应时检测事件主体的权限。下面我们以 `SUPERUSER` 为例,对该机制的应用做一下介绍。
|
||||
|
||||
```python
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot import on_command
|
||||
|
||||
matcher = on_command("测试超管", permission=SUPERUSER)
|
||||
|
||||
|
||||
@matcher.handle()
|
||||
async def _(bot: Bot):
|
||||
await matcher.send("超管命令测试成功")
|
||||
|
||||
|
||||
@matcher.got("key1", "超管提问")
|
||||
async def _(bot: Bot, event: Event):
|
||||
await matcher.send("超管命令got成功")
|
||||
```
|
||||
|
||||
在这段代码中,我们事件响应器指定了 `SUPERUSER` 这样一个权限,那么机器人只会响应超级管理员的 `测试超管` 命令,并且会响应该超级管理员的连续对话。
|
||||
|
||||
::: tip 提示
|
||||
|
||||
在这里需要强调的是,`Permission` 与 `Rule` 的表现并不相同, `Rule` 只会在初次响应时生效,在余下的对话中并没有限制事件;但是 `Permission` 会持续生效,在连续对话中会一直对事件主体加以限制。
|
||||
|
||||
:::
|
||||
|
||||
## 进阶
|
||||
|
||||
`Permission` 除了可以在注册事件响应器时加以应用,还可以在编写事件处理函数 `handler` 时主动调用,我们可以利用这个特性在一个 `handler` 里对不同权限的事件主体进行区别响应,下面我们以 `CQHTTP` 中的 `GROUP_ADMIN` (普通管理员非群主)和 `GROUP_OWNER` 为例,说明下怎么进行主动调用。
|
||||
|
||||
```python
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters.cqhttp import Bot
|
||||
from nonebot.adapters.cqhttp import GroupMessageEvent
|
||||
from nonebot.adapters.cqhttp import GROUP_ADMIN, GROUP_OWNER
|
||||
|
||||
matcher = on_command("测试权限")
|
||||
|
||||
@matcher.handle()
|
||||
async def _(bot: Bot, event: GroupMessageEvent):
|
||||
if await GROUP_ADMIN(bot, event):
|
||||
await matcher.send("管理员测试成功")
|
||||
elif await GROUP_OWNER(bot, event):
|
||||
await matcher.send("群主测试成功")
|
||||
else:
|
||||
await matcher.send("群员测试成功")
|
||||
|
||||
```
|
||||
|
||||
在这段代码里,我们并没有对命令的权限指定,这个命令会响应所有在群聊中的 `测试权限` 命令,但是在 `handler` 里,我们对两个 `Permission` 进行主动调用,从而可以对不同的角色进行不同的响应。
|
||||
|
||||
## 自定义
|
||||
|
||||
如同 `Rule` 一样, `Permission` 也是由非负数个 `PermissionChecker` 组成的,但只需其中一个返回 `True` 时就会匹配成功。下面则是 `PermissionChecker` 和 `Permission` 示例:
|
||||
|
||||
```python
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot.permission import Permission
|
||||
|
||||
async def async_checker(bot: Bot, event: Event) -> bool:
|
||||
return True
|
||||
|
||||
def sync_checker(bot: Bot, event: Event) -> bool:
|
||||
return True
|
||||
|
||||
def check(arg1, arg2):
|
||||
|
||||
async def _checker(bot: Bot, event: Event) -> bool:
|
||||
return bool(arg1 + arg2)
|
||||
|
||||
return Permission(_checker)
|
||||
```
|
||||
|
||||
`Permission` 和 `PermissionChecker` 之间可以使用 `或 |` 互相组合:
|
||||
|
||||
```python
|
||||
from nonebot.permission import Permission
|
||||
|
||||
Permission(async_checker1) | sync_checker | async_checker2
|
||||
```
|
||||
|
||||
同样地,如果想用 `Permission(*checkers)` 包裹构造 `Permission` ,函数必须是异步的;但是在利用 `或 |` 符号连接构造时, `Nonebot` 会自动包裹同步函数为异步函数。
|
||||
|
@ -6,9 +6,9 @@
|
||||
|
||||
## 从 NoneBot v1 迁移
|
||||
|
||||
`APScheduler` 作为 `nonebot` v1 的可选依赖,为众多 bot 提供了方便的定时任务功能。`nonebot2` 已将 `APScheduler` 独立为 `nonebot_plugin_apscheduler` 插件,你可以在 [插件广场](https://v2.nonebot.dev/plugin-store.html) 中找到它。
|
||||
`APScheduler` 作为 `nonebot` v1 的可选依赖,为众多 bot 提供了方便的定时任务功能。`nonebot2` 已将 `APScheduler` 独立为 `nonebot_plugin_apscheduler` 插件,你可以在 [商店](https://v2.nonebot.dev/store.html) 中找到它。
|
||||
|
||||
相比于 `nonebot` v1,`nonebot` v2只需要安装插件并修改 `scheduler` 的导入方式即可完成迁移。
|
||||
相比于 `nonebot` v1,`nonebot` v2 只需要安装插件并修改 `scheduler` 的导入方式即可完成迁移。
|
||||
|
||||
## 安装插件
|
||||
|
||||
|
@ -191,9 +191,6 @@ Adapter 类型
|
||||
* `api: str`: API 名称
|
||||
|
||||
|
||||
* `self_id: Optional[str]`: 指定调用 API 的机器人
|
||||
|
||||
|
||||
* `**data`: API 数据
|
||||
|
||||
|
||||
@ -256,7 +253,7 @@ await bot.send_msg(message="hello world")
|
||||
|
||||
## _class_ `Message`
|
||||
|
||||
基类:`list`, `abc.ABC`
|
||||
基类:`List`[`nonebot.adapters._base.T_MessageSegment`], `abc.ABC`
|
||||
|
||||
消息数组
|
||||
|
||||
|
@ -312,12 +312,108 @@ CQHTTP 协议 Bot 适配。继承属性参考 [BaseBot](./#class-basebot) 。
|
||||
CQHTTP 协议 MessageSegment 适配。具体方法参考协议消息段类型或源码。
|
||||
|
||||
|
||||
### `is_text()`
|
||||
|
||||
|
||||
### _static_ `anonymous(ignore_failure=None)`
|
||||
|
||||
|
||||
### _static_ `at(user_id)`
|
||||
|
||||
|
||||
### _static_ `contact(type_, id)`
|
||||
|
||||
|
||||
### _static_ `contact_group(group_id)`
|
||||
|
||||
|
||||
### _static_ `contact_user(user_id)`
|
||||
|
||||
|
||||
### _static_ `dice()`
|
||||
|
||||
|
||||
### _static_ `face(id_)`
|
||||
|
||||
|
||||
### _static_ `forward(id_)`
|
||||
|
||||
|
||||
### _static_ `image(file, type_=None, cache=True, proxy=True, timeout=None)`
|
||||
|
||||
|
||||
### _static_ `json(data)`
|
||||
|
||||
|
||||
### _static_ `location(latitude, longitude, title=None, content=None)`
|
||||
|
||||
|
||||
### _static_ `music(type_, id_)`
|
||||
|
||||
|
||||
### _static_ `music_custom(url, audio, title, content=None, img_url=None)`
|
||||
|
||||
|
||||
### _static_ `node(id_)`
|
||||
|
||||
|
||||
### _static_ `node_custom(user_id, nickname, content)`
|
||||
|
||||
|
||||
### _static_ `poke(type_, id_)`
|
||||
|
||||
|
||||
### _static_ `record(file, magic=None, cache=None, proxy=None, timeout=None)`
|
||||
|
||||
|
||||
### _static_ `reply(id_)`
|
||||
|
||||
|
||||
### _static_ `rps()`
|
||||
|
||||
|
||||
### _static_ `shake()`
|
||||
|
||||
|
||||
### _static_ `share(url='', title='', content=None, image=None)`
|
||||
|
||||
|
||||
### _static_ `text(text)`
|
||||
|
||||
|
||||
### _static_ `video(file, cache=None, proxy=None, timeout=None)`
|
||||
|
||||
|
||||
### _static_ `xml(data)`
|
||||
|
||||
|
||||
### `type`
|
||||
|
||||
|
||||
* 类型: `str`
|
||||
|
||||
|
||||
* 说明: 消息段类型
|
||||
|
||||
|
||||
### `data`
|
||||
|
||||
|
||||
* 类型: `Dict[str, Union[str, list]]`
|
||||
|
||||
|
||||
* 说明: 消息段数据
|
||||
|
||||
|
||||
## _class_ `Message`
|
||||
|
||||
基类:[`nonebot.adapters._base.Message`](README.md#nonebot.adapters._base.Message)
|
||||
基类:[`nonebot.adapters._base.Message`](README.md#nonebot.adapters._base.Message)[`nonebot.adapters.cqhttp.message.MessageSegment`]
|
||||
|
||||
CQHTTP 协议 Message 适配。
|
||||
|
||||
|
||||
### `extract_plain_text()`
|
||||
|
||||
# NoneBot.adapters.cqhttp.permission 模块
|
||||
|
||||
|
||||
|
@ -292,7 +292,7 @@ message += MessageSegment.atDingtalkIds(event.senderId)
|
||||
|
||||
## _class_ `Message`
|
||||
|
||||
基类:[`nonebot.adapters._base.Message`](README.md#nonebot.adapters._base.Message)
|
||||
基类:[`nonebot.adapters._base.Message`](README.md#nonebot.adapters._base.Message)[`nonebot.adapters.ding.message.MessageSegment`]
|
||||
|
||||
钉钉 协议 Message 适配。
|
||||
|
||||
|
@ -963,7 +963,7 @@ Mirai-API-HTTP 协议 MessageSegment 适配。具体方法参考 [mirai-api-http
|
||||
|
||||
## _class_ `MessageChain`
|
||||
|
||||
基类:[`nonebot.adapters._base.Message`](README.md#nonebot.adapters._base.Message)
|
||||
基类:[`nonebot.adapters._base.Message`](README.md#nonebot.adapters._base.Message)[`nonebot.adapters.mirai.message.MessageSegment`]
|
||||
|
||||
Mirai 协议 Message 适配
|
||||
|
||||
|
@ -106,6 +106,30 @@ NoneBot 主要配置。大小写不敏感。
|
||||
|
||||
|
||||
|
||||
### `log_level`
|
||||
|
||||
|
||||
* **类型**: `Union[int, str]`
|
||||
|
||||
|
||||
* **默认值**: `None`
|
||||
|
||||
|
||||
* **说明**
|
||||
|
||||
配置 NoneBot 日志输出等级,可以为 `int` 类型等级或等级名称,参考 [loguru 日志等级](https://loguru.readthedocs.io/en/stable/api/logger.html#levels)。
|
||||
|
||||
|
||||
|
||||
* **示例**
|
||||
|
||||
|
||||
```default
|
||||
LOG_LEVEL=25
|
||||
LOG_LEVEL=INFO
|
||||
```
|
||||
|
||||
|
||||
### `api_root`
|
||||
|
||||
|
||||
|
@ -14,7 +14,7 @@ sidebarDepth: 0
|
||||
|
||||
基类:`abc.ABC`
|
||||
|
||||
Driver 基类。将后端框架封装,以满足适配器使用。
|
||||
Driver 基类。
|
||||
|
||||
|
||||
### `_adapters`
|
||||
@ -32,33 +32,33 @@ Driver 基类。将后端框架封装,以满足适配器使用。
|
||||
|
||||
|
||||
|
||||
### `_ws_connection_hook`
|
||||
### `_bot_connection_hook`
|
||||
|
||||
|
||||
* **类型**
|
||||
|
||||
`Set[T_WebSocketConnectionHook]`
|
||||
`Set[T_BotConnectionHook]`
|
||||
|
||||
|
||||
|
||||
* **说明**
|
||||
|
||||
WebSocket 连接建立时执行的函数
|
||||
Bot 连接建立时执行的函数
|
||||
|
||||
|
||||
|
||||
### `_ws_disconnection_hook`
|
||||
### `_bot_disconnection_hook`
|
||||
|
||||
|
||||
* **类型**
|
||||
|
||||
`Set[T_WebSocketDisconnectionHook]`
|
||||
`Set[T_BotDisconnectionHook]`
|
||||
|
||||
|
||||
|
||||
* **说明**
|
||||
|
||||
WebSocket 连接断开时执行的函数
|
||||
Bot 连接断开时执行的函数
|
||||
|
||||
|
||||
|
||||
@ -120,6 +120,21 @@ Driver 基类。将后端框架封装,以满足适配器使用。
|
||||
|
||||
|
||||
|
||||
### _property_ `bots`
|
||||
|
||||
|
||||
* **类型**
|
||||
|
||||
`Dict[str, Bot]`
|
||||
|
||||
|
||||
|
||||
* **说明**
|
||||
|
||||
获取当前所有已连接的 Bot
|
||||
|
||||
|
||||
|
||||
### `register_adapter(name, adapter, **kwargs)`
|
||||
|
||||
|
||||
@ -144,33 +159,33 @@ Driver 基类。将后端框架封装,以满足适配器使用。
|
||||
驱动类型名称
|
||||
|
||||
|
||||
### _abstract property_ `server_app`
|
||||
|
||||
驱动 APP 对象
|
||||
|
||||
|
||||
### _abstract property_ `asgi`
|
||||
|
||||
驱动 ASGI 对象
|
||||
|
||||
|
||||
### _abstract property_ `logger`
|
||||
|
||||
驱动专属 logger 日志记录器
|
||||
|
||||
|
||||
### _property_ `bots`
|
||||
|
||||
|
||||
* **类型**
|
||||
|
||||
`Dict[str, Bot]`
|
||||
|
||||
### _abstract_ `run(host=None, port=None, *args, **kwargs)`
|
||||
|
||||
|
||||
* **说明**
|
||||
|
||||
获取当前所有已连接的 Bot
|
||||
启动驱动框架
|
||||
|
||||
|
||||
|
||||
* **参数**
|
||||
|
||||
|
||||
* `host: Optional[str]`: 驱动绑定 IP
|
||||
|
||||
|
||||
* `post: Optional[int]`: 驱动绑定端口
|
||||
|
||||
|
||||
* `*args`
|
||||
|
||||
|
||||
* `**kwargs`
|
||||
|
||||
|
||||
|
||||
@ -226,41 +241,47 @@ Driver 基类。将后端框架封装,以满足适配器使用。
|
||||
在 WebSocket 连接断开后,调用该函数来注销 bot 对象
|
||||
|
||||
|
||||
### _abstract_ `run(host=None, port=None, *args, **kwargs)`
|
||||
## _class_ `ReverseDriver`
|
||||
|
||||
基类:`nonebot.drivers.Driver`
|
||||
|
||||
Reverse Driver 基类。将后端框架封装,以满足适配器使用。
|
||||
|
||||
|
||||
* **说明**
|
||||
### _abstract property_ `server_app`
|
||||
|
||||
启动驱动框架
|
||||
驱动 APP 对象
|
||||
|
||||
|
||||
### _abstract property_ `asgi`
|
||||
|
||||
* **参数**
|
||||
驱动 ASGI 对象
|
||||
|
||||
|
||||
* `host: Optional[str]`: 驱动绑定 IP
|
||||
|
||||
|
||||
* `post: Optional[int]`: 驱动绑定端口
|
||||
|
||||
|
||||
* `*args`
|
||||
|
||||
|
||||
* `**kwargs`
|
||||
|
||||
|
||||
|
||||
### _abstract async_ `_handle_http()`
|
||||
### _abstract async_ `_handle_http(*args, **kwargs)`
|
||||
|
||||
用于处理 HTTP 类型请求的函数
|
||||
|
||||
|
||||
### _abstract async_ `_handle_ws_reverse()`
|
||||
### _abstract async_ `_handle_ws_reverse(*args, **kwargs)`
|
||||
|
||||
用于处理 WebSocket 类型请求的函数
|
||||
|
||||
|
||||
## _class_ `HTTPRequest`
|
||||
|
||||
基类:`object`
|
||||
|
||||
HTTP 请求封装。参考 [asgi http scope](https://asgi.readthedocs.io/en/latest/specs/www.html#http-connection-scope)。
|
||||
|
||||
|
||||
## _class_ `HTTPResponse`
|
||||
|
||||
基类:`object`
|
||||
|
||||
HTTP 响应封装。参考 [asgi http scope](https://asgi.readthedocs.io/en/latest/specs/www.html#http-connection-scope)。
|
||||
|
||||
|
||||
## _class_ `WebSocket`
|
||||
|
||||
基类:`object`
|
||||
|
@ -79,7 +79,7 @@ FastAPI 驱动框架设置,详情参考 FastAPI 文档
|
||||
|
||||
## _class_ `Driver`
|
||||
|
||||
基类:[`nonebot.drivers.Driver`](README.md#nonebot.drivers.Driver)
|
||||
基类:[`nonebot.drivers.ReverseDriver`](README.md#nonebot.drivers.ReverseDriver)
|
||||
|
||||
FastAPI 驱动框架
|
||||
|
||||
|
@ -12,7 +12,7 @@ sidebarDepth: 0
|
||||
|
||||
## _class_ `Driver`
|
||||
|
||||
基类:[`nonebot.drivers.Driver`](README.md#nonebot.drivers.Driver)
|
||||
基类:[`nonebot.drivers.ReverseDriver`](README.md#nonebot.drivers.ReverseDriver)
|
||||
|
||||
Quart 驱动框架
|
||||
|
||||
|
@ -35,6 +35,21 @@ sidebarDepth: 0
|
||||
### `module`
|
||||
|
||||
|
||||
* **类型**
|
||||
|
||||
`Optional[ModuleType]`
|
||||
|
||||
|
||||
|
||||
* **说明**
|
||||
|
||||
事件响应器所在模块
|
||||
|
||||
|
||||
|
||||
### `plugin_name`
|
||||
|
||||
|
||||
* **类型**
|
||||
|
||||
`Optional[str]`
|
||||
@ -43,7 +58,37 @@ sidebarDepth: 0
|
||||
|
||||
* **说明**
|
||||
|
||||
事件响应器所在模块名称
|
||||
事件响应器所在插件名
|
||||
|
||||
|
||||
|
||||
### `module_name`
|
||||
|
||||
|
||||
* **类型**
|
||||
|
||||
`Optional[str]`
|
||||
|
||||
|
||||
|
||||
* **说明**
|
||||
|
||||
事件响应器所在模块名
|
||||
|
||||
|
||||
|
||||
### `module_prefix`
|
||||
|
||||
|
||||
* **类型**
|
||||
|
||||
`Optional[str]`
|
||||
|
||||
|
||||
|
||||
* **说明**
|
||||
|
||||
事件响应器所在模块前缀
|
||||
|
||||
|
||||
|
||||
|
@ -50,7 +50,7 @@ sidebarDepth: 0
|
||||
* **说明**: 插件模块对象
|
||||
|
||||
|
||||
### `export`
|
||||
### _property_ `export`
|
||||
|
||||
|
||||
* **类型**: `Export`
|
||||
@ -282,7 +282,7 @@ sidebarDepth: 0
|
||||
|
||||
|
||||
|
||||
## `on_startswith(msg, rule=None, **kwargs)`
|
||||
## `on_startswith(msg, rule=None, ignorecase=False, **kwargs)`
|
||||
|
||||
|
||||
* **说明**
|
||||
@ -294,12 +294,15 @@ sidebarDepth: 0
|
||||
* **参数**
|
||||
|
||||
|
||||
* `msg: str`: 指定消息开头内容
|
||||
* `msg: Union[str, Tuple[str, ...]]`: 指定消息开头内容
|
||||
|
||||
|
||||
* `rule: Optional[Union[Rule, T_RuleChecker]]`: 事件响应规则
|
||||
|
||||
|
||||
* `ignorecase: bool`: 是否忽略大小写
|
||||
|
||||
|
||||
* `permission: Optional[Permission]`: 事件响应权限
|
||||
|
||||
|
||||
@ -329,7 +332,7 @@ sidebarDepth: 0
|
||||
|
||||
|
||||
|
||||
## `on_endswith(msg, rule=None, **kwargs)`
|
||||
## `on_endswith(msg, rule=None, ignorecase=False, **kwargs)`
|
||||
|
||||
|
||||
* **说明**
|
||||
@ -341,12 +344,15 @@ sidebarDepth: 0
|
||||
* **参数**
|
||||
|
||||
|
||||
* `msg: str`: 指定消息结尾内容
|
||||
* `msg: Union[str, Tuple[str, ...]]`: 指定消息结尾内容
|
||||
|
||||
|
||||
* `rule: Optional[Union[Rule, T_RuleChecker]]`: 事件响应规则
|
||||
|
||||
|
||||
* `ignorecase: bool`: 是否忽略大小写
|
||||
|
||||
|
||||
* `permission: Optional[Permission]`: 事件响应权限
|
||||
|
||||
|
||||
@ -663,7 +669,7 @@ sidebarDepth: 0
|
||||
* `cmd: Union[str, Tuple[str, ...]]`: 命令前缀
|
||||
|
||||
|
||||
* `**kwargs`: 其他传递给 `on_command` 的参数,将会覆盖命令组默认值
|
||||
* `**kwargs`: 其他传递给 `on_shell_command` 的参数,将会覆盖命令组默认值
|
||||
|
||||
|
||||
|
||||
@ -940,7 +946,10 @@ sidebarDepth: 0
|
||||
* **参数**
|
||||
|
||||
|
||||
* `msg: str`: 指定消息开头内容
|
||||
* `msg: Union[str, Tuple[str, ...]]`: 指定消息开头内容
|
||||
|
||||
|
||||
* `ignorecase: bool`: 是否忽略大小写
|
||||
|
||||
|
||||
* `rule: Optional[Union[Rule, T_RuleChecker]]`: 事件响应规则
|
||||
@ -987,7 +996,10 @@ sidebarDepth: 0
|
||||
* **参数**
|
||||
|
||||
|
||||
* `msg: str`: 指定消息结尾内容
|
||||
* `msg: Union[str, Tuple[str, ...]]`: 指定消息结尾内容
|
||||
|
||||
|
||||
* `ignorecase: bool`: 是否忽略大小写
|
||||
|
||||
|
||||
* `rule: Optional[Union[Rule, T_RuleChecker]]`: 事件响应规则
|
||||
|
@ -91,7 +91,7 @@ Rule(async_function, run_sync(sync_function))
|
||||
|
||||
|
||||
|
||||
## `startswith(msg)`
|
||||
## `startswith(msg, ignorecase=False)`
|
||||
|
||||
|
||||
* **说明**
|
||||
@ -107,7 +107,7 @@ Rule(async_function, run_sync(sync_function))
|
||||
|
||||
|
||||
|
||||
## `endswith(msg)`
|
||||
## `endswith(msg, ignorecase=False)`
|
||||
|
||||
|
||||
* **说明**
|
||||
|
@ -46,7 +46,7 @@ sidebarDepth: 0
|
||||
|
||||
|
||||
|
||||
## `T_WebSocketConnectionHook`
|
||||
## `T_BotConnectionHook`
|
||||
|
||||
|
||||
* **类型**
|
||||
@ -57,12 +57,12 @@ sidebarDepth: 0
|
||||
|
||||
* **说明**
|
||||
|
||||
WebSocket 连接建立时执行的函数
|
||||
Bot 连接建立时执行的函数
|
||||
|
||||
|
||||
|
||||
|
||||
## `T_WebSocketDisconnectionHook`
|
||||
## `T_BotDisconnectionHook`
|
||||
|
||||
|
||||
* **类型**
|
||||
@ -73,7 +73,7 @@ sidebarDepth: 0
|
||||
|
||||
* **说明**
|
||||
|
||||
WebSocket 连接断开时执行的函数
|
||||
Bot 连接断开时执行的函数
|
||||
|
||||
|
||||
|
||||
|
@ -13,10 +13,10 @@ pip install nonebot-adapter-cqhttp
|
||||
QQ 协议端举例:
|
||||
|
||||
- [go-cqhttp](https://github.com/Mrs4s/go-cqhttp) (基于 [MiraiGo](https://github.com/Mrs4s/MiraiGo))
|
||||
- [cqhttp-mirai-embedded](https://github.com/yyuueexxiinngg/cqhttp-mirai/tree/embedded)
|
||||
- [Mirai](https://github.com/mamoe/mirai) + [cqhttp-mirai](https://github.com/yyuueexxiinngg/cqhttp-mirai)
|
||||
- [onebot-kotlin](https://github.com/yyuueexxiinngg/onebot-kotlin)
|
||||
- [Mirai](https://github.com/mamoe/mirai) + [onebot-mirai](https://github.com/yyuueexxiinngg/onebot-kotlin)
|
||||
- [Mirai](https://github.com/mamoe/mirai) + [Mirai Native](https://github.com/iTXTech/mirai-native) + [CQHTTP](https://github.com/richardchien/coolq-http-api)
|
||||
- [OICQ-http-api](https://github.com/takayama-lily/onebot) (基于 [OICQ](https://github.com/takayama-lily/oicq))
|
||||
- [node-onebot](https://github.com/takayama-lily/node-onebot) (基于 [abot](https://github.com/takayama-lily/abot), [OICQ](https://github.com/takayama-lily/oicq))
|
||||
|
||||
这里以 [go-cqhttp](https://github.com/Mrs4s/go-cqhttp) 为例
|
||||
|
||||
@ -24,59 +24,74 @@ QQ 协议端举例:
|
||||
2. 运行 exe 文件或者使用 `./go-cqhttp` 启动
|
||||
3. 生成默认配置文件并修改默认配置
|
||||
|
||||
```hjson{2,3,35-36,42}
|
||||
{
|
||||
```yml{2,3,18,57,58}
|
||||
account:
|
||||
uin: 机器人QQ号
|
||||
password: 机器人密码
|
||||
encrypt_password: false
|
||||
password_encrypted: ""
|
||||
enable_db: true
|
||||
access_token: ""
|
||||
relogin: {
|
||||
enabled: true
|
||||
relogin_delay: 3
|
||||
max_relogin_times: 0
|
||||
}
|
||||
_rate_limit: {
|
||||
password: "机器人密码"
|
||||
encrypt: false
|
||||
relogin:
|
||||
disabled: false
|
||||
delay: 3
|
||||
interval: 0
|
||||
max-times: 0
|
||||
|
||||
use-sso-address: true
|
||||
|
||||
heartbeat:
|
||||
disabled: false
|
||||
interval: 5
|
||||
|
||||
message:
|
||||
post-format: array
|
||||
ignore-invalid-cqcode: false
|
||||
force-fragment: false
|
||||
fix-url: false
|
||||
proxy-rewrite: ""
|
||||
report-self-message: false
|
||||
remove-reply-at: false
|
||||
extra-reply-data: false
|
||||
|
||||
output:
|
||||
log-level: warn
|
||||
debug: false
|
||||
|
||||
default-middlewares: &default
|
||||
access-token: ""
|
||||
filter: ""
|
||||
rate-limit:
|
||||
enabled: false
|
||||
frequency: 1
|
||||
bucket_size: 1
|
||||
}
|
||||
ignore_invalid_cqcode: false
|
||||
force_fragmented: false
|
||||
heartbeat_interval: 0
|
||||
http_config: {
|
||||
enabled: false
|
||||
host: "0.0.0.0"
|
||||
port: 5700
|
||||
timeout: 0
|
||||
post_urls: {}
|
||||
}
|
||||
ws_config: {
|
||||
enabled: false
|
||||
host: "0.0.0.0"
|
||||
port: 6700
|
||||
}
|
||||
ws_reverse_servers: [
|
||||
{
|
||||
enabled: true
|
||||
reverse_url: ws://127.0.0.1:8080/cqhttp/ws
|
||||
reverse_api_url: ws://you_websocket_api.server
|
||||
reverse_event_url: ws://you_websocket_event.server
|
||||
reverse_reconnect_interval: 3000
|
||||
}
|
||||
]
|
||||
post_message_format: array
|
||||
use_sso_address: false
|
||||
debug: false
|
||||
log_level: ""
|
||||
web_ui: {
|
||||
enabled: false
|
||||
host: 127.0.0.1
|
||||
web_ui_port: 9999
|
||||
web_input: false
|
||||
}
|
||||
}
|
||||
bucket: 1
|
||||
|
||||
servers:
|
||||
- http:
|
||||
disabled: true
|
||||
host: 127.0.0.1
|
||||
port: 5700
|
||||
timeout: 5
|
||||
middlewares:
|
||||
<<: *default
|
||||
post:
|
||||
|
||||
- ws:
|
||||
disabled: true
|
||||
host: 127.0.0.1
|
||||
port: 6700
|
||||
middlewares:
|
||||
<<: *default
|
||||
|
||||
- ws-reverse:
|
||||
disabled: false
|
||||
universal: ws://127.0.0.1:8080/cqhttp/ws
|
||||
api: ws://your_websocket_api.server
|
||||
event: ws://your_websocket_event.server
|
||||
reconnect-interval: 3000
|
||||
middlewares:
|
||||
<<: *default
|
||||
|
||||
database:
|
||||
leveldb:
|
||||
enable: true
|
||||
```
|
||||
|
||||
其中 `ws://127.0.0.1:8080/cqhttp/ws` 中的 `127.0.0.1` 和 `8080` 应分别对应 nonebot 配置的 HOST 和 PORT。
|
||||
|
@ -8,7 +8,7 @@
|
||||
nb plugin new
|
||||
```
|
||||
|
||||
插件通常有两种形式,下面分别介绍
|
||||
下面分别对两种通常的插件形式做具体介绍
|
||||
|
||||
## 单文件形式
|
||||
|
||||
|
@ -37,7 +37,8 @@ AweSome-Bot
|
||||
:::warning 提示
|
||||
如果您使用如 `VSCode` / `PyCharm` 等 IDE 启动 nonebot,请检查 IDE 当前工作空间目录是否与当前侧边栏打开目录一致。
|
||||
|
||||
* 在二者不一致的环境下可能导致 nonebot 读取配置文件和插件等不符合预期
|
||||
- 注意:在二者不一致的环境下可能导致 nonebot 读取配置文件和插件等不符合预期
|
||||
|
||||
:::
|
||||
|
||||
通过 `nb-cli`
|
||||
|
@ -103,15 +103,15 @@ async def raw_handler(bot: DingBot, event: MessageEvent):
|
||||
|
||||
![机器人所在群的 Webhook 地址](./images/ding/webhook.png)
|
||||
|
||||
获取到Webhook地址后,用户可以向这个地址发起HTTP POST 请求,即可实现给该钉钉群发送消息。
|
||||
获取到 Webhook 地址后,用户可以向这个地址发起 HTTP POST 请求,即可实现给该钉钉群发送消息。
|
||||
|
||||
对于这种通过 Webhook 推送的消息,钉钉需要开发者进行安全方面的设置(目前有3种安全设置方式,请根据需要选择一种),如下:
|
||||
对于这种通过 Webhook 推送的消息,钉钉需要开发者进行安全方面的设置(目前有 3 种安全设置方式,请根据需要选择一种),如下:
|
||||
|
||||
1. **自定义关键词:** 最多可以设置10个关键词,消息中至少包含其中1个关键词才可以发送成功。
|
||||
1. **自定义关键词:** 最多可以设置 10 个关键词,消息中至少包含其中 1 个关键词才可以发送成功。
|
||||
例如添加了一个自定义关键词:监控报警,则这个机器人所发送的消息,必须包含监控报警这个词,才能发送成功。
|
||||
2. **加签:** 发送请求时带上验签的值,可以在机器人设置里看到密钥。
|
||||
![加签密钥](./images/ding/jiaqian.png)
|
||||
3. **IP地址(段):** 设定后,只有来自IP地址范围内的请求才会被正常处理。支持两种设置方式:IP地址和IP地址段,暂不支持IPv6地址白名单。
|
||||
3. **IP 地址(段):** 设定后,只有来自 IP 地址范围内的请求才会被正常处理。支持两种设置方式:IP 地址和 IP 地址段,暂不支持 IPv6 地址白名单。
|
||||
|
||||
如果你选择 1/3 两种安全设置,你需要自己确认当前网络和发送的消息能被钉钉接受,然后使用 `bot.send` 的时候将 webhook 地址传入 webhook 参数即可。
|
||||
|
||||
|
@ -72,7 +72,7 @@ pip install . # 不推荐
|
||||
适配器可以通过 `nb-cli` 在创建项目时根据你的选择自动安装,也可以自行使用 `pip` 安装
|
||||
|
||||
```bash
|
||||
pip install nonebot-adapter-<adapter-name>
|
||||
pip install <adapter-name>
|
||||
```
|
||||
|
||||
```bash
|
||||
@ -88,9 +88,9 @@ nb adapter list
|
||||
# 列出所有的插件
|
||||
nb plugin list
|
||||
# 搜索插件
|
||||
nb plugin search xxx
|
||||
nb plugin search <plugin-name>
|
||||
# 安装插件
|
||||
nb plugin install xxx
|
||||
nb plugin install <plugin-name>
|
||||
```
|
||||
|
||||
如果急于上线 Bot 或想要使用现成的插件,以下插件可作为参考:
|
||||
@ -106,4 +106,10 @@ nb plugin install xxx
|
||||
|
||||
### 其他插件
|
||||
|
||||
还有更多的插件在 [这里](/plugin-store.md) 等着你发现~
|
||||
还有更多的插件在 [这里](/store.html) 等着你发现~
|
||||
|
||||
## 安装开发环境(可选)
|
||||
|
||||
NoneBot v2 全程使用 `VSCode` 搭配 `Pylance` 的开发环境进行开发,在严格的类型检查下,NoneBot v2 具有完善的类型设计与声明。
|
||||
|
||||
在围绕 NoneBot v2 进行开发时,使用 `VSCode` 搭配 `Pylance` 进行类型检查是非常推荐的。这有利于统一代码风格及避免低级错误的发生。
|
||||
|
@ -39,10 +39,11 @@ if __name__ == "__main__":
|
||||
|
||||
在 `bot.py` 文件中添加以下行:
|
||||
|
||||
```python{5}
|
||||
```python{6}
|
||||
import nonebot
|
||||
|
||||
nonebot.init()
|
||||
|
||||
# 加载插件目录,该目录下为各插件,以下划线开头的插件将不会被加载
|
||||
nonebot.load_plugins("awesome_bot/plugins")
|
||||
|
||||
@ -68,10 +69,11 @@ if __name__ == "__main__":
|
||||
|
||||
在 `bot.py` 文件中添加以下行:
|
||||
|
||||
```python{5,7}
|
||||
```python{6,8}
|
||||
import nonebot
|
||||
|
||||
nonebot.init()
|
||||
|
||||
# 加载一个 pip 安装的插件
|
||||
nonebot.load_plugin("nonebot_plugin_status")
|
||||
# 加载本地的单独插件
|
||||
@ -83,6 +85,63 @@ if __name__ == "__main__":
|
||||
nonebot.run()
|
||||
```
|
||||
|
||||
## 从 json 文件中加载插件
|
||||
|
||||
在 `bot.py` 文件中添加以下行:
|
||||
|
||||
```python{6}
|
||||
import nonebot
|
||||
|
||||
nonebot.init()
|
||||
|
||||
# 从 plugin.json 加载插件
|
||||
nonebot.load_from_json("plugin.json")
|
||||
|
||||
app = nonebot.get_asgi()
|
||||
|
||||
if __name__ == "__main__":
|
||||
nonebot.run()
|
||||
```
|
||||
|
||||
**json 文件示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"plugins": ["nonebot_plugin_status", "awesome_bot.plugins.xxx"],
|
||||
"plugin_dirs": ["awesome_bot/plugins"]
|
||||
}
|
||||
```
|
||||
|
||||
## 从 toml 文件中加载插件
|
||||
|
||||
在 `bot.py` 文件中添加以下行:
|
||||
|
||||
```python{6}
|
||||
import nonebot
|
||||
|
||||
nonebot.init()
|
||||
|
||||
# 从 pyproject.toml 加载插件
|
||||
nonebot.load_from_toml("pyproject.toml")
|
||||
|
||||
app = nonebot.get_asgi()
|
||||
|
||||
if __name__ == "__main__":
|
||||
nonebot.run()
|
||||
```
|
||||
|
||||
**toml 文件示例:**
|
||||
|
||||
```toml
|
||||
[nonebot.plugins]
|
||||
plugins = ["nonebot_plugin_status", "awesome_bot.plugins.xxx"]
|
||||
plugin_dirs = ["awesome_bot/plugins"]
|
||||
```
|
||||
|
||||
::: tip
|
||||
nb-cli 默认使用 `pyproject.toml` 加载插件。
|
||||
:::
|
||||
|
||||
## 子插件(嵌套插件)
|
||||
|
||||
在插件中同样可以加载子插件,例如如下插件目录结构:
|
||||
|
@ -67,6 +67,12 @@ pip install nonebot-adapter-mirai
|
||||
|
||||
4. 修改配置文件
|
||||
|
||||
::: warning
|
||||
|
||||
由于NoneBot2的架构设计等原因, 部分功能的支持可能需要推迟到MAH 2.0正式发布后再完成
|
||||
|
||||
:::
|
||||
|
||||
::: tip
|
||||
|
||||
在此之前, 你可能需要了解我们为 MAH 设计的两种通信方式
|
||||
@ -92,6 +98,13 @@ pip install nonebot-adapter-mirai
|
||||
|
||||
- 这是当使用正向 Websocket 时的配置举例
|
||||
|
||||
::: warning
|
||||
|
||||
在默认情况下, NoneBot和MAH会同时监听8080端口, 这会导致端口冲突的错误
|
||||
请确保二者配置不在同一端口下
|
||||
|
||||
:::
|
||||
|
||||
- MAH 的`setting.yml`文件
|
||||
|
||||
- ```yaml
|
||||
@ -106,9 +119,12 @@ pip install nonebot-adapter-mirai
|
||||
- `.env`文件
|
||||
|
||||
- ```shell
|
||||
PORT=2333
|
||||
|
||||
MIRAI_AUTH_KEY=1234567890
|
||||
MIRAI_HOST=127.0.0.1 # 当MAH运行在本机时
|
||||
MIRAI_PORT=8080 # MAH的监听端口
|
||||
PORT=2333 # 防止与MAH接口冲突
|
||||
```
|
||||
|
||||
- `bot.py`文件
|
||||
@ -155,7 +171,7 @@ pip install nonebot-adapter-mirai
|
||||
|
||||
- ```shell
|
||||
HOST=127.0.0.1 # 当MAH运行在本机时
|
||||
PORT=2333
|
||||
PORT=2333 # 防止与MAH接口冲突
|
||||
|
||||
MIRAI_AUTH_KEY=1234567890
|
||||
MIRAI_HOST=127.0.0.1 # 当MAH运行在本机时
|
||||
|
@ -41,7 +41,7 @@ NoneBot.adapters.cqhttp.message 模块
|
||||
|
||||
.. automodule:: nonebot.adapters.cqhttp.message
|
||||
:members:
|
||||
:private-members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
NoneBot.adapters.cqhttp.permission 模块
|
||||
|
@ -179,15 +179,17 @@ def init(*, _env_file: Optional[str] = None, **kwargs):
|
||||
"""
|
||||
global _driver
|
||||
if not _driver:
|
||||
logger.info("NoneBot is initializing...")
|
||||
logger.success("NoneBot is initializing...")
|
||||
env = Env()
|
||||
logger.opt(
|
||||
colors=True).info(f"Current <y><b>Env: {env.environment}</b></y>")
|
||||
config = Config(**kwargs,
|
||||
_common_config=env.dict(),
|
||||
_env_file=_env_file or f".env.{env.environment}")
|
||||
|
||||
default_filter.level = "DEBUG" if config.debug else "INFO"
|
||||
default_filter.level = (
|
||||
"DEBUG" if config.debug else
|
||||
"INFO") if config.log_level is None else config.log_level
|
||||
logger.opt(
|
||||
colors=True).info(f"Current <y><b>Env: {env.environment}</b></y>")
|
||||
logger.opt(colors=True).debug(
|
||||
f"Loaded <y><b>Config</b></y>: {escape_tag(str(config.dict()))}")
|
||||
|
||||
@ -223,7 +225,7 @@ def run(host: Optional[str] = None,
|
||||
nonebot.run(host="127.0.0.1", port=8080)
|
||||
|
||||
"""
|
||||
logger.info("Running NoneBot...")
|
||||
logger.success("Running NoneBot...")
|
||||
get_driver().run(host, port, *args, **kwargs)
|
||||
|
||||
|
||||
|
@ -11,8 +11,8 @@ from copy import copy
|
||||
from functools import reduce, partial
|
||||
from typing_extensions import Protocol
|
||||
from dataclasses import dataclass, field
|
||||
from typing import (Any, Set, Dict, Union, TypeVar, Mapping, Optional, Iterable,
|
||||
Awaitable, TYPE_CHECKING)
|
||||
from typing import (Any, Set, List, Dict, Union, TypeVar, Mapping, Optional,
|
||||
Iterable, Awaitable, TYPE_CHECKING)
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@ -152,7 +152,6 @@ class Bot(abc.ABC):
|
||||
:参数:
|
||||
|
||||
* ``api: str``: API 名称
|
||||
* ``self_id: Optional[str]``: 指定调用 API 的机器人
|
||||
* ``**data``: API 数据
|
||||
|
||||
:示例:
|
||||
@ -176,11 +175,7 @@ class Bot(abc.ABC):
|
||||
result = None
|
||||
|
||||
try:
|
||||
if "self_id" in data and data["self_id"]:
|
||||
bot = self.driver.bots[str(data["self_id"])]
|
||||
result = await bot._call_api(api, **data)
|
||||
else:
|
||||
result = await self._call_api(api, **data)
|
||||
result = await self._call_api(api, **data)
|
||||
except Exception as e:
|
||||
exception = e
|
||||
|
||||
@ -316,7 +311,7 @@ class MessageSegment(abc.ABC, Mapping):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Message(list, abc.ABC):
|
||||
class Message(List[T_MessageSegment], abc.ABC):
|
||||
"""消息数组"""
|
||||
|
||||
def __init__(self,
|
||||
|
@ -15,10 +15,9 @@ NoneBot 使用 `pydantic`_ 以及 `python-dotenv`_ 来读取配置。
|
||||
"""
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from datetime import timedelta
|
||||
from ipaddress import IPv4Address
|
||||
from typing import Any, Set, Dict, Tuple, Mapping, Optional
|
||||
from typing import Any, Set, Dict, Union, Tuple, Mapping, Optional
|
||||
|
||||
from pydantic import BaseSettings, IPvAnyAddress
|
||||
from pydantic.env_settings import SettingsError, InitSettingsSource, EnvSettingsSource
|
||||
@ -173,6 +172,25 @@ class Config(BaseConfig):
|
||||
|
||||
是否以调试模式运行 NoneBot。
|
||||
"""
|
||||
log_level: Optional[Union[int, str]] = None
|
||||
"""
|
||||
- **类型**: ``Union[int, str]``
|
||||
- **默认值**: ``None``
|
||||
|
||||
:说明:
|
||||
|
||||
配置 NoneBot 日志输出等级,可以为 ``int`` 类型等级或等级名称,参考 `loguru 日志等级`_。
|
||||
|
||||
:示例:
|
||||
|
||||
.. code-block:: default
|
||||
|
||||
LOG_LEVEL=25
|
||||
LOG_LEVEL=INFO
|
||||
|
||||
.. _loguru 日志等级:
|
||||
https://loguru.readthedocs.io/en/stable/api/logger.html#levels
|
||||
"""
|
||||
|
||||
# bot connection configs
|
||||
api_root: Dict[str, str] = {}
|
||||
|
@ -7,11 +7,12 @@
|
||||
|
||||
import abc
|
||||
import asyncio
|
||||
from typing import Set, Dict, Type, Optional, Callable, TYPE_CHECKING
|
||||
from typing import (Any, Set, List, Dict, Type, Tuple, Optional, Callable,
|
||||
MutableMapping, TYPE_CHECKING)
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.config import Env, Config
|
||||
from nonebot.typing import T_WebSocketConnectionHook, T_WebSocketDisconnectionHook
|
||||
from nonebot.typing import T_BotConnectionHook, T_BotDisconnectionHook
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from nonebot.adapters import Bot
|
||||
@ -19,7 +20,7 @@ if TYPE_CHECKING:
|
||||
|
||||
class Driver(abc.ABC):
|
||||
"""
|
||||
Driver 基类。将后端框架封装,以满足适配器使用。
|
||||
Driver 基类。
|
||||
"""
|
||||
|
||||
_adapters: Dict[str, Type["Bot"]] = {}
|
||||
@ -27,15 +28,15 @@ class Driver(abc.ABC):
|
||||
:类型: ``Dict[str, Type[Bot]]``
|
||||
:说明: 已注册的适配器列表
|
||||
"""
|
||||
_ws_connection_hook: Set[T_WebSocketConnectionHook] = set()
|
||||
_bot_connection_hook: Set[T_BotConnectionHook] = set()
|
||||
"""
|
||||
:类型: ``Set[T_WebSocketConnectionHook]``
|
||||
:说明: WebSocket 连接建立时执行的函数
|
||||
:类型: ``Set[T_BotConnectionHook]``
|
||||
:说明: Bot 连接建立时执行的函数
|
||||
"""
|
||||
_ws_disconnection_hook: Set[T_WebSocketDisconnectionHook] = set()
|
||||
_bot_disconnection_hook: Set[T_BotDisconnectionHook] = set()
|
||||
"""
|
||||
:类型: ``Set[T_WebSocketDisconnectionHook]``
|
||||
:说明: WebSocket 连接断开时执行的函数
|
||||
:类型: ``Set[T_BotDisconnectionHook]``
|
||||
:说明: Bot 连接断开时执行的函数
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
@ -62,6 +63,18 @@ class Driver(abc.ABC):
|
||||
:说明: 已连接的 Bot
|
||||
"""
|
||||
|
||||
@property
|
||||
def bots(self) -> Dict[str, "Bot"]:
|
||||
"""
|
||||
:类型:
|
||||
|
||||
``Dict[str, Bot]``
|
||||
:说明:
|
||||
|
||||
获取当前所有已连接的 Bot
|
||||
"""
|
||||
return self._clients
|
||||
|
||||
def register_adapter(self, name: str, adapter: Type["Bot"], **kwargs):
|
||||
"""
|
||||
:说明:
|
||||
@ -88,108 +101,12 @@ class Driver(abc.ABC):
|
||||
"""驱动类型名称"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def server_app(self):
|
||||
"""驱动 APP 对象"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def asgi(self):
|
||||
"""驱动 ASGI 对象"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def logger(self):
|
||||
"""驱动专属 logger 日志记录器"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def bots(self) -> Dict[str, "Bot"]:
|
||||
"""
|
||||
:类型:
|
||||
|
||||
``Dict[str, Bot]``
|
||||
:说明:
|
||||
|
||||
获取当前所有已连接的 Bot
|
||||
"""
|
||||
return self._clients
|
||||
|
||||
@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_WebSocketConnectionHook) -> T_WebSocketConnectionHook:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
装饰一个函数使他在 bot 通过 WebSocket 连接成功时执行。
|
||||
|
||||
:函数参数:
|
||||
|
||||
* ``bot: Bot``: 当前连接上的 Bot 对象
|
||||
"""
|
||||
self._ws_connection_hook.add(func)
|
||||
return func
|
||||
|
||||
def on_bot_disconnect(
|
||||
self,
|
||||
func: T_WebSocketDisconnectionHook) -> T_WebSocketDisconnectionHook:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
装饰一个函数使他在 bot 通过 WebSocket 连接断开时执行。
|
||||
|
||||
:函数参数:
|
||||
|
||||
* ``bot: Bot``: 当前连接上的 Bot 对象
|
||||
"""
|
||||
self._ws_disconnection_hook.add(func)
|
||||
return func
|
||||
|
||||
def _bot_connect(self, bot: "Bot") -> None:
|
||||
"""在 WebSocket 连接成功后,调用该函数来注册 bot 对象"""
|
||||
self._clients[bot.self_id] = bot
|
||||
|
||||
async def _run_hook(bot: "Bot") -> None:
|
||||
coros = list(map(lambda x: x(bot), self._ws_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._ws_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))
|
||||
|
||||
@abc.abstractmethod
|
||||
def run(self,
|
||||
host: Optional[str] = None,
|
||||
@ -212,17 +129,174 @@ class Driver(abc.ABC):
|
||||
f"<g>Loaded adapters: {', '.join(self._adapters)}</g>")
|
||||
|
||||
@abc.abstractmethod
|
||||
async def _handle_http(self):
|
||||
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 对象"""
|
||||
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 ForwardDriver(Driver):
|
||||
pass
|
||||
|
||||
|
||||
class ReverseDriver(Driver):
|
||||
"""
|
||||
Reverse Driver 基类。将后端框架封装,以满足适配器使用。
|
||||
"""
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def server_app(self):
|
||||
"""驱动 APP 对象"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def asgi(self):
|
||||
"""驱动 ASGI 对象"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
async def _handle_http(self, *args, **kwargs):
|
||||
"""用于处理 HTTP 类型请求的函数"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
async def _handle_ws_reverse(self):
|
||||
async def _handle_ws_reverse(self, *args, **kwargs):
|
||||
"""用于处理 WebSocket 类型请求的函数"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class WebSocket(object):
|
||||
class HTTPRequest:
|
||||
"""HTTP 请求封装。参考 `asgi http scope`_。
|
||||
|
||||
.. _asgi http scope:
|
||||
https://asgi.readthedocs.io/en/latest/specs/www.html#http-connection-scope
|
||||
"""
|
||||
|
||||
def __init__(self, scope: MutableMapping[str, Any]):
|
||||
self._scope = scope
|
||||
|
||||
@property
|
||||
def type(self) -> str:
|
||||
return "http"
|
||||
|
||||
@property
|
||||
def scope(self) -> MutableMapping[str, Any]:
|
||||
return self._scope
|
||||
|
||||
@property
|
||||
def http_version(self) -> str:
|
||||
raise self.scope["http_version"]
|
||||
|
||||
@property
|
||||
def method(self) -> str:
|
||||
raise self.scope["method"]
|
||||
|
||||
@property
|
||||
def schema(self) -> str:
|
||||
raise self.scope["schema"]
|
||||
|
||||
@property
|
||||
def path(self) -> str:
|
||||
return self.scope["path"]
|
||||
|
||||
@property
|
||||
def query_string(self) -> bytes:
|
||||
return self.scope["query_string"]
|
||||
|
||||
@property
|
||||
def headers(self) -> List[Tuple[bytes, bytes]]:
|
||||
return list(self.scope["headers"])
|
||||
|
||||
@property
|
||||
def body(self) -> bytes:
|
||||
return self.scope["body"]
|
||||
|
||||
|
||||
class HTTPResponse:
|
||||
"""HTTP 响应封装。参考 `asgi http scope`_。
|
||||
|
||||
.. _asgi http scope:
|
||||
https://asgi.readthedocs.io/en/latest/specs/www.html#http-connection-scope
|
||||
"""
|
||||
|
||||
def __init__(self, status: int, headers: List[Tuple[bytes, bytes]],
|
||||
body: bytes):
|
||||
self.status: int = status
|
||||
self.headers: List[Tuple[bytes, bytes]] = headers
|
||||
self.body: bytes = body
|
||||
|
||||
@property
|
||||
def type(self) -> str:
|
||||
return "http"
|
||||
|
||||
|
||||
class WebSocket:
|
||||
"""WebSocket 连接封装,统一接口方便外部调用。"""
|
||||
|
||||
@abc.abstractmethod
|
||||
|
@ -24,7 +24,7 @@ from nonebot.typing import overrides
|
||||
from nonebot.utils import DataclassEncoder
|
||||
from nonebot.exception import RequestDenied
|
||||
from nonebot.config import Env, Config as NoneBotConfig
|
||||
from nonebot.drivers import Driver as BaseDriver, WebSocket as BaseWebSocket
|
||||
from nonebot.drivers import ReverseDriver, WebSocket as BaseWebSocket
|
||||
|
||||
|
||||
class Config(BaseSettings):
|
||||
@ -76,7 +76,7 @@ class Config(BaseSettings):
|
||||
extra = "ignore"
|
||||
|
||||
|
||||
class Driver(BaseDriver):
|
||||
class Driver(ReverseDriver):
|
||||
"""
|
||||
FastAPI 驱动框架
|
||||
|
||||
@ -106,40 +106,40 @@ class Driver(BaseDriver):
|
||||
self._server_app.websocket("/{adapter}/ws/")(self._handle_ws_reverse)
|
||||
|
||||
@property
|
||||
@overrides(BaseDriver)
|
||||
@overrides(ReverseDriver)
|
||||
def type(self) -> str:
|
||||
"""驱动名称: ``fastapi``"""
|
||||
return "fastapi"
|
||||
|
||||
@property
|
||||
@overrides(BaseDriver)
|
||||
@overrides(ReverseDriver)
|
||||
def server_app(self) -> FastAPI:
|
||||
"""``FastAPI APP`` 对象"""
|
||||
return self._server_app
|
||||
|
||||
@property
|
||||
@overrides(BaseDriver)
|
||||
@overrides(ReverseDriver)
|
||||
def asgi(self):
|
||||
"""``FastAPI APP`` 对象"""
|
||||
return self._server_app
|
||||
|
||||
@property
|
||||
@overrides(BaseDriver)
|
||||
@overrides(ReverseDriver)
|
||||
def logger(self) -> logging.Logger:
|
||||
"""fastapi 使用的 logger"""
|
||||
return logging.getLogger("fastapi")
|
||||
|
||||
@overrides(BaseDriver)
|
||||
@overrides(ReverseDriver)
|
||||
def on_startup(self, func: Callable) -> Callable:
|
||||
"""参考文档: `Events <https://fastapi.tiangolo.com/advanced/events/#startup-event>`_"""
|
||||
return self.server_app.on_event("startup")(func)
|
||||
|
||||
@overrides(BaseDriver)
|
||||
@overrides(ReverseDriver)
|
||||
def on_shutdown(self, func: Callable) -> Callable:
|
||||
"""参考文档: `Events <https://fastapi.tiangolo.com/advanced/events/#startup-event>`_"""
|
||||
return self.server_app.on_event("shutdown")(func)
|
||||
|
||||
@overrides(BaseDriver)
|
||||
@overrides(ReverseDriver)
|
||||
def run(self,
|
||||
host: Optional[str] = None,
|
||||
port: Optional[int] = None,
|
||||
@ -176,7 +176,7 @@ class Driver(BaseDriver):
|
||||
log_config=LOGGING_CONFIG,
|
||||
**kwargs)
|
||||
|
||||
@overrides(BaseDriver)
|
||||
@overrides(ReverseDriver)
|
||||
async def _handle_http(self, adapter: str, request: Request):
|
||||
data = await request.body()
|
||||
data_dict = json.loads(data.decode())
|
||||
@ -211,7 +211,7 @@ class Driver(BaseDriver):
|
||||
asyncio.create_task(bot.handle_message(data_dict))
|
||||
return Response("", 204)
|
||||
|
||||
@overrides(BaseDriver)
|
||||
@overrides(ReverseDriver)
|
||||
async def _handle_ws_reverse(self, adapter: str,
|
||||
websocket: FastAPIWebSocket):
|
||||
ws = WebSocket(websocket)
|
||||
|
@ -16,8 +16,7 @@ import uvicorn
|
||||
|
||||
from nonebot.config import Config as NoneBotConfig
|
||||
from nonebot.config import Env
|
||||
from nonebot.drivers import Driver as BaseDriver
|
||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||
from nonebot.drivers import ReverseDriver, WebSocket as BaseWebSocket
|
||||
from nonebot.exception import RequestDenied
|
||||
from nonebot.log import logger
|
||||
from nonebot.typing import overrides
|
||||
@ -35,7 +34,7 @@ except ImportError:
|
||||
_AsyncCallable = TypeVar("_AsyncCallable", bound=Callable[..., Coroutine])
|
||||
|
||||
|
||||
class Driver(BaseDriver):
|
||||
class Driver(ReverseDriver):
|
||||
"""
|
||||
Quart 驱动框架
|
||||
|
||||
@ -45,7 +44,7 @@ class Driver(BaseDriver):
|
||||
* ``/{adapter name}/ws``: WebSocket 上报
|
||||
"""
|
||||
|
||||
@overrides(BaseDriver)
|
||||
@overrides(ReverseDriver)
|
||||
def __init__(self, env: Env, config: NoneBotConfig):
|
||||
super().__init__(env, config)
|
||||
|
||||
@ -57,40 +56,40 @@ class Driver(BaseDriver):
|
||||
view_func=self._handle_ws_reverse)
|
||||
|
||||
@property
|
||||
@overrides(BaseDriver)
|
||||
@overrides(ReverseDriver)
|
||||
def type(self) -> str:
|
||||
"""驱动名称: ``quart``"""
|
||||
return 'quart'
|
||||
|
||||
@property
|
||||
@overrides(BaseDriver)
|
||||
@overrides(ReverseDriver)
|
||||
def server_app(self) -> Quart:
|
||||
"""``Quart`` 对象"""
|
||||
return self._server_app
|
||||
|
||||
@property
|
||||
@overrides(BaseDriver)
|
||||
@overrides(ReverseDriver)
|
||||
def asgi(self):
|
||||
"""``Quart`` 对象"""
|
||||
return self._server_app
|
||||
|
||||
@property
|
||||
@overrides(BaseDriver)
|
||||
@overrides(ReverseDriver)
|
||||
def logger(self):
|
||||
"""fastapi 使用的 logger"""
|
||||
return self._server_app.logger
|
||||
|
||||
@overrides(BaseDriver)
|
||||
@overrides(ReverseDriver)
|
||||
def on_startup(self, func: _AsyncCallable) -> _AsyncCallable:
|
||||
"""参考文档: `Startup and Shutdown <https://pgjones.gitlab.io/quart/how_to_guides/startup_shutdown.html>`_"""
|
||||
return self.server_app.before_serving(func) # type: ignore
|
||||
|
||||
@overrides(BaseDriver)
|
||||
@overrides(ReverseDriver)
|
||||
def on_shutdown(self, func: _AsyncCallable) -> _AsyncCallable:
|
||||
"""参考文档: `Startup and Shutdown <https://pgjones.gitlab.io/quart/how_to_guides/startup_shutdown.html>`_"""
|
||||
return self.server_app.after_serving(func) # type: ignore
|
||||
|
||||
@overrides(BaseDriver)
|
||||
@overrides(ReverseDriver)
|
||||
def run(self,
|
||||
host: Optional[str] = None,
|
||||
port: Optional[int] = None,
|
||||
@ -126,7 +125,7 @@ class Driver(BaseDriver):
|
||||
log_config=LOGGING_CONFIG,
|
||||
**kwargs)
|
||||
|
||||
@overrides(BaseDriver)
|
||||
@overrides(ReverseDriver)
|
||||
async def _handle_http(self, adapter: str):
|
||||
request: Request = _request
|
||||
|
||||
@ -157,7 +156,7 @@ class Driver(BaseDriver):
|
||||
asyncio.create_task(bot.handle_message(data))
|
||||
return Response('', 204)
|
||||
|
||||
@overrides(BaseDriver)
|
||||
@overrides(ReverseDriver)
|
||||
async def _handle_ws_reverse(self, adapter: str):
|
||||
websocket: QuartWebSocket = _websocket
|
||||
if adapter not in self._adapters:
|
||||
|
@ -12,11 +12,12 @@ NoneBot 使用 `loguru`_ 来记录日志信息。
|
||||
|
||||
import sys
|
||||
import logging
|
||||
from typing import Union
|
||||
|
||||
from loguru import logger as logger_
|
||||
import loguru
|
||||
|
||||
# logger = logging.getLogger("nonebot")
|
||||
logger = logger_
|
||||
logger: "loguru.Logger" = loguru.logger
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -44,11 +45,16 @@ logger = logger_
|
||||
class Filter:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.level = "DEBUG"
|
||||
self.level: Union[int, str] = "DEBUG"
|
||||
|
||||
def __call__(self, record):
|
||||
module = sys.modules.get(record["name"])
|
||||
if module:
|
||||
plugin_name = getattr(module, "__plugin_name__", record["name"])
|
||||
record["name"] = plugin_name
|
||||
record["name"] = record["name"].split(".")[0]
|
||||
levelno = logger.level(self.level).no
|
||||
levelno = logger.level(self.level).no if isinstance(self.level,
|
||||
str) else self.level
|
||||
return record["level"].no >= levelno
|
||||
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
"""
|
||||
|
||||
from functools import wraps
|
||||
from types import ModuleType
|
||||
from datetime import datetime
|
||||
from contextvars import ContextVar
|
||||
from collections import defaultdict
|
||||
@ -36,6 +37,9 @@ current_event: ContextVar = ContextVar("current_event")
|
||||
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
|
||||
@ -46,7 +50,7 @@ class MatcherMeta(type):
|
||||
expire_time: Optional[datetime]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (f"<Matcher from {self.module or 'unknow'}, "
|
||||
return (f"<Matcher from {self.module_name or 'unknown'}, "
|
||||
f"type={self.type}, priority={self.priority}, "
|
||||
f"temp={self.temp}>")
|
||||
|
||||
@ -56,10 +60,28 @@ class MatcherMeta(type):
|
||||
|
||||
class Matcher(metaclass=MatcherMeta):
|
||||
"""事件响应器类"""
|
||||
module: Optional[str] = None
|
||||
module: Optional[ModuleType] = None
|
||||
"""
|
||||
:类型: ``Optional[ModuleType]``
|
||||
:说明: 事件响应器所在模块
|
||||
"""
|
||||
plugin_name: Optional[str] = module and getattr(module, "__plugin_name__",
|
||||
None)
|
||||
"""
|
||||
:类型: ``Optional[str]``
|
||||
:说明: 事件响应器所在模块名称
|
||||
:说明: 事件响应器所在插件名
|
||||
"""
|
||||
module_name: Optional[str] = module and getattr(module, "__module_name__",
|
||||
None)
|
||||
"""
|
||||
:类型: ``Optional[str]``
|
||||
:说明: 事件响应器所在模块名
|
||||
"""
|
||||
module_prefix: Optional[str] = module and getattr(module,
|
||||
"__module_prefix__", None)
|
||||
"""
|
||||
:类型: ``Optional[str]``
|
||||
:说明: 事件响应器所在模块前缀
|
||||
"""
|
||||
|
||||
type: str = ""
|
||||
@ -136,8 +158,9 @@ class Matcher(metaclass=MatcherMeta):
|
||||
self.state = self._default_state.copy()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (f"<Matcher from {self.module or 'unknown'}, type={self.type}, "
|
||||
f"priority={self.priority}, temp={self.temp}>")
|
||||
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)
|
||||
@ -153,7 +176,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
priority: int = 1,
|
||||
block: bool = False,
|
||||
*,
|
||||
module: Optional[str] = None,
|
||||
module: Optional[ModuleType] = None,
|
||||
default_state: Optional[T_State] = None,
|
||||
default_state_factory: Optional[T_StateFactory] = None,
|
||||
expire_time: Optional[datetime] = None) -> Type["Matcher"]:
|
||||
@ -185,6 +208,12 @@ class Matcher(metaclass=MatcherMeta):
|
||||
"Matcher", (Matcher,), {
|
||||
"module":
|
||||
module,
|
||||
"plugin_name":
|
||||
module and getattr(module, "__plugin_name__", None),
|
||||
"module_name":
|
||||
module and getattr(module, "__module_name__", None),
|
||||
"module_prefix":
|
||||
module and getattr(module, "__module_prefix__", None),
|
||||
"type":
|
||||
type_,
|
||||
"rule":
|
||||
|
@ -7,7 +7,7 @@ NoneBot 内部处理并按优先级分发事件给所有事件响应器,提供
|
||||
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
from typing import Set, Type, TYPE_CHECKING
|
||||
from typing import Set, Type, Optional, TYPE_CHECKING
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.rule import TrieRule
|
||||
@ -174,7 +174,7 @@ async def _run_matcher(Matcher: Type[Matcher], bot: "Bot", event: "Event",
|
||||
return
|
||||
|
||||
|
||||
async def handle_event(bot: "Bot", event: "Event"):
|
||||
async def handle_event(bot: "Bot", event: "Event") -> Optional[Exception]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -199,7 +199,7 @@ async def handle_event(bot: "Bot", event: "Event"):
|
||||
except NoLogException:
|
||||
show_log = False
|
||||
if show_log:
|
||||
logger.opt(colors=True).info(log_msg)
|
||||
logger.opt(colors=True).success(log_msg)
|
||||
|
||||
state = {}
|
||||
coros = list(map(lambda x: x(bot, event, state), _event_preprocessors))
|
||||
@ -208,15 +208,15 @@ async def handle_event(bot: "Bot", event: "Event"):
|
||||
if show_log:
|
||||
logger.debug("Running PreProcessors...")
|
||||
await asyncio.gather(*coros)
|
||||
except IgnoredException:
|
||||
except IgnoredException as e:
|
||||
logger.opt(colors=True).info(
|
||||
f"Event {event.get_event_name()} is <b>ignored</b>")
|
||||
return
|
||||
return e
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running EventPreProcessors. "
|
||||
"Event ignored!</bg #f8bbd0></r>")
|
||||
return
|
||||
return e
|
||||
|
||||
# Trie Match
|
||||
_, _ = TrieRule.get_value(bot, event, state)
|
||||
@ -237,14 +237,16 @@ async def handle_event(bot: "Bot", event: "Event"):
|
||||
results = await asyncio.gather(*pending_tasks, return_exceptions=True)
|
||||
|
||||
for result in results:
|
||||
if not isinstance(result, Exception):
|
||||
continue
|
||||
if isinstance(result, StopPropagation):
|
||||
if not break_flag:
|
||||
break_flag = True
|
||||
logger.debug("Stop event propagation")
|
||||
elif isinstance(result, Exception):
|
||||
break_flag = True
|
||||
logger.debug("Stop event propagation")
|
||||
else:
|
||||
logger.opt(colors=True, exception=result).error(
|
||||
"<r><bg #f8bbd0>Error when checking Matcher.</bg #f8bbd0></r>"
|
||||
)
|
||||
return result
|
||||
|
||||
coros = list(map(lambda x: x(bot, event, state), _event_postprocessors))
|
||||
if coros:
|
||||
@ -256,3 +258,4 @@ async def handle_event(bot: "Bot", event: "Event"):
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running EventPostProcessors</bg #f8bbd0></r>"
|
||||
)
|
||||
return e
|
||||
|
@ -7,9 +7,10 @@
|
||||
import re
|
||||
import json
|
||||
from types import ModuleType
|
||||
from functools import reduce
|
||||
from dataclasses import dataclass
|
||||
from collections import defaultdict
|
||||
from contextvars import Context, ContextVar, copy_context
|
||||
from contextvars import Context, copy_context
|
||||
from typing import Any, Set, List, Dict, Type, Tuple, Union, Optional, TYPE_CHECKING
|
||||
|
||||
import tomlkit
|
||||
@ -49,11 +50,14 @@ class Plugin(object):
|
||||
- **类型**: ``ModuleType``
|
||||
- **说明**: 插件模块对象
|
||||
"""
|
||||
export: Export
|
||||
"""
|
||||
- **类型**: ``Export``
|
||||
- **说明**: 插件内定义的导出内容
|
||||
"""
|
||||
|
||||
@property
|
||||
def export(self) -> Export:
|
||||
"""
|
||||
- **类型**: ``Export``
|
||||
- **说明**: 插件内定义的导出内容
|
||||
"""
|
||||
return getattr(self.module, "__export__", Export())
|
||||
|
||||
@property
|
||||
def matcher(self) -> Set[Type[Matcher]]:
|
||||
@ -61,13 +65,16 @@ class Plugin(object):
|
||||
- **类型**: ``Set[Type[Matcher]]``
|
||||
- **说明**: 插件内定义的 ``Matcher``
|
||||
"""
|
||||
return _plugin_matchers[self.name]
|
||||
# return reduce(
|
||||
# lambda x, y: x | _plugin_matchers[y],
|
||||
# filter(lambda x: x.startswith(self.name), _plugin_matchers.keys()),
|
||||
# set())
|
||||
return _plugin_matchers.get(self.name, set())
|
||||
|
||||
|
||||
def _store_matcher(matcher: Type[Matcher]):
|
||||
if matcher.module:
|
||||
plugin_name = matcher.module.split(".", maxsplit=1)[0]
|
||||
_plugin_matchers[plugin_name].add(matcher)
|
||||
if matcher.plugin_name:
|
||||
_plugin_matchers[matcher.plugin_name].add(matcher)
|
||||
|
||||
|
||||
def on(type: str = "",
|
||||
@ -282,8 +289,9 @@ def on_request(rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
return matcher
|
||||
|
||||
|
||||
def on_startswith(msg: str,
|
||||
def on_startswith(msg: Union[str, Tuple[str, ...]],
|
||||
rule: Optional[Optional[Union[Rule, T_RuleChecker]]] = None,
|
||||
ignorecase: bool = False,
|
||||
**kwargs) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
@ -292,8 +300,9 @@ def on_startswith(msg: str,
|
||||
|
||||
:参数:
|
||||
|
||||
* ``msg: str``: 指定消息开头内容
|
||||
* ``msg: Union[str, Tuple[str, ...]]``: 指定消息开头内容
|
||||
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
|
||||
* ``ignorecase: bool``: 是否忽略大小写
|
||||
* ``permission: Optional[Permission]``: 事件响应权限
|
||||
* ``handlers: Optional[List[Union[T_Handler, Handler]]]``: 事件处理函数列表
|
||||
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
|
||||
@ -306,11 +315,12 @@ def on_startswith(msg: str,
|
||||
|
||||
- ``Type[Matcher]``
|
||||
"""
|
||||
return on_message(startswith(msg) & rule, **kwargs)
|
||||
return on_message(startswith(msg, ignorecase) & rule, **kwargs)
|
||||
|
||||
|
||||
def on_endswith(msg: str,
|
||||
def on_endswith(msg: Union[str, Tuple[str, ...]],
|
||||
rule: Optional[Optional[Union[Rule, T_RuleChecker]]] = None,
|
||||
ignorecase: bool = False,
|
||||
**kwargs) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
@ -319,8 +329,9 @@ def on_endswith(msg: str,
|
||||
|
||||
:参数:
|
||||
|
||||
* ``msg: str``: 指定消息结尾内容
|
||||
* ``msg: Union[str, Tuple[str, ...]]``: 指定消息结尾内容
|
||||
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
|
||||
* ``ignorecase: bool``: 是否忽略大小写
|
||||
* ``permission: Optional[Permission]``: 事件响应权限
|
||||
* ``handlers: Optional[List[Union[T_Handler, Handler]]]``: 事件处理函数列表
|
||||
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
|
||||
@ -333,7 +344,7 @@ def on_endswith(msg: str,
|
||||
|
||||
- ``Type[Matcher]``
|
||||
"""
|
||||
return on_message(endswith(msg) & rule, **kwargs)
|
||||
return on_message(endswith(msg, ignorecase) & rule, **kwargs)
|
||||
|
||||
|
||||
def on_keyword(keywords: Set[str],
|
||||
@ -546,7 +557,7 @@ class CommandGroup:
|
||||
:参数:
|
||||
|
||||
* ``cmd: Union[str, Tuple[str, ...]]``: 命令前缀
|
||||
* ``**kwargs``: 其他传递给 ``on_command`` 的参数,将会覆盖命令组默认值
|
||||
* ``**kwargs``: 其他传递给 ``on_shell_command`` 的参数,将会覆盖命令组默认值
|
||||
|
||||
:返回:
|
||||
|
||||
@ -631,6 +642,7 @@ class MatcherGroup:
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
final_kwargs.pop("type", None)
|
||||
final_kwargs.pop("permission", None)
|
||||
matcher = on_metaevent(**final_kwargs)
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
@ -717,7 +729,8 @@ class MatcherGroup:
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
def on_startswith(self, msg: str, **kwargs) -> Type[Matcher]:
|
||||
def on_startswith(self, msg: Union[str, Tuple[str, ...]],
|
||||
**kwargs) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -725,7 +738,8 @@ class MatcherGroup:
|
||||
|
||||
:参数:
|
||||
|
||||
* ``msg: str``: 指定消息开头内容
|
||||
* ``msg: Union[str, Tuple[str, ...]]``: 指定消息开头内容
|
||||
* ``ignorecase: bool``: 是否忽略大小写
|
||||
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
|
||||
* ``permission: Optional[Permission]``: 事件响应权限
|
||||
* ``handlers: Optional[List[Union[T_Handler, Handler]]]``: 事件处理函数列表
|
||||
@ -746,7 +760,8 @@ class MatcherGroup:
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
def on_endswith(self, msg: str, **kwargs) -> Type[Matcher]:
|
||||
def on_endswith(self, msg: Union[str, Tuple[str, ...]],
|
||||
**kwargs) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -754,7 +769,8 @@ class MatcherGroup:
|
||||
|
||||
:参数:
|
||||
|
||||
* ``msg: str``: 指定消息结尾内容
|
||||
* ``msg: Union[str, Tuple[str, ...]]``: 指定消息结尾内容
|
||||
* ``ignorecase: bool``: 是否忽略大小写
|
||||
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
|
||||
* ``permission: Optional[Permission]``: 事件响应权限
|
||||
* ``handlers: Optional[List[Union[T_Handler, Handler]]]``: 事件处理函数列表
|
||||
@ -928,11 +944,10 @@ def _load_plugin(manager: PluginManager, plugin_name: str) -> Optional[Plugin]:
|
||||
try:
|
||||
module = manager.load_plugin(plugin_name)
|
||||
|
||||
plugin = Plugin(plugin_name, module,
|
||||
getattr(module, "__export__", Export()))
|
||||
plugin = Plugin(plugin_name, module)
|
||||
plugins[plugin_name] = plugin
|
||||
logger.opt(
|
||||
colors=True).info(f'Succeeded to import "<y>{plugin_name}</y>"')
|
||||
colors=True).success(f'Succeeded to import "<y>{plugin_name}</y>"')
|
||||
return plugin
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
|
@ -1,7 +1,7 @@
|
||||
import re
|
||||
from types import ModuleType
|
||||
from contextvars import ContextVar
|
||||
from typing import Any, Set, List, Dict, Type, Tuple, Union, Optional
|
||||
from dataclasses import dataclass
|
||||
from typing import Set, List, Dict, Type, Tuple, Union, Optional, TYPE_CHECKING
|
||||
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.handler import Handler
|
||||
@ -9,32 +9,28 @@ from nonebot.permission import Permission
|
||||
from nonebot.rule import Rule, ArgumentParser
|
||||
from nonebot.typing import T_State, T_StateFactory, T_Handler, T_RuleChecker
|
||||
|
||||
from .export import Export, export
|
||||
from .manager import PluginManager
|
||||
|
||||
plugins: Dict[str, "Plugin"] = ...
|
||||
|
||||
_export: ContextVar["Export"] = ...
|
||||
_tmp_matchers: ContextVar[Set[Type[Matcher]]] = ...
|
||||
|
||||
|
||||
class Export(dict):
|
||||
|
||||
def __call__(self, func, **kwargs):
|
||||
...
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
...
|
||||
|
||||
def __getattr__(self, name):
|
||||
...
|
||||
PLUGIN_NAMESPACE: str = ...
|
||||
|
||||
|
||||
@dataclass(eq=False)
|
||||
class Plugin(object):
|
||||
name: str
|
||||
module: ModuleType
|
||||
matcher: Set[Type[Matcher]]
|
||||
export: Export
|
||||
|
||||
@property
|
||||
def export(self) -> Export:
|
||||
...
|
||||
|
||||
@property
|
||||
def matcher(self) -> Set[Type[Matcher]]:
|
||||
...
|
||||
|
||||
|
||||
def on(type: str = ...,
|
||||
def on(type: str = "",
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
permission: Optional[Permission] = ...,
|
||||
*,
|
||||
@ -94,8 +90,9 @@ def on_request(rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
|
||||
|
||||
def on_startswith(
|
||||
msg: str,
|
||||
msg: Union[str, Tuple[str, ...]],
|
||||
rule: Optional[Optional[Union[Rule, T_RuleChecker]]] = ...,
|
||||
ignorecase: bool = ...,
|
||||
*,
|
||||
permission: Optional[Permission] = ...,
|
||||
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
|
||||
@ -107,8 +104,9 @@ def on_startswith(
|
||||
...
|
||||
|
||||
|
||||
def on_endswith(msg: str,
|
||||
def on_endswith(msg: Union[str, Tuple[str, ...]],
|
||||
rule: Optional[Optional[Union[Rule, T_RuleChecker]]] = ...,
|
||||
ignorecase: bool = ...,
|
||||
*,
|
||||
permission: Optional[Permission] = ...,
|
||||
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
|
||||
@ -121,7 +119,7 @@ def on_endswith(msg: str,
|
||||
|
||||
|
||||
def on_keyword(keywords: Set[str],
|
||||
rule: Optional[Optional[Union[Rule, T_RuleChecker]]] = ...,
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
*,
|
||||
permission: Optional[Permission] = ...,
|
||||
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
|
||||
@ -147,16 +145,24 @@ def on_command(cmd: Union[str, Tuple[str, ...]],
|
||||
...
|
||||
|
||||
|
||||
def on_shell_command(cmd: Union[str, Tuple[str, ...]],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = None,
|
||||
parser: Optional[ArgumentParser] = None,
|
||||
**kwargs) -> Type[Matcher]:
|
||||
def on_shell_command(
|
||||
cmd: Union[str, Tuple[str, ...]],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
|
||||
parser: Optional[ArgumentParser] = ...,
|
||||
*,
|
||||
permission: Optional[Permission] = ...,
|
||||
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
|
||||
temp: bool = ...,
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
|
||||
def on_regex(pattern: str,
|
||||
flags: Union[int, re.RegexFlag] = 0,
|
||||
flags: Union[int, re.RegexFlag] = ...,
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
*,
|
||||
permission: Optional[Permission] = ...,
|
||||
@ -169,67 +175,26 @@ def on_regex(pattern: str,
|
||||
...
|
||||
|
||||
|
||||
def load_plugin(module_path: str) -> Optional[Plugin]:
|
||||
...
|
||||
|
||||
|
||||
def load_plugins(*plugin_dir: str) -> Set[Plugin]:
|
||||
...
|
||||
|
||||
|
||||
def load_all_plugins(module_path: Set[str],
|
||||
plugin_dir: Set[str]) -> Set[Plugin]:
|
||||
...
|
||||
|
||||
|
||||
def load_from_json(file_path: str, encoding: str = ...) -> Set[Plugin]:
|
||||
...
|
||||
|
||||
|
||||
def load_from_toml(file_path: str, encoding: str = ...) -> Set[Plugin]:
|
||||
...
|
||||
|
||||
|
||||
def load_builtin_plugins(name: str = ...):
|
||||
...
|
||||
|
||||
|
||||
def get_plugin(name: str) -> Optional[Plugin]:
|
||||
...
|
||||
|
||||
|
||||
def get_loaded_plugins() -> Set[Plugin]:
|
||||
...
|
||||
|
||||
|
||||
def export() -> Export:
|
||||
...
|
||||
|
||||
|
||||
def require(name: str) -> Export:
|
||||
...
|
||||
|
||||
|
||||
class CommandGroup:
|
||||
|
||||
def __init__(self,
|
||||
cmd: Union[str, Tuple[str, ...]],
|
||||
*,
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
permission: Optional[Permission] = ...,
|
||||
*,
|
||||
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
|
||||
temp: bool = ...,
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...):
|
||||
self.basecmd: Tuple[str, ...] = ...
|
||||
self.base_kwargs: Dict[str, Any] = ...
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...):
|
||||
...
|
||||
|
||||
def command(self,
|
||||
cmd: Union[str, Tuple[str, ...]],
|
||||
*,
|
||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
|
||||
permission: Optional[Permission] = ...,
|
||||
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
|
||||
temp: bool = ...,
|
||||
@ -244,7 +209,7 @@ class CommandGroup:
|
||||
cmd: Union[str, Tuple[str, ...]],
|
||||
*,
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
|
||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]],
|
||||
parser: Optional[ArgumentParser] = ...,
|
||||
permission: Optional[Permission] = ...,
|
||||
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
|
||||
@ -267,7 +232,8 @@ class MatcherGroup:
|
||||
temp: bool = ...,
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...):
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...):
|
||||
...
|
||||
|
||||
def on(self,
|
||||
@ -286,57 +252,58 @@ class MatcherGroup:
|
||||
def on_metaevent(
|
||||
self,
|
||||
*,
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
handlers: Optional[List[Union[T_Handler, Handler]]] = None,
|
||||
temp: bool = False,
|
||||
priority: int = 1,
|
||||
block: bool = False,
|
||||
state: Optional[T_State] = None,
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
|
||||
temp: bool = ...,
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
def on_message(
|
||||
self,
|
||||
*,
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
permission: Optional[Permission] = None,
|
||||
handlers: Optional[List[Union[T_Handler, Handler]]] = None,
|
||||
temp: bool = False,
|
||||
priority: int = 1,
|
||||
block: bool = True,
|
||||
state: Optional[T_State] = None,
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
permission: Optional[Permission] = ...,
|
||||
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
|
||||
temp: bool = ...,
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
def on_notice(
|
||||
self,
|
||||
*,
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
handlers: Optional[List[Union[T_Handler, Handler]]] = None,
|
||||
temp: bool = False,
|
||||
priority: int = 1,
|
||||
block: bool = False,
|
||||
state: Optional[T_State] = None,
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
|
||||
temp: bool = ...,
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
def on_request(
|
||||
self,
|
||||
*,
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
handlers: Optional[List[Union[T_Handler, Handler]]] = None,
|
||||
temp: bool = False,
|
||||
priority: int = 1,
|
||||
block: bool = False,
|
||||
state: Optional[T_State] = None,
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
|
||||
temp: bool = ...,
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
def on_startswith(
|
||||
self,
|
||||
msg: str,
|
||||
msg: Union[str, Tuple[str, ...]],
|
||||
*,
|
||||
rule: Optional[Optional[Union[Rule, T_RuleChecker]]] = ...,
|
||||
ignorecase: bool = ...,
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
permission: Optional[Permission] = ...,
|
||||
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
|
||||
temp: bool = ...,
|
||||
@ -348,9 +315,10 @@ class MatcherGroup:
|
||||
|
||||
def on_endswith(
|
||||
self,
|
||||
msg: str,
|
||||
msg: Union[str, Tuple[str, ...]],
|
||||
*,
|
||||
rule: Optional[Optional[Union[Rule, T_RuleChecker]]] = ...,
|
||||
ignorecase: bool = ...,
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
permission: Optional[Permission] = ...,
|
||||
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
|
||||
temp: bool = ...,
|
||||
@ -364,7 +332,7 @@ class MatcherGroup:
|
||||
self,
|
||||
keywords: Set[str],
|
||||
*,
|
||||
rule: Optional[Optional[Union[Rule, T_RuleChecker]]] = ...,
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
permission: Optional[Permission] = ...,
|
||||
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
|
||||
temp: bool = ...,
|
||||
@ -408,7 +376,7 @@ class MatcherGroup:
|
||||
def on_regex(
|
||||
self,
|
||||
pattern: str,
|
||||
flags: Union[int, re.RegexFlag] = 0,
|
||||
flags: Union[int, re.RegexFlag] = ...,
|
||||
*,
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
permission: Optional[Permission] = ...,
|
||||
@ -419,3 +387,40 @@ class MatcherGroup:
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
|
||||
def load_plugin(module_path: str) -> Optional[Plugin]:
|
||||
...
|
||||
|
||||
|
||||
def load_plugins(*plugin_dir: str) -> Set[Plugin]:
|
||||
...
|
||||
|
||||
|
||||
def load_all_plugins(module_path: Set[str],
|
||||
plugin_dir: Set[str]) -> Set[Plugin]:
|
||||
...
|
||||
|
||||
|
||||
def load_from_json(file_path: str, encoding: str = ...) -> Set[Plugin]:
|
||||
...
|
||||
|
||||
|
||||
def load_from_toml(file_path: str, encoding: str = ...) -> Set[Plugin]:
|
||||
...
|
||||
|
||||
|
||||
def load_builtin_plugins(name: str = ...) -> Optional[Plugin]:
|
||||
...
|
||||
|
||||
|
||||
def get_plugin(name: str) -> Optional[Plugin]:
|
||||
...
|
||||
|
||||
|
||||
def get_loaded_plugins() -> Set[Plugin]:
|
||||
...
|
||||
|
||||
|
||||
def require(name: str) -> Optional[Export]:
|
||||
...
|
||||
|
@ -12,8 +12,8 @@ from importlib.machinery import PathFinder, SourceFileLoader
|
||||
|
||||
from .export import _export, Export
|
||||
|
||||
_current_plugin: ContextVar[Optional[str]] = ContextVar("_current_plugin",
|
||||
default=None)
|
||||
_current_plugin: ContextVar[Optional[ModuleType]] = ContextVar(
|
||||
"_current_plugin", default=None)
|
||||
|
||||
_internal_space = ModuleType(__name__ + "._internal")
|
||||
_internal_space.__path__ = [] # type: ignore
|
||||
@ -53,14 +53,13 @@ class _InternalModule(ModuleType):
|
||||
class PluginManager:
|
||||
|
||||
def __init__(self,
|
||||
namespace: Optional[str] = None,
|
||||
namespace: str,
|
||||
plugins: Optional[Iterable[str]] = None,
|
||||
search_path: Optional[Iterable[str]] = None,
|
||||
*,
|
||||
id: Optional[str] = None):
|
||||
self.namespace: Optional[str] = namespace
|
||||
self.namespace_module: Optional[ModuleType] = self._setup_namespace(
|
||||
namespace)
|
||||
self.namespace: str = namespace
|
||||
self.namespace_module: ModuleType = self._setup_namespace(namespace)
|
||||
|
||||
self.id: str = id or str(uuid.uuid4())
|
||||
self.internal_id: str = md5(
|
||||
@ -73,12 +72,7 @@ class PluginManager:
|
||||
# ensure can be loaded
|
||||
self.list_plugins()
|
||||
|
||||
def _setup_namespace(self,
|
||||
namespace: Optional[str] = None
|
||||
) -> Optional[ModuleType]:
|
||||
if not namespace:
|
||||
return None
|
||||
|
||||
def _setup_namespace(self, namespace: str) -> ModuleType:
|
||||
try:
|
||||
module = importlib.import_module(namespace)
|
||||
except ImportError:
|
||||
@ -156,14 +150,18 @@ class PluginManager:
|
||||
def load_all_plugins(self) -> List[ModuleType]:
|
||||
return [self.load_plugin(name) for name in self.list_plugins()]
|
||||
|
||||
def _rewrite_module_name(self, module_name) -> Optional[str]:
|
||||
def _rewrite_module_name(self, module_name: str) -> Optional[str]:
|
||||
prefix = f"{self.internal_module.__name__}."
|
||||
if module_name.startswith(self.namespace + "."):
|
||||
path = module_name.split(".")
|
||||
length = self.namespace.count(".") + 1
|
||||
return f"{prefix}{'.'.join(path[length:])}"
|
||||
raw_name = module_name[len(self.namespace) +
|
||||
1:] if module_name.startswith(self.namespace +
|
||||
".") else None
|
||||
# dir plugins
|
||||
if raw_name and raw_name.split(".")[0] in self.search_plugins():
|
||||
return f"{prefix}{raw_name}"
|
||||
# third party plugin or renamed dir plugins
|
||||
elif module_name in self.plugins or module_name.startswith(prefix):
|
||||
return module_name
|
||||
# dir plugins
|
||||
elif module_name in self.search_plugins():
|
||||
return f"{prefix}{module_name}"
|
||||
return None
|
||||
@ -194,36 +192,44 @@ class PluginLoader(SourceFileLoader):
|
||||
def __init__(self, manager: PluginManager, fullname: str, path) -> None:
|
||||
self.manager = manager
|
||||
self.loaded = False
|
||||
self._plugin_token = None
|
||||
self._export_token = None
|
||||
super().__init__(fullname, path)
|
||||
|
||||
def create_module(self, spec) -> Optional[ModuleType]:
|
||||
if self.name in sys.modules:
|
||||
self.loaded = True
|
||||
return sys.modules[self.name]
|
||||
prefix = self.manager.internal_module.__name__
|
||||
plugin_name = self.name[len(prefix):] if self.name.startswith(
|
||||
prefix) else self.name
|
||||
self._plugin_token = _current_plugin.set(plugin_name.lstrip("."))
|
||||
self._export_token = _export.set(Export())
|
||||
# return None to use default module creation
|
||||
return super().create_module(spec)
|
||||
|
||||
def exec_module(self, module: ModuleType) -> None:
|
||||
if self.loaded:
|
||||
return
|
||||
# really need?
|
||||
# setattr(module, "__manager__", self.manager)
|
||||
if self._export_token:
|
||||
setattr(module, "__export__", _export.get())
|
||||
|
||||
export = Export()
|
||||
_export_token = _export.set(export)
|
||||
|
||||
prefix = self.manager.internal_module.__name__
|
||||
is_dir_plugin = self.name.startswith(prefix + ".")
|
||||
module_name = self.name[len(prefix) +
|
||||
1:] if is_dir_plugin else self.name
|
||||
_plugin_token = _current_plugin.set(module)
|
||||
|
||||
setattr(module, "__export__", export)
|
||||
setattr(module, "__plugin_name__",
|
||||
module_name.split(".")[0] if is_dir_plugin else module_name)
|
||||
setattr(module, "__module_name__", module_name)
|
||||
setattr(module, "__module_prefix__", prefix if is_dir_plugin else "")
|
||||
|
||||
# try:
|
||||
# super().exec_module(module)
|
||||
# except Exception as e:
|
||||
# raise ImportError(
|
||||
# f"Error when executing module {module_name} from {module.__file__}."
|
||||
# ) from e
|
||||
super().exec_module(module)
|
||||
|
||||
if self._plugin_token:
|
||||
_current_plugin.reset(self._plugin_token)
|
||||
if self._export_token:
|
||||
_export.reset(self._export_token)
|
||||
_current_plugin.reset(_plugin_token)
|
||||
_export.reset(_export_token)
|
||||
return
|
||||
|
||||
|
||||
|
@ -175,7 +175,8 @@ class TrieRule:
|
||||
})
|
||||
|
||||
|
||||
def startswith(msg: str) -> Rule:
|
||||
def startswith(msg: Union[str, Tuple[str, ...]],
|
||||
ignorecase: bool = False) -> Rule:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -185,17 +186,24 @@ def startswith(msg: str) -> Rule:
|
||||
|
||||
* ``msg: str``: 消息开头字符串
|
||||
"""
|
||||
if isinstance(msg, str):
|
||||
msg = (msg,)
|
||||
|
||||
pattern = re.compile(
|
||||
f"^(?:{'|'.join(re.escape(prefix) for prefix in msg)})",
|
||||
re.IGNORECASE if ignorecase else 0)
|
||||
|
||||
async def _startswith(bot: "Bot", event: "Event", state: T_State) -> bool:
|
||||
if event.get_type() != "message":
|
||||
return False
|
||||
text = event.get_plaintext()
|
||||
return text.startswith(msg)
|
||||
return bool(pattern.match(text))
|
||||
|
||||
return Rule(_startswith)
|
||||
|
||||
|
||||
def endswith(msg: str) -> Rule:
|
||||
def endswith(msg: Union[str, Tuple[str, ...]],
|
||||
ignorecase: bool = False) -> Rule:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -205,11 +213,18 @@ def endswith(msg: str) -> Rule:
|
||||
|
||||
* ``msg: str``: 消息结尾字符串
|
||||
"""
|
||||
if isinstance(msg, str):
|
||||
msg = (msg,)
|
||||
|
||||
pattern = re.compile(
|
||||
f"(?:{'|'.join(re.escape(prefix) for prefix in msg)})$",
|
||||
re.IGNORECASE if ignorecase else 0)
|
||||
|
||||
async def _endswith(bot: "Bot", event: "Event", state: T_State) -> bool:
|
||||
if event.get_type() != "message":
|
||||
return False
|
||||
return event.get_plaintext().endswith(msg)
|
||||
text = event.get_plaintext()
|
||||
return bool(pattern.match(text))
|
||||
|
||||
return Rule(_endswith)
|
||||
|
||||
|
@ -55,21 +55,21 @@ T_StateFactory = Callable[["Bot", "Event"], Awaitable[T_State]]
|
||||
事件处理状态 State 类工厂函数
|
||||
"""
|
||||
|
||||
T_WebSocketConnectionHook = Callable[["Bot"], Awaitable[None]]
|
||||
T_BotConnectionHook = Callable[["Bot"], Awaitable[None]]
|
||||
"""
|
||||
:类型: ``Callable[[Bot], Awaitable[None]]``
|
||||
|
||||
:说明:
|
||||
|
||||
WebSocket 连接建立时执行的函数
|
||||
Bot 连接建立时执行的函数
|
||||
"""
|
||||
T_WebSocketDisconnectionHook = Callable[["Bot"], Awaitable[None]]
|
||||
T_BotDisconnectionHook = Callable[["Bot"], Awaitable[None]]
|
||||
"""
|
||||
:类型: ``Callable[[Bot], Awaitable[None]]``
|
||||
|
||||
:说明:
|
||||
|
||||
WebSocket 连接断开时执行的函数
|
||||
Bot 连接断开时执行的函数
|
||||
"""
|
||||
T_CallingAPIHook = Callable[["Bot", str, Dict[str, Any]], Awaitable[None]]
|
||||
"""
|
||||
|
@ -17,8 +17,8 @@
|
||||
"scripts": {
|
||||
"dev": "vuepress dev docs",
|
||||
"build": "vuepress build docs",
|
||||
"lint": "npx prettier -c docs/**/* !docs/api/**/*",
|
||||
"lint:fix": "npx prettier --write docs/**/* !docs/api/**/*"
|
||||
"lint": "npx prettier --config .prettierrc -c docs/**/* !docs/api/**/*",
|
||||
"lint:fix": "npx prettier --config .prettierrc --write docs/**/* !docs/api/**/*"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
|
@ -100,14 +100,14 @@ def _check_at_me(bot: "Bot", event: "Event"):
|
||||
# check the first segment
|
||||
if event.message[0] == at_me_seg:
|
||||
event.to_me = True
|
||||
del event.message[0]
|
||||
event.message.pop(0)
|
||||
if event.message and event.message[0].type == "text":
|
||||
event.message[0].data["text"] = event.message[0].data[
|
||||
"text"].lstrip()
|
||||
if not event.message[0].data["text"]:
|
||||
del event.message[0]
|
||||
if event.message and event.message[0] == at_me_seg:
|
||||
del event.message[0]
|
||||
event.message.pop(0)
|
||||
if event.message and event.message[0].type == "text":
|
||||
event.message[0].data["text"] = event.message[0].data[
|
||||
"text"].lstrip()
|
||||
|
@ -68,8 +68,7 @@ class Bot(BaseBot):
|
||||
async def handle_message(self, message: dict):
|
||||
...
|
||||
|
||||
async def call_api(self, api: str, *, self_id: Optional[str],
|
||||
**data) -> Any:
|
||||
async def call_api(self, api: str, *, **data) -> Any:
|
||||
...
|
||||
|
||||
async def send(self, event: Event, message: Union[str, Message,
|
||||
@ -77,12 +76,13 @@ class Bot(BaseBot):
|
||||
**kwargs) -> Any:
|
||||
...
|
||||
|
||||
async def send_private_msg(self,
|
||||
*,
|
||||
user_id: int,
|
||||
message: Union[str, Message],
|
||||
auto_escape: bool = ...,
|
||||
self_id: Optional[int] = ...) -> Dict[str, Any]:
|
||||
async def send_private_msg(
|
||||
self,
|
||||
*,
|
||||
user_id: int,
|
||||
message: Union[str, Message],
|
||||
auto_escape: bool = ...,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -93,16 +93,17 @@ class Bot(BaseBot):
|
||||
* ``user_id``: 对方 QQ 号
|
||||
* ``message``: 要发送的内容
|
||||
* ``auto_escape``: 消息内容是否作为纯文本发送(即不解析 CQ 码),只在 ``message`` 字段是字符串时有效
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def send_group_msg(self,
|
||||
*,
|
||||
group_id: int,
|
||||
message: Union[str, Message],
|
||||
auto_escape: bool = ...,
|
||||
self_id: Optional[int] = ...) -> Dict[str, Any]:
|
||||
async def send_group_msg(
|
||||
self,
|
||||
*,
|
||||
group_id: int,
|
||||
message: Union[str, Message],
|
||||
auto_escape: bool = ...,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -113,18 +114,19 @@ class Bot(BaseBot):
|
||||
* ``group_id``: 群号
|
||||
* ``message``: 要发送的内容
|
||||
* ``auto_escape``: 消息内容是否作为纯文本发送(即不解析 CQ 码),只在 ``message`` 字段是字符串时有效
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def send_msg(self,
|
||||
*,
|
||||
message_type: Optional[str] = ...,
|
||||
user_id: Optional[int] = ...,
|
||||
group_id: Optional[int] = ...,
|
||||
message: Union[str, Message],
|
||||
auto_escape: bool = ...,
|
||||
self_id: Optional[int] = ...) -> Dict[str, Any]:
|
||||
async def send_msg(
|
||||
self,
|
||||
*,
|
||||
message_type: Optional[str] = ...,
|
||||
user_id: Optional[int] = ...,
|
||||
group_id: Optional[int] = ...,
|
||||
message: Union[str, Message],
|
||||
auto_escape: bool = ...,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -137,14 +139,15 @@ class Bot(BaseBot):
|
||||
* ``group_id``: 群号(消息类型为 ``group`` 时需要)
|
||||
* ``message``: 要发送的内容
|
||||
* ``auto_escape``: 消息内容是否作为纯文本发送(即不解析 CQ 码),只在 ``message`` 字段是字符串时有效
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def delete_msg(self,
|
||||
*,
|
||||
message_id: int,
|
||||
self_id: Optional[int] = ...) -> None:
|
||||
async def delete_msg(
|
||||
self,
|
||||
*,
|
||||
message_id: int,
|
||||
) -> None:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -153,14 +156,15 @@ class Bot(BaseBot):
|
||||
:参数:
|
||||
|
||||
* ``message_id``: 消息 ID
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def get_msg(self,
|
||||
*,
|
||||
message_id: int,
|
||||
self_id: Optional[int] = ...) -> Dict[str, Any]:
|
||||
async def get_msg(
|
||||
self,
|
||||
*,
|
||||
message_id: int,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -169,14 +173,15 @@ class Bot(BaseBot):
|
||||
:参数:
|
||||
|
||||
* ``message_id``: 消息 ID
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def get_forward_msg(self,
|
||||
*,
|
||||
id: int,
|
||||
self_id: Optional[int] = ...) -> None:
|
||||
async def get_forward_msg(
|
||||
self,
|
||||
*,
|
||||
id: int,
|
||||
) -> None:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -185,15 +190,16 @@ class Bot(BaseBot):
|
||||
:参数:
|
||||
|
||||
* ``id``: 合并转发 ID
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def send_like(self,
|
||||
*,
|
||||
user_id: int,
|
||||
times: int = ...,
|
||||
self_id: Optional[int] = ...) -> None:
|
||||
async def send_like(
|
||||
self,
|
||||
*,
|
||||
user_id: int,
|
||||
times: int = ...,
|
||||
) -> None:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -203,16 +209,17 @@ class Bot(BaseBot):
|
||||
|
||||
* ``user_id``: 对方 QQ 号
|
||||
* ``times``: 赞的次数,每个好友每天最多 10 次
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def set_group_kick(self,
|
||||
*,
|
||||
group_id: int,
|
||||
user_id: int,
|
||||
reject_add_request: bool = ...,
|
||||
self_id: Optional[int] = ...) -> None:
|
||||
async def set_group_kick(
|
||||
self,
|
||||
*,
|
||||
group_id: int,
|
||||
user_id: int,
|
||||
reject_add_request: bool = ...,
|
||||
) -> None:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -223,16 +230,17 @@ class Bot(BaseBot):
|
||||
* ``group_id``: 群号
|
||||
* ``user_id``: 要踢的 QQ 号
|
||||
* ``reject_add_request``: 拒绝此人的加群请求
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def set_group_ban(self,
|
||||
*,
|
||||
group_id: int,
|
||||
user_id: int,
|
||||
duration: int = ...,
|
||||
self_id: Optional[int] = ...) -> None:
|
||||
async def set_group_ban(
|
||||
self,
|
||||
*,
|
||||
group_id: int,
|
||||
user_id: int,
|
||||
duration: int = ...,
|
||||
) -> None:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -243,17 +251,18 @@ class Bot(BaseBot):
|
||||
* ``group_id``: 群号
|
||||
* ``user_id``: 要禁言的 QQ 号
|
||||
* ``duration``: 禁言时长,单位秒,``0`` 表示取消禁言
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def set_group_anonymous_ban(self,
|
||||
*,
|
||||
group_id: int,
|
||||
anonymous: Optional[Dict[str, Any]] = ...,
|
||||
anonymous_flag: Optional[str] = ...,
|
||||
duration: int = ...,
|
||||
self_id: Optional[int] = ...) -> None:
|
||||
async def set_group_anonymous_ban(
|
||||
self,
|
||||
*,
|
||||
group_id: int,
|
||||
anonymous: Optional[Dict[str, Any]] = ...,
|
||||
anonymous_flag: Optional[str] = ...,
|
||||
duration: int = ...,
|
||||
) -> None:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -265,15 +274,16 @@ class Bot(BaseBot):
|
||||
* ``anonymous``: 可选,要禁言的匿名用户对象(群消息上报的 ``anonymous`` 字段)
|
||||
* ``anonymous_flag``: 可选,要禁言的匿名用户的 flag(需从群消息上报的数据中获得)
|
||||
* ``duration``: 禁言时长,单位秒,无法取消匿名用户禁言
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def set_group_whole_ban(self,
|
||||
*,
|
||||
group_id: int,
|
||||
enable: bool = ...,
|
||||
self_id: Optional[int] = ...) -> None:
|
||||
async def set_group_whole_ban(
|
||||
self,
|
||||
*,
|
||||
group_id: int,
|
||||
enable: bool = ...,
|
||||
) -> None:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -283,16 +293,17 @@ class Bot(BaseBot):
|
||||
|
||||
* ``group_id``: 群号
|
||||
* ``enable``: 是否禁言
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def set_group_admin(self,
|
||||
*,
|
||||
group_id: int,
|
||||
user_id: int,
|
||||
enable: bool = ...,
|
||||
self_id: Optional[int] = ...) -> None:
|
||||
async def set_group_admin(
|
||||
self,
|
||||
*,
|
||||
group_id: int,
|
||||
user_id: int,
|
||||
enable: bool = ...,
|
||||
) -> None:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -303,15 +314,16 @@ class Bot(BaseBot):
|
||||
* ``group_id``: 群号
|
||||
* ``user_id``: 要设置管理员的 QQ 号
|
||||
* ``enable``: ``True`` 为设置,``False`` 为取消
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def set_group_anonymous(self,
|
||||
*,
|
||||
group_id: int,
|
||||
enable: bool = ...,
|
||||
self_id: Optional[int] = ...) -> None:
|
||||
async def set_group_anonymous(
|
||||
self,
|
||||
*,
|
||||
group_id: int,
|
||||
enable: bool = ...,
|
||||
) -> None:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -321,16 +333,17 @@ class Bot(BaseBot):
|
||||
|
||||
* ``group_id``: 群号
|
||||
* ``enable``: 是否允许匿名聊天
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def set_group_card(self,
|
||||
*,
|
||||
group_id: int,
|
||||
user_id: int,
|
||||
card: str = ...,
|
||||
self_id: Optional[int] = ...) -> None:
|
||||
async def set_group_card(
|
||||
self,
|
||||
*,
|
||||
group_id: int,
|
||||
user_id: int,
|
||||
card: str = ...,
|
||||
) -> None:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -341,15 +354,16 @@ class Bot(BaseBot):
|
||||
* ``group_id``: 群号
|
||||
* ``user_id``: 要设置的 QQ 号
|
||||
* ``card``: 群名片内容,不填或空字符串表示删除群名片
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def set_group_name(self,
|
||||
*,
|
||||
group_id: int,
|
||||
group_name: str,
|
||||
self_id: Optional[int] = ...) -> None:
|
||||
async def set_group_name(
|
||||
self,
|
||||
*,
|
||||
group_id: int,
|
||||
group_name: str,
|
||||
) -> None:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -359,15 +373,16 @@ class Bot(BaseBot):
|
||||
|
||||
* ``group_id``: 群号
|
||||
* ``group_name``: 新群名
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def set_group_leave(self,
|
||||
*,
|
||||
group_id: int,
|
||||
is_dismiss: bool = ...,
|
||||
self_id: Optional[int] = ...) -> None:
|
||||
async def set_group_leave(
|
||||
self,
|
||||
*,
|
||||
group_id: int,
|
||||
is_dismiss: bool = ...,
|
||||
) -> None:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -377,17 +392,18 @@ class Bot(BaseBot):
|
||||
|
||||
* ``group_id``: 群号
|
||||
* ``is_dismiss``: 是否解散,如果登录号是群主,则仅在此项为 True 时能够解散
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def set_group_special_title(self,
|
||||
*,
|
||||
group_id: int,
|
||||
user_id: int,
|
||||
special_title: str = ...,
|
||||
duration: int = ...,
|
||||
self_id: Optional[int] = ...) -> None:
|
||||
async def set_group_special_title(
|
||||
self,
|
||||
*,
|
||||
group_id: int,
|
||||
user_id: int,
|
||||
special_title: str = ...,
|
||||
duration: int = ...,
|
||||
) -> None:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -399,16 +415,17 @@ class Bot(BaseBot):
|
||||
* ``user_id``: 要设置的 QQ 号
|
||||
* ``special_title``: 专属头衔,不填或空字符串表示删除专属头衔
|
||||
* ``duration``: 专属头衔有效期,单位秒,-1 表示永久,不过此项似乎没有效果,可能是只有某些特殊的时间长度有效,有待测试
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def set_friend_add_request(self,
|
||||
*,
|
||||
flag: str,
|
||||
approve: bool = ...,
|
||||
remark: str = ...,
|
||||
self_id: Optional[int] = ...) -> None:
|
||||
async def set_friend_add_request(
|
||||
self,
|
||||
*,
|
||||
flag: str,
|
||||
approve: bool = ...,
|
||||
remark: str = ...,
|
||||
) -> None:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -419,17 +436,18 @@ class Bot(BaseBot):
|
||||
* ``flag``: 加好友请求的 flag(需从上报的数据中获得)
|
||||
* ``approve``: 是否同意请求
|
||||
* ``remark``: 添加后的好友备注(仅在同意时有效)
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def set_group_add_request(self,
|
||||
*,
|
||||
flag: str,
|
||||
sub_type: str,
|
||||
approve: bool = ...,
|
||||
reason: str = ...,
|
||||
self_id: Optional[int] = ...) -> None:
|
||||
async def set_group_add_request(
|
||||
self,
|
||||
*,
|
||||
flag: str,
|
||||
sub_type: str,
|
||||
approve: bool = ...,
|
||||
reason: str = ...,
|
||||
) -> None:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -441,29 +459,25 @@ class Bot(BaseBot):
|
||||
* ``sub_type``: ``add`` 或 ``invite``,请求类型(需要和上报消息中的 ``sub_type`` 字段相符)
|
||||
* ``approve``: 是否同意请求/邀请
|
||||
* ``reason``: 拒绝理由(仅在拒绝时有效)
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def get_login_info(self,
|
||||
*,
|
||||
self_id: Optional[int] = ...) -> Dict[str, Any]:
|
||||
async def get_login_info(self) -> Dict[str, Any]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
获取登录号信息。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
"""
|
||||
...
|
||||
|
||||
async def get_stranger_info(self,
|
||||
*,
|
||||
user_id: int,
|
||||
no_cache: bool = ...,
|
||||
self_id: Optional[int] = ...) -> Dict[str, Any]:
|
||||
async def get_stranger_info(
|
||||
self,
|
||||
*,
|
||||
user_id: int,
|
||||
no_cache: bool = ...,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -473,30 +487,25 @@ class Bot(BaseBot):
|
||||
|
||||
* ``user_id``: QQ 号
|
||||
* ``no_cache``: 是否不使用缓存(使用缓存可能更新不及时,但响应更快)
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def get_friend_list(self,
|
||||
*,
|
||||
self_id: Optional[int] = ...
|
||||
) -> List[Dict[str, Any]]:
|
||||
async def get_friend_list(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
获取好友列表。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
"""
|
||||
...
|
||||
|
||||
async def get_group_info(self,
|
||||
*,
|
||||
group_id: int,
|
||||
no_cache: bool = ...,
|
||||
self_id: Optional[int] = ...) -> Dict[str, Any]:
|
||||
async def get_group_info(
|
||||
self,
|
||||
*,
|
||||
group_id: int,
|
||||
no_cache: bool = ...,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -506,32 +515,26 @@ class Bot(BaseBot):
|
||||
|
||||
* ``group_id``: 群号
|
||||
* ``no_cache``: 是否不使用缓存(使用缓存可能更新不及时,但响应更快)
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def get_group_list(self,
|
||||
*,
|
||||
self_id: Optional[int] = ...
|
||||
) -> List[Dict[str, Any]]:
|
||||
async def get_group_list(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
获取群列表。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
"""
|
||||
...
|
||||
|
||||
async def get_group_member_info(
|
||||
self,
|
||||
*,
|
||||
group_id: int,
|
||||
user_id: int,
|
||||
no_cache: bool = ...,
|
||||
self_id: Optional[int] = ...) -> Dict[str, Any]:
|
||||
self,
|
||||
*,
|
||||
group_id: int,
|
||||
user_id: int,
|
||||
no_cache: bool = ...,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -542,15 +545,15 @@ class Bot(BaseBot):
|
||||
* ``group_id``: 群号
|
||||
* ``user_id``: QQ 号
|
||||
* ``no_cache``: 是否不使用缓存(使用缓存可能更新不及时,但响应更快)
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def get_group_member_list(
|
||||
self,
|
||||
*,
|
||||
group_id: int,
|
||||
self_id: Optional[int] = ...) -> List[Dict[str, Any]]:
|
||||
self,
|
||||
*,
|
||||
group_id: int,
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -559,16 +562,16 @@ class Bot(BaseBot):
|
||||
:参数:
|
||||
|
||||
* ``group_id``: 群号
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def get_group_honor_info(self,
|
||||
*,
|
||||
group_id: int,
|
||||
type: str = ...,
|
||||
self_id: Optional[int] = ...
|
||||
) -> Dict[str, Any]:
|
||||
async def get_group_honor_info(
|
||||
self,
|
||||
*,
|
||||
group_id: int,
|
||||
type: str = ...,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -578,14 +581,15 @@ class Bot(BaseBot):
|
||||
|
||||
* ``group_id``: 群号
|
||||
* ``type``: 要获取的群荣誉类型,可传入 ``talkative`` ``performer`` ``legend`` ``strong_newbie`` ``emotion`` 以分别获取单个类型的群荣誉数据,或传入 ``all`` 获取所有数据
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def get_cookies(self,
|
||||
*,
|
||||
domain: str = ...,
|
||||
self_id: Optional[int] = ...) -> Dict[str, Any]:
|
||||
async def get_cookies(
|
||||
self,
|
||||
*,
|
||||
domain: str = ...,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -594,28 +598,24 @@ class Bot(BaseBot):
|
||||
:参数:
|
||||
|
||||
* ``domain``: 需要获取 cookies 的域名
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def get_csrf_token(self,
|
||||
*,
|
||||
self_id: Optional[int] = ...) -> Dict[str, Any]:
|
||||
async def get_csrf_token(self) -> Dict[str, Any]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
获取 CSRF Token。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
"""
|
||||
...
|
||||
|
||||
async def get_credentials(self,
|
||||
*,
|
||||
domain: str = ...,
|
||||
self_id: Optional[int] = ...) -> Dict[str, Any]:
|
||||
async def get_credentials(
|
||||
self,
|
||||
*,
|
||||
domain: str = ...,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -624,15 +624,16 @@ class Bot(BaseBot):
|
||||
:参数:
|
||||
|
||||
* ``domain``: 需要获取 cookies 的域名
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def get_record(self,
|
||||
*,
|
||||
file: str,
|
||||
out_format: str,
|
||||
self_id: Optional[int] = ...) -> Dict[str, Any]:
|
||||
async def get_record(
|
||||
self,
|
||||
*,
|
||||
file: str,
|
||||
out_format: str,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -642,14 +643,15 @@ class Bot(BaseBot):
|
||||
|
||||
* ``file``: 收到的语音文件名(CQ 码的 ``file`` 参数),如 ``0B38145AA44505000B38145AA4450500.silk``
|
||||
* ``out_format``: 要转换到的格式,目前支持 ``mp3``、``amr``、``wma``、``m4a``、``spx``、``ogg``、``wav``、``flac``
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def get_image(self,
|
||||
*,
|
||||
file: str,
|
||||
self_id: Optional[int] = ...) -> Dict[str, Any]:
|
||||
async def get_image(
|
||||
self,
|
||||
*,
|
||||
file: str,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -658,70 +660,51 @@ class Bot(BaseBot):
|
||||
:参数:
|
||||
|
||||
* ``file``: 收到的图片文件名(CQ 码的 ``file`` 参数),如 ``6B4DE3DFD1BD271E3297859D41C530F5.jpg``
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def can_send_image(self,
|
||||
*,
|
||||
self_id: Optional[int] = ...) -> Dict[str, Any]:
|
||||
async def can_send_image(self) -> Dict[str, Any]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
检查是否可以发送图片。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
"""
|
||||
...
|
||||
|
||||
async def can_send_record(self,
|
||||
*,
|
||||
self_id: Optional[int] = ...) -> Dict[str, Any]:
|
||||
async def can_send_record(self) -> Dict[str, Any]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
检查是否可以发送语音。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
"""
|
||||
...
|
||||
|
||||
async def get_status(self,
|
||||
*,
|
||||
self_id: Optional[int] = ...) -> Dict[str, Any]:
|
||||
async def get_status(self) -> Dict[str, Any]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
获取插件运行状态。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
"""
|
||||
...
|
||||
|
||||
async def get_version_info(self,
|
||||
*,
|
||||
self_id: Optional[int] = ...) -> Dict[str, Any]:
|
||||
async def get_version_info(self) -> Dict[str, Any]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
获取版本信息。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
"""
|
||||
...
|
||||
|
||||
async def set_restart(self,
|
||||
*,
|
||||
delay: int = ...,
|
||||
self_id: Optional[int] = ...) -> None:
|
||||
async def set_restart(
|
||||
self,
|
||||
*,
|
||||
delay: int = ...,
|
||||
) -> None:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -730,18 +713,15 @@ class Bot(BaseBot):
|
||||
:参数:
|
||||
|
||||
* ``delay``: 要延迟的毫秒数,如果默认情况下无法重启,可以尝试设置延迟为 2000 左右
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
|
||||
"""
|
||||
...
|
||||
|
||||
async def clean_cache(self, *, self_id: Optional[int] = ...) -> None:
|
||||
async def clean_cache(self) -> None:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
清理数据目录。
|
||||
|
||||
:参数:
|
||||
|
||||
* ``self_id``: 机器人 QQ 号
|
||||
"""
|
||||
...
|
||||
|
@ -1,6 +1,9 @@
|
||||
import re
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from base64 import b64encode
|
||||
from functools import reduce
|
||||
from typing import Any, Dict, Union, Tuple, Mapping, Iterable, Optional
|
||||
from typing import Any, List, Dict, Union, Tuple, Mapping, Iterable, Optional
|
||||
|
||||
from nonebot.typing import overrides
|
||||
from nonebot.adapters import Message as BaseMessage, MessageSegment as BaseMessageSegment
|
||||
@ -79,17 +82,23 @@ class MessageSegment(BaseMessageSegment):
|
||||
return MessageSegment("forward", {"id": id_})
|
||||
|
||||
@staticmethod
|
||||
def image(file: str,
|
||||
def image(file: Union[str, bytes, BytesIO, Path],
|
||||
type_: Optional[str] = None,
|
||||
cache: bool = True,
|
||||
proxy: bool = True,
|
||||
timeout: Optional[int] = None) -> "MessageSegment":
|
||||
if isinstance(file, BytesIO):
|
||||
file = file.read()
|
||||
if isinstance(file, bytes):
|
||||
file = f"base64://{b64encode(file).decode()}"
|
||||
elif isinstance(file, Path):
|
||||
file = f"file:///{file.resolve()}"
|
||||
return MessageSegment(
|
||||
"image", {
|
||||
"file": file,
|
||||
"type": type_,
|
||||
"cache": cache,
|
||||
"proxy": proxy,
|
||||
"cache": _b2s(cache),
|
||||
"proxy": _b2s(proxy),
|
||||
"timeout": timeout
|
||||
})
|
||||
|
||||
@ -148,17 +157,23 @@ class MessageSegment(BaseMessageSegment):
|
||||
return MessageSegment("poke", {"type": type_, "id": id_})
|
||||
|
||||
@staticmethod
|
||||
def record(file: str,
|
||||
def record(file: Union[str, bytes, BytesIO, Path],
|
||||
magic: Optional[bool] = None,
|
||||
cache: Optional[bool] = None,
|
||||
proxy: Optional[bool] = None,
|
||||
timeout: Optional[int] = None) -> "MessageSegment":
|
||||
if isinstance(file, BytesIO):
|
||||
file = file.read()
|
||||
if isinstance(file, bytes):
|
||||
file = f"base64://{b64encode(file).decode()}"
|
||||
elif isinstance(file, Path):
|
||||
file = f"file:///{file.resolve()}"
|
||||
return MessageSegment(
|
||||
"record", {
|
||||
"file": file,
|
||||
"magic": _b2s(magic),
|
||||
"cache": cache,
|
||||
"proxy": proxy,
|
||||
"cache": _b2s(cache),
|
||||
"proxy": _b2s(proxy),
|
||||
"timeout": timeout
|
||||
})
|
||||
|
||||
@ -191,23 +206,30 @@ class MessageSegment(BaseMessageSegment):
|
||||
return MessageSegment("text", {"text": text})
|
||||
|
||||
@staticmethod
|
||||
def video(file: str,
|
||||
def video(file: Union[str, bytes, BytesIO, Path],
|
||||
cache: Optional[bool] = None,
|
||||
proxy: Optional[bool] = None,
|
||||
timeout: Optional[int] = None) -> "MessageSegment":
|
||||
return MessageSegment("video", {
|
||||
"file": file,
|
||||
"cache": cache,
|
||||
"proxy": proxy,
|
||||
"timeout": timeout
|
||||
})
|
||||
if isinstance(file, BytesIO):
|
||||
file = file.read()
|
||||
if isinstance(file, bytes):
|
||||
file = f"base64://{b64encode(file).decode()}"
|
||||
elif isinstance(file, Path):
|
||||
file = f"file:///{file.resolve()}"
|
||||
return MessageSegment(
|
||||
"video", {
|
||||
"file": file,
|
||||
"cache": _b2s(cache),
|
||||
"proxy": _b2s(proxy),
|
||||
"timeout": timeout
|
||||
})
|
||||
|
||||
@staticmethod
|
||||
def xml(data: str) -> "MessageSegment":
|
||||
return MessageSegment("xml", {"data": data})
|
||||
|
||||
|
||||
class Message(BaseMessage):
|
||||
class Message(BaseMessage[MessageSegment]):
|
||||
"""
|
||||
CQHTTP 协议 Message 适配。
|
||||
"""
|
||||
|
10
packages/nonebot-adapter-cqhttp/poetry.lock
generated
10
packages/nonebot-adapter-cqhttp/poetry.lock
generated
@ -167,7 +167,7 @@ reference = "aliyun"
|
||||
|
||||
[[package]]
|
||||
name = "nonebot2"
|
||||
version = "2.0.0-alpha.12"
|
||||
version = "2.0.0a13.post1"
|
||||
description = "An asynchronous python bot framework."
|
||||
category = "main"
|
||||
optional = false
|
||||
@ -226,7 +226,7 @@ reference = "aliyun"
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "0.16.0"
|
||||
version = "0.17.0"
|
||||
description = "Read key-value pairs from a .env file and set them as environment variables"
|
||||
category = "main"
|
||||
optional = false
|
||||
@ -418,7 +418,7 @@ reference = "aliyun"
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.7.3"
|
||||
content-hash = "b8ec196a78675b4098ab7509cbdbd311ffcbcf1ce8b625c589f1e95596801c71"
|
||||
content-hash = "f1908ea0987f3c4a50d18c8d0350b73602fa57e9349e1aa95b3190064ebcc881"
|
||||
|
||||
[metadata.files]
|
||||
certifi = [
|
||||
@ -500,8 +500,8 @@ pygtrie = [
|
||||
{file = "pygtrie-2.4.2.tar.gz", hash = "sha256:43205559d28863358dbbf25045029f58e2ab357317a59b11f11ade278ac64692"},
|
||||
]
|
||||
python-dotenv = [
|
||||
{file = "python-dotenv-0.16.0.tar.gz", hash = "sha256:9fa413c37d4652d3fa02fea0ff465c384f5db75eab259c4fc5d0c5b8bf20edd4"},
|
||||
{file = "python_dotenv-0.16.0-py2.py3-none-any.whl", hash = "sha256:31d752f5b748f4e292448c9a0cac6a08ed5e6f4cefab85044462dcad56905cec"},
|
||||
{file = "python-dotenv-0.17.0.tar.gz", hash = "sha256:471b782da0af10da1a80341e8438fca5fadeba2881c54360d5fd8d03d03a4f4a"},
|
||||
{file = "python_dotenv-0.17.0-py2.py3-none-any.whl", hash = "sha256:49782a97c9d641e8a09ae1d9af0856cc587c8d2474919342d5104d85be9890b2"},
|
||||
]
|
||||
pyyaml = [
|
||||
{file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
|
||||
|
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "nonebot-adapter-cqhttp"
|
||||
version = "2.0.0-alpha.12"
|
||||
version = "2.0.0-alpha.13"
|
||||
description = "OneBot(CQHTTP) adapter for nonebot2"
|
||||
authors = ["yanyongyu <yyy@nonebot.dev>"]
|
||||
license = "MIT"
|
||||
@ -25,7 +25,7 @@ exclude = ["nonebot/__init__.py", "nonebot/adapters/__init__.py"]
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.7.3"
|
||||
httpx = "^0.17.0"
|
||||
nonebot2 = "^2.0.0-alpha.12"
|
||||
nonebot2 = "^2.0.0-alpha.13"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
nonebot2 = { path = "../../", develop = true }
|
||||
|
@ -155,7 +155,7 @@ class MessageSegment(BaseMessageSegment):
|
||||
return {"msgtype": self.type, self.type: copy(self.data)}
|
||||
|
||||
|
||||
class Message(BaseMessage):
|
||||
class Message(BaseMessage[MessageSegment]):
|
||||
"""
|
||||
钉钉 协议 Message 适配。
|
||||
"""
|
||||
|
@ -266,7 +266,7 @@ class MessageSegment(BaseMessageSegment):
|
||||
return cls(type=MessageType.POKE, name=name)
|
||||
|
||||
|
||||
class MessageChain(BaseMessage):
|
||||
class MessageChain(BaseMessage[MessageSegment]):
|
||||
"""
|
||||
Mirai 协议 Message 适配
|
||||
|
||||
|
@ -4,7 +4,15 @@ sidebar: auto
|
||||
|
||||
# 更新日志
|
||||
|
||||
## v2.0.0a12
|
||||
## v2.0.0a14
|
||||
|
||||
- 修改日志等级,支持输出等级自定义
|
||||
- 修复日志输出模块名错误
|
||||
- 修改 `Matcher` 属性 `module` 类型
|
||||
- 新增 `Matcher` 属性 `plugin_name` `module_name` `module_prefix`
|
||||
- 移除 `bot.call_api` 参数 `self_id` 切换机器人支持
|
||||
|
||||
## v2.0.0a13.post1
|
||||
|
||||
- 分离 `handler` 与 `matcher`
|
||||
- 修复 `cqhttp` secret 校验出错
|
||||
|
@ -1,3 +1,4 @@
|
||||
ENVIRONMENT=dev
|
||||
LOG_LEVEL=25
|
||||
CUSTOM_CONFIG=common
|
||||
FASTAPI_RELOAD_DIRS=["test_plugins"]
|
||||
|
@ -2,6 +2,7 @@ DRIVER=nonebot.drivers.fastapi
|
||||
HOST=0.0.0.0
|
||||
PORT=2333
|
||||
DEBUG=true
|
||||
LOG_LEVEL=DEBUG
|
||||
|
||||
SUPERUSERS=["123123123"]
|
||||
NICKNAME=["bot"]
|
||||
|
Loading…
Reference in New Issue
Block a user