简洁高效
采用异步 I/O,摆脱官方 API 繁杂的操作,以更加简洁和可读性更高的代码让你专注于你的业务逻辑。
diff --git a/404.html b/404.html new file mode 100644 index 00000000..f3cba621 --- /dev/null +++ b/404.html @@ -0,0 +1,19 @@ + + +
+ + +404
But if you don't change your direction, and if you keep looking, you may end up where you are heading.
=0)c=r.activeElement;else{var f=i.tabbableGroups[0],p=f&&f.firstTabbableNode;c=p||d("fallbackFocus")}if(!c)throw new Error("Your focus-trap needs to have at least one focusable element");return c},v=function(){if(i.containerGroups=i.containers.map(function(c){var f=hr(c,n.tabbableOptions),p=vr(c,n.tabbableOptions),T=f.length>0?f[0]:void 0,N=f.length>0?f[f.length-1]:void 0,M=p.find(function(P){return ue(P)}),m=p.slice().reverse().find(function(P){return ue(P)}),w=!!f.find(function(P){return oe(P)>0});return{container:c,tabbableNodes:f,focusableNodes:p,posTabIndexesFound:w,firstTabbableNode:T,lastTabbableNode:N,firstDomTabbableNode:M,lastDomTabbableNode:m,nextTabbableNode:function(U){var Q=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!0,K=f.indexOf(U);return K<0?Q?p.slice(p.indexOf(U)+1).find(function(V){return ue(V)}):p.slice(0,p.indexOf(U)).reverse().find(function(V){return ue(V)}):f[K+(Q?1:-1)]}}}),i.tabbableGroups=i.containerGroups.filter(function(c){return c.tabbableNodes.length>0}),i.tabbableGroups.length<=0&&!d("fallbackFocus"))throw new Error("Your focus-trap must have at least one container with at least one tabbable node in it at all times");if(i.containerGroups.find(function(c){return c.posTabIndexesFound})&&i.containerGroups.length>1)throw new Error("At least one node with a positive tabindex was found in one of your focus-trap's multiple containers. Positive tabindexes are only supported in single-container focus-traps.")},y=function x(c){var f=c.activeElement;if(f)return f.shadowRoot&&f.shadowRoot.activeElement!==null?x(f.shadowRoot):f},g=function x(c){if(c!==!1&&c!==y(document)){if(!c||!c.focus){x(h());return}c.focus({preventScroll:!!n.preventScroll}),i.mostRecentlyFocusedNode=c,br(c)&&c.select()}},E=function(c){var f=d("setReturnFocus",c);return f||(f===!1?!1:c)},b=function(c){var f=c.target,p=c.event,T=c.isBackward,N=T===void 0?!1:T;f=f||Fe(p),v();var M=null;if(i.tabbableGroups.length>0){var m=l(f,p),w=m>=0?i.containerGroups[m]:void 0;if(m<0)N?M=i.tabbableGroups[i.tabbableGroups.length-1].lastTabbableNode:M=i.tabbableGroups[0].firstTabbableNode;else if(N){var P=lt(i.tabbableGroups,function(H){var te=H.firstTabbableNode;return f===te});if(P<0&&(w.container===f||De(f,n.tabbableOptions)&&!ue(f,n.tabbableOptions)&&!w.nextTabbableNode(f,!1))&&(P=m),P>=0){var U=P===0?i.tabbableGroups.length-1:P-1,Q=i.tabbableGroups[U];M=oe(f)>=0?Q.lastTabbableNode:Q.lastDomTabbableNode}else ge(p)||(M=w.nextTabbableNode(f,!1))}else{var K=lt(i.tabbableGroups,function(H){var te=H.lastTabbableNode;return f===te});if(K<0&&(w.container===f||De(f,n.tabbableOptions)&&!ue(f,n.tabbableOptions)&&!w.nextTabbableNode(f))&&(K=m),K>=0){var V=K===i.tabbableGroups.length-1?0:K+1,G=i.tabbableGroups[V];M=oe(f)>=0?G.firstTabbableNode:G.firstDomTabbableNode}else ge(p)||(M=w.nextTabbableNode(f))}}else M=d("fallbackFocus");return M},A=function(c){var f=Fe(c);if(!(l(f,c)>=0)){if(ye(n.clickOutsideDeactivates,c)){s.deactivate({returnFocus:n.returnFocusOnDeactivate});return}ye(n.allowOutsideClick,c)||c.preventDefault()}},k=function(c){var f=Fe(c),p=l(f,c)>=0;if(p||f instanceof Document)p&&(i.mostRecentlyFocusedNode=f);else{c.stopImmediatePropagation();var T,N=!0;if(i.mostRecentlyFocusedNode)if(oe(i.mostRecentlyFocusedNode)>0){var M=l(i.mostRecentlyFocusedNode),m=i.containerGroups[M].tabbableNodes;if(m.length>0){var w=m.findIndex(function(P){return P===i.mostRecentlyFocusedNode});w>=0&&(n.isKeyForward(i.recentNavEvent)?w+1 ComWeChatBot Client 是 PC hook 微信的协议端。 需在服务配置开启 正向 websocket。 go-cqhttp 以下简称 gocq。它是基于 Mirai 以及 MiraiGo 的 OneBot Golang 原生实现。 注意 go-cq 的配置中, 更改默认适配器可以让你搭建其他平台的机器人,除了 AmiyaBot 对象拥有一个适配器参数 adapter,接受一个 从左侧导航挑选合适的适配器,更改你的实例。 KOOK 是一款免费无广告的语音沟通工具,并提供了强大的官方机器人支持。 KOOK 适配器不需要 mirai-api-http 以下简称 mah。是一个在全平台下运行,提供 QQ Android 协议支持的高效率机器人库。 如要使用此适配器,需要 mah 同时开启了 websocket 和 http 服务。AmiyaBot 将通过 mirai_api_http 方法实例化它的适配器。 OneBot 11 以下简称 ob11。该标准是从原 CKYU 平台的 CQHTTP 插件接口修改而来的通用聊天机器人应用接口标准。 OneBot 12 以下简称 ob12 。是一个聊天机器人应用接口标准,旨在统一不同聊天平台上的机器人应用开发接口,使开发者只需编写一次业务逻辑代码即可应用到多种机器人平台。 按照 基础指南 创建的机器人即为 QQ 频道机器人,无需额外的适配器。 考虑到开发者事件接收时可以实现负载均衡,QQ 提供了分片逻辑,事件通知会落在不同的分片上,可参考官方文档 分片连接LoadBalance 了解分片机制。 创建分片连接需要使用分片适配器 注意 每个分片的启动应当按顺序缓慢进行,切勿同时启动,以免 gateway 返回的信息一致造成连接失败。 使用 全域机器人指同时支持群聊、消息记录和频道的机器人 全域适配器的参数和用法和QQ 群机器人一致,请参考文档。 创建后机器人能够同时接收到来自群聊、消息记录和频道的消息。 在 QQ 开放平台 完成企业主体入驻,即可创建可在 QQ 群聊里使用的 QQ 群机器人。 QQ 群聊适配器需要在本地启动资源服务让腾讯端能够访问媒体资源,默认在公网下使用。如果无法在公网下部署,请参考自定义资源服务。 考虑到开发者事件接收时可以实现负载均衡,QQ 提供了分片逻辑,事件通知会落在不同的分片上,可参考官方文档 分片连接LoadBalance 了解分片机制。 注意 每个分片的启动应当按顺序缓慢进行,切勿同时启动,以免 gateway 返回的信息一致造成连接失败。 引入 多数情况下我们推荐使用第三方托管服务来搭建资源服务,如 腾讯云COS 或 阿里云OSS 等。 通过自定义默认的 AmiyaBot 为异步程序,一般情况下应该遵循异步编程来进行开发。但在使用一些标准或第三方库时,不能保证其方法是异步的,使用 IO 阻塞的方法容易造成线程阻塞,影响业务逻辑。 AmiyaBot Chain 对象在构建消息时,可使用辅助类介入媒体消息或浏览器的构建过程。 继承 该函数会在 chain 构建图片消息时调用,每张图片调用一次。传入一个参数 该函数会在 chain 构建语音消息时调用,每个语音文件调用一次。传入文件路径 该函数会在 chain 构建视频消息时调用,每个视频文件调用一次。传入文件路径 该函数会在 chain 构建浏览器渲染的图片并打开了页面时调用,提供了浏览器的 提示 构建浏览器渲染的图片同样也会调用一次 使用示例 AmiyaBot 提供了 Sqlite 和 MySQL 数据库的 ORM 支持。基于 peewee ,封装了部分表结构管理以及查询转换。日常使用的查询操作详见 peewee文档 温馨提示 AmiyaBot 仅能提供有限的数据库支持,推荐你使用自己的更优的数据库解决方案。 对于轻量级机器人应用,Sqlite 足以应对大部分的日常操作, 可自动创建数据库。 在 table 装饰的表模型内增加或删除字段,启动程序时会自动修改到数据库表结构中。暂不支持更新字段类型。 内置了部分扩展查询,方便日常使用。 批量插入 插入或更新(适配Sqlite和MySQL) 将查询结果转换为字典 将查询结果转换为字典列表 分页查询工具 AmiyaBot 提供了简易的全局事件总线的方法 publish subscribe 提示 响应方法同时支持异步和同步函数 通过装饰器订阅事件 也可以通过传入响应函数订阅事件 取消订阅需要传入订阅时的响应方法对象(且需要保证其内存地址是一致的) unsubscribe AmiyaBot 内置了异步的 HTTP 请求工具,基于 aiohttp。 不推荐使用 requests 库 requests 是著名的 python http 请求工具库。但其请求方法均为同步的。AmiyaBot 为异步程序,使用同步请求将会阻塞主线程运行。 如果是无法干预的同步请求行为,可以使用内置的线程池方法更变为异步。详见 处理阻塞操作。 直接引入 调用这个属性会尝试返回 json 格式化的 调用这个属性可以返回请求结果( 如果请求失败,可以调用这个属性获取异常( post 方法默认在请求头内添加 post_form 方法类似 post 方法。唯一不同的是请求体仅接受字典类型,发送请求时会被构建为 form data 表单数据。 文件上传 post_upload 方法以 form 表单的方式提交文件。 如果需要发送更多类型的请求,如 download_async 是提供的异步下载文件的方法。默认返回 bytes 类型的数据。 如某些场景需要使用同步下载,可使用同模块中的 download_async 方法。 AmiyaBot 内置了基于 FastApi 的HTTP服务构建工具。 运行代码,访问 http://127.0.0.1:8088/docs 即可看到生成了如下两个接口。 接口的路由将默认使用控制器类名以及方法名组成,如果需要自定义路由,在方法装饰器 route 传入 router_path 参数即可。 在创建 HttpServer 时传入 auth_key 参数,则在调用接口时,需要在请求头(Header)里添加 AmiyaBot 仅对 FastApi 的路由注册做了简易的封装。如需要扩展其用法,可获取 app 实例后参考官方文档进一步使用。 AmiyaBot 接收的消息和事件,都会历经一个完整的生命周期。你可以介入这些周期来对业务进行进一步的调整。 在接收消息到回复发送完毕,总共会经过至少五个周期,如下图所示,紫色的节点就是可以介入的周期。 如果消息响应里存在等待事件,那么周期将会延长,再次从消息接收开始,直至等待事件结束后继续向下执行。 通过 AmiyaBot 对象或 PluginInstance 对象注册周期钩子函数。 所有钩子均可以同时存在多个,按加载顺序逐个调用。 消息创建完毕阶段,可以在此阶段修改 Message 对象并返回。存在多个此钩子时,按加载顺序逐个调用,参数接受的 Message 对象受上一个函数的执行结果影响。 该钩子函数可返回三种结果: 当存在等待事件且消息分配器无返回或等待事件属于强制等待类型时,在进入等待事件前执行此钩子。 当消息分配器有返回,在执行消息响应器前执行此钩子。存在多个此钩子时,按加载顺序逐个调用。全部执行完成后当有其中一个返回 False,则不往下继续执行并结束生命周期。 当消息响应器执行完毕且存在返回时,在发送其返回前执行此钩子。可以在此阶段修改 Chain 对象并返回。存在多个此钩子时,按加载顺序逐个调用,参数接受的 Chain 对象受上一个函数的执行结果影响。 当消息响应器执行完毕且存在返回时,在发送其返回结束后执行此钩子。 当消息响应器执行完毕(无论有没有返回)后执行此钩子。 目前事件生命周期只有一个。 本篇将会讲解 AmiyaBot 插件的工作原理以及插件导入和卸载的方法。 插件设计的目的是因为 兔兔-v6 通常会以可执行文件的形式发布,将一些功能模块分散为插件的形式,更有助于用户灵活的管理自己的机器人功能,以及对单独的功能进行热更新。 AmiyaBot 的插件加载得益于 Python 作为脚本语言的一个优点:动态执行代码,配合标准库的 开发插件可以参考主项目的 插件开发 形式,但本篇并不是作为主项目的引导文档。在主项目里,插件是一个 zip 压缩包,在加载时解压并导入,完成代码的动态执行。 但在本框架的设计里,插件还能有另外几种导入方式。 插件实例可以直接在你的程序里定义,这是正常的做法,事实上这对你的功能管理(如动态增加或软删除功能时)有很大帮助。 此时,该 AmiyaBot 实例便拥有了这个插件定义的所有消息响应、事件或定时任务等功能。 这是写有插件注册代码的一个 Python 文件 以 Python 文件导入这个插件 单文件插件是最简单的开发方式,这是在不考虑文件大小、静态资源或变量污染的前提下。 如果一个插件的逻辑非常复杂,它可能会是以 Package 目录的形式存在。此时插件的目录应如下所示。 以目录导入这个插件 如果一些插件甚至带有静态资源,虽然 Package 目录同样能实现,但为了方便传播,它可能会是一个 zip 压缩文件。 以 zip 导入这个插件 这里将会发生另一种情况,在 zip 导入时,并不是像上述两种方式一样相当于执行 这有点抽象,你可以理解为使用了 python 命令执行了 那么当 install_plugin 里提供了参数解决这一问题。先看看这个方法的参数列表。 解决上述问题,我们只需要添加参数 注意 每次执行加载时都会解压一次全部文件,但如果路径已存在,将不会覆盖原文件。如果插件有一些原文件修改的更新,应注意这一特性。 插件实例都有一个唯一 ID(属性 plugin_id),通过这个 ID 可以卸载指定的插件。 uninstall_plugin AmiyaBot 使用 logging 模块构建日志体系。 AmiyaBot 的日志同样拥有与 logging 相同的等级输出。输出的日志会储存在 日志的固有格式如下所示,分别为 提示 使用启动参数 将输出如下日志 如果你并不需要处理异常,仅仅希望异常不会终止你的程序,log 模块提供了上下文管理的方式,来捕获并输出在上下文中产出的异常。 你可以创建一个独立的日志模块以标记输出。 LoggerManager 如果你不喜欢默认的日志输出,你也可以全局修改日志模块。但必须注意的事,你自定义的模块,必须包含常规的几个等级输出方法。 改变默认的 Playwright 启动行为。 使用启动参数修改一些内置变量 代码部署 可执行文件部署 AmiyaBot 提供了全局定时任务管理器,基于 apscheduler。 ⚠️注意⚠️不兼容的更新 在版本 1.6.9 开始,不再支持全局定义定时任务,以及移除了 custom 参数,使用更泛用的 apscheduler trigger 控制任务执行时机。 此方法已不再支持,全局变量 tasks_control 已删除。 bot.timed_task 装饰器的 custom 参数已删除。 如果你正在开发插件,并且你不希望在插件的卸载方法里手动取消定时任务,请务必通过 通过 插件开发详情请查看 插件开发文档。 注意 使用 kwargs 自定义定时任务时,不可使用参数 使用 为需要取消的定时任务传入 sub_tag 参数,可以重复,重复的 sub_tag 会被整合为一组。 按标签名取消定时任务 示例:执行一次后取消任务 API 是实现适配器部分基础逻辑的基础,你也可以调用 API 来进一步完成你的业务逻辑。 请注意 不用的适配器下,api 的方法不尽相同,在创作多平台机器人时,请注意可能产生的影响。 我们强烈建议在使用不同的适配器时,引入相应的 API 类注解 api 属性。它们通常在适配器模块的 api 实际上是调用了不同平台提供的接口。在不同平台下,调用接口的鉴权规则也不一样。所有适配器的 api 都存在以下三个方法,参数也是一样的。方法内部实现了不同平台的鉴权规则,你只需要直接调用接口即可。 可以使用除 get、post 以外的一些 method 调用接口。 为什么没有 headers 参数 方法内部实现了不同平台的鉴权规则,控制了 headers 的内容,你不需要手动控制。如果你希望自己实现请求,请使用 👉 进阶指南 - HTTP 请求 🚧 API 文档尚处于建设阶段,缺少 api 说明和参数释义,请酌情阅读。🚧 发送 QQ 频道 ARK 模板消息,详情请查看发送 ark 消息。 发送 提示 私信回复里无效 Chain 对象在实例化的时候,默认会在消息体头部添加 发送 提示 私信回复里无效 发送 QQ 频道卡片消息,详情请查看 embed 消息 使用原生消息模板扩展 Chain 对象 温馨提示 消息扩展暂仅支持 除了以上内置的消息类型外, 使用 mirai-api-http 消息类型 Dice 发送一个点数 6 的骰子魔法表情 使用 go-cqhttp 消息类型 链接分享 发送一个百度首页链接分享 发送 CQ 码目前仅支持 仅限 QQ 内置的小黄人表情 合并转发消息不是 Chain 对象的方法,需要使用适配器提供的工具类构建。 注意 QQ 频道机器人暂时不支持发送合并转发消息,目前仅支持 add_message 如果 add_message_by_id 为 发送一张使用 html 页面生成的图片,在同等工作量下,它通常比使用 PIL 合成的图片更加生动,技术难度也比 PIL 要低得多。 操作系统支持 HTML制图需要使用 playwright 模块,所以仅支持以下操作系统: 命令行执行以下命令安装 Chromium 内核: 在 bot(包括多账号实例)启动的 start 方法内设置参数 创建文件 hello.html 并定义全局的 init 方法。 将需要渲染的数据传入模板: 示例在触发会话并开始发送消息时,Chain 对象将会调用 Chromium 无头浏览器,渲染 发挥你的想象,写出更美观的页面! 支持直接使用网站URL生成图片。 注意 在页面加载完毕后,默认预留200ms的渲染时间。如果页面有部分元素是异步渲染的,将有可能不显示在图片内。可通过参数 设置参数 触发会话时,渲染 发送一张图片 当传入的类型为字符串时,image 方法会认为值为本地图片的路径并尝试读取文件。也可以直接传入 bytes 类型。 添加一张由 Markdown 文本渲染的图片,直接传入 md 格式的文本,使用 HTML 模式渲染。建议先阅读 发送 html 生成的图片 了解如何启动 Chromium。 注意 这并非 QQ 机器人官方提供的 发送 markdown 消息 ,要发送官方的 Markdown 模版消息请查看 Markdown 模版。 提示 文本量大时,渲染将会耗费较长时间,默认预留200ms的渲染时间。可通过参数 更改全局配置可以使用自己的 HTML 文件渲染 Markdown HTML 内需要实现以下方法获取到 Markdown 文本 发送 QQ 频道 Markdown 模板消息,详情请查看发送 markdown 消息。 自定义按钮通过内置的 InlineKeyboard 类构建。 发送频道跳转超链接 注意 仅支持 QQ 频道 发送一段文字消息 设置 在文本内使用 此方法可以将文字转换为图片发送,使用调色模板可以进行文字调色,亦可插入图片渲染。 在文本内使用 [cl 文字内容@#颜色代码 cle] 格式的模板,将指定文字内容改变颜色。 text_image 无法插入表情 如果 在 images 参数内传入一个包含 ImageElem 对象的列表,可在文字图片内渲染图片。 ImageElem 简单尝试一下,发送一张 你可以收到如下的回复 可以使用自己的 ttf 字体文件更换字体 发送一段 mp4 格式的视频 注意 QQ 频道机器人暂时不支持发送视频,目前仅支持官方 QQ 群。 发送一段 wav 格式的音频 注意 QQ 频道机器人暂时不支持发送语音,目前仅支持官方 QQ 群、 在一些使用场景里,需要机器人与使用者产生连续的对话。比如询问使用者以获取信息等。 Message 对象内置了连续对话支持。 参数列表 使用 wait 方法实现一个简单的连续对话 等待通常不会影响消息分配器运作,也就是说 仅在不能触发任何其他功能 的时候,消息才会返回到当前等待处。(也包括本功能的初始触发方式,一般功能的优先级默认为 1,比等待事件的默认优先级高) 如果你不希望如此,使用参数 如果在等待过程中,希望 wait 接收到符合期望的消息后再返回到等待处,可以使用 data_filter 参数过滤消息。 同一个子频道内的同一个用户只能存在一个等待事件,当一个新的等待事件创建后,上一个未使用的等待事件会被注销并引发 注意 该方法不可用于支持私信的功能里 参数列表 wait_channel 方法用于等待子频道全体成员的回复。 与 wait 方法不同的是,wait_channel 返回的不是 Message 对象,而是 ChannelMessagesItem 属性 方法 下面来看一个简单的例子 关闭等待事件 wait_channel 与 wait 的用法是大致相同的,但是 wait_channel 在接收到有效消息并返回后,不会像 wait 那样关闭事件,而是保持接收子频道的消息。在你的业务逻辑正常结束时,你必须使用 请注意 请务必让你的业务逻辑有机会关闭等待事件,否则等待事件没有被正常关闭时,它可能会持续拦截子频道消息直至超时自动关闭。 如果你持续调用 wait_channel(如示例所示),但你不希望在处理业务时错过子频道内的消息,可以设置参数 监听频道发生的事件。 频道事件 如下为 频道事件 - GUILD_CREATE 官方文档示例的 websocket 消息体。 在 on_event 里接收到时 频道的事件名和内容可以查看官方文档 事件订阅 mirai-api-http 戳一戳事件推送的消息。 监听 type 的值 针对 go-cqhttp 事件的 数据结构,AmiyaBot 对其进行了分类,可以使用以下事件名精确获取。 如:戳一戳事件中 go-cqhttp 对其进行了三级分类。 监听 通过事件名 其他类别的事件同理,通过事件数据中的 AmiyaBot 内置了全局的用户层面异常捕获让程序不会轻易崩溃。如果你希望获取这些异常,可以通过注册异常处理器来获得它们。在执行消息响应或在消息响应筛选过程产生异常时,会执行这些注册的异常处理函数。 异常处理函数接受三个参数,分别是 异常类型实例 在开始之前,我们希望你知道 AmiyaBot 是一款针对 QQ 频道机器人的框架。如果你想使用第三方机器人服务(如 mirai-api-http 或 go-cqhttp),可通过更改实例的适配器使用。 我们建议你先在 QQ开放平台 了解 QQ 频道机器人的运营规则,这非常重要,因为在本文档里,关于这部分的内容将会非常少。如果你并不了解 QQ 频道机器人,后续的文档可能会对你造成疑惑。 那么恭喜你,你的 QQ 机器人已经可以正常运作了。 「这是个人迈出的一小步,但却是人类迈出的一大步。」—— 阿姆斯特朗 如果你在平台创建的是私域机器人,在 此时机器人只能接收指定前缀的对话 使用 AmiyaBot 功能开发的关键模块一共有三个,分别是 首先需要知道的是,如何注册消息响应。 何为机器人的功能? 消息响应我们会在后续的文档称之为功能,因为机器人的主要功能,一般就是通过响应用户的消息实现的。 装饰器 参数 实例化 GroupConfig 对象创建一个功能组,并将其设置到 bot 实例内。 为消息响应设置 注意 仅当 on_message 里没有设置该参数时,功能组的参数才会对其生效。否则优先使用 on_message 的参数。 AmiyaBot 私域模式建议在对话中包含指定前缀才能进入消息分配器。设置前缀触发词后,在一些特殊的情况下,可以通过 添加参数 关键字传入 完全匹配的句式指对话的文本内容全等于 keyword 关键字(不包括 @ 部分),使用工具类 提示 在私域机器人下且不需要 @ 唤起的场合,Equal 会无视前缀检查。 keywords 支持由 当关键字校验存在冲突时,可以通过指定优先级供消息分配器选择。 提示 优先级允许为 0 或负数。 当关键字检查无法满足功能的触发方式时,就需要使用自定义检查。 当消息响应获得执行权,自定义检查结果(Verify)将储存在 Message.verify 中。 Verify 对象 返回元组第二个值,即可以指定动态优先级。 返回元组第三个值,可以向 Message 的检查结果里添加关键值信息。可以是任意类型。 设置参数 AmiyaBot 支持同时启动多个账号,所有账号之间可以共享资源以及功能。 你可以同时创建多个互不相关的实例,只需要实例化多个 MultipleAccounts 多账号支持动态创建和删除里面的实例,也允许先创建空多账号实例。随后往里面插入 AmiyaBot 对象。 插入一个实例 示例 撤回一条消息。 使用 Message 对象的 Message.recall() 你可以在任何能获取到 Message 对象的地方使用 使用适配器实例的 AmiyaBot.instance.recall_message() 如果是通过在消息响应器里面返回 Message.send() 方法返回一个 调用 Message.wait() 没有返回执行 在上一节 注册消息响应 中讲述了如何注册一个接收指定消息的函数,当接收到消息时,函数被执行时,参数 重要变动 从 接下来,使用 Message 对象提供的属性完成业务逻辑,并发送消息。 主动消息限制 主动消息需要频道设置允许机器人推送,发送消息也存在数量限制。 send_message 是发送主动消息的方法,它的第一个参数 chain 需要传入一个 Chain 对象。可以通过实例化一个无 Message 的 Chain 得到。 传入参数 提示 事件响应的方法通常可以返回触发了该事件的实例。 在消息响应器内返回 Chain 对象,或使用 Message 对象的 send 方法,均可发送消息。 这是最简单的一条文本消息: 更多类型请查看左侧导航消息构建元素。 Chain 对象支持链式语法,用于构建复杂的消息结构。 合并转发消息需要使用独立的工具类创建 实例化 Chain 对象时,不传入 Message 对象构建的。本文称之为 “空 Chain”。空 Chain 一般用于预构建消息。 Chain 对象在构建消息时,可使用辅助类介入媒体消息或浏览器的构建过程。请移步 进阶指南 - 介入媒体消息的构建过程 在本章后续文档中,为了减少篇幅量,将不会再在代码示例中出现 你将会在诸多场景中看到 bot 变量的存在。 使用适配器 运行实例,访问 https://console.amiyabot.com/#/test 并输入配置的地址和 appid 连接测试服务,即可模拟测试对话。 AmiyaBot 是一款 QQ 机器人框架,基于异步I/O构建,并提供了装饰器模式(Decorator Pattern)以及插件化动态导入(Dynamic import)的编程方式。 内置多种适配器兼容不同的服务来源以及丰富的消息构建类型,旨在帮助你更高效地编写业务逻辑,实现你的创意。 框架结构可分为 业务模块由 运转中心在访问这些对象时,顶级对象会组合所有子级对象的属性提供给运转中心。 实现上述模式的思路来源于 Vue 的 组件设计 ,所以在代码层面,插件与账号实例都属于“组件”,理论上账号实例之间也能互相嵌套。但在设计时加入了限制,防止这种关系的出现。 业务模块通过各种装饰器来定义业务逻辑以及规则。这些装饰器都来源于它们继承的工厂对象(BotHandlerFactory),工厂对象是业务模块与运转中心的桥梁,业务函数会被工厂对象收集并封装,最终提供给运转中心调用。 账号实例图解 通讯模块主要形式为 账号实例里面有一个重要参数:adapter,它指定了通讯模块的运行方式(选择适配器)以适配不同运营方的连接协议。 通讯模块负责解决对接收和发送的数据进行解包与封包,所以它与数据处理模块紧密相连。适配器必须为数据处理模块提供统一的接口,在与不同的运营方服务通讯过程中,运转中心获取到的源数据格式都必须是一致的。而接收到运转中心需要发出的数据时,适配器需要把数据封包为运营方协定的格式,完成发送。 账号实例也可以脱离运转中心的调控随时调用适配器与运营方服务通讯,完成一些特殊情况下的操作。 运转中心包含事件处理,消息处理、分配器和日志 等各种将所有模块相连的处理单元,是业务层的主要调度模块。在接收到由适配器传入的消息后,运转中心会将消息归类并分发到不同的处理模块。 分配到消息处理的数据,将会对数据和业务模块收集的消息响应器(on_message)进行匹配,并将匹配成功的消息响应器按优先级排序,选取最高优先级的执行。 消息响应器的执行结果会经由运转中心返回到适配器,进行封包后发送。 这个过程在运转中心内称为“生命周期”,工厂对象可以介入这些周期影响消息处理的分配与执行结果。 存储模块以及运转中心的日志等模块,在本文将忽略不谈。这些模块依赖第三方库或 Python 的标准库,本框架仅对其做了适度的封装。 你应该添加一个文档并详细描述插件的使用方法 demo 允许直接使用 markdown 文件路径作为文档,只需要把路径传入到 PluginInstance 对象的 若使用了 AmiyaBotPluginInstance 对象创建插件,可使用参数 提示 我们不推荐这种做法,除非你的插件文档只有少量文本。 AmiyaBotPluginInstance 是 兔兔-v6 项目中的对插件类 PluginInstance 的扩展实现。使用该类创建插件时,能够获得更多的能力。 在 demo 项目里导入 AmiyaBotPluginInstance 继承了 PluginInstance,并且拥有以下新参数和方法 你可以通过对接控制台使用户能够使用控制台的界面管理你的插件。如下所示。 请阅读 介绍文档 了解如何编辑 jsonSchema.json 文件。 读取一个指定名称的配置项,如果没有频道级别的配置则返回同名全局配置,如果也没有全局配置,返回 写入配置,传入 插件加载时会进行下面的校验,校验不通过则会报错: 当有下列需求的时候,建议考虑提供 schema 文件: 提供 插件初次安装初次加载时,会将 当插件需要在别的插件的基础上运行,可在 requirements 参数内传入 Requirement 提示 插件添加依赖后,仅当全部依赖安装成功后,才会进行自身的安装。 插件推荐发布为 zip 压缩包 将整个插件文件夹压缩为 zip 包即可。如果你的插件使用了第三方库,你可能需要将第三方库手动放进插件包内。 如在插件内使用了库 当 注意 若无法通过上述手段将第三方包添加进插件包,你的插件将可能不支持 可执行文件部署 。你可以在你的插件文档内注明,并让代码部署的用户使用 如果你是在 demo 下开发的插件,运行脚本 run_build.py 即可自动打包。 注意 脚本打包尚处于试验阶段,你仍然需要手动添加第三方包进插件 在 pluginsDev 目录内,创建一个 Python package 形式的模块。为了防止加载插件时的全局环境变量污染。你的业务逻辑必须在另一个入口文件内进行。 创建入口文件 在 这些消息响应器都会在插件被加载时注册到主体程序中。 如果你的插件包含静态资源,在读取它们时,应该以插件的绝对目录读取。 在 注意 请不要在 调试插件的方法有很多种,一般来说,你可以直接打包插件然后放到主项目中加载并使用来观察效果,显然这不够效率。AmiyaBot 提供了 测试实例,在主项目中,有现成的示例脚本。 在 兔兔-v6 根目录下的 run_test.py 内,修改如下位置的代码。 运行 插件并不强制要求在 demo 的项目下开发,如果插件不需要使用 demo 的内置模块,那么你可以在自己任意的目录内开发而不需要克隆 demo 项目以及插件仓库的代码。 以下说明默认在 demo 项目内。 克隆 兔兔-v6 在 demo 项目内克隆插件开发仓库 此时的目录结构应为 建议安装 兔兔-v6 的依赖,如果不需要使用 demo 的内置模块或是你在自己的目录内开发插件。那么只需要安装 amiyabot 即可。 本篇介绍如何使用 draft-07 版本的 JsonSchema 规范编辑 插件对接控制台的配置文件。 字段定义在 properties 对象里: 示例 默认值 默认值 当 type 为 integer 时,只能输入整数,为 number 时可以输入浮点数。 默认值 下拉选择框不需要 当 配置相应的 format 以使用不同种类的选择器 当使用范围选择器时,数据为数组类型,代表范围,如 如果你需要 如果你需要 有时候有些复杂配置不能用只有一级的对象(字典)存放,使用子级对象组建多层的对象(字典)。 以上配置调用 你可以在插件被安装或卸载时执行一些操作,只需要继承 PluginInstance 并覆盖其方法。 访问 控制台-插件商店,点击【上传创意插件】完成流程即可发布到商店。 上传插件时需要输入一个密钥,在你更新或下架你的插件时,需要输入这个密钥校验。请牢记你的密钥。 在 “实例”指的是机器人的账号,兔兔支持登录多个机器人账号同时使用,多个账号之间共享数据。 在左侧导航选择“实例管理”,并点击“添加实例” 在 QQ 开放平台 查看并在实例配置填写你注册的频道机器人信息,表单字段就是对应内容。 在 QQ 开放平台 查看并在实例配置填写你注册的群机器人信息,表单字段就是对应内容。 在 KOOK 开发者平台 查看并在实例配置填写你创建的机器人应用的 需要配置 go-cqhttp 目录下的文件 access-token 可在 go-cqhttp 目录下的文件 AuthKey 可在 mirai-console 目录下的文件 点击“保存并启动”运行实例,当实例状态为在线时,即启动成功。 温馨提示 实例的连接需要时间,通常只需要几秒钟不到。如果显示离线可以等待几秒再刷新页面。 配置时打开选项 兔兔在启动后会开启一个 HTTP 服务,默认端口为 8088。提供给 控制台 调用。你可以根据需要在启动前修改配置 authKey 为连接控制台时的密钥,默认不需要。 危险 当您试图将控制台暴露至公网时,请务必设定 authKey! 注意 不推荐您使用纯数字,或是简单的数字、字母组合等弱口令作为 authKey(如 浏览器打开控制台 https://console.amiyabot.com,在主界面填入你的服务地址。 点击“测试并连接”,成功进入后即可开始配置。 host 配置为 危险 当您试图从公网连接至控制台时,请务必通过反向代理加密连接! 我们的团队成员 Initial-heart 为你提供了 一篇通过 Nginx 反向代理加密连接的博客,你可以根据这篇博客对你的连接进行加密。 选择安装你需要的功能插件 插件商店可以实时查看有新版本的插件,需要时可通过商店更新。 插件通常是一个zip压缩包或是 Python Package 的形式,你可以将它放在 plugins 目录下,启动时可以自动安装。 安装好的插件可以在插件管理查看,现在,你已经可以使用它们了。 部分插件可能存在需要配置的信息,可在插件配置选项卡查看并编辑(需插件支持)。 本章收录了用户在使用官方插件时向我们提问较多的问题。 请注意 公招图片识别功能需要申请并配置相应的百度智能云 API 请根据插件文档配置相关信息。 我不知道如何申请百度云 api 注意 若使用的操作系统不在 官方支持列表 中,您可能无法正确执行该操作,也无法获得官方的任何支持。 偶现该情况是正常现象,这与设备性能等因素有关,通常再请求一次即可正常返回图片。 请理解为卡池在现在复刻了。复刻的卡池中显然会出现任何非限定或特殊干员。 可是我的各个限定干员都已经被设置为不可抽取了 本章收录了用户在部署/使用过程中向我们提问较多的问题。 请使用组合键 通常情况下,这是 git 未正确安装导致的,可能是由于你没有认真看文档根本没有安装 git 所导致。请根据文档安装 git 安装完成后,再次重复上述操作,此时应该会显示如下的文本,这就证明你配置好了 git,可以再次尝试打开可执行文件了。 我很确定我已经安装了git,但是我在黑框内输入git后还是会显示“不是内部或外部命令” 这个现象大概率是因为设备的环境变量没有配置好导致的,请参考 此文章 尝试将 git 的路径添加入环境变量中再次尝试开头的操作。 我在窗口最上方看到了一些进度条,可是没能跑完就闪退了 我尝试进入控制台时出现了 检查浏览器顶端的地址栏 通常,地址栏中协议(也就是网址最开头的http/https)应该使用http 如果你的网址以https开头,请改为http后再重新尝试访问 部分内核的浏览器可能会拦截公网对私网不安全的访问请求 若你访问控制台的设备与运行兔兔的设备不是同一台设备 你需要修改兔兔本体的一些设置 我不知道服务地址应该填什么 由于作者仅涉足过mirai,本处仅将提供mirai相关问题的解决思路,不过部分解决思路也可用在gocq中 我们先来对配置单的具体内容进行一些解释 以下是兔兔的窗口中可能出现的报错信息以及相应处理方法 请检查mirai是否正确登陆了bot的qq rt,检查mirai本体配置中authKey的值与控制台填写的是否一致 适配器类型应该是 实例配置单中的qq号是否填写错误了 温馨提示:对mcl一切配置文件的更改都应该在mcl不在运行的情况下进行 我很确定我的路径中没有中文字符 注意 若是在此处修改了端口值,你应该在控制台中填写实例配置单时相对应地修改端口值 既然你是代码部署的,一些常见的细节我将会省略不解释,希望你能看懂 我已经安装好插件了 划重点 windows的cmd存在 单击窗体内黑色部分进入快速选择 这么一个机制 标志是窗口标题最前面出现了 操作系统支持 部分插件依赖本地的 Git 服务,必须安装 Git 以确保正常使用。点击下载 并安装。或自行到官网下载合适自己系统的版本。 根据你的设备系统或习惯,你可以选择 可执行文件部署 或 代码部署 两种方式其一。 Windows 系统推荐使用可执行文件部署,可执行文件部署是一键部署的模式,部署难度低。 下载并解压,运行 不再继续提供支持,请使用代码部署。 不支持 Centos,目前在 Ubuntu 20.04 测试可用。 下载并解压,进入 请根据操作系统按顺序执行以下命令。 注意 推荐使用 Python 3.8 ~ 3.9 运行本项目代码,并参照可执行文件的系统支持选择部署的操作系统。 默认为 Chromium 部分系统由于版本过低(如 Windows Server 2012),可能不支持 chromium 内核。推荐修改为火狐(firefox)内核启动。 日志显示同可执行文件部署,成功后可以进入下一节。 《明日方舟》机器人 兔兔-v6 为本框架的主体项目。是 Amiya-Bot V5 的迭代版本。本框架原名为 AmiyaBot-core,是围绕着主体项目进行开发的。 本章接下来的文档,是提供给 兔兔-v6 部署用户的指引,如果你的目的是开发一个完全由自己定制的机器人,请阅读开发指南。 你也可以从 兔兔-v6 的基础上进行定制。但之中的一部分代码服务于其本身的《明日方舟》功能系列插件,以及控制台项目 Amiya-Bot-console2,如果你不需要这些代码,也许你难以将它们抽离。请谨慎选择此方式。 那么,如何部署属于自己的 兔兔-v6(以下简称“兔兔”),请阅读接下来的文档。 现在,你可以直接使用官方版的兔兔啦!🎉🎉🎉 每次启动时会检查资源仓库并更新新资源。 提示 资源的解包需要一定时间,无法获取更新时可稍后再试。 从 V6 开始程序将不再自动更新,也不会推送更新消息。请关注 官方频道 获取最新的通知。 当有更新时可到 下载页面 下载更新包。 插件同样也不会自动更新。请关注插件商店和频道的公告。 QQ 机器人目前分为两种,一种是由腾讯官方运营的 频道机器人,另一种则是由第三方技术提供支持的QQ群机器人。 如果你选择了频道机器人,并且注册了“公域机器人”,很遗憾地告诉你,你不能继续部署兔兔了。因为兔兔有官方版本,你不能发布一个功能与官方版本相当接近的机器人。你可以加入 官方频道 并找到机器人“AmiyaBot”,点开资料卡添加到自己的频道里。 当然,你可以基于 AmiyaBot 框架开发自己的机器人并发布。请参阅 开发指南。 如果是“私域机器人”,你仍然可以继续部署。 请在 KOOK 开发者平台,创建一个机器人应用。 如果你选择了 QQ 群机器人,你可能需要克服一些困难才能顺利部署。你必须部署第三方技术提供的服务才能继续部署兔兔。 第三方技术目前支持: 选择其中一个进行部署即可。 请按照 go-cqhttp 官网 的说明进行部署。 修改配置中的 mirai-api-http 有一定的部署难度,需要你拥有一定的计算机科学基础。但对比其他第三方服务,mirai 在很多方面都占有优势。 我们的团队成员 Initial-heart 为你提供了一篇部署 mirai-api-http 的博客,这篇博文详尽的描述了部署 mirai-api-http 的前期准备、部署、配置等整套部署 mirai-api-http 所需的流程。 https://www.initbili.top/2022/8d92a2feb3e2/#2-部署-mirai 非常重要 请耐心根据教程部署完。部署完第三方服务,你就完成 80% 的工作了!务必部署成功再往下继续,否则后续的操作将毫无意义。 如果一切就绪,那么接着来看开始部署部分。 AmiyaBot 是异步 Python QQ 机器人框架。基于 QQ 官方API 。内置了丰富的消息构建方法和适配器,让你轻松实现你的创意。 AmiyaBot 诞生于2019年,是一个《明日方舟》民间 QQ 机器人,至本框架发布时已迭代了 5 个里程碑版本。一直以来,AmiyaBot 都秉着 “为所有人带来快乐” 的初衷,跌跌撞撞到现在,依旧坚持开源、免费为用户提供服务。 2022年5月13日重大变故后,AmiyaBot 决定迁移至 QQ 频道成为正式的机器人。原群聊机器人(Amiya2号)终止服务。 AmiyaBot 依旧坚持初心,将快乐带给所有人。加入我们的 官方群、 官方频道 或 开始使用。 AmiyaBot 一路走来离不开大家的默默支持,如果你喜欢并支持我们,可以为我们打赏一瓶快乐水。我相信快乐是可以互相传递的,你的快乐水带给我们快乐的同时,想必 AmiyaBot 也给你带去了快乐吧。 但同时我们希望你量力而为,你对 AmiyaBot 的认可就已经是最大的支持了。如果可以的话,恳请你在 Github 为 AmiyaBot 点上一颗小小的️ ⭐ star。 感谢各位对 AmiyaBot 社区做出的贡献,是大家共同塑造了 AmiyaBot 社区的繁荣。期待未来我们能携手创造更多可能!🌹 AmiyaBot 是一款 QQ 机器人框架,基于异步I/O构建,并提供了装饰器模式(Decorator Pattern)以及插件化动态导入(Dynamic import)的编程方式。 内置多种适配器兼容不同的服务来源以及丰富的消息构建类型,旨在帮助你更高效地编写业务逻辑,实现你的创意。 框架结构可分为 业务模块由 运转中心在访问这些对象时,顶级对象会组合所有子级对象的属性提供给运转中心。 实现上述模式的思路来源于 Vue 的 组件设计 ,所以在代码层面,插件与账号实例都属于“组件”,理论上账号实例之间也能互相嵌套。但在设计时加入了限制,防止这种关系的出现。 业务模块通过各种装饰器来定义业务逻辑以及规则。这些装饰器都来源于它们继承的工厂对象(BotHandlerFactory),工厂对象是业务模块与运转中心的桥梁,业务函数会被工厂对象收集并封装,最终提供给运转中心调用。 账号实例图解 通讯模块主要形式为 账号实例里面有一个重要参数:adapter,它指定了通讯模块的运行方式(选择适配器)以适配不同运营方的连接协议。 通讯模块负责解决对接收和发送的数据进行解包与封包,所以它与数据处理模块紧密相连。适配器必须为数据处理模块提供统一的接口,在与不同的运营方服务通讯过程中,运转中心获取到的源数据格式都必须是一致的。而接收到运转中心需要发出的数据时,适配器需要把数据封包为运营方协定的格式,完成发送。 账号实例也可以脱离运转中心的调控随时调用适配器与运营方服务通讯,完成一些特殊情况下的操作。 运转中心包含事件处理,消息处理、分配器和日志 等各种将所有模块相连的处理单元,是业务层的主要调度模块。在接收到由适配器传入的消息后,运转中心会将消息归类并分发到不同的处理模块。 分配到消息处理的数据,将会对数据和业务模块收集的消息响应器(on_message)进行匹配,并将匹配成功的消息响应器按优先级排序,选取最高优先级的执行。 消息响应器的执行结果会经由运转中心返回到适配器,进行封包后发送。 这个过程在运转中心内称为“生命周期”,工厂对象可以介入这些周期影响消息处理的分配与执行结果。 存储模块以及运转中心的日志等模块,在本文将忽略不谈。这些模块依赖第三方库或 Python 的标准库,本框架仅对其做了适度的封装。 AmiyaBot 是异步 Python QQ 机器人框架。基于 QQ 官方API 。内置了丰富的消息构建方法和适配器,让你轻松实现你的创意。 AmiyaBot 诞生于2019年,是一个《明日方舟》民间 QQ 机器人,至本框架发布时已迭代了 5 个里程碑版本。一直以来,AmiyaBot 都秉着 “为所有人带来快乐” 的初衷,跌跌撞撞到现在,依旧坚持开源、免费为用户提供服务。 2022年5月13日重大变故后,AmiyaBot 决定迁移至 QQ 频道成为正式的机器人。原群聊机器人(Amiya2号)终止服务。 Progressive Python QQ Bot Framework 渐进式 QQ 机器人框架,可使用内置适配器对接官方 QQ 群、OneBot v11/12、KOOK 等机器人平台。 采用异步 I/O,摆脱官方 API 繁杂的操作,以更加简洁和可读性更高的代码让你专注于你的业务逻辑。 支持同时创建多个机器人实例,为所有实例注册共享的消息处理方法,以及在多账号实例内动态增删机器人。 通过适配器来更改机器人的服务来源,提供更大的灵活性和可扩展性。支持插件开发方案,将业务和主程序分离,使机器人更加生态化和可定制化。 内置的 PIL 图像合成模块以及 HTML 转换器,支持合成文字图片或通过渲染 WEB 和 Markdown 合成图片,轻松实现你的绝佳创意。 AmiyaBot 一路走来离不开大家的默默支持,如果你喜欢并支持我们,可以为我们打赏一瓶快乐水。我相信快乐是可以互相传递的,你的快乐水带给我们快乐的同时,想必 AmiyaBot 也给你带去了快乐吧。 但同时我们希望你量力而为,你对 AmiyaBot 的认可就已经是最大的支持了。如果可以的话,恳请你在 Github 为 AmiyaBot 点上一颗小小的️ ⭐ star。 感谢各位对 AmiyaBot 社区做出的贡献,是大家共同塑造了 AmiyaBot 社区的繁荣。期待未来我们能携手创造更多可能!🌹 你们的鼎力支持让我们感到创作 AmiyaBot 项目是值得的。感谢有你,让 AmiyaBot 秉承初衷。❤️ 打赏请前往爱发电主页。=4))break}this._dirtyLevel===1&&(this._dirtyLevel=0),ze()}return this._dirtyLevel>=4}set dirty(t){this._dirtyLevel=t?4:0}run(){if(this._dirtyLevel=0,!this.active)return this.fn();let t=ke,n=it;try{return ke=!0,it=this,this._runnings++,Os(this),this.fn()}finally{Is(this),this._runnings--,it=n,ke=t}}stop(){this.active&&(Os(this),Is(this),this.onStop&&this.onStop(),this.active=!1)}}function _o(e){return e.value}function Os(e){e._trackId++,e._depsLength=0}function Is(e){if(e.deps.length>e._depsLength){for(let t=e._depsLength;tComWeChatBot Client Beta
连接 ComWeChatBot
参数名 类型 释义 默认值 host str 服务的 ip 地址 ws_port int 服务的 websocket 端口 http_port int 服务的 http 端口 from amiyabot.adapters.comwechat import com_wechat
+
+token = '******' # access-token
+
+adapter_service = com_wechat('127.0.0.1', 8080, 8060)
+
+bot = AmiyaBot(appid=appid, token=token, adapter=adapter_service)
go-cqhttp
post-format
需更改为 array
。# config.yml
+message:
+ # 上报数据类型
+ # 可选: string, array
+ post-format: array
连接 gocq
参数名 类型 释义 默认值 host str gocq 服务的 ip 地址 ws_port int gocq 服务的 websocket 端口 http_port int gocq 服务的 http 端口 from amiyabot.adapters.cqhttp import cq_http
+
+qq = '******' # 机器人的 QQ 号
+token = '******' # gocq 的 access-token
+
+adapter_service = cq_http('127.0.0.1', 8080, 5700)
+
+bot = AmiyaBot(appid=qq, token=token, adapter=adapter_service)
适配器
bot.instance.api
有差异外,你几乎可以完全按照文档的开发方式进行。我们尽量为所有适配器的基本逻辑实现了相同的效果,使得在不同平台下 AmiyaBot 的开发差异变得极其之小。适配器参数 adapter
BotAdapterProtocol
的子类。默认值为 QQ 频道机器人的适配器 QQGuildBotInstance
。class AmiyaBot(BotHandlerFactory):
+ def __init__(self,
+ ...
+ adapter: Type[BotAdapterProtocol] = QQGuildBotInstance):
+ ...
KOOK 机器人
appid
,只需要传入 应用的 websocket token 参数即可。from amiyabot import KOOKBotInstance
+
+ws_token = '******' # websocket Token
+
+bot = AmiyaBot(token=ws_token, adapter=KOOKBotInstance)
mirai-api-http
连接 mah
参数名 类型 释义 默认值 host str mah 服务的 ip 地址 ws_port int mah 服务的 websocket 端口 http_port int mah 服务的 http 端口 from amiyabot.adapters.mirai import mirai_api_http
+
+qq = '******' # 机器人的 QQ 号
+auth_key = '******' # mah 的 verifyKey
+
+adapter_service = mirai_api_http('127.0.0.1', 8060, 8080)
+
+bot = AmiyaBot(appid=qq, token=auth_key, adapter=adapter_service)
OneBot v11 Beta
使用此适配器,可连接任何 OneBot 11 实现的机器人平台。连接 onebot11
参数名 类型 释义 默认值 host str ob11 服务的 ip 地址 ws_port int ob11 服务的 websocket 端口 http_port int ob11 服务的 http 端口 from amiyabot.adapters.onebot.v11 import onebot11
+
+appid = '******' # 机器人的账号(如需要)
+token = '******' # access-token
+
+adapter_service = onebot11('127.0.0.1', 8080, 8060)
+
+bot = AmiyaBot(appid=appid, token=token, adapter=adapter_service)
OneBot v12 Beta
使用此适配器,可连接任何 OneBot 12 实现的机器人平台。连接 onebot12
参数名 类型 释义 默认值 host str ob12 服务的 ip 地址 ws_port int ob12 服务的 websocket 端口 http_port int ob12 服务的 http 端口 from amiyabot.adapters.onebot.v12 import onebot12
+
+appid = '******' # 机器人的账号(如需要)
+token = '******' # access-token
+
+adapter_service = onebot12('127.0.0.1', 8080, 8060)
+
+bot = AmiyaBot(appid=appid, token=token, adapter=adapter_service)
QQ 频道机器人
事件分片
qq_guild_shards
from amiyabot.adapters.tencent.qqGuild import qq_guild_shards
+
+bot1 = AmiyaBot(appid='...', token='...', adapter=qq_guild_shards(0, 2))
+bot2 = AmiyaBot(appid='...', token='...', adapter=qq_guild_shards(1, 2))
# 仅作示意,实际上每个分片应当是独立的服务。
+def start():
+ asyncio.create_task(bot1.start())
+ time.sleep(2)
+ asyncio.create_task(bot2.start())
qq_guild_shards
参数参数名 类型 释义 默认值 shard_index int 分片下标,从 0 开始 shards int 分片总数 沙箱环境
QQGuildSandboxBotInstance
适配器将 API 调用更改为沙箱环境。沙箱环境只会收到测试频道的事件,且调用 openapi 仅能操作测试频道。from amiyabot.adapters.tencent.qqGuild import QQGuildSandboxBotInstance
+
+bot = AmiyaBot(..., adapter=QQGuildSandboxBotInstance)
QQ 全域机器人 Beta
创建全域适配器
from amiyabot.adapters.tencent.qqGlobal import qq_global
+
+client_secret = '******' # 密钥
+
+bot = AmiyaBot(appid='******', token='******', adapter=qq_global(client_secret))
QQ 群机器人 Beta
在公网下使用
from amiyabot.adapters.tencent.qqGroup import qq_group
+
+client_secret = '******' # 密钥
+
+bot = AmiyaBot(appid='******', token='******', adapter=qq_group(client_secret))
qq_group
参数参数名 类型 释义 默认值 client_secret str 机器人密钥 default_chain_builder 默认消息构建器 None default_chain_builder_options 默认消息构建器参数 QQGroupChainBuilderOptions() shard_index int 分片下标,从 0 开始 0 shards int 分片总数 1 事件分片
bot1 = AmiyaBot(appid='...', token='...', adapter=qq_group(client_secret, shard_index=0, shards=2))
+bot2 = AmiyaBot(appid='...', token='...', adapter=qq_group(client_secret, shard_index=1, shards=2))
# 仅作示意,实际上每个分片应当是独立的服务。
+def start():
+ asyncio.create_task(bot1.start())
+ time.sleep(2)
+ asyncio.create_task(bot2.start())
修改资源服务配置
QQGroupChainBuilderOptions
修改默认的资源服务配置。参数名 类型 释义 默认值 host str 资源服务监听地址 0.0.0.0 port int 资源服务监听端口 8086 resource_path str 临时文件存放目录 ./resource http_server_options dict HttpServer **kwargs from amiyabot.adapters.tencent.qqGroup import qq_group
+from amiyabot.adapters.tencent.qqGroup.builder import QQGroupChainBuilderOptions
+
+bot = AmiyaBot(
+ appid='******',
+ token='******',
+ adapter=qq_group(
+ client_secret='******',
+ default_chain_builder_options=QQGroupChainBuilderOptions(
+ '0.0.0.0',
+ 8086,
+ './resource',
+ ),
+ ),
+)
自定义资源服务
ChainBuilder
,来实现上传文件到托管服务以及返回生成的 url。继承 ChainBuilder 并实现相关方法。
from typing import Union
+from graiax import silkcoder
+from amiyabot import ChainBuilder
+
+class MyBuilder(ChainBuilder):
+ @classmethod
+ async def get_image(cls, image: Union[str, bytes]) -> Union[str, bytes]:
+ # 上传图片到第三方托管服务
+ ...
+ return url # 返回访问资源的 URL
+
+ @classmethod
+ async def get_voice(cls, voice_file: str) -> str:
+ # 上传语音文件到第三方托管服务,语音文件必须是 silk 格式
+ voice: bytes = await silkcoder.async_encode(voice_file, ios_adaptive=True)
+ ...
+ return url # 返回访问资源的 URL
+
+ @classmethod
+ async def get_video(cls, video_file: str) -> str:
+ # 上传视频文件到第三方托管服务
+ ...
+ return url # 返回访问资源的 URL
+
+
+bot = AmiyaBot(..., adapter=qq_group(default_chain_builder=MyBuilder()))
处理 IO 阻塞的操作
run_in_thread_pool
util
库里提供的利用线程池将同步方法转变为异步方法执行的函数。参数名 类型 释义 默认值 block_func Callable IO 阻塞的方法 *args 原方法参数 **kwargs 原方法字典参数 from amiyabot.util import run_in_thread_pool
+
+# IO 阻塞的方法
+block_io_method(arg1, arg2, arg3='xxx')
+
+# 转变为异步执行
+await run_in_thread_pool(block_io_method, arg1, arg2, arg3='xxx')
介入 Chain 构建媒体消息时的操作
创建辅助类
ChainBuilder
类并覆写其方法创建一个自定义的辅助类。在实例化 Chain 时,传入辅助类实例即可介入 Chain 的操作。from typing import Union
+from amiyabot import Chain, ChainBuilder
+from playwright.async_api import Page
+
+class MyBuilder(ChainBuilder):
+ @classmethod
+ async def get_image(cls, image: Union[str, bytes]) -> Union[str, bytes]:
+ ...
+ return image
+
+ @classmethod
+ async def get_voice(cls, voice_file: str) -> str:
+ ...
+ return voice_file
+
+ @classmethod
+ async def get_video(cls, video_file: str) -> str:
+ ...
+ return video_file
+
+ @classmethod
+ async def on_page_rendered(cls, page: Page):
+ ...
+
+
+# 在构造参数里使用辅助类
+chain = Chain(..., chain_builder=MyBuilder())
+
+# 为属性赋值使用辅助类
+chain.builder = MyBuilder()
get_image
image
,类型为 str
(文件路径或 url) 或 bytes
(图片字节数据)。
如果函数有返回值(必须是以上两种类型),chain 会使用返回值构建图片消息。get_voice
voice_file
。
如果函数有返回值,chain 会使用返回值构建语音消息。get_video
video_file
。
如果函数有返回值,chain 会使用返回值构建视频消息。on_page_rendered
Page
对象,可在此对 Page
进行操作(如对页面进行 JS 注入等)。get_image
函数。class BaiduSearch(ChainBuilder):
+ @classmethod
+ async def on_page_rendered(cls, page: Page):
+ """
+ 可以在截图前先对页面进行操作,比如 ”百度一下“
+ """
+ await page.locator('#kw').fill('AmiyaBot')
+ await page.locator('#su').click()
+ await asyncio.sleep(2)
+
+
+@bot.on_message(keywords='hello')
+async def _(data: Message):
+ chain = Chain(data, chain_builder=BaiduSearch())
+
+ return chain.html(
+ 'https://www.baidu.com/',
+ is_template=False,
+ render_time=1000,
+ )
数据库支持
Sqlite
connect_database
方法默认创建 Sqlite 数据库。from amiyabot.database import * # 引入数据库模块
+
+db = connect_database('database_name') # 创建或连接一个 Sqlite 数据库文件
+
+
+# 创建数据库基础模型,继承 ModelClass 获得 AmiyaBot 的扩展查询方法
+class BotBaseModel(ModelClass):
+ class Meta:
+ database = db
+
+
+# 创建表模型,使用 table 装饰器进行表结构管理,将会自动创建或更新表结构
+@table
+class TableName(BotBaseModel):
+ field1 = CharField()
+ field2 = IntegerField(null=True)
+
+
+TableName.select() # ORM 操作详见 peewee 文档
MySQL
connect_database
参数设置 mysql=True
以及 config
即可更改为 MySQL 数据库。from amiyabot.database import *
+
+db = connect_database('database_name', is_mysql=True, config=MysqlConfig(
+ host='127.0.0.1',
+ port=3306,
+ user='root',
+ password=''
+))
表结构管理
@table
+class TableName(BotBaseModel):
+ field1 = CharField()
+ field2 = IntegerField(null=True)
+ field3 = TextField(null=True)
+ ...
扩展查询
batch_insert
参数名 类型 释义 默认值 rows List[dict] 数据列表 chunk_size int 分片插入大小 200 data = [
+ {...},
+ {...},
+ {...}, ...
+]
+
+TableName.batch_insert(data)
insert_or_update
参数名 类型 释义 默认值 insert dict 如果是插入的数据 update dict 如果是更新的数据 None conflict_target list 构成约束的列(仅Sqlite需要) None preserve list 一个列的列表,其值应从原始插入中保留 None 查询转换工具
convert_model
参数名 类型 释义 默认值 model peewee 查询结果 select_model Select peewee 查询对象 data = convert_model(TableName.get_or_none())
query_to_list
参数名 类型 释义 默认值 query list peewee 查询结果 select_model Select peewee 查询对象 data = query_to_list(TableName.select())
select_for_paginate
参数名 类型 释义 默认值 model ModelSelect peewee 查询对象 page int 当前页 page_size int 页行数 data = select_for_paginate(TableName.select(), 1, 10)
+
+data['list'] # 查询结果
+data['total'] # 总条数
事件总线
from amiyabot import event_bus
发布事件
参数名 类型 释义 默认值 event_name str 事件名 data Any 事件数据 None event_bus.publish('myEvent', data=...)
订阅事件
参数名 类型 释义 默认值 event_name str 事件名 method Callable 响应方法(可选) None @event_bus.subscribe('myEvent')
+async def event_handler(data):
+ ...
async def event_handler(data):
+ ...
+
+event_bus.subscribe('myEvent', event_handler)
取消订阅
参数名 类型 释义 默认值 event_name str 事件名 method Callable 响应方法 None event_bus.unsubscribe('myEvent', event_handler)
发送 HTTP 请求
http_requests
实例和 download_async
方法即可使用。from amiyabot.network.httpRequests import http_requests
+from amiyabot.network.download import download_async
+
+await http_requests.get()
+await http_requests.post()
+await http_requests.post_form()
+await http_requests.post_upload()
+await http_requests.request()
+await download_async()
返回值
http_requests
的请求均返回字符串的请求结果(如果请求失败会返回空字符串),但有些不一样。这个 “字符串” 可以使用一些额外的属性。json
responseText
内容。res = await http_requests.post('/interface', {...})
+if res:
+ data = res.json['data']
response
aiohttp.ClientResponse
的实例),可以获取请求的状态码和其他信息。res = await http_requests.post('/interface', {...})
+
+status = res.response.status
error
Exception
的实例)。res = await http_requests.post('/interface', {...})
+
+error = res.error
GET 请求
res: str = await http_requests.get()
参数名 类型 释义 默认值 interface str 请求地址 **kwargs request 参数 POST 请求
'Content-Type': 'application/json'
,请求体仅接受字典或列表类型数据。res: str = await http_requests.post()
参数名 类型 释义 默认值 interface str 请求地址 payload dict, list 请求体 headers dict 追加的请求头 **kwargs request 参数 FORM 表单请求
res: str = await http_requests.post_form()
参数名 类型 释义 默认值 interface str 请求地址 payload dict 请求体 headers dict 追加的请求头 **kwargs request 参数 文件上传
res: str = await http_requests.post_upload()
参数名 类型 释义 默认值 interface str 请求地址 file bytes 文件 bytes filename str 文件名 file file_field str 表单数据中存放文件的字段名 file payload dict 请求体 headers dict 追加的请求头 **kwargs request 参数 自定义请求
PUT
、PATCH
、DELETE
等,或需要自定义更多的请求场景,可使用 request 方法。res: str = await http_requests.request()
参数名 类型 释义 默认值 url str 请求地址 method dict 请求方法 post request_name dict 请求过程的 LOG 标识 **kwargs request 参数 下载文件
file: bytes = await download_async()
参数名 类型 释义 默认值 url str 请求地址 headers dict 追加的请求头 stringify bool 是否返回字符串结果 False **kwargs request 参数 同步下载
from amiyabot.network.download import download_sync
+
+file: bytes = download_sync()
`,43),e=[l];function p(r,d,c,y,i,D){return s(),a("div",null,e)}const B=t(n,[["render",p]]);export{h as __pageData,B as default};
diff --git a/assets/develop_advanced_httpRequests.md.d5ec3c2e.lean.js b/assets/develop_advanced_httpRequests.md.d5ec3c2e.lean.js
new file mode 100644
index 00000000..3537fb39
--- /dev/null
+++ b/assets/develop_advanced_httpRequests.md.d5ec3c2e.lean.js
@@ -0,0 +1 @@
+import{_ as t,o as s,c as a,V as o}from"./chunks/framework.63f12d77.js";const h=JSON.parse('{"title":"发送 HTTP 请求","description":"","frontmatter":{},"headers":[],"relativePath":"develop/advanced/httpRequests.md","filePath":"develop/advanced/httpRequests.md","lastUpdated":1722566694000}'),n={name:"develop/advanced/httpRequests.md"},l=o("",43),e=[l];function p(r,d,c,y,i,D){return s(),a("div",null,e)}const B=t(n,[["render",p]]);export{h as __pageData,B as default};
diff --git a/assets/develop_advanced_httpSupport.md.82709c73.js b/assets/develop_advanced_httpSupport.md.82709c73.js
new file mode 100644
index 00000000..5d6687e4
--- /dev/null
+++ b/assets/develop_advanced_httpSupport.md.82709c73.js
@@ -0,0 +1,21 @@
+import{_ as s,o as a,c as n,V as t}from"./chunks/framework.63f12d77.js";const h=JSON.parse('{"title":"HTTP服务器支持","description":"","frontmatter":{},"headers":[],"relativePath":"develop/advanced/httpSupport.md","filePath":"develop/advanced/httpSupport.md","lastUpdated":1722566694000}'),l={name:"develop/advanced/httpSupport.md"},o=t(`参数名 类型 释义 默认值 url str 请求地址 headers dict 追加的请求头 stringify bool 是否返回字符串结果 False progress bool 是否显示进度条 False **kwargs request 参数 HTTP服务器支持
创建服务
HttpServer 类
参数名 类型 释义 默认值 host str 服务监听地址 port int 服务监听端口 title str swagger 页面标题 AmiyaBot description str swagger 页面注释 https://www.amiyabot.com auth_key str 请求头密钥 fastapi_options dict FastAPI **kwargs uvicorn_options dict uvicorn.Config **kwargs from amiyabot import HttpServer
+
+server = HttpServer(host='0.0.0.0', port=8088)
+
+
+@server.controller
+class Bot:
+ @server.route(method='get')
+ async def get_name(self):
+ return 'AmiyaBot'
+
+ @server.route(method='post')
+ async def say_hello(self):
+ return server.response(message='hello')
+
+
+asyncio.run(server.serve())
/bot/getName
+/bot/sayHello
自定义路由
@server.route(method='get', router_path='/custom/getBotName')
+async def get_name(self):
+ ...
请求头密钥
authKey
字段并匹配参数的值,才允许访问。server = HttpServer(..., auth_key='my_auth_key')
FastApi 扩展
server = HttpServer()
+app: FastAPI = server.app
生命周期
消息生命周期
message_created
@bot.message_created
+async def _(data: Message, instance: BotAdapterProtocol):
+ if ...:
+ data.text = ...
+ return data
message_before_waiter_set
from amiyabot import Waiter
+
+@bot.message_before_waiter_set
+async def _(data: Message, waiter: Waiter, instance: BotAdapterProtocol):
+ ...
message_before_handle
@bot.message_before_handle
+async def _(data: Message, factory_name: str, instance: BotAdapterProtocol):
+ if ...:
+ return False
+ return True
message_before_send
@bot.message_before_send
+async def _(chain: Chain, factory_name: str, instance: BotAdapterProtocol):
+ ...
+ return chain
message_after_send
@bot.message_after_send
+async def _(chain: Chain, factory_name: str, instance: BotAdapterProtocol):
+ ...
message_after_handle
@bot.message_after_handle
+async def _(chain: Optional[Chain], factory_name: str, instance: BotAdapterProtocol):
+ ...
事件生命周期
@bot.event_created
+async def _(event: EventType, instance: BotAdapterProtocol):
+ ...
加载插件
前言
importlib
和 zipimport
,就能够很好的完成外部代码的导入与执行,理解这一原理,需要你熟悉 Python 的导入机制。而“动态执行代码”,如果你不是第一次接触脚本语言,想必你对其应有了一定的了解。开发插件
导入插件实例
from amiyabot import AmiyaBot, PluginInstance
+
+plugin = PluginInstance(
+ name='我的插件',
+ version='1.0',
+ plugin_id='my-plugin',
+ description='我的第一个插件'
+)
+
+@plugin.on_message(keywords='hello')
+async def _(data: Message):
+ return Chain(data).text(f'hello, {data.nickname}')
+
+
+bot = AmiyaBot(...) # or MultipleAccounts
+bot.install_plugin(plugin)
导入 Python 文件
myPlugin.py
from amiyabot import PluginInstance
+
+# bot 变量名是必须的,详见插件开发文档。
+bot = PluginInstance(
+ name='我的插件',
+ version='1.0',
+ plugin_id='my-plugin',
+ description='我的第一个插件'
+)
+
+@bot.on_message(keywords='hello')
+async def _(data: Message):
+ return Chain(data).text(f'hello, {data.nickname}')
bot.install_plugin('myPlugin.py') # 相当于执行了代码:import myPlugin
导入 Python Package 目录
myPlugin
+├── __init__.py
+├── a.py
+├── b.py
+│ ...
+...
bot.install_plugin('myPlugin') # 相当于执行了代码:import myPlugin
导入 zip 压缩包
myPlugin.zip
+├── assetsFolder
+├── config.yaml
+├── __init__.py
+├── a.py
+├── b.py
+│ ...
+...
bot.install_plugin('myPlugin.zip')
import myPlugin
语句。而是通过标准库 zipimport
,读取了此压缩包,并执行 __init__.py
。__init__.py
python __init__.py
__init__.py
在相对导入其目录下的模块时,由于它属于顶级模块,插件将会 加载失败 并抛出如下异常。from .main import bot
plugin install error: Traceback (most recent call last):
+ File "F:\\Project\\Amiya-Bot-core\\amiyabot\\log\\manager.py", line 116, in sync_catch
+ yield
+ File "F:\\Project\\Amiya-Bot-core\\amiyabot\\handler\\__init__.py", line 287, in install_plugin
+ module = zipimport.zipimporter(plugin).load_module('__init__')
+ File "<frozen zipimport>", line 259, in load_module
+ File "plugins\\myPlugin.zip\\__init__.py", line 1, in <module>
+ from .main import bot
+ImportError: attempted relative import with no known parent package
参数名 类型 释义 默认值 plugin str, PluginInstance 插件(文件、package 的路径或 PluginInstance 实例) extract_plugin bool 是否解压插件 False extract_plugin_dest str 插件解压后的目录名(默认为 zip 文件名) extract_plugin=True
,将 zip 导入转换为 Package 目录导入。bot.install_plugin('myPlugin.zip', extract_plugin=True)
卸载插件
参数名 类型 释义 默认值 plugin_id str 插件 ID remove bool 是否删除插件的原文件 False bot.uninstall_plugin('my-plugin')
日志模块
输出日志
log
文件夹下。from amiyabot import log
+
+log.info(...)
+log.error(...)
+log.debug(...)
+log.warning(...)
+log.critical(...)
时间
、日志模块名
、日志等级
和日志内容
。DEBUG 模式下会显示日志的输出文件名和位置。normal:
+2022-11-02 18:22:40,425 [ Bot][ INFO] initialize completed.
+
+debug mode:
+2022-11-02 18:22:40,425 [ Bot][ INFO][test.py:5] initialize completed.
--debug
开启 DEBUG 模式。输出异常日志
log.error()
方法不仅可以传入字符串,也支持传入 Exception 的子类。传入 Exception 子类时,将会输出完整的异常追踪(Traceback)。try:
+ a = 0
+ a += '1'
+except Exception as e:
+ log.error(e, desc='calc error:')
2022-11-02 18:44:22,155 [ Bot][ ERROR] calc error: Traceback (most recent call last):
+ File "F:\\Project\\Amiya-Bot-core\\logTest.py", line 5, in <module>
+ a += '1'
+TypeError: unsupported operand type(s) for +=: 'int' and 'str'
参数名 类型 释义 默认值 desc str 异常标题 ignore List[Type[Exception]] 仅捕获但不输出的异常列表 None handler Callable 捕获异常后执行的方法 None async def err_handler(err: Exception):
+ print(err)
+
+# 异步方式
+async with log.catch('calc error:', ignore=[TypeError, ...], handler=err_handler):
+ a = 0
+ a += '1'
+
+# 同步方式
+with log.sync_catch(...):
+ a = 0
+ a += '1'
创建日志模块
from amiyabot.log import LoggerManager
+
+logger = LoggerManager('MyLogger')
+logger.info('this is a log.')
+
+# 2022-11-02 18:32:05,053 [MyLogger][ INFO] this is a log.
参数名 类型 释义 默认值 name str logger 模块名称 level int 日志等级 logging.INFO formatter str 日志格式 %(asctime)s [%(name)8s][%(levelname)8s]%(message)s
save_path str 日志文件保存目录 log save_filename str 日志文件名 running 自定义全局日志模块
from amiyabot.log import UserLogger
+
+
+class Mylogger:
+ def info(self, text: str): ...
+
+ def error(self, text: str): ...
+
+ def debug(self, text: str): ...
+
+ def warning(self, text: str): ...
+
+ def critical(self, text: str): ...
+
+
+UserLogger.logger = Mylogger() # 将默认日志处理替换为自定义的类
改变 Playwright 启动
from playwright.async_api import Playwright, Browser
+from amiyabot import BrowserLaunchConfig
+
+
+class MyBrowserLauncher(BrowserLaunchConfig):
+ def __init__(self):
+ super().__init__()
+
+ self.browser_type = 'firefox' # 修改浏览器属性
+
+ # 或改写 launch_browser 方法
+ async def launch_browser(self, playwright: Playwright) -> Browser:
+ ...
+
+ # 返回通过任意方式创建的 Browser 对象
+ # return await playwright.webkit.launch()
+ # return await playwright.chromium.launch()
+ return await playwright.firefox.launch()
+
+
+bot = AmiyaBot()
+asyncio.run(
+ bot.start(launch_browser=MyBrowserLauncher())
+)
启动参数
参数 是否需要值 默认值 释义 --debug 否 以 DEBUG 模式启动程序 --browser-width 是 1280 浏览器视窗默认宽度(px) --browser-height 是 720 浏览器视窗默认高度(px) --browser-render-time 是 200 浏览器默认渲染时间(ms) --text-max-length 是 100 Chain 对象转换文字图片的文字长度最大值 --browser-page-not-close 否 取消自动关闭 playwright 的网页窗口 在 兔兔-v6 下使用
python amiya.py --text-max-length 200 --browser-render-time 1000
AmiyaBot-v6.x.x-master.exe --debug --browser-page-not-close
定时任务
from amiyabot import AmiyaBot, tasks_control
+
+@tasks_control.timed_task(each=120)
+async def _():
+ ...
参数名 类型 释义 默认值 each int 循环执行间隔时间,单位(秒) customCallable自定义循环规则sub_tag str 子标签 default_tag 简单使用
from amiyabot import AmiyaBot
+from amiyabot.factory import BotHandlerFactory
+
+bot = AmiyaBot(...) # 或 MultipleAccounts / PluginInstance 对象
+
+# 每 60 秒执行一次任务
+@bot.timed_task(each=60)
+async def _(instance: BotHandlerFactory):
+ ...
插件开发
PluginInstance.timed_task
定义任务。PluginInstance.timed_task
定义的任务可以在插件被卸载时自动取消。from amiyabot import PluginInstance
+from amiyabot.factory import BotHandlerFactory
+
+bot = PluginInstance(...)
+
+@bot.timed_task(each=60)
+async def _(instance: BotHandlerFactory):
+ ...
timed_task 装饰器
参数名 类型 释义 默认值 each int 循环执行间隔时间,单位(秒),如果使用其他触发方式,请使用 kwargs 形式的 scheduler.add_job 参数 None sub_tag str 子标签 default_tag run_when_added bool 添加时立即执行一次任务 default_tag **kwargs scheduler.add_job 参数 func
以及 id
。自定义执行时机
scheduler.add_job
参数定义任务,可以使任务的执行时机更加灵活。from amiyabot import PluginInstance
+from amiyabot.factory import BotHandlerFactory
+from apscheduler.triggers.cron import CronTrigger
+
+bot = PluginInstance(...)
+
+# 每个小时中的 5 分 30 秒时执行
+@bot.timed_task(trigger='cron', minute=5, second=30)
+async def _(instance: BotHandlerFactory):
+ ...
+
+# 每分钟的第 20 秒和 40 秒时执行
+@bot.timed_task(trigger=CronTrigger('20,40'))
+async def _(instance: BotHandlerFactory):
+ ...
取消定时任务
bot.remove_timed_task()
参数名 类型 释义 默认值 sub_tag str 需要取消的定时任务的子标签名 from amiyabot import PluginInstance
+from amiyabot.factory import BotHandlerFactory
+
+bot = PluginInstance(...)
+
+@bot.timed_task(each=60, sub_tag='test')
+async def _(instance: BotHandlerFactory):
+ ...
+ bot.remove_timed_task('test')
API
api
属性位于 AmiyaBot
的 instance
属性下。bot = AmiyaBot(...)
+
+await bot.instance.api.get_me()
引入 API 类以注解变量
api
模块下。from amiyabot.adapters.tencent.qqGuild.api import QQGuildAPI
+# from amiyabot.adapters.cqhttp.api import CQHttpAPI
+# from amiyabot.adapters.kook.api import KOOKAPI
+
+
+api: QQGuildAPI = bot.instance.api
+await api.get_me()
共同的方法
get
参数名 类型 释义 默认值 url str 接口 url params Union[dict, None] get 参数 **kwargs request 参数 post
参数名 类型 释义 默认值 url str 接口 url payload Union[dict, None] post 参数 is_form_data bool 是否使用 form 表单提交(仅 QQ 频道拥有此参数) **kwargs request 参数 request
参数名 类型 释义 默认值 url str 接口 url method str 请求 method payload Union[dict, None] 请求参数 **kwargs request 参数 res = await bot.instance.api.post('/interface', {...})
api 的返回
`,21),n=[l];function p(r,d,c,i,h,y){return a(),s("div",null,n)}const b=t(o,[["render",p]]);export{D as __pageData,b as default};
diff --git a/assets/develop_basic_api_index.md.c8b00309.lean.js b/assets/develop_basic_api_index.md.c8b00309.lean.js
new file mode 100644
index 00000000..ec82c943
--- /dev/null
+++ b/assets/develop_basic_api_index.md.c8b00309.lean.js
@@ -0,0 +1 @@
+import{_ as t,o as a,c as s,V as e}from"./chunks/framework.63f12d77.js";const D=JSON.parse('{"title":"API","description":"","frontmatter":{},"headers":[],"relativePath":"develop/basic/api/index.md","filePath":"develop/basic/api/index.md","lastUpdated":1722566694000}'),o={name:"develop/basic/api/index.md"},l=e("",21),n=[l];function p(r,d,c,i,h,y){return a(),s("div",null,n)}const b=t(o,[["render",p]]);export{D as __pageData,b as default};
diff --git a/assets/develop_basic_api_qqbot.md.068658d1.js b/assets/develop_basic_api_qqbot.md.068658d1.js
new file mode 100644
index 00000000..7bb5de03
--- /dev/null
+++ b/assets/develop_basic_api_qqbot.md.068658d1.js
@@ -0,0 +1 @@
+import{_ as t,o as d,c as e,V as r}from"./chunks/framework.63f12d77.js";const m=JSON.parse('{"title":"QQ 频道 API","description":"","frontmatter":{},"headers":[],"relativePath":"develop/basic/api/qqbot.md","filePath":"develop/basic/api/qqbot.md","lastUpdated":1722566694000}'),a={name:"develop/basic/api/qqbot.md"},h=r('QQ 频道 API
add_message_reaction
参数名 类型 释义 默认值 channel_id str message_id str emoji_type int emoji_id str add_pin
参数名 类型 释义 默认值 channel_id str message_id str create_announces
参数名 类型 释义 默认值 guild_id str message_id Union[str, None] channel_id Union[str, None] announces_type Union[int, None] recommend_channels Union[str, None] create_channel
参数名 类型 释义 默认值 guild_id str channel_name str channel_type int channel_sub_type int position Union[int, None] parent_id Union[str, None] private_type Union[int, None] private_user_ids Union[List[str], None] speak_permission Union[int, None] application_id Union[str, None] create_guild_api_permission_link
参数名 类型 释义 默认值 guild_id str channel_id str path str method str desc str create_guild_role
参数名 类型 释义 默认值 guild_id str name Union[str, None] color Union[int, None] hoist int create_schedule
参数名 类型 释义 默认值 channel_id str name str description str start_timestamp str end_timestamp str creator Union[dict, None] jump_channel_id Union[str, None] remind_type Union[str, None] create_thread
参数名 类型 释义 默认值 channel_id str title str content str thread_format int delete_announces
参数名 类型 释义 默认值 guild_id str message_id Union[str, None] delete_channel
参数名 类型 释义 默认值 channel_id str delete_guild_member
参数名 类型 释义 默认值 guild_id str user_id str add_blacklist bool delete_history_msg_days int delete_guild_role
参数名 类型 释义 默认值 guild_id str role_id str delete_message
参数名 类型 释义 默认值 message_id str target_id str is_direct bool hidetip bool delete_message_reaction
参数名 类型 释义 默认值 channel_id str message_id str emoji_type int emoji_id str delete_pin
参数名 类型 释义 默认值 channel_id str message_id str delete_schedule
参数名 类型 释义 默认值 channel_id str schedule_id str delete_thread
参数名 类型 释义 默认值 channel_id str thread_id str delete_user_role
参数名 类型 释义 默认值 guild_id str user_id str role_id str gateway
参数名 类型 释义 默认值 gateway_bot
参数名 类型 释义 默认值 get_channel
参数名 类型 释义 默认值 channel_id str get_channel_online_nums
参数名 类型 释义 默认值 channel_id str get_channels
参数名 类型 释义 默认值 guild_id str get_guild
参数名 类型 释义 默认值 guild_id str get_guild_api_permission
参数名 类型 释义 默认值 guild_id str get_guild_member
参数名 类型 释义 默认值 guild_id str user_id str get_guild_members
参数名 类型 释义 默认值 guild_id str after str limit int get_guild_roles
参数名 类型 释义 默认值 guild_id str get_guild_roles_members
参数名 类型 释义 默认值 guild_id str role_id str start_index str limit int get_guilds
参数名 类型 释义 默认值 before Union[str, None] after Union[str, None] limit int get_me
参数名 类型 释义 默认值 get_me_dms
参数名 类型 释义 默认值 recipient_id str src_guild_id str get_message
参数名 类型 释义 默认值 channel_id str message_id str get_message_reactions
参数名 类型 释义 默认值 channel_id str message_id str emoji_type int emoji_id str cookie Union[str, None] limit int get_message_setting
参数名 类型 释义 默认值 guild_id str get_pins
参数名 类型 释义 默认值 channel_id str get_role_permission
参数名 类型 释义 默认值 channel_id str role_id str get_schedule
参数名 类型 释义 默认值 channel_id str schedule_id str get_schedules
参数名 类型 释义 默认值 channel_id str since int get_thread
参数名 类型 释义 默认值 channel_id str thread_id str get_threads
参数名 类型 释义 默认值 channel_id str get_user_avatar
参数名 类型 释义 默认值 args _empty kwargs _empty get_user_permission
参数名 类型 释义 默认值 channel_id str user_id str modify_channel
参数名 类型 释义 默认值 channel_id str channel_name str position Union[int, None] parent_id Union[str, None] private_type Union[int, None] speak_permission Union[int, None] modify_guild_role
参数名 类型 释义 默认值 guild_id str role_id str name Union[str, None] color Union[int, None] hoist int modify_schedule
参数名 类型 释义 默认值 channel_id str schedule_id str name str description str start_timestamp str end_timestamp str creator Union[dict, None] jump_channel_id Union[str, None] remind_type Union[str, None] mute_all
参数名 类型 释义 默认值 guild_id str mute_end_timestamp str mute_seconds str mute_all_lift
参数名 类型 释义 默认值 guild_id str mute_user
参数名 类型 释义 默认值 guild_id str user_id str mute_end_timestamp str mute_seconds str mute_user_lift
参数名 类型 释义 默认值 guild_id str user_id str mute_users
参数名 类型 释义 默认值 guild_id str user_ids List[str] mute_end_timestamp str mute_seconds str mute_users_lift
参数名 类型 释义 默认值 guild_id str user_ids List[str] post_message
参数名 类型 释义 默认值 guild_id str src_guild_id str channel_id str req MessageSendRequest set_role_permission
参数名 类型 释义 默认值 channel_id str role_id str add Union[str, None] remove Union[str, None] set_user_permission
参数名 类型 释义 默认值 channel_id str user_id str add Union[str, None] remove Union[str, None] set_user_role
',114),i=[h];function l(s,o,n,b,_,u){return d(),e("div",null,i)}const g=t(a,[["render",l]]);export{m as __pageData,g as default};
diff --git a/assets/develop_basic_api_qqbot.md.068658d1.lean.js b/assets/develop_basic_api_qqbot.md.068658d1.lean.js
new file mode 100644
index 00000000..60c5da5f
--- /dev/null
+++ b/assets/develop_basic_api_qqbot.md.068658d1.lean.js
@@ -0,0 +1 @@
+import{_ as t,o as d,c as e,V as r}from"./chunks/framework.63f12d77.js";const m=JSON.parse('{"title":"QQ 频道 API","description":"","frontmatter":{},"headers":[],"relativePath":"develop/basic/api/qqbot.md","filePath":"develop/basic/api/qqbot.md","lastUpdated":1722566694000}'),a={name:"develop/basic/api/qqbot.md"},h=r("",114),i=[h];function l(s,o,n,b,_,u){return d(),e("div",null,i)}const g=t(a,[["render",l]]);export{m as __pageData,g as default};
diff --git a/assets/develop_basic_chainBuild_ark.md.4bf94265.js b/assets/develop_basic_chainBuild_ark.md.4bf94265.js
new file mode 100644
index 00000000..3e0c9f02
--- /dev/null
+++ b/assets/develop_basic_chainBuild_ark.md.4bf94265.js
@@ -0,0 +1,9 @@
+import{_ as s,o as a,c as n,V as l}from"./chunks/framework.63f12d77.js";const B=JSON.parse('{"title":"Ark 消息","description":"","frontmatter":{},"headers":[],"relativePath":"develop/basic/chainBuild/ark.md","filePath":"develop/basic/chainBuild/ark.md","lastUpdated":1722566694000}'),p={name:"develop/basic/chainBuild/ark.md"},o=l(`参数名 类型 释义 默认值 guild_id str user_id str role_id str channel_id Union[str, None] Ark 消息
Chain().ark()
参数名 类型 释义 默认值 template_id str 模版 ID kv List[dict] 模版 key-value 数据 kv_data = [
+ {'key': '#PROMPT#', 'value': '通知提醒'},
+ {'key': '#METATITLE#', 'value': '标题'},
+ {'key': '#METASUBTITLE#', 'value': '子标题'},
+ {'key': '#METACOVER#', 'value': 'https://vfiles.gtimg.cn/vupload/20211029/bf0ed01635493790634.jpg'},
+ {'key': '#METAURL#', 'value': 'https://qq.com'},
+]
+
+Chain(data, at=False).ark(37, kv_data)
At
@XXX
Chain().at()
参数名 类型 释义 默认值 user int, str @ 的用户ID,默认为 Message 对象的用户 enter bool 是否 @ 用户后换行 False Chain(data).at(12345678).text('hello, world')
@XXX
。At 所有人
@所有人
,需要机器人拥有发送 @所有人
消息的权限Chain().at_all()
Chain(data).at_all()
Embed 消息
Chain().embed()
参数名 类型 释义 默认值 title str 标题 prompt str 消息弹窗内容 thumbnail str 缩略图 url fields List[str] embed 字段数据 Chain(data, at=False).embed(
+ '标题',
+ '消息通知',
+ 'xxxxxx',
+ ['当前等级:黄金', '之前等级:白银', '😁继续努力'],
+)
原生模板
mirai-api-http
和 cq-http
。mirai-api-http
和 cq-http
还提供了多种消息类型用于发送丰富的消息内容。Chain().extend()
参数名 类型 释义 默认值 data Any 原始消息类型格式数据 示例
Chain(data).extend(
+ {
+ 'type': 'Dice',
+ 'value': 6
+ }
+)
Chain(data).extend(
+ {
+ 'type': 'share',
+ 'data': {
+ 'url': 'https://www.baidu.com',
+ 'title': '百度'
+ }
+ }
+)
发送 CQ 码
cq-http
适配器。可通过适配器的 API 对象发送或扩展消息发送。from amiyabot import CQCode, CQHttpBotInstance
+
+instance: CQHttpBotInstance = bot.instance
+
+
+@bot.on_message(keywords='hello')
+async def _(data: Message):
+ # 通过 API 发送
+ instance.api.send_cq_code(data.user_id,
+ data.channel_id,
+ f'hello, {data.nickname} [CQ:face,id=123]')
+
+ # 通过扩展消息发送
+ return Chain(data).extend(
+ CQCode(f'hello, {data.nickname} [CQ:face,id=123]')
+ )
emoji 表情
Chain().face()
参数名 类型 释义 默认值 face_id int, str 表情ID Chain(data).face(175)
合并转发消息 Beta
mirai-api-http
和 cq-http
。from amiyabot.adapters.mirai import MiraiForwardMessage
+# from amiyabot.adapters.cqhttp import CQHTTPForwardMessage
+
+...
+
+@bot.on_message(keywords='hello')
+async def _(data: Message):
+ forward = MiraiForwardMessage(data)
+ # forward = CQHTTPForwardMessage(data)
+
+ chain = Chain().text(f'hello, {data.nickname}')
+
+ await forward.add_message(chain, user_id=..., nickname='...')
+ await forward.send()
添加自定义消息
参数名 类型 释义 默认值 chain Chain, list Chain 对象,可为空 Chain user_id int 用户 ID nickname str 用户昵称(可自定义) time int 发送时间 0 user_id
为实际 QQ 用户的 QQ 号,可以是任意人,在合并消息内显示其头像。nickname
为自定义的昵称。chain
参数传入了空 Chain,则 user_id
和 nickname
为必须参数。await forward.add_message(Chain(data).text(...))
+await forward.add_message(Chain().text(...), user_id=..., nickname='...')
添加指定 ID 的消息
参数名 类型 释义 默认值 message_id int 消息 ID await forward.add_message_by_id(5128)
添加嵌套的合并转发消息
add_message
的 chain
参数传入 ForwardMessage 类的 node
属性,即可完成合并消息嵌套。@bot.on_message(keywords='hello')
+async def _(data: Message):
+ forward = MiraiForwardMessage(data)
+
+ await forward.add_message(...)
+ await forward.add_message(...)
+
+ forward2 = MiraiForwardMessage(data)
+
+ await forward2.add_message(forward.node, user_id=..., nickname='...')
+ await forward2.send()
发送 & 撤回
callback = await forward.send() # 发送
+if callback:
+ await callback.recall() # 撤回
HTML 生成的图片
安装 Chromium
# Windows or MacOS
+playwright install chromium
+# Linux
+playwright install --with-deps chromium
启动时打开 Chromium
launch_browser=True
bot.start(launch_browser=True)
Chain().html()
参数名 类型 释义 默认值 path str 模板文件路径或网站URL data Any 传入模板文件的数据(数据可被 json 序列化) width int 浏览器视窗宽度 1280 height int 浏览器视窗高度 720 is_template bool 是否为模板文件 True render_time int 渲染时间(毫秒) 200 Chain(data).html('template.html', {...})
创建html模板文件
<!-- hello.html -->
+<div id="content"></div>
+
+<script>
+ // 必须定义全局的 init 方法,接收一个 data 参数
+ window.init = (data) => {
+ document.querySelector('#content').innerText = data.username
+ }
+</script>
@bot.on_message(keywords='hello')
+async def _(data: Message):
+ return Chain(data).html('hello.html', {'username': data.nickname})
hello.html
并在页面内执行 JavaScript 语句 init({'username': 'vivien8261'})
。
渲染结束后,无头浏览器截图生成图片,然后执行常规的图片发送方法。兔兔-v6 效果
通过网站URL制图
render_time
设置需要的时间。is_template=False
@bot.on_message(keywords='hello')
+async def _(data: Message):
+ return Chain(data).html('https://www.baidu.com/',
+ is_template=False,
+ render_time=1000)
https://www.baidu.com/
页面,并在等待 1000ms
后截图发送图片。图片
Chain().image()
参数名 类型 释义 默认值 target str, bytes 图片文件路径或图片 bytes url str 网络图片的URL Chain(data).image(target)
如果是网络图片,可以使用 url 参数传入。Chain(data).image(url=target)
Markdown 生成的图片
Chain().markdown()
参数名 类型 释义 默认值 content str markdown 文本 render_time int 渲染时间(毫秒) 200 is_dark bool 是否使用暗黑样式 False Chain(data).markdown(text)
render_time
设置需要的时间。更换渲染 Markdown 的 HTML 文件
from amiyabot.builtin.messageChain import ChainConfig
+
+ChainConfig.md_template = './myMarkdown.html'
+ChainConfig.md_template_dark = './myDarkMarkdown.html'
window.init = (data) => {
+ const markdownText = data['content']
+}
Markdown 模版消息
Chain().markdown_template()
参数名 类型 释义 默认值 template_id str 模版 ID params List[dict] 模版 key-value 数据 keyboard InlineKeyboard 按钮消息(自定义) keyboard_template_id str 按钮消息(模版) params = [
+ {'key': 'para1', 'values': ['段落1']},
+ {'key': 'para2', 'values': ['段落2']},
+ {'key': 'desc', 'values': ['简介']},
+]
+
+Chain(data, at=False).markdown_template('101993071_1658748972', params)
按钮消息
使用按钮模版
# 必须跟随 md 模版发送
+Chain(data, at=False).markdown_template(
+ '101993071_1658748972',
+ params,
+ keyboard_template_id='102005657_1703561314',
+)
自定义按钮
from amiyabot import InlineKeyboard
+
+@bot.on_message(keywords='hello')
+async def _(data: Message):
+ keyboard = InlineKeyboard(data.instance.appid)
+
+ # 添加第一行按钮组
+ row = keyboard.add_row()
+ row.add_button('1', '按钮1')
+ row.add_button('2', '按钮2')
+
+ # 添加第二行按钮组
+ row2 = keyboard.add_row()
+ row2.add_button('3', '按钮3')
+ row2.add_button('4', '更多功能...', action_type=0)
+
+ # 必须跟随 md 模版发送
+ chain = Chain(data, at=False).markdown_template(
+ '102005657_1704356453',
+ [{'key': 'content', 'values': ['Markdown 测试']}],
+ keyboard=keyboard,
+ )
+
+ # 通过主动消息发送
+ await bot[data.instance.appid].send_message(chain, channel_id=data.channel_id)
频道跳转超链接
#子频道
,点击可以跳转至子频道,仅支持当前频道内的子频道Chain().tag()
参数名 类型 释义 默认值 target int 子频道ID Chain(data).tag(12345678)
文字
Chain().text()
参数名 类型 释义 默认值 text str 内容文本 auto_convert bool 是否超出字数后转换为图片 True Chain(data).text('hello, world')
auto_convert=True
可开启自动转换,当文字超过一定长度时(默认配置为 100),会自动将本段落转换为图片发送。插入表情
[face:ID]
模板也可以插入 QQ 表情。Chain(data).text('hello, world[face:175]')
文字生成的图片
Chain().text_image()
参数名 类型 释义 默认值 text str 内容文本 images List[ImageElem] 插入图片 None width int 图片宽度 None height int 图片高度 None bgcolor str 图片背景色 #F5F5F5 Chain(data).text_image('hello, world')
调色模板
@bot.on_message(keywords='hello')
+async def _(data: Message):
+ return Chain(data).text(f'hello [cl {data.nickname}@#ff0000 cle]')
Chain.text
的文本内使用了调色模板,将会强制转换为图片。同时 [face:ID]
模板也会失效。渲染图片
参数名 类型 释义 默认值 path str 图片路径 size int 图片大小 pos (int, int) 图片渲染位置 (x, y) hello, world
的文字图片,并在里面插入一张图片。
需要注意的是,因为 hello, world
文字只有一行,所以需要指定一下图片高度。否则插入的图片可能会显示不全。
你可以一次插入很多张图片,所以任何时候,都请把握好你的文字图片宽高与插入的图片的大小、坐标之间的影响。from amiyabot.builtin.lib.imageCreator import ImageElem
+
+
+@bot.on_message(keywords='hello')
+async def _(data: Message):
+ image = ImageElem(path='face.png', size=80, pos=(0, 20))
+
+ return Chain(data).text_image('hello,world', images=[image], height=100)
更换字体
from amiyabot.builtin.lib.imageCreator import FontStyle
+
+FontStyle.file = './font.ttf'
视频
Chain().video()
参数名 类型 释义 默认值 file str 视频文件路径 Chain(data).video(file)
@bot.on_message(keywords='hello')
+async def _(data: Message):
+ return Chain(data).video('xxxxxx.mp4')
语音
mirai-api-http
和 cq-http
。Chain().voice()
参数名 类型 释义 默认值 file str 语音 wav 文件路径 title str 语音标题 voice Chain(data).voice(file)
@bot.on_message(keywords='hello')
+async def _(data: Message):
+ return Chain(data).voice('阿米娅_问候.wav')
创建连续对话
Message.wait()
参数名 类型 释义 默认值 reply Chain Chain 对象 force bool 使用强制等待 False max_time int 最长等待时间(秒数) 30 data_filter Callable Message 过滤器 level int 优先级 0 @bot.on_message(keywords='hello')
+async def _(data: Message):
+
+ reply = await data.wait(Chain(data).text('tell me your name please~'))
+
+ if reply:
+ return Chain(reply).text(f'hello,{reply.text}')
force 强制等待
force=True
,可以忽略分配器让消息强制返回到等待处。data_filter 消息过滤器
async def my_data_filter(data: Message):
+ if ...:
+ return True # 返回 True 代表此则消息符合期望,将返回到等待处
+
+
+@bot.on_message(keywords='hello')
+async def _(data: Message):
+ reply = await data.wait(Chain(data).text('tell me your name please~'), data_filter=my_data_filter)
+
+ if reply:
+ return Chain(reply).text(f'hello,{reply.text}')
关于 wait 方法你需要知道的事
None
。WaitEventCancel
异常,进行中的业务将会被终止,这是符合预期的,通常这个异常会被全局异常捕捉器过滤。Message.wait_channel()
参数名 类型 释义 默认值 reply Chain Chain 对象 force bool 使用强制等待 False clean bool 是否清空消息列表 True max_time int 最长等待时间(秒数) 30 data_filter Callable Message 过滤器 level int 优先级 0 ChannelMessagesItem
对象。内含等待事件的实例,和该次返回的消息。属性 类型 释义 event ChannelWaitEvent 等待事件的实例 message Message Message对象 方法名 参数 释义 异步 close_event 关闭等待事件 否 @bot.on_message(keywords='hello')
+async def _(data: Message):
+ await data.send(Chain(data).text('hello everyone, tell me your name please~'))
+ while True:
+ await asyncio.sleep(0)
+ event = await data.wait_channel()
+ if event:
+ reply = event.message
+
+ if reply.text == 'stop':
+ event.close_event() # 关闭等待事件
+ break
+
+ await data.send(Chain(reply).text(f'hello,{reply.text}'))
close_event()
close_event
关闭它。不清除消息队列
clean=False
让事件不清除消息队列。让你可以按顺序获取到子频道内的消息。await data.wait_channel(clean=False)
事件监听
一般来说消息(MESSAGE_CREATE
、AT_MESSAGE_CREATE
以及DIRECT_MESSAGE_CREATE
)也属于事件,但是在构建阶段,这些消息事件会被归类并产出 Message 对象。剩下的事件类型,则会产出 Event 对象。可以使用 on_event 装饰器去获取事件。注册事件响应
from amiyabot import Event, BotAdapterProtocol
+
+...
+
+@bot.on_event()
+async def _(event: Event, instance: BotAdapterProtocol):
+ ...
+
+@bot.on_event('GUILD_CREATE')
+async def _(event: Event, instance: BotAdapterProtocol):
+ ...
+
+@bot.on_event(['CHANNEL_CREATE', 'CHANNEL_UPDATE'])
+async def _(event: Event, instance: BotAdapterProtocol):
+ ...
on_event
接受一个事件名或事件名列表作为参数(无参数则监听全事件)。事件名可参阅对应适配器服务的官方文档。Event 对象
属性 类型 释义 appid str 发生该事件的 Bot AppId event_name str 事件名 data dict 事件的内容字典 频道事件
Event.data
的值为 websocket 消息体里的 d
字段内容。{
+ "op": 0,
+ "s": 6,
+ "t": "GUILD_CREATE",
+ "d": {
+ "description": "频道介绍",
+ "icon": "",
+ "id": "200000000",
+ "joined_at": "2021-10-21T11:20:18+08:00",
+ "max_members": 300,
+ "member_count": 17,
+ "name": "频道名称",
+ "op_user_id": "100000000",
+ "owner_id": "100000000"
+ }
+}
@bot.on_event('GUILD_CREATE')
+async def _(event: Event, instance: BotAdapterProtocol):
+
+ print(event.data['name']) // 频道名称
+ print(event.data['description']) // 频道介绍
mirai-api-http 事件
{
+ "type": "NudgeEvent",
+ "fromId": 123456,
+ "subject": {
+ "id": 123456,
+ "kind": "Group"
+ },
+ "action": "戳了戳",
+ "suffix": "的脸",
+ "target": 123456
+}
NudgeEvent
即可。@bot.on_event('NudgeEvent')
+async def _(event: Event, instance: BotAdapterProtocol):
+ ...
go-cqhttp 事件
{
+ "post_type": "notice",
+ "notice_type": "notify",
+ "sub_type": "poke"
+}
notice
事件时,无法获得准确的事件。因为在 notice_type
的分类里,还包含了好友添加、消息撤回等一系列子事件。
当然,如果你需要监听这个大类的事件时,仍然可以监听这个事件名。# 监听 post_type 为 notice 的事件
+@bot.on_event('notice')
+async def _(event: Event, instance: BotAdapterProtocol):
+ ...
+
+# 监听 post_type 为 notice 且 notice_type 为 notify 的事件
+@bot.on_event('notice.notify')
+async def _(event: Event, instance: BotAdapterProtocol):
+ ...
notice.notify.poke
,即可监听到准确的戳一戳事件。@bot.on_event('notice.notify.poke')
+async def _(event: Event, instance: BotAdapterProtocol):
+ ...
post_type.xxxxx_type.sub_type
定义事件监听名即可。异常监听
Exception
、适配器实例 BotAdapterProtocol
、产生异常的数据 (Message
或 Event
)from amiyabot import BotAdapterProtocol
+
+@bot.on_exception()
+async def _(err: Exception,
+ instance: BotAdapterProtocol,
+ data: Union[Message, Event]):
+ ...
指定异常类型
@bot.on_exception(KeyError)
+async def _(err: Exception,
+ instance: BotAdapterProtocol,
+ data: Union[Message, Event]):
+ ...
指定多种异常类型
@bot.on_exception([OSError, IndexError])
+async def _(err: Exception,
+ instance: BotAdapterProtocol,
+ data: Union[Message, Event]):
+ ...
开始使用
接下来的文档将围绕 QQ 频道机器人展开。如果需要 更改适配器 请在安装依赖后阅读适配器文档。安装依赖
pip install amiyabot
创建你的第一个 Bot
appid
和 token
创建一个 AmiyaBot 实例import asyncio
+
+from amiyabot import AmiyaBot, Message, Chain
+
+bot = AmiyaBot(appid='******', token='******')
+
+
+@bot.on_message(keywords='hello')
+async def _(data: Message):
+ return Chain(data).text(f'hello, {data.nickname}')
+
+
+asyncio.run(bot.start())
@机器人 hello
,你预期会看到如下输出。创建私域机器人
AmiyaBot
的参数里设置 private=True
来开启私域模式,私域机器人支持接收非 @机器人
的消息,你可以使机器人以更灵活的方式触发功能。# 配置 private=True 让实例改为私域
+bot = AmiyaBot(appid='******', token='******', private=True)
使用前缀触发词唤醒机器人
bot = AmiyaBot(...)
+
+# 添加前缀触发词
+bot.set_prefix_keywords(['amiya', 'amy'])
沙箱环境
QQGuildSandboxBotInstance
适配器将 API 调用更改为沙箱环境。沙箱环境只会收到测试频道的事件,且调用 openapi 仅能操作测试频道。from amiyabot.adapters.tencent.qqGuild import QQGuildSandboxBotInstance
+
+bot = AmiyaBot(..., adapter=QQGuildSandboxBotInstance)
注册消息响应器
AmiyaBot
、Message
和Chain
。AmiyaBot
为机器人实例,包含了消息和事件的注册器。Message
为接收的消息主体,内含预解析的消息内容,以及一些相关操作函数。Message 对象在此仅用于参数类型注解,供编辑器智能提示使用,任何时候,你都不需要实例化 Message 对象。Chain
为机器人消息的创建工具。任何需要发送消息的时候,消息都必须由 Chain 类创建。核心会调用 Chain 类的 build 方法生成消息链。on_message 装饰器
on_message
作用于你的业务逻辑主体函数,以此注册消息响应来实现你的机器人功能。# 当和机器人的对话中包含 'hello' 关键字时,将会触发该函数
+@bot.on_message(keywords='hello')
+async def _(data: Message):
+ return Chain(data).text(f'hello, {data.nickname}')
参数列表
参数名 类型 释义 默认值 group_id str 功能组ID keywords 触发关键字,支持多种类型,详见本章文档 verify Callable 自定义校验方法,当该参数被赋值时,keywords 将会失效 check_prefix bool 是否校验前缀或指定需要校验的前缀 True allow_direct bool 是否支持通过私信使用该功能 False direct_only bool 是否仅支持私信 False level int 关键字校验成功后函数的候选默认等级 0 功能组
group_id
可以为该功能设置功能组。功能组可以批量为功能设置参数。参数名 类型 释义 默认值 group_id str 功能组ID check_prefix bool 是否校验前缀或指定需要校验的前缀 True allow_direct bool 是否支持通过私信使用该功能 False direct_only bool 是否仅支持私信 False from amiyabot import GroupConfig
+
+fn_group = GroupConfig('test', check_prefix=False)
+bot.set_group_config(fn_group) # 注册功能组
group_id
参数# 传入功能组名称设置组别
+@bot.on_message(group_id='test', keywords='...')
+async def _(data: Message):
+ ...
+
+
+# 传入功能组对象设置组别(效果相同)
+@bot.on_message(group_id=fn_group, keywords='...')
+async def _(data: Message):
+ ...
私域模式的前缀校验
忽略前缀检查
和 校验完全匹配
的方式绕过检查。接收不包含前缀的消息
check_prefix=False
可忽略前缀检查@bot.on_message(keywords='hello', check_prefix=False)
+async def _(data: Message):
+ return Chain(data).text(f'hello, {data.nickname}')
接收指定前缀的消息
check_prefix
参数改为字符串列表可以临时修改前缀检查为指定单词@bot.on_message(keywords='hello', check_prefix=['amiya', '🐰'])
+async def _(data: Message):
+ return Chain(data).text(f'hello, {data.nickname}')
校验符合正则检查的句式
re.compile
对象,可以检查对话文本是否符合正则表达式。import re
+...
+
+@bot.on_message(keywords=re.compile(r'hello,\\d+'))
+async def _(data: Message):
+ return Chain(data).text(f'hello, {data.nickname}')
校验完全匹配的句式
Equal
即可达到效果。from amiyabot import Equal
+...
+
+@bot.on_message(keywords=Equal('hello, amiya'))
+async def _(data: Message):
+ return Chain(data).text(f'hello, {data.nickname}')
组合多个和多种 keywords
字符串、正则、Equal
构成的列表,组合中包含 Equal
时,Equal
依然会无视前缀检查。@bot.on_message(
+ keywords=[
+ 'hello',
+ 'hey',
+ Equal('hello, amiya'),
+ re.compile(r'hello,(\\d+)'),
+ ]
+)
+async def _(data: Message):
+ return Chain(data).text(f'hello, {data.nickname}')
功能优先级
分配器的工作原理,是在完成检查之后,将通过校验的候选函数列表按优先级倒序排序,然后选取第一个执行。
所有函数的默认优先级都为 1
,如果不指定优先级,分配器会按照加载的先后顺序选择。# 如果不指定优先级,当对话内容为 "helloworld" 时,第一个函数会首先通过校验并输出。
+# 因为在模块加载阶段,第一个函数更早注册完毕。
+
+@bot.on_message(keywords='hello', level=1)
+async def _(data: Message):
+ return Chain(data).text(f'hello, {data.nickname}')
+
+
+@bot.on_message(keywords='helloworld', level=2)
+async def _(data: Message):
+ return Chain(data).text('hello,world')
自定义检查
自定义检查是一个协程函数,参数为 Message 对象,返回一个布尔值(必选)
、优先级(可选)
和 关键值(可选)
的元组。async def my_verify(data: Message):
+ if 'hello' in data.text:
+ return True
+
+
+@bot.on_message(verify=my_verify)
+async def _(data: Message):
+ return Chain(data).text(f'hello, {data.nickname}')
属性 类型 释义 result bool 检查结果 weight int 优先级 keypoint Any 关键值信息 动态输出优先级的值
async def my_verify(data: Message):
+ if ...:
+ return True, 2
+ elif ...:
+ return True, 1
+ return False
输出关键值
async def my_verify(data: Message):
+ if ...:
+ return True, 1, {'name': 'my name'}
+ return False
+
+
+@bot.on_message(verify=my_verify)
+async def _(data: Message):
+ info = data.verify.keypoint # {'name': 'my name'}
使功能在私信里可用
allow_direct=True
,允许功能在私信里触发。
设置参数 direct_only=True
,功能仅私信可触发。@bot.on_message(keywords='hello', allow_direct=True)
+async def _(data: Message):
+ return Chain(data).text(f'hello, {data.nickname}')
+
+
+# 仅私信可触发
+@bot.on_message(keywords='hi', direct_only=True)
+async def _(data: Message):
+ return Chain(data).text(f'hey, {data.nickname}')
多账号
创建多个单独实例
AmiyaBot
。多实例启动时,一些内置的资源对象不会重复创建多个,而是被它们共享,以节约内存。import asyncio
+
+from amiyabot import AmiyaBot, Message, Chain
+
+bot1 = AmiyaBot(appid='******', token='******', private=True)
+bot2 = AmiyaBot(appid='******', token='******')
+
+
+@bot1.on_message(keywords='hello')
+async def _(data: Message):
+ return Chain(data).text(f'hello, {data.nickname}')
+
+
+@bot2.on_message(keywords='hey')
+async def _(data: Message):
+ return Chain(data).text(f'hey, {data.nickname}')
+
+
+asyncio.run(
+ asyncio.wait([
+ bot1.start(),
+ bot2.start()
+ ])
+)
创建一个多账号实例
MultipleAccounts
可以创建一个多账号实例,参数是多个 AmiyaBot 实例。它拥有与 AmiyaBot 一样的所有方法,所以你可以像 AmiyaBot 一样使用它来注册你的功能。
通过 MultipleAccounts 注册的功能,将被所有包含的 AmiyaBot 共享。而其中的 AmiyaBot 实例,仍能单独注册属于自己的私有功能。import asyncio
+
+from amiyabot import AmiyaBot, MultipleAccounts, Message, Chain
+
+bot1 = AmiyaBot(appid='******', token='******', private=True)
+bot2 = AmiyaBot(appid='******', token='******')
+
+bot = MultipleAccounts(bot1, bot2, AmiyaBot(appid='******', token='******'), ...)
+
+
+# 公共功能
+@bot.on_message(keywords='hello')
+async def _(data: Message):
+ return Chain(data).text(f'hello, {data.nickname}')
+
+
+# bot1 的私有功能
+@bot1.on_message(keywords='hey')
+async def _(data: Message):
+ return Chain(data).text(f'hey, my name is Amiyabot')
+
+
+asyncio.run(bot.start())
多账号热插拔
MultipleAccounts.append()
参数名 类型 释义 默认值 item AmiyaBot AmiyaBot 实例 enable_chromium bool 启动时开启 chromium False start_up bool 插入后立即启动 True # 创建一个空多账号
+bot = MultipleAccounts()
+# 插入并启动一个实例
+bot.append(AmiyaBot(appid='12345', ...))
+# 删除实例,同时关闭实例的连接
+del bot['12345']
撤回消息
在 Message 对象中撤回
recall
方法,撤回当前消息。@bot.on_message(keywords='hello')
+async def _(data: Message):
+ await data.recall()
recall
方法。比如在连续对话或异常监听里。@bot.on_message(keywords='hello')
+async def _(data: Message):
+ # 等待事件返回的也是 Message 对象
+ reply = await data.wait(Chain(data).text('...'))
+ if reply:
+ await reply.recall()
@bot.on_exception()
+async def _(err: Exception,
+ instance: BotAdapterProtocol,
+ data: Union[Message, Event]):
+ if type(data) is Message:
+ await data.recall()
手动撤回
recall_message
方法参数名 类型 释义 默认值 message_id str 消息ID(通常可以在 Message.message_id 获取到) data Message Message 对象 None await bot.instance.recall_message(message_id='......', target_id='......')
撤回 Bot 发送的消息
Chain
对象或等待函数 Message.wait()
发送的消息,是无法撤回的,但后者可以通过另外的方法达到撤回效果。发送消息的方法 是否可撤回 Message.send() ✅ 可以撤回 Message.wait() ❌ 无法撤回 Message.wait_channel() ❌ 无法撤回 return Chain() ❌ 无法撤回 @bot.on_message(keywords='hello')
+async def _(data: Message):
+ chain = Chain(data).text(f'hello, {data.nickname}')
+
+ callback = await data.send(chain) # ✅ 可以撤回
+ if callback:
+ await callback.recall() # 使用回调对象撤回
+
+ wait = await data.wait(chain) # ❌ 无法撤回
+ event = await data.wait_channel(chain) # ❌ 无法撤回
+
+ return chain # ❌ 无法撤回
MessageCallback
对象或其组成的列表(语音或频道多图消息会产生分开发送的结果)。如果消息没有发送成功则返回 None
。MessageCallback.recall()
即可撤回发送的消息。@bot.on_message(keywords='hello')
+async def _(data: Message):
+
+ callback = await data.send(...)
+ if callback:
+ await callback.recall()
+ # 或
+ # for item in callback:
+ # await item.recall()
撤回等待的消息
send
时获得的 MessageCallback
,因此你无法在使用该方法发送消息的情况下撤回,但你可以配合 send
达到这个效果。@bot.on_message(keywords='hello')
+async def _(data: Message):
+ callback = await data.send(Chain(data).text('hello, what\\'s your name?')) # 使用 send 方法代替 wait 发送消息
+ wait = await data.wait() # 只等待,不发送消息
+
+ if callback and not wait:
+ await callback.recall()
+
+ ...
撤回合并转发的消息
`,26),t=[p];function e(c,r,y,D,F,i){return a(),n("div",null,t)}const d=s(o,[["render",e]]);export{B as __pageData,d as default};
diff --git a/assets/develop_basic_recallMessage.md.35c06f76.lean.js b/assets/develop_basic_recallMessage.md.35c06f76.lean.js
new file mode 100644
index 00000000..a3dbbe75
--- /dev/null
+++ b/assets/develop_basic_recallMessage.md.35c06f76.lean.js
@@ -0,0 +1 @@
+import{_ as s,o as a,c as n,V as l}from"./chunks/framework.63f12d77.js";const B=JSON.parse('{"title":"撤回消息","description":"","frontmatter":{},"headers":[],"relativePath":"develop/basic/recallMessage.md","filePath":"develop/basic/recallMessage.md","lastUpdated":1722566694000}'),o={name:"develop/basic/recallMessage.md"},p=l("",26),t=[p];function e(c,r,y,D,F,i){return a(),n("div",null,t)}const d=s(o,[["render",e]]);export{B as __pageData,d as default};
diff --git a/assets/develop_basic_recvMessage.md.4e302953.js b/assets/develop_basic_recvMessage.md.4e302953.js
new file mode 100644
index 00000000..5547702d
--- /dev/null
+++ b/assets/develop_basic_recvMessage.md.4e302953.js
@@ -0,0 +1,11 @@
+import{_ as t,o as s,c as a,V as e}from"./chunks/framework.63f12d77.js";const _=JSON.parse('{"title":"接收消息","description":"","frontmatter":{},"headers":[],"relativePath":"develop/basic/recvMessage.md","filePath":"develop/basic/recvMessage.md","lastUpdated":1722566694000}'),d={name:"develop/basic/recvMessage.md"},r=e(`接收消息
data: Message
就是接收到的消息的内容。Message
对象是接收到消息之后预处理化的一个消息数据对象。内含这则消息相关的各项属性,以及针对这则消息的一些操作API。
该对象主要应用在功能函数和自定义检查中。建议在开发时引入 Message 对象并注解在对应地方。from amiyabot import Message
+
+
+async def my_verify(data: Message):
+ ...
+
+
+@bot.on_message(verify=my_verify)
+async def _(data: Message):
+ print('recv:', data.text)
+ ...
Message 对象
1.9.8
起,如果配置了前缀触发词,Message 对象内的消息文本系列字段将不再包含触发对话的前缀(Commit 99ada6b), 前缀在对话文本中被移除后将赋值到 text_prefix
属性内。属性
属性 类型 释义 instance BotAdapterProtocol bot 实例 message dict 原始消息字典 message_id str 消息 ID message_type str 消息类型(适用于群聊适配器) face List[str] 消息内表情 ID 列表 image List[str] 消息内图片 URL 列表 text str 消息文本(不包含触发词、中间件处理) text_digits str 消息文本(不包含触发词、中间件处理、中文转数字处理) text_unsigned str 消息文本(不包含触发词、去字符处理) text_original str 消息文本(原始文本) text_words List[str] 消息文本分词 text_prefix str 消息触发词 at_target List[str] 消息内 @ 的对象列表 is_at bool 是否 @ 机器人 is_admin bool 是否为子频道管理员 is_direct bool 是否是私信消息 user_id str 用户 ID guild_id str 频道 ID src_guild_id str 来源频道 ID,私信下有效 channel_id str 子频道 ID nickname str 用户昵称 avatar str 用户头像的 URL joined_at ISO8601 timestamp 用户加入频道的时间 verify Verify 对象 自定义检查的结果 time int 消息时间 方法
方法名 参数 释义 异步 send reply 发送一条消息 是 wait reply,force,max_time,data_filter 等待用户消息 是 wait_channel reply,force,clean,max_time,data_filter 等待子频道消息 是 recall 撤回消息 是 发送主动消息
详见官方文档 接入流程-语料配置-消息类型AmiyaBot.send_message()
message = Chain().text('hello')
参数名 类型 释义 默认值 chain Chain Chain 对象 user_id str 用户ID channel_id str 子频道ID direct_src_guild_id str 来源频道ID 发送一条主动子频道消息
bot = AmiyaBot(...)
+message = Chain().text('hello')
+
+await bot.send_message(message, channel_id='******')
发送一条主动私信 Beta
user_id
(用户ID)和 direct_src_guild_id
(来源频道ID),send_message 将会发送主动私信消息。bot = AmiyaBot(...)
+message = Chain().text('hello')
+
+await bot.send_message(message,
+ user_id='*******',
+ direct_src_guild_id='*******')
在多账号实例里使用 send_message
bot = MultipleAccounts(
+ [
+ AmiyaBot(appid='111111', ...),
+ AmiyaBot(appid='222222', ...),
+ ]
+)
+
+await bot['222222'].send_message(...)
在事件响应里使用 send_message
事件监听
@bot.on_event('MESSAGE_DELETE')
+async def _(event: Event, instance: BotAdapterProtocol):
+ await instance.send_message(...)
异常监听
@bot.on_exception()
+async def _(err: Exception,
+ instance: BotAdapterProtocol,
+ data: Union[Message, Event]):
+ await instance.send_message(...)
发送消息
@bot.on_message(keywords='hello')
+async def _(data: Message):
+ await data.send(Chain(data).text('hello'))
+
+ return Chain(data).text(data.nickname)
Chain 对象
参数名 类型 释义 默认值 data Message Message 对象 None at bool 是否 @ 用户 True reference bool 是否回复用户(引用消息) False chain_builder ChainBuilder Chain 辅助构建实例 ChainBuilder() Chain
对象是构建你的消息体的工具类。任何需要发送消息的时候,消息都必须由 Chain 类创建。
Chain 对象提供丰富的消息构建方式,可以让你发送多彩的文字图片,甚至是html模板。Chain(data).text('hello, world')
构建消息的方法
普通消息
只需要按顺序以链式使用上述方法,即可拼接出内容丰富的消息。Chain 在最终构建消息的时候,会优化图片与文字的组成,减少消息的请求数量。Chain(data).text(...).image(...).text(...).html(...)
合并转发消息
空 Chain
Chain().text(...).image(...)
使用辅助类扩展构建
声明
AmiyaBot
或 MultipleAccounts
类的实例化代码。统一以变量 bot
表示已经实例化的 AmiyaBot
或 MultipleAccounts
类。# 装饰器
+@bot.on_message(...)
+
+# 调用方法
+bot.some_func(...)
+
+...
使用测试实例调试
test_instance
并配置 host、port 参数即可启动一个测试实例。import asyncio
+
+from amiyabot import AmiyaBot, Message, Chain
+from amiyabot.adapters.test import test_instance
+
+bot = AmiyaBot(appid='123456',
+ token='',
+ adapter=test_instance('127.0.0.1', 32001))
+
+
+@bot.on_message(keywords='hello', check_prefix=False)
+async def _(data: Message):
+ return Chain(data).text(f'hello, {data.nickname}')
+
+
+asyncio.run(bot.start())
设计
业务模块
,通讯模块
,数据处理模块
,存储模块
和运转中心
五个组成部分。它们独立运作,并在彼此之间相互提供数据。业务模块
插件(Plugin)
、账号实例(AmiyaBot)
和多账号实例(MultipleAccounts)
组成。它们一般会被组织成层层嵌套的树状结构。账号实例和多账号实例是同质的,它们均可以被运转中心(CenterProcessor)直接访问,运转中心无法访问插件。插件设计
业务设计
通讯与数据处理模块
适配器(Adapter)
,用于对接由机器人运营方提供的服务。适配器
运转中心
消息处理
结语
添加插件文档
添加一个 markdown 文档
Amiya-Bot
+├── pluginsDev
+│ ├── myPlugin
+│ │ ├── __init__.py
+│ │ ├── main.py
+│ │ ├── README.md
+│ │ └── HOWTOUSE.md
+│ ...
+...
document
参数。添加使用指引文档
instruction
,其作用是当 bot 的用户在使用 “查看功能指引” 类的指令时,向其展示 instruction
的内容。document
更偏向于提供给 bot 的部署者查看的文档。在没有配置 instruction
时,“查看功能指引” 将返回 document
的内容。import os
+
+from amiyabot import PluginInstance, AmiyaBotPluginInstance
+
+plugin_dir = os.path.dirname(__file__)
+
+bot = PluginInstance(
+ document=f'{plugin_dir}/README.md'
+)
+# 或
+bot = AmiyaBotPluginInstance(
+ document=f'{plugin_dir}/README.md',
+ instruction=f'{plugin_dir}/HOWTOUSE.md'
+)
直接在参数内编写
bot = PluginInstance(
+ document='''
+ ...
+ '''
+)
AmiyaBotPluginInstance
AmiyaBotPluginInstance
创建插件。from core import AmiyaBotPluginInstance
+
+bot = AmiyaBotPluginInstance(
+ name='我的插件',
+ version='1.0',
+ plugin_id='my-plugin',
+ description='我的第一个插件',
+ channel_config_default=...,
+ channel_config_schema=...,
+ global_config_default=...,
+ global_config_schema=...,
+ deprecated_config_delete_days=...,
+)
参数
参数名 类型 释义 默认值 instruction str 使用指引文档 None requirements List[Requirement] 插件依赖 None channel_config_default str 频道级别配置默认值 None channel_config_schema str 频道级别配置表单的 JsonSchema None global_config_default str 全局级别配置默认值 None global_config_schema str 全局级别配置表单的 JsonSchema None deprecated_config_delete_days int 旧配置项失效的天数 7 json
文件路径。(默认值允许使用 yaml
文件路径)重置为默认
时会使用默认值的 JSON 数据覆盖,创建新配置项时使用默认值的 JSON 数据填充。plugin_dir = os.path.dirname(__file__)
+
+bot = AmiyaBotPluginInstance(
+ channel_config_default=f'{plugin_dir}/config.yaml',
+ channel_config_schema=f'{plugin_dir}/jsonSchema.json'
+)
使用 JsonSchema 对接控制台
get_config
None
。传入 channel_id=None
可以直接读取全局配置。参数名 类型 释义 默认值 config_name str 配置名称 channel_id str 频道ID None config_value = bot.get_config('name', channel_id='...')
set_config
channel_id=None
可以强制指定写入全局配置。参数名 类型 释义 默认值 config_name str 配置名称 config_value Any 配置值,仅支持可被 JSON 序列化的值类型 channel_id str 频道ID None bot.set_config('name', 'value', channel_id='...')
说明
channel_config_default
时,界面可以新建频道配置。global_config_default
作为默认全局配置写入数据库。
该过程发生在构造函数,因此您如果需要对全局配置进行初始化操作,您需要在您的插件实例的构造函数,或者 install 函数中进行。添加插件依赖
Requirement
类的列表,当插件安装时,会从插件市场服务尝试寻找对应的插件依赖并一同安装。参数名 类型 释义 默认值 plugin_id str 插件ID version str 版本号(可选) None official bool 是否官方插件 False from core import AmiyaBotPluginInstance, Requirement
+
+bot = AmiyaBotPluginInstance(
+ requirements=[
+ Requirement('other-plugin'),
+ Requirement('other-plugin2', '2.0'),
+ ]
+)
打包插件
直接打包
示例
pypyodbc
,在本地 python 环境内找到 pypyodbc.py(有时候会是 package 文件夹),并拷贝进 zip 包内。# main.py
+import pypyodbc
+
+...
myPlugin.zip
+├── __init__.py
+├── main.py
+└── pypyodbc.py
main.py
内的导入语句 import pypyodbc
被执行时,优先使用插件内的 pypyodbc 包。pip install xxx
安装第三方依赖。使用脚本打包
python run_build.py --type plugins
创建插件
main.py
Amiya-Bot
+├── pluginsDev
+│ ├── myPlugin
+│ │ ├── __init__.py
+│ │ └── main.py
+│ ...
+...
编写插件程序
main.py
内编写插件的注册程序from amiyabot import PluginInstance
+
+bot = PluginInstance(
+ name='我的插件',
+ version='1.0',
+ plugin_id='my-plugin',
+ description='我的第一个插件'
+)
PluginInstance
对象继承了工厂类 BotHandlerFactory
,这意味着你完全可以按照 开发指南 去编写你的插件功能。@bot.on_message(keywords='hello')
+async def _(data: Message):
+ return Chain(data).text(f'hello, {data.nickname}')
+
+@bot.on_event('GUILD_CREATE')
+async def _(event: Event, instance: BotAdapterProtocol):
+ ...
插件内静态资源的使用
Amiya-Bot
+├── pluginsDev
+│ ├── myPlugin
+│ │ ├── __init__.py
+│ │ ├── main.py
+│ │ └── file.txt
+│ ...
+...
# main.py
+import os
+
+plugin_dir = os.path.dirname(__file__)
+file = open(f'{plugin_dir}/file.txt')
导出插件的实例
__init__.py
内导出插件的入口,命名为 bot。这个命名是固定的,即使 main 内部的 PluginInstance 实例变量名不为 bot,在导出时都必须使用 as bot
重命名。from .main import bot
__init__.py
内编写多余的代码,我们希望这个文件有且只有上面的一行。插件在加载时,会临时添加插件目录为系统路径,这会使主模块的一些全局一等对象污染主体程序,导致不可预测的后果。调试插件
在测试脚本中调试
# 显式导入插件开发目录,可以在编辑器 DEBUG 调试。
+from pluginsDev.src.user import bot as plugin
+
+
+async def install_plugin():
+ # 导入插件
+ bot.install_plugin(plugin)
+ # 直接导入插件 zip 包
+ # bot.install_plugin('plugins/amiyabot-arknights-operator-1.5.zip', extract_plugin=True)
python run_test.py
,访问测试网址并使用。详见 测试实例准备开发环境
克隆仓库
git clone https://github.com/AmiyaBot/Amiya-Bot.git
cd Amiya-Bot
+git clone https://github.com/AmiyaBot/Amiya-Bot-plugins.git pluginsDev
Amiya-Bot
+├── pluginsDev
+│ │
+│ ...
+...
pip install amiyabot
JsonSchema 解释
{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "required": [],
+ "properties": {}
+}
required
:必填字段(暂未支持前端校验,敬请期待...)properties
:字段配置基本配置
<config_name>
:配置名称,是使用 get_config 方法时的参数<config_name>.type
:类型<config_name>.title
(可选):表单标题,不配置则使用 config_name<config_name>.description
(可选):表单tips,鼠标移动到页面的问号图标时显示的内容<config_name>.default
(可选):默认值,需按照类型填写。{
+ "properties": {
+ "imagesCachePath": {
+ "title": "图片缓存目录",
+ "description": "图片缓存的目录路径,可以为绝对路径",
+ "type": "string",
+ "default": "./imagesCache"
+ }
+ }
+}
文本输入框
default
的类型为字符串{
+ "properties": {
+ "<config_name>": {
+ "type": "string",
+ "default": "text"
+ }
+ }
+}
数字输入框
default
的类型为数字minimum
(可选): 可输入的最小值maximum
(可选): 可输入的最大值{
+ "properties": {
+ "<config_name>": {
+ "type": "integer",
+ "minimum": -10,
+ "maximum": 20,
+ "default": 0
+ },
+ "<config_name2>": {
+ "type": "number",
+ "default": 0.5
+ }
+ }
+}
切换按钮
default
的类型为布尔型{
+ "properties": {
+ "<config_name>": {
+ "type": "boolean",
+ "default": false
+ }
+ }
+}
下拉选择框
type
字段,默认值 default
的类型为内容的值类型{
+ "properties": {
+ "<config_name>": {
+ "enum": [
+ "option1",
+ "option2",
+ "option3"
+ ],
+ "default": "option2"
+ }
+ }
+}
日期时间选择器
type
配置为 string
时,若配置了对应的 format
字段,可更改为日期时间选择器。
默认值 default
的类型为字符串或数值{
+ "properties": {
+ "<config_name>": {
+ "type": "string",
+ "format": "datetime"
+ }
+ }
+}
format 选项
YYYY-MM-DD
HH:mm:ss
YYYY-MM-DD HH:mm:ss
[ YYYY-MM-DD, YYYY-MM-DD ]
[ HH:mm:ss, HH:mm:ss ]
[ YYYY-MM-DD HH:mm:ss, YYYY-MM-DD HH:mm:ss ]
[
+ "2023-05-20 08:00:00",
+ "2023-05-21 16:00:00"
+]
动态输入-值数组
get_config
方法返回 [value1, value2, value3, ...]
形式的值数值,可以使用动态输入值数组。
默认值 default
的类型为数组type
配置为 array
items
为数组配置 type
:值类型,仅支持 string
、integer
、number
以及 object{
+ "properties": {
+ "<config_name>": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+}
动态输入-对象数组
get_config
方法返回 [{...}, {...}, {...}, ...]
形式的值数值,可以使用动态输入对象(字典)数组。
默认值 default
的类型为数组{
+ "properties": {
+ "<config_name>": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {}
+ }
+ }
+ }
+}
子级对象
type
配置为 object
properties
根据上文 基本配置 添加任意输入类型或将 type
配置为 object
继续作为新的子级对象{
+ "properties": {
+ "<config_name>": {
+ "type": "object",
+ "properties": {}
+ }
+ }
+}
示例
{
+ "properties": {
+ "user": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "age": {
+ "type": "integer"
+ },
+ "attrs": {
+ "type": "object",
+ "properties": {
+ "atk": {
+ "type": "number"
+ },
+ "def": {
+ "type": "number"
+ }
+ }
+ }
+ }
+ }
+ }
+}
bot.get_config()
方法将返回以下内容{
+ "user": {
+ "name": ...,
+ "age": ...,
+ "attrs": {
+ "atk": ...,
+ "def": ...
+ }
+ }
+}
插件生命周期
from amiyabot import PluginInstance
+
+
+class MyPlugin(PluginInstance):
+ def install(self):
+ # 插件被安装时执行的操作
+ ...
+
+ def uninstall(self):
+ # 插件被卸载时执行的操作
+ ...
+
+
+bot = MyPlugin(
+ name='我的插件',
+ version='1.0',
+ plugin_id='my-plugin',
+ description='我的第一个插件'
+)
发布到插件商店
插件密钥
下载
pip install amiyabot
兔兔-v6
',4),r=a("thead",null,[a("tr",null,[a("th",null,"操作系统"),a("th",null,"下载"),a("th",null,"备注")])],-1),i=a("td",null,"Windows",-1),d=a("td",null,"仅支持 Windows 10、Windows Server 2016 及以上系统",-1),y=JSON.parse('{"title":"下载","description":"","frontmatter":{"aside":false},"headers":[],"relativePath":"download.md","filePath":"download.md","lastUpdated":1722566694000}'),p={name:"download.md"},f=Object.assign(p,{setup(c){return(_,h)=>(e(),s("div",null,[n,a("table",null,[r,a("tbody",null,[a("tr",null,[i,a("td",null,[l(t,{version:"win32"})]),d])])])]))}});export{y as __pageData,f as default};
diff --git a/assets/download.md.0155255c.lean.js b/assets/download.md.0155255c.lean.js
new file mode 100644
index 00000000..ce35c567
--- /dev/null
+++ b/assets/download.md.0155255c.lean.js
@@ -0,0 +1 @@
+import{d as t}from"./chunks/download.1cbae1cc.js";import{o as e,c as s,C as a,J as l,V as o}from"./chunks/framework.63f12d77.js";import"./chunks/index.ca03c6e9.js";const n=o("",4),r=a("thead",null,[a("tr",null,[a("th",null,"操作系统"),a("th",null,"下载"),a("th",null,"备注")])],-1),i=a("td",null,"Windows",-1),d=a("td",null,"仅支持 Windows 10、Windows Server 2016 及以上系统",-1),y=JSON.parse('{"title":"下载","description":"","frontmatter":{"aside":false},"headers":[],"relativePath":"download.md","filePath":"download.md","lastUpdated":1722566694000}'),p={name:"download.md"},f=Object.assign(p,{setup(c){return(_,h)=>(e(),s("div",null,[n,a("table",null,[r,a("tbody",null,[a("tr",null,[i,a("td",null,[l(t,{version:"win32"})]),d])])])]))}});export{y as __pageData,f as default};
diff --git a/assets/guide_deploy_advanced_index.md.bfe56c56.js b/assets/guide_deploy_advanced_index.md.bfe56c56.js
new file mode 100644
index 00000000..b074df0d
--- /dev/null
+++ b/assets/guide_deploy_advanced_index.md.bfe56c56.js
@@ -0,0 +1 @@
+import{_ as a,o as t,c as d,C as e,a as n}from"./chunks/framework.63f12d77.js";const g=JSON.parse('{"title":"说明","description":"","frontmatter":{},"headers":[],"relativePath":"guide/deploy/advanced/index.md","filePath":"guide/deploy/advanced/index.md","lastUpdated":1722566694000}'),o={name:"guide/deploy/advanced/index.md"},s=e("h1",{id:"说明",tabindex:"-1"},[n("说明 "),e("a",{class:"header-anchor",href:"#说明","aria-label":'Permalink to "说明"'},"")],-1),c=e("p",null,"本部分文档可能只是对控制台暂未支持的部分的一种补充,文档中的内容可能不会一直维护,也不会一直有效,在不久的将来可能都会在控制台中实现。届时对应的文档也会从这里删去。当然,这并不是绝对的,部分功能可能无法或者不会在控制台中实现,那么控制台将可能会有指向这些文档的链接。目录名称“高级使用”仅是用来区分可以在控制台内直接实现的操作的,并无其他含义。",-1),i=e("p",null,"请理解:由于本部分的特殊性,我们可能不会高频率的维护这些文档以至于它们可能无法与最新的版本相匹配,甚至可能随时删除或停止维护这些文档。您使用过期文档导致生产事故等的情况我们不负任何责任,也不对其造成的影响负责。",-1),r=e("p",null,"如果您已阅读并理解,同意以上部分,则请您浏览左侧的目录跳转至您需要的部分开始阅读。",-1),l=[s,c,i,r];function _(p,h,u,m,f,x){return t(),d("div",null,l)}const $=a(o,[["render",_]]);export{g as __pageData,$ as default};
diff --git a/assets/guide_deploy_advanced_index.md.bfe56c56.lean.js b/assets/guide_deploy_advanced_index.md.bfe56c56.lean.js
new file mode 100644
index 00000000..b074df0d
--- /dev/null
+++ b/assets/guide_deploy_advanced_index.md.bfe56c56.lean.js
@@ -0,0 +1 @@
+import{_ as a,o as t,c as d,C as e,a as n}from"./chunks/framework.63f12d77.js";const g=JSON.parse('{"title":"说明","description":"","frontmatter":{},"headers":[],"relativePath":"guide/deploy/advanced/index.md","filePath":"guide/deploy/advanced/index.md","lastUpdated":1722566694000}'),o={name:"guide/deploy/advanced/index.md"},s=e("h1",{id:"说明",tabindex:"-1"},[n("说明 "),e("a",{class:"header-anchor",href:"#说明","aria-label":'Permalink to "说明"'},"")],-1),c=e("p",null,"本部分文档可能只是对控制台暂未支持的部分的一种补充,文档中的内容可能不会一直维护,也不会一直有效,在不久的将来可能都会在控制台中实现。届时对应的文档也会从这里删去。当然,这并不是绝对的,部分功能可能无法或者不会在控制台中实现,那么控制台将可能会有指向这些文档的链接。目录名称“高级使用”仅是用来区分可以在控制台内直接实现的操作的,并无其他含义。",-1),i=e("p",null,"请理解:由于本部分的特殊性,我们可能不会高频率的维护这些文档以至于它们可能无法与最新的版本相匹配,甚至可能随时删除或停止维护这些文档。您使用过期文档导致生产事故等的情况我们不负任何责任,也不对其造成的影响负责。",-1),r=e("p",null,"如果您已阅读并理解,同意以上部分,则请您浏览左侧的目录跳转至您需要的部分开始阅读。",-1),l=[s,c,i,r];function _(p,h,u,m,f,x){return t(),d("div",null,l)}const $=a(o,[["render",_]]);export{g as __pageData,$ as default};
diff --git a/assets/guide_deploy_advanced_mysql.md.55d14788.js b/assets/guide_deploy_advanced_mysql.md.55d14788.js
new file mode 100644
index 00000000..58a89ad1
--- /dev/null
+++ b/assets/guide_deploy_advanced_mysql.md.55d14788.js
@@ -0,0 +1,6 @@
+import{_ as s,o as a,c as n,V as l}from"./chunks/framework.63f12d77.js";const _=JSON.parse('{"title":"使用 Mysql","description":"","frontmatter":{},"headers":[],"relativePath":"guide/deploy/advanced/mysql.md","filePath":"guide/deploy/advanced/mysql.md","lastUpdated":1722566694000}'),o={name:"guide/deploy/advanced/mysql.md"},p=l(`使用 Mysql
config\\database.yaml
中可以配置使用的数据库,如需更改数据库类型为 Mysql
,请按下面的配置说明进行配置:mode: 'mysql'
+config:
+ host: '数据库地址'
+ port: 数据库端口
+ user: '登录用户名'
+ password: '数据库密码'
配置实例
添加实例
QQ频道机器人(官方)
QQ群机器人(官方)
KOOK机器人
Token
即可。KOOK 机器人不需要 appid,但你仍然需要在配置里随便填写一个 appid,推荐使用 KOOK 应用的 Client Id
。CQ-Http QQ群机器人
APP ID
为登录在 go-cqhttp 的 QQ 号。TOKEN
填写配置的 access-token,没有配置则不需要填写。CQ-Http 配置
请准确填写你部署 go-cqhttp 时的信息。config.yml
里数据类型为 array,修改完后重启 go-cqhttp 生效。message:
+ # 上报数据类型
+ # 可选: string,array
+ post-format: array
config.yml
找到# 默认中间件锚点
+default-middlewares: &default
+ # 访问密钥, 强烈推荐在公网的服务器设置
+ access-token: '*******'
+ # 事件过滤器文件目录
+ filter: ''
+ # API限速设置
+ # 该设置为全局生效
+ # 原 cqhttp 虽然启用了 rate_limit 后缀, 但是基本没插件适配
+ # 目前该限速设置为令牌桶算法, 请参考:
+ # https://baike.baidu.com/item/%E4%BB%A4%E7%89%8C%E6%A1%B6%E7%AE%97%E6%B3%95/6597000?fr=aladdin
+ rate-limit:
+ enabled: false # 是否启用限速
+ frequency: 1 # 令牌回复频率, 单位秒
+ bucket: 1 # 令牌桶大小
Mirai-api-http QQ群机器人
APP ID
为登录在 mirai-api-http 的 QQ 号。TOKEN
为 mirai-api-http 的 AuthKey。Mirai-api-http 配置
请准确填写你部署 mah 时的信息。config/net.mamoe.mirai-api-http/setting.yml
找到adapters:
+ - http
+ - ws
+debug: false
+enableVerify: true
+verifyKey: ******* # 此处即是 AuthKey
+singleMode: false
+cacheSize: 4096
+adapterSettings:
+ http:
+ host: 0.0.0.0
+ port: 8080
+ cors: [ * ]
+ ws:
+ host: 0.0.0.0
+ port: 8060
+ reservedSyncId: -1
运行实例
配置可控实例
可控实例
,并填写一个群号。可接收到该实例的一些相关通知。连接控制台
config/server.yaml
。host: 127.0.0.1
+port: 8088
+authKey:
如果不这么做,您的服务将可能面临极大的安全风险!因此造成的一切后果与项目组无关,由您自行承担!123456
, 000000
, abcdef
等)。
如果您坚持这么做,您的服务将可能面临较大的安全风险!因此造成的一切后果与项目组无关,由您自行承担!
特别的,当您尝试使用纯数字的 authKey 时,由于 YAML 规范定义其为整数类型,您的 authKey 将可能因类型错误无法识别。如果您坚持使用纯数字的 authKey,请使用单引号 ''
或双引号 ""
将 authKey 括起。连接
默认为 http://127.0.0.1:8088公网/局域网访问
0.0.0.0
即可让服务通过本机 IP 访问host: 0.0.0.0
如果不这么做,您的服务将面临极大的安全风险!因此造成的一切后果与项目组无关,由您自行承担!安装插件
在插件商店安装插件
通过插件包自动安装
管理插件
🎉 恭喜你,到这里你已经部署完成了!快去体验兔兔吧!🎉
',13),l=[p];function c(d,_,h,u,m,g){return t(),o("div",null,l)}const q=e(n,[["render",c]]);export{b as __pageData,q as default};
diff --git a/assets/guide_deploy_console_plugin.md.6a874539.lean.js b/assets/guide_deploy_console_plugin.md.6a874539.lean.js
new file mode 100644
index 00000000..ce0d706d
--- /dev/null
+++ b/assets/guide_deploy_console_plugin.md.6a874539.lean.js
@@ -0,0 +1 @@
+import{_ as a}from"./chunks/plugin3.7807f651.js";import{_ as e,o as t,c as o,V as s}from"./chunks/framework.63f12d77.js";const r="/assets/plugin.88ce690c.png",i="/assets/plugin2.14fbd7cf.png",b=JSON.parse('{"title":"安装插件","description":"","frontmatter":{},"headers":[],"relativePath":"guide/deploy/console/plugin.md","filePath":"guide/deploy/console/plugin.md","lastUpdated":1722566694000}'),n={name:"guide/deploy/console/plugin.md"},p=s("",13),l=[p];function c(d,_,h,u,m,g){return t(),o("div",null,l)}const q=e(n,[["render",c]]);export{b as __pageData,q as default};
diff --git a/assets/guide_deploy_faq_PluginProblem.md.ccf6151b.js b/assets/guide_deploy_faq_PluginProblem.md.ccf6151b.js
new file mode 100644
index 00000000..fcaa4c77
--- /dev/null
+++ b/assets/guide_deploy_faq_PluginProblem.md.ccf6151b.js
@@ -0,0 +1 @@
+import{_ as a,o as l,c as e,V as s}from"./chunks/framework.63f12d77.js";const b=JSON.parse('{"title":"插件常见问题","description":"","frontmatter":{},"headers":[],"relativePath":"guide/deploy/faq/PluginProblem.md","filePath":"guide/deploy/faq/PluginProblem.md","lastUpdated":1722566694000}'),o={name:"guide/deploy/faq/PluginProblem.md"},t=s('插件常见问题
公招图片识别没法用
如何给兔兔添加表情
plugins/amiyabot-user*/face
下,直接将图片文件扔到文件夹内即可。代码部署后部分插件无法正常产生图片
playwright install chromium
playwright install --with-deps chromium
有时候公招图/专精材料图显示不完整
抽卡时出现了(按游戏卡池发布顺序来说)不可能出现的新干员
抽卡时出现了黑色的人物条/出现了限定/赠送/肉鸽干员
',20),i=[t];function n(r,p,c,d,u,h){return l(),e("div",null,i)}const _=a(o,[["render",n]]);export{b as __pageData,_ as default};
diff --git a/assets/guide_deploy_faq_PluginProblem.md.ccf6151b.lean.js b/assets/guide_deploy_faq_PluginProblem.md.ccf6151b.lean.js
new file mode 100644
index 00000000..dfd0971c
--- /dev/null
+++ b/assets/guide_deploy_faq_PluginProblem.md.ccf6151b.lean.js
@@ -0,0 +1 @@
+import{_ as a,o as l,c as e,V as s}from"./chunks/framework.63f12d77.js";const b=JSON.parse('{"title":"插件常见问题","description":"","frontmatter":{},"headers":[],"relativePath":"guide/deploy/faq/PluginProblem.md","filePath":"guide/deploy/faq/PluginProblem.md","lastUpdated":1722566694000}'),o={name:"guide/deploy/faq/PluginProblem.md"},t=s("",20),i=[t];function n(r,p,c,d,u,h){return l(),e("div",null,i)}const _=a(o,[["render",n]]);export{b as __pageData,_ as default};
diff --git a/assets/guide_deploy_faq_commonProblem.md.66ab12e1.js b/assets/guide_deploy_faq_commonProblem.md.66ab12e1.js
new file mode 100644
index 00000000..48ca74f9
--- /dev/null
+++ b/assets/guide_deploy_faq_commonProblem.md.66ab12e1.js
@@ -0,0 +1,21 @@
+import{_ as a,o as e,c as l,V as o}from"./chunks/framework.63f12d77.js";const b=JSON.parse('{"title":"常见问题","description":"","frontmatter":{},"headers":[],"relativePath":"guide/deploy/faq/commonProblem.md","filePath":"guide/deploy/faq/commonProblem.md","lastUpdated":1722566694000}'),t={name:"guide/deploy/faq/commonProblem.md"},i=o(`常见问题
我看不懂什么是克隆仓库/代码部署的指令会报错
使用可执行文件部署时,兔兔的窗口闪现了一下/出现了一小段时间又消失了(闪退)
git 未正确配置
win+R
输入 cmd
后回车,在跳出来的黑色框框中输入 git
后回车,如果显示'git'不是内部或外部命令,也不是可运行的程序或批处理文件。
usage: git [-v | --version] [-h | --help] [-C <path>] [-c <name>=<value>]
+ [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
+ [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]
+ [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
+ [--super-prefix=<path>] [--config-env=<name>=<envvar>]
+ <command> [<args>]
+
+These are common Git commands used in various situations:
+
+start a working area (see also: git help tutorial)
+ clone Clone a repository into a new directory
+ init Create an empty Git repository or reinitialize an existing one
+
+work on the current change (see also: git help everyday)
+ add Add file contents to the index
+ mv Move or rename a file, a directory, or a symlink
+ restore Restore working tree files
+ rm Remove files from the working tree and from the index
+
+...
资源包没能完成下载
resource/gamedata
文件夹内的内容产生了变动,可能导致无法更新。直接删除整个 resource/gamedata
文件夹重新启动程序即可启动时弹窗提示“无法定位程序输入点...”
无法进入控制台
ERR_NETWORK 服务器异常
chrome://flags/#block-insecure-private-network-requests
Block insecure private network requests
设置为 Disabled
, 然后重启 config/server.yaml
host
的值修改为0.0.0.0
http://127.0.0.1:8088
,而服务密钥留空host
修改为 0.0.0.0
,其次你需要给云服务器运营商的防火墙设置一个入站规则(关于如何做到这一点,请自行百度),通常端口是 8088,协议为tcp,并且操作系统内也需要设置入站规则(也请自行百度) config/server.yaml
中修改 authKey
的值并填入控制台的服务密钥中)。更进一步,推荐使用Nginx反代服务提供更高的安全性,请参考文档控制台中实例无法连接
mirai/config/net.mamoe.mirai-api-http/settings.yml
中verifyKey
的值CQ-Http
或是Mirai-api-http
而不是QQ-Bot
mirai/config/net.mamoe.mirai-api-http/settings.yml
中http
下port
的值mirai/config/net.mamoe.mirai-api-http/settings.yml
中ws
下port
的值 (如果你参考的是初心佬的文章,那这两项应该分别为8080
和8060
)指定bot不存在
Auth Key错误
Cannot connect to mirai-api-http websocket server
Got code 401
Mirai-api-http
或者CQ-Http
而不是QQ-Bot
无效参数
mcl无法正常启动
系统找不到指定的路径
当前qq版本太低
config/Console/AutoLogin.yml
将protocol
更改为IPAD
bots
文件夹下所有内容登录存在安全风险
苹果手机无法安装滑块助手
Open with TxCaptchaHelper
按钮了,而是直接将教程中获取到的ticket填入ticket栏中即可A JNI error has occured.
有大段看不懂/完全没有中文信息的红色报错
Address already in use
config/net.mamoe.mirai-api-http/setting.yml
中两个端口的值解决,值理论上可以是1024~65535中的任何一个数字代码部署启动时/使用特定命令时会报错
git pull
+pip install --upgrade amiyabot
可执行文件部署启动时会报错
我通常称这种行为叫旧瓶装新酒部署过程看起来一切都很正常,但兔兔就是不理人
通常 有的话还不回应就见鬼了选择
二字 而进入了快速选择 应用本体的运行将会被冻结 产生应用卡住不动的表象 这种情况下 只要敲击回车就可以推出快速选择 这一点对mirai和兔兔两者都适用更新兔兔/迁移兔兔时,如何保留原来的数据
`,70),s=[i];function n(r,c,d,p,h,u){return e(),l("div",null,s)}const g=a(t,[["render",n]]);export{b as __pageData,g as default};
diff --git a/assets/guide_deploy_faq_commonProblem.md.66ab12e1.lean.js b/assets/guide_deploy_faq_commonProblem.md.66ab12e1.lean.js
new file mode 100644
index 00000000..6ddfea94
--- /dev/null
+++ b/assets/guide_deploy_faq_commonProblem.md.66ab12e1.lean.js
@@ -0,0 +1 @@
+import{_ as a,o as e,c as l,V as o}from"./chunks/framework.63f12d77.js";const b=JSON.parse('{"title":"常见问题","description":"","frontmatter":{},"headers":[],"relativePath":"guide/deploy/faq/commonProblem.md","filePath":"guide/deploy/faq/commonProblem.md","lastUpdated":1722566694000}'),t={name:"guide/deploy/faq/commonProblem.md"},i=o("",70),s=[i];function n(r,c,d,p,h,u){return e(),l("div",null,s)}const g=a(t,[["render",n]]);export{b as __pageData,g as default};
diff --git a/assets/guide_deploy_getStarted.md.86630331.js b/assets/guide_deploy_getStarted.md.86630331.js
new file mode 100644
index 00000000..643234d2
--- /dev/null
+++ b/assets/guide_deploy_getStarted.md.86630331.js
@@ -0,0 +1,36 @@
+import{d as n}from"./chunks/download.1cbae1cc.js";import{o as l,c as o,C as s,J as p,V as a}from"./chunks/framework.63f12d77.js";import"./chunks/index.ca03c6e9.js";const e="/assets/running.f2502c35.png",t=a('开始部署
安装 Git
部署
通过可执行文件部署
',8),c=s("thead",null,[s("tr",null,[s("th",null,"操作系统"),s("th",null,"下载"),s("th",null,"备注")])],-1),r=s("td",null,"Windows",-1),i=s("td",null,"仅支持 Windows 10、Windows Server 2016 及以上系统",-1),y=s("tr",null,[s("td",null,"Linux"),s("td",null,[s("del",null,"AmiyaBot-v6.4.6-linux.zip")]),s("td",null,"不再继续提供支持,请使用代码部署")],-1),d=a('Windows
AmiyaBot-v6.x.x-win32.exe
,如下图成功运行后可以进入下一节。Linux
原内容
package/dist
目录,运行 AmiyaBot-v6.x.x-linux
。cd package/dist/
+chmod 777 ./AmiyaBot-v6.x.x-linux
+./AmiyaBot-v6.x.x-linux
通过代码部署
git clone --depth 1 https://github.com/AmiyaBot/Amiya-Bot.git
+cd Amiya-Bot
# Windows
+python -m venv venv
+call venv/Scripts/activate.bat
# Linux or MacOS
+python -m venv venv
+source venv/bin/activate
pip install -r requirements.txt
# Windows or MacOS
+playwright install chromium
+# Linux
+playwright install --with-deps chromium
# Windows or MacOS
+playwright install firefox
+# Linux
+playwright install --with-deps firefox
使用火狐内核需要修改入口程序
amiya.py
,点击查看代码import ...
+
+from amiyabot import BrowserLaunchConfig # 导入浏览器启动配置类
+
+
+# 创建新启动类
+class Launcher(BrowserLaunchConfig):
+ def __init__(self):
+ super().__init__()
+
python amiya.py
简要说明
官方版兔兔
如何更新
游戏资源更新
当游戏有更新的当日,可以通过重启程序来获得更新。程序更新
通常情况下,你只需要解压更新包的 exe 程序到你原来的目录下并启动新的程序就可以了。插件更新
选择你的运营方
频道机器人
KOOK 机器人
QQ 群机器人
使用 go-cqhttp
post-format: array
。# config.yml
+message:
+ # 上报数据类型
+ # 可选: string, array
+ post-format: array
使用 mirai-api-http
结语
AmiyaBot 简介
介绍
她的过去...
2022年6月11日,剥离出其核心部分成为框架,也就是本文档所指的 AmiyaBot。
2022年9月14日,V6 版本 的用户自部署群聊机器人发布,由用户延续原群聊机器人(Amiya2号)的初衷。
2024年6月18日,官方全域机器人正式发布。现在
特别鸣谢
设计
业务模块
,通讯模块
,数据处理模块
,存储模块
和运转中心
五个组成部分。它们独立运作,并在彼此之间相互提供数据。业务模块
插件(Plugin)
、账号实例(AmiyaBot)
和多账号实例(MultipleAccounts)
组成。它们一般会被组织成层层嵌套的树状结构。账号实例和多账号实例是同质的,它们均可以被运转中心(CenterProcessor)直接访问,运转中心无法访问插件。插件设计
业务设计
通讯与数据处理模块
适配器(Adapter)
,用于对接由机器人运营方提供的服务。适配器
运转中心
消息处理
结语
下载
pip install amiyabot
兔兔-v6
操作系统 下载 备注 Windows AmiyaBot--win32.zip 仅支持 Windows 10、Windows Server 2016 及以上系统 AmiyaBot 简介
介绍
她的过去...
2022年6月11日,剥离出其核心部分成为框架,也就是本文档所指的 AmiyaBot。
2022年9月14日,V6 版本 的用户自部署群聊机器人发布,由用户延续原群聊机器人(Amiya2号)的初衷。
2024年6月18日,官方全域机器人正式发布。现在
AmiyaBot
简洁高效
多账号 & 热插拔
适配器 & 插件支持
丰富的消息类型
赞助
特别鸣谢
充电鸣谢