# Technical Documentation: koishijs/koishi > ℹ️ **Provenance:** Hybrid Fusion: `koishijs/koishi` + `koishijs/docs` · [CodeWiki Reference](https://codewiki.google/github.com/koishijs/koishi) · Recency: Active (< 180 days) ## 1. Project Overview & Quickstart (koishijs/koishi) ./packages/koishi/readme.md ## 2. Official Technical Reference & Guides (koishijs/docs) ## File: readme.md # [koishi.chat](https://koishi.chat) 这里是 Koishi 的官方文档仓库。 ## 许可证 本文档仓库使用 CC-BY-SA-4.0 和 AGPL-3.0 许可证开源: - 文档内容以 CC-BY-SA-4.0 许可证开源 (`.vitepress` 目录以外的部分) - 对原主题库进行的扩展使用 AGPL-3.0 许可证开源 (`.vitepress` 目录中的部分) --- ## File: de-DE/guide/develop/config.md # 配置文件 ::: warning 配置文件的结构未来可能会发生变化,请留意后续更新。 ::: 每个 Koishi 应用都有一个配置文件,它管理了应用及其插件的全部配置。在绝大多数情况下,我们都可以使用控制台修改这些配置,而无需手动编辑配置文件。但作为开发指南的一部分,我们还是需要了解一下配置文件的结构,并介绍一些你可能会用到的进阶用法。 默认情况下配置文件的格式为 [YAML](https://en.wikipedia.org/wiki/YAML),它是一种易于阅读和编辑的文本格式,你可以用任何文本编辑器打开。 ## 应用目录 配置文件所在的目录叫**应用目录**。根据你的安装方式,应用目录的位置可能不同: - 模板项目:你创建的项目目录,例如 `D:/dev/koishi-app` - 启动器 (zip):解压目录下 `data/instances/default` - 启动器 (msi):`C:/Users/你的用户名/AppData/Roaming/Koishi/Desktop/data/instances/default` - 启动器 (pkg):`~/Library/Application Support/Koishi/Desktop/data/instances/default` 配置文件是应用目录下名为 `koishi.yml` 的文件。当你遇到问题时,开发者可能会要求你提供配置文件的内容。此时去上面的地方找就好了。 ## 理解配置文件 尝试打开配置文件,你会发现它的内容大致如下: ```yaml # 全局设置 host: localhost port: 5140 # 插件列表 plugins: # group 表示这是一个插件组 group:console: # 波浪线前缀表示一个不启用的插件 ~auth: console: logger: insight: market: # 以缩进的方式显示插件的配置项 registry: endpoint: https://registry.npmmirror.com # 这里是一些零散的插件 github: dialogue: ``` 具体而言,配置文件中包含的内容如下。 ### 全局设置 全局设置对应于配置文件中 `plugins:` 一行以上的部分。这里会包含一些最基础的配置项,例如网络设置、指令前缀、默认权限等。修改这里的配置项,会影响整个 Koishi 应用的行为而非某个插件。你可以在 [这个页面](../../api/core/app.md) 了解全部的全局设置。 ### 插件配置 `plugins` 是一个 YAML 对象,它的每一个键对应于插件的名称,而值则对应于插件的配置。当没有进行配置时,值可以省略 (或者写成 `{}`)。当存在配置时,值需要在插件的基础上缩进并写在接下来的几行中。例如: ```yaml koishi.yml plugins: dialogue: # 这里是 koishi-plugin-dialogue 的配置 context: enable: true ``` ### 插件名称 插件名称通常对应于插件发布时的包名。例如: - `market` 对应于官方插件 `@koishijs/plugin-market` - `dialogue` 对应于社区插件 `koishi-plugin-dialogue` 除了插件的包名外,插件名称还可以拥有一个可选的前缀 (`~`) 和后缀 (`:xxx`)。插件名称前的波浪线 (`~`) 表示该插件不会被启用。插件名称后的冒号后是插件的别名,当某个插件需要存在多组配置时这会非常有用。 ### 插件组 你可以将插件组理解为一个名为 `group` 的特殊插件。它的语法与 `plugins` 一致,都是一个包含了插件名称和插件配置的 YAML 对象。使用插件组不仅能更好地帮助你整理插件,还能批量控制其中插件的行为。插件组也支持嵌套,例如: ```yaml koishi.yml plugins: group:official: # 一层嵌套插件组下的 help 插件 help: group:console: # 两层嵌套插件组下的 market 插件 market: ``` ### 元信息 一些以 `$` 开头的属性会记录插件和插件组的元信息。例如: ```yaml koishi.yml plugins: group:console: # 在控制台中折叠该插件组 $collapsed: true status: # 仅对于 telegram 平台启用该插件 $filter: $eq: - $: platform - telegram ``` ## 修改配置文件 ::: tip 如果你不了解 YAML 的语法,请不要随意修改配置文件,否则将可能导致 Koishi 应用无法运行。你可以在 [这篇教程](https://www.runoob.com/w3cnote/yaml-intro.html) 中学习 YAML 的语法。 ::: 当你启动 Koishi 应用时,Koishi 会读取上述配置文件并加载所需的插件。反过来,如果你想调整 Koishi 及其插件的行为,你就需要修改这个配置文件。 如果你使用的是模板项目,你需要手动修改它并重新启动 Koishi 应用;如果你使用的是启动器,则你可以直接在「插件配置」中进行调整,Koishi 会自动将这些改动写入配置文件。事实上你会发现,配置文件的结构与「插件配置」页面基本是一致的。 绝大多数的功能都可以通过「插件配置」页面来完成,但目前尚有一些功能没有做好相应的交互界面,这时你仍然需要手动修改配置文件。具体的步骤与模板项目类似: 1. 关闭当前 Koishi 应用 2. 在 [应用目录](#应用目录) 下找到配置文件并进行编辑 3. 保存配置文件后再次启动 Koishi 应用 ## 使用环境变量 你可以通过插值语法在配置文件中使用环境变量。例如: ```yaml title=koishi.yml plugins: adapter-discord: token: ${{ env.DISCORD_TOKEN }} ``` 当项目启动时,会将环境变量中的值替换进去。 除了系统提供的环境变量外,Koishi 还原生支持 [dotenv](https://github.com/motdotla/dotenv)。你可以在当前目录创建一个 `.env` 文件,并在里面填写你的环境变量。这个文件已经被包含在 `.gitignore` 中,你可以在其中填写隐私信息 (例如账号密码) 而不用担心被上传到远端。例如在上面的例子中你就可以这样写: ```sh title=.env DISCORD_TOKEN = xxx ``` 环境变量的另一个作用是条件判断。例如官方提供的模板项目里: ```yaml title=koishi.yml plugins: desktop: $if: env.KOISHI_AGENT?.includes('Desktop') ``` 这样一来,只有当你使用桌面客户端启动 Koishi 时,这个插件才会被启用。 --- ## File: de-DE/guide/develop/publish.md # 发布插件 为了让别人更方便地使用你编写的插件,你需要将其作为一个 npm 包进行发布。只需满足一定的规范,你的插件就能显示在 [插件市场](../../market/) 中,其他人可以通过控制台来安装它。 :::tip 本节中介绍的命令行都需要在 [应用目录](./config.md#应用目录) 下运行。 ::: ## 准备工作 首先让我们关注插件目录中的 `package.json` 文件。这个文件非常重要,它包含了要发布插件的一切元信息。 ```diff{6} root ├── plugins │ └── example │ ├── src │ │ └── index.ts │ └── package.json # 你应该修改这里 ├── koishi.yml └── package.json # 而不是这里 ``` :::tip 请注意 `package.json` 文件不是唯一的,它在应用目录和每个插件目录都会存在。请确保你修改了正确的文件。 ::: 打开上述文件,你会看到它大概长这样: ```json title=package.json { "name": "koishi-plugin-example", "version": "1.0.0", // …… } ``` 其中最重要的属性有两个:`name` 是要发布的包名,`version` 是当前版本号。可以看到,这里的包名相比实际在插件市场中看到的插件名多了一个 `koishi-plugin-` 的前缀,这使得我们很容易区分 Koishi 插件与其他 npm 包,同时也方便了用户安装和配置插件。 :::tip 请注意:包名和版本号都是唯一的:包名不能与其他已经发布的包相同,而同一个包的同一个版本号也只能发布一次。如果出现了包名冲突或版本号冲突,则会在之后的发布流程中出现错误提示。你可以自行根据错误提示更改包名或更新插件版本。 ::: ## 补充更多信息 除了包名和版本号以外,`package.json` 还包括了插件的依赖、描述、贡献者、许可证、关键词等更多信息。你并不需要一上来就把所有信息都填写完整,因为你可以随后再进行修改。但请别忘了,这些内容也是插件的一部分,修改完成后别忘了 [更新版本](#更新插件版本) 并 [再次发布](#发布插件)。 ### 准入条件 :::tip 使用模板项目创建的插件一定是符合要求的,因此你可以跳过这一节。 ::: 要想显示在插件市场中,插件的 `package.json` 需要满足以下基本要求: - [`name`](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#name) 必须符合以下格式之一: - `koishi-plugin-*` - `@bar/koishi-plugin-*` - `@koishijs/plugin-*` (官方插件) - 其中 `*` 是由数字、小写字母和连字符 `-` 组成的字符串 - [`name`](https://docs.npmjs.com/cli/v10/configuring-npm/package-json#name) 不能与已发布的插件重复或相似 - [`version`](https://docs.npmjs.com/cli/v10/configuring-npm/package-json#version) 应当符合 [语义化版本](https://semver.org/lang/zh-CN/) (通常从 `1.0.0` 开始) - [`peerDependencies`](https://docs.npmjs.com/cli/v10/configuring-npm/package-json#peerdependencies) 必须包含 `koishi` - 不能声明 [`private`](https://docs.npmjs.com/cli/v10/configuring-npm/package-json#private) 为 `true` (否则你的插件无法发布) - 最新版本不能被 [弃用](https://docs.npmjs.com/deprecating-and-undeprecating-packages-or-package-versions) (一种常见的情况是:你已经发布了某个插件,又希望更换一个名字重新发布,此时你可以通过弃用的方式让旧的名字不显示在插件市场中) 一个符合上述标准的示例: ```json title=package.json { "name": "koishi-plugin-example", "version": "1.0.0", "peerDependencies": { "koishi": "^4.3.2" } } ``` ### 添加相关信息 除去上面的基本要求外,`package.json` 中还有一些字段能帮助显示插件的相关信息。 ```json title=package.json { "name": "koishi-plugin-example", "version": "1.0.0", "contributors": [ // 贡献者 "Alice ", "Bob " ], "license": "MIT", // 许可证 "homepage": "https://example.com", // 主页 "repository": { // 源码仓库 "type": "git", "url": "git+https://github.com/alice/koishi-plugin-example.git" }, "keywords": ["example"], // 关键词 "peerDependencies": { "koishi": "^4.3.2" } } ``` - **contributors:** 插件维护者,应该是一个数组,其中的元素通常使用 `名字 <邮箱>` 的格式 - **license:** 插件许可证,你可以在 [这里](https://choosealicense.com/licenses/) 了解各种许可证的详细信息 - **homepage:** 插件主页,可以是一个网址 (比如你的 GitHub 项目地址) - **repository:** 插件源码仓库,应该是一个对象,其中 `type` 字段指定仓库类型,`url` 字段指定仓库地址 - **keywords:** 插件关键词,应该是一个字符串数组,会用于插件市场中的搜索功能 :::tip `package.json` 中还有一些字段没有在这里提及,如果你对此感兴趣,可以前往 [npmjs.com](https://docs.npmjs.com/files/package.json/) 查看文档。 ::: ### `koishi` 字段 除此以外,我们还提供了一个额外的 `koishi` 字段,用于指定与 Koishi 相关的信息。 ```json title=package.json { "name": "koishi-plugin-dialogue", "version": "1.0.0", "peerDependencies": { "koishi": "^4.3.2" }, "koishi": { "description": { // 不同语言的插件描述 "en": "English Description", "zh": "中文描述" }, "service": { "required": ["database"], // 必需的服务 "optional": ["assets"], // 可选的服务 "implements": ["dialogue"], // 实现的服务 }, "locales": ["en", "zh"], // 支持的语言 } } ``` - **description:** 插件描述,应该是一个对象,其中的键代表语言名,值是对应语言下的描述 - **service:** 插件的服务相关信息,具体包含下列属性: - **implements:** 实现的服务,应该是一个服务名构成的数组 - **locales:** 插件支持的语言,应该是一个语言名构成的数组 - **preview:** 配置为 `true` 可以让插件显示为「开发中」状态 - **hidden:** 配置为 `true` 可以让插件市场中不显示该插件 (通常情况下你不需要这么做) :::tip 此外,还有一些字段与 [Koishi Online](../../cookbook/practice/online.md) 的部署流程相关 (如 `browser`, `exports` 等)。由于不影响主线开发,你可以稍后再进行了解。 ::: ## 发布插件 编辑完上面的清单文件并 [构建源代码](./workspace.md#构建源代码) 后,你就可以公开发布你的插件了。 :::tabs code ```npm npm run pub [...name] ``` ```yarn yarn pub [...name] ``` ::: - **name:** 要发布的插件列表,缺省时表示全部 (此处 `name` 不包含 `koishi-plugin-` 前缀,而是你的工作区目录名) 这将发布所有版本号发生变动的插件。 :::tip 从插件成功发布到进插件市场需要一定的时间 (通常在 15 分钟内),请耐心等待。 ::: :::: tip 如果你配置了国内镜像,你可能会遇到以下的错误提示: ```text No token found and can't prompt for login when running with --non-interactive. ``` 此时你需要在发布时使用官方镜像,具体操作如下: :::tabs code ```npm npm run pub [...name] -- --registry https://registry.npmjs.org ``` ```yarn yarn pub [...name] --registry https://registry.yarnpkg.com ``` ::: 对于 Yarn v2 及以上版本,你还可以分别针对发布和安装设置不同的镜像: :::tabs code ```yarn # 安装时使用国内镜像 yarn config set npmRegistryServer https://registry.npmmirror.com # 发布时使用官方镜像 yarn config set npmPublishRegistry https://registry.yarnpkg.com ``` ::: :::: ## 更新插件版本 初始创建的插件版本号为 `1.0.0`。当你修改过插件后,你需要更新版本号才能重新发布。在应用目录运行下面的命令以更新版本号: :::tabs code ```npm npm run bump [...name] -- [-1|-2|-3|-p|-v ] [-r] ``` ```yarn yarn bump [...name] [-1|-2|-3|-p|-v ] [-r] ``` ::: - **name:** 要更新的插件列表,不能为空 - **-1, --major:** 跳到下一个大版本,例如 `3.1.4` -> `4.0.0` - **-2, --minor:** 跳到下一个中版本,例如 `3.1.4` -> `3.2.0` - **-3, --patch:** 跳到下一个小版本,例如 `3.1.4` -> `3.1.5` - **-p, --prerelease:** 跳到下一个预览版本,具体行为如下 - 如果当前版本是 `alpha.x`,则跳到 `beta.0` - 如果当前版本是 `beta.x`,则跳到 `rc.0` - 如果当前版本是 `rc.x`,则移除 prerelease 部分 - 其他情况下,跳到下一个大版本的 `alpha.0` - **-v, --version:** 设置具体的版本号 - **-r, --recursive:** 递归更新依赖版本 - 缺省情况:按照当前版本的最后一位递增 当进行此操作时,其他相关插件的依赖版本也会同步更新,确保所有工作区内依赖的插件版本一致。进一步,如果你希望更新了依赖版本的插件也同时更新自身的版本,那么可以附加 `-r, --recursive` 选项。 --- ## File: de-DE/guide/develop/script.md # 启动脚本 Koishi 提供了一套命令行工具,用于读取配置文件快速启动应用。 :::tip 本节中介绍的命令行都需要在 [应用目录](./config.md#应用目录) 下运行。 ::: ## 基本用法 我们通常使用 **启动脚本** 来启动 Koishi 应用。打开应用目录下的 `package.json` 文件: ```json title=package.json { "scripts": { "dev": "cross-env NODE_ENV=development koishi start -r esbuild-register -r yml-register", "start": "koishi start" } } ``` 在应用目录运行下面的命令行以启动 Koishi 应用: :::tabs code ```npm npm run start ``` ```yarn yarn start ``` ::: 在本节的后续部分,我们会介绍上述启动脚本的更多参数。无论你做何改动,你都可以使用上面的命令行来快速启动。这也是启动脚本的意义所在。 ### 启动参数 启动脚本支持 Node.js 的 [命令行参数](https://nodejs.org/api/cli.html)。例如,上面的 `-r` 对应于 `--require`,它将允许你加载 `.ts` 和 `.yml` 后缀的文件。 除了 Node.js 的命令行参数,Koishi 还提供了一些额外的参数。我们将在下面逐一介绍。 ### 自动重启 Koishi 的命令行工具支持自动重启。当运行 Koishi 的进程崩溃时,如果 Koishi 已经启动成功,则监视进程将自动重新启动一个新的进程。 ## 开发模式 除了 `start` 以外,模板项目还准备了名为 `dev` 的开发模式启动脚本。在应用目录运行下面的命令行可以以开发模式启动应用: :::tabs code ```npm npm run dev ``` ```yarn yarn dev ``` ::: 如你所见,`dev` 相当于在 `start` 指令的基础上添加了额外的参数和环境变量。这些参数为我们启用了额外的特性,而环境变量则能影响插件的部分行为。 ### TypeScript 支持 Koishi 模板项目原生地支持 TypeScript 开发。上述 `-r esbuild-register` 参数允许我们在运行时直接使用工作区插件的 TypeScript 源代码。 你也可以自行扩展更多的后缀名支持。例如,如果你更喜欢 CoffeeScript,你可以这样修改你的开发脚本为: ```json title=package.json { "scripts": { "dev": "koishi start -r coffeescript/register" }, "devDependencies": { "coffeescript": "^2.7.0" } } ``` 这样你就可以使用 CoffeeScript 编写你的插件源代码 (当然你还得自行处理构建逻辑),甚至连配置文件都可以使用 `koishi.coffee` 书写了。 :::danger 我们并不推荐使用高级语言来编写配置文件,因为动态的配置无法支持环境变量、配置热重载和插件市场等特性。大部分情况下我们建议仅将 `-r` 用于开发目的。 ::: ### 模块热替换 如果你开发着一个巨大的 Koishi 项目,可能光是加载一遍全部插件就需要好几秒了。在这种时候,像前端框架一样支持模块热替换就成了一个很棒的主意。幸运的是,Koishi 也做到了这一点!内置插件 @koishijs/plugin-hmr 实现了插件级别的热替换。每当你修改你的本地文件时,Koishi 就会尝试重载你的插件,并在命令行中提醒你。 这里的行为也可以在配置文件中进行定制: ```yaml title=koishi.yml plugins: group:develop: $if: env.NODE_ENV === 'development' hmr: root: '.' # 要忽略的文件列表,支持 glob patterns ignore: - some-file ``` ::: tip 由于部分 Linux 系统有着 8192 个文件的监听数量限制,你可能会发现运行 `yarn dev` 后出现了如下的报错: ```text NOSPC: System limit for number of file watchers reached ``` 此时你可以使用下面的命令来增加监听数量限制: ```sh echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p ``` 另一种方案是只监听部分子路径,例如将 `root` 改为 `external/foo` (其中 `foo` 是你正在开发的插件目录,参见下一节的工作区指南),这将忽略其他目录下的变化,并依然对你的插件进行热重载。当你同时开发多个插件时,你也可以将 `root` 改成一个数组来使用。 ::: --- ## File: de-DE/guide/develop/setup.md # 环境搭建 本节将介绍推荐的开发环境搭建流程。如果某些软件已经安装完成,可以跳过对应的步骤。 ### 注册 npm 如果你打算发布插件,你还需要注册一个 npm 账号。这一步非常简单,只需前往这里的 [注册页面](https://www.npmjs.com/signup)。填写你的用户名、邮箱和密码,勾选同意协议,点击注册即可。 注册完成后,你就可以在命令行中使用 `npm login` 来登录你的账号: ```sh npm login --registry=https://registry.npmjs.org ``` ## 版本控制 我们强烈推荐使用版本控制系统 (VCS) 来管理你的代码。这一方面允许你在任何时候回退到之前的版本,另一方面也能让你与其他开发者协作。 ### 安装 Git Git 是最普遍使用的版本控制工具。前往 [官网](https://git-scm.com/downloads),点击右上角的青色按钮下载安装包。 国内的 Windows 用户也可以选择从 [镜像](https://registry.npmmirror.com/binary.html?path=git-for-windows/) 下载。如果不知道下载哪个版本,可以在上面的官网中看到 (比如图中就是 2.39.1)。 获取到安装包后,双击运行。安装过程无需手动配置,一直点击下一步即可完成安装。 安装完成后,可以在命令行中输入 `git --version` 来查看版本号,以确认安装成功: ```sh git --version # git version 2.39.1 ``` 最后你还需要设置你的姓名和邮箱。它们将会默认作为你创建的插件的作者,也会出现在你的提交记录中: ```sh git config --global user.name "Your Name" git config –-global user.email "you@example.com" ``` ### 注册 GitHub 通常来说我还会建议你注册一个 GitHub 账号。[GitHub](https://github.com) 是一个代码托管平台,我们可以在上面创建仓库来存放我们的代码。由于篇幅有限,请在互联网搜索相关的教程,自行完成注册。如果发现无法注册,也不用担心,你仍然可以在本地进行开发。 ## 安装 Koishi 打开命令行,并进入你想要创建 Koishi 项目的目录。 ::: tip 这个目录不宜过长,且路径中请避免出现中文或者空格。我们推荐的目录如下: - Windows:`C:\dev` 或者 `D:\dev` (也不要直接在盘根创建项目,最好是建一层目录) - 其他操作系统:`~/dev` ::: 输入下面的命令以创建 Koishi 项目: ::: tabs code ```npm npm init koishi@latest ``` ```yarn yarn create koishi ``` ::: 跟随提示即可完成全套初始化流程。 如果你顺利完成了上述操作,你的应用此时应该已经是启动状态,并弹出了控制台界面。接下来的几节中我们将学习更多的命令行用法,因此我们可以先关闭 Koishi。在命令行中按下 `Ctrl+C` 组合键即可停止 Koishi 的运行。 --- ## File: de-DE/guide/develop/workspace.md # 工作区开发 Koishi 的核心是插件系统,绝大部分 Koishi 功能都可以通过插件实现。本章节将介绍如何使用模板项目开发和构建自己的 Koishi 插件。 :::tip 本节中介绍的命令行都需要在 [应用目录](./config.md#应用目录) 下运行。 ::: ## 创建新插件 在应用目录运行下面的命令以创建一个新的插件工作区: :::tabs code ```npm npm run setup [name] -- [-c] [-m] [-G] ``` ```yarn yarn setup [name] [-c] [-m] [-G] ``` ::: - **name:** 插件的包名,缺省时将进行提问 - **-c, --console:** 创建一个带控制台扩展的插件 - **-m, --monorepo:** 创建 monorepo 的插件 - **-G, --no-git:** 跳过 git 初始化 我们假设你创建了一个叫 `example` 的插件。那么,你将看到下面的目录结构: ```diff{3-6} root ├── external │ └── example │ ├── src │ │ └── index.ts │ └── package.json ├── koishi.yml └── package.json ``` 打开 `index.ts` 文件,并修改其中的代码: ```ts{6-11} import { Context } from 'koishi' export const name = 'example' export function apply(ctx: Context) { // 如果收到“天王盖地虎”,就回应“宝塔镇河妖” ctx.on('message', (session) => { if (session.content === '天王盖地虎') { session.send('宝塔镇河妖') } }) } ``` 以 [开发模式](./script.md#开发模式) 重新运行你的项目,点击右上角的「添加插件」按钮,选择你刚才创建的插件名称,你会立即在网页控制台的配置界面中看到 `example` 插件。只需点击启用,你就可以实现与机器人的对话了: 天王盖地虎 宝塔镇河妖 ### 创建私域插件 如果你发现想要创建的插件名称已经被占用了,除了重新想名字或在后面加上数字之外,你还可以改为创建私域插件。私域插件使用你自己的 [npm 用户名](./setup.md#注册-npm) 作为包名前缀,因此不用担心与其他人的插件冲突。 假设你的 npm 用户名是 `alice`,那么你可以使用下面的命令创建一个私域插件工作区: :::tabs code ```npm npm run setup @alice/example ``` ```yarn yarn setup @alice/example ``` ::: 此外,你还需要额外修改 `tsconfig.json` 文件。打开这个文件,你将看到下面的内容: ```json {6} { "extends": "./tsconfig.base", "compilerOptions": { "baseUrl": ".", "paths": { // "@scope/koishi-plugin-*": ["external/*/src"], "@alice/koishi-plugin-*": ["external/*/src"], }, }, } ``` 找到高亮的一行代码,将其复制一份,并将 `@scope` 替换为你的 npm 用户名,然后将复制的这一行代码前面的注释符号去掉。 ## 构建源代码 上面的插件暂时还只能在开发模式下运行。如果想要在生产模式下使用或发布到插件市场,你需要构建你的源代码。在应用目录运行下面的命令: :::tabs code ```npm npm run build [...name] ``` ```yarn yarn build [...name] ``` ::: - **name:** 要构建的插件列表,缺省时表示全部插件 还是以上面的插件 `example` 为例: - 后端代码将输出到 `external/example/lib` 目录 - 前端代码将输出到 `external/example/dist` 目录 (如果存在) ## 添加依赖 插件创建时,`package.json` 中已经包含了一些必要的依赖。如果你需要添加其他依赖,可以使用下面的命令: :::tabs code ```npm npm install [...deps] -w koishi-plugin-[name] ``` ```yarn yarn workspace koishi-plugin-[name] add [...deps] ``` ::: - **name:** 你的插件名称 - **deps:** 要添加的依赖列表 如果要添加的是 `devDependencies` 或者 `peerDependencies`,你也需要在命令后面加上 `-D` 或 `-P` 参数。关于服务类插件的依赖声明,请参考 [后续章节](../plugin/service.md#关于-peerdependencies)。 ## 更新依赖版本 尽管 npm 和 yarn 等包管理器都提供了依赖更新功能,但它们对工作区开发的支持都不是很好。因此,我们也提供了一个简单的命令用于批量更新依赖版本。 :::tabs code ```npm npm run dep ``` ```yarn yarn dep ``` ::: 这将按照每个 `package.json` 中声明的依赖版本进行更新。举个例子,如果某个依赖的版本是 `^1.1.4`,而这个依赖之后发布了新版本 `1.2.3` 和 `2.3.3`,那么运行该指令后,依赖的版本将会被更新为 `^1.2.3`。 ## 二次开发 :::tip 阅读本节前请确保你已经完成 [版本控制](./setup.md#版本控制) 中的全部准备工作。 ::: :::tip 如果你想要贡献原始仓库,在开始执行下面的操作之前,请确保你对要开发的仓库有写入权限。如果没有,你应当先创建属于自己的 fork,然后将下面的仓库名称替换为你的 fork 仓库名称。举个例子,假如你的 GitHub 用户名是 `alice`,那么下面你使用的仓库名称应当是 `alice/koishi-plugin-forward` 而不是 `koishijs/koishi-plugin-forward`。 ::: 二次开发是指调试或修改其他仓库中的插件。这种情况下,你需要先将对应的仓库克隆到本地,然后在本地进行调试和修改。 ### 开发插件 其他人创建的工作区插件可以直接克隆到你的 `external` 目录下。例如,你可以使用下面的命令将 `koishi-plugin-forward` 插件克隆到本地: :::tabs code ```npm npm run clone koishijs/koishi-plugin-forward ``` ```yarn yarn clone koishijs/koishi-plugin-forward ``` ::: ### 开发 Koishi 工作区不仅可以用于插件的二次开发,还可以用于开发 Koishi 本身。只需使用下面的命令将 Koishi 仓库克隆到本地,并完成构建: :::tabs code ```npm npm run clone koishijs/koishi npm run build -w @root/koishi ``` ```yarn yarn clone koishijs/koishi yarn workspace @root/koishi build ``` ::: 通常来说,非插件仓库在克隆下来之后还需经过路径配置才可以正常使用。不过不同担心,模板项目支持已经内置了 Koishi 生态中的几个核心仓库 ([koishi](https://github.com/koishijs/koishi), [satori](https://github.com/satorijs/satori), [minato](https://github.com/shigma/minato)) 的路径配置。 完成上述操作后,现在你的 `yarn dev` 已经能直接使用 Koishi 的 TypeScript 源码了! --- ## File: de-DE/guide/database/builtin.md # 内置数据结构 通常来说,中间件、插件的设计可以让机器人的开发变得更加模块化,但是缺乏统一的数据流管理也带来了额外的问题。如果每个中间件分别从数据库中读取和更新自己所需的字段,那会造成大量重复的请求,导致严重的资源浪费;将所有可能请求的数据都在中间件的一开始就请求完成,也并不会解决问题,因为一条信息的解读可能只需要少数几个字段,而大部分都是不需要的;更严重的是,后一种做法将导致资源单次请求,多次更新,从而产生种种数据安全性问题。 针对这些问题,Koishi 提供了一套完善的数据流管理机制,它能够在保证数据安全的同时,最大化地减少数据库访问次数。在这一节中,我们将会介绍这套机制的使用方法。 ## 观察者对象 假设我们正在开发一个抽奖插件,每调用一次 lottery 指令,用户会获得一件物品,并存入用户表的 `inventory` 属性中。下面是这个插件的实现: ```ts{13-14,18-19} declare function getLottery(): string // ---cut--- // 定义一个 inventory 字段,用于存放物品列表 declare module 'koishi' { interface User { inventory: string[] } } ctx.model.extend('user', { inventory: 'list', }) ctx.command('lottery') // 声明所需字段 .userFields(['inventory']) .action(({ session }) => { // 这里假设 inventory 是一个字符串,表示抽到的物品 const item = getLottery() // 将抽到的物品存放到 user.items 中 session.user.inventory.push(item) return `恭喜您获得了 ${item}!` }) ``` 我们都知道,写入数据库是一个异步的操作,而上面的代码看起来完全没有异步操作。然而如果你运行这段代码,你会发现用户数据被成功地更新了。这就归功于观察者机制。 `session.user` 是一个 **观察者 (Observer)** 对象,它会检测在其上面做的一切更改并缓存下来。当中间件执行完毕后,Koishi 又会自动将变化的部分进行更新,同时将缓冲区清空。我们因此得以直接在 `session.user` 上进行赋值,而不必手动调用 `ctx.database` 上的方法。 ### 声明所需字段 `cmd.userFields()` 方法用于声明所需的用户字段。未声明的字段将不会被加载,也无法直接被修改。这样做的好处是,无论用户表有多少字段,我们都可以只加载所需的字段,从而提高性能。同理我们也有 `cmd.channelFields()` 方法,功能类似。 这两个方法不仅可以接受一个可迭代对象,还可以接受一个回调函数。第一个参数是当前的 `Argv` 对象,第二个参数是 `Set`,可以通过 add / delete 方法来添加或删除字段。因此上面的代码等价于: ```ts cmd.userFields((argv, fields) => { fields.add('inventory') }) ``` ### 阻塞式更新 观察者机制不仅可以将多次更新合并成一次以提高程序性能,更能解决数据竞争的问题。如果两条消息在临近的时间点被接收到,如果单纯地使用 get / set 进行处理,可能会发生后一次 get 在前一次 set 之前完成,导致本应获得 2 件物品,但实际只获得了 1 件的问题。而观察者会随时同步同源数据,数据安全得以保证。 当然,如果你确实需要阻塞式地等待数据写入,我们也提供了 `user.$update()` 方法。顺便一提,一旦成功执行了观察者的 `$update()` 方法,之前的缓冲区将会被清空,因此之后不会重复更新数据;对于缓冲区为空的观察者,`$update()` 方法也会直接返回,不会产生任何的数据库访问。这些都是我们优化的几个细节。 你可以在 [这里](../../api/utils/observer.md) 看到完整的观察者 API。 ## 进阶用法 ### attach 事件 Koishi 内置了四个与观察者相关的事件,分别是: - `before-attach-channel`:在频道观察者被绑定到会话上之前触发 - `attach-channel`:在频道观察者被绑定到会话上之后触发 - `before-attach-user`:在用户观察者被绑定到会话上之前触发 - `attach-user`:在用户观察者被绑定到会话上之后触发 下面是一个例子,我们在用户对象上实现了一个 `msgCount` 字段,用于存放收到的信息数量: ```ts // 定义一个 msgCount 字段,用于存放收到的信息数量 declare module 'koishi' { interface User { msgCount: number } } ctx.model.extend('user', { msgCount: 'integer', }) ctx.before('attach-user', (session, fields) => { fields.add('msgCount') }) ctx.middleware((session: Session<'msgCount'>, next) => { // 这里更新了 msgCount 数据 session.user.msgCount++ return next() }) ``` ### 手动绑定 如果要绑定的字段无法提前判断,我们也提供了动态补充观察者字段的方法: ```ts declare const fields: any[] // ---cut--- // 绑定一个用户观察者,确保 fields 中的字段都被加载 session.observeUser(fields) // 绑定一个频道观察者,确保 fields 中的字段都被加载 session.observeChannel(fields) ``` --- ## File: de-DE/guide/database/index.md # 基本用法 ::: tip `ctx.database` 并非内置服务,因此如果你的插件需要使用数据库功能,需要[声明依赖](../plugin/service.md#inject-属性)。 ::: 对于几乎所有大型机器人项目,数据库的使用都是不可或缺的。但如果每个插件都独立处理与数据库的交互,这将导致插件之间的兼容性非常差——用户要么选择同时安装多个数据库,要么只能放弃一些功能。为此,Koishi 设计了一整套对象关系映射 (ORM) 接口,它易于扩展并广泛地运用于各种插件中,足以应对绝大部分使用场景。 ## `get`:查询数据 使用 `database.get()` 方法以获取特定表中的数据。下面是一个最基本的形式: ```ts // 获取 schedule 表中 id 为 1234 的数据行,返回一个数组 await ctx.database.get('schedule', 1234) // 获取 schedule 表中 id 为 1234 或 5678 的数据行,返回一个数组 await ctx.database.get('schedule', [1234, 5678]) ``` 对于复杂的数据表,如果你只需要获取少数字段,你可以通过第三个参数手动指定要获取的字段: ```ts // 返回的数组中每个元素只会包含 command, time 属性 await ctx.database.get('schedule', [1234], ['command', 'time']) ``` 你还可以向第二个参数传入一个对象,用来查询非主键上的数据或者同时指定多列的值: ```ts // 获取名为 schedule 的表中 assignee 为 telegram:123456 的数据行 await ctx.database.get('schedule', { assignee: ['telegram:123456'], }) ``` 对于需要进行复杂的数据库搜索的,ORM 也提供了相对应的方法: ```ts // 获取名为 schedule 的表中 id 大于 2 但是小于等于 5 的数据行 await ctx.database.get('schedule', { id: { $gt: 2, $lte: 5 }, }) ``` 我们甚至也支持逻辑运算: ```ts // 上述两个搜索条件的或运算 await ctx.database.get('schedule', { $or: [ { assignee: ['telegram:123456'] }, { id: { $gt: 2, $lte: 5 } }, ], }) ``` 你可以在 [这里](../../api/database/query.md) 看到完整的查询表达式 API。 ## `create`:插入数据 使用 `database.create()` 方法以插入数据。 ```ts // 向 schedule 表中添加一行数据,data 是要添加的数据行 // 返回值是添加的行的完整数据 (包括自动填充的 id 和默认属性等) await ctx.database.create('schedule', row) ``` 如果你想要批量插入数据,可以使用下面介绍的 `database.upsert()` 方法。 ## `set`:修改数据 `database.set()` 方法需要传入三个参数:表名、查询条件和要修改的数据。 ```ts // 第二个参数也可以使用上面介绍的查询表达式 await ctx.database.set('schedule', 1234, { assignee: 'telegram:123456', time: new Date(), }) ``` 如果要修改的数据与已有数据相关,可以使用求值表达式: ```ts // 让所有日期为今天的数据行的 count 字段在原有基础上增加 1 await ctx.database.set('foo', { date: new Date() }, (row) => ({ // { $add: [a, b] } 相当于 a + b // { $: field } 相当于对当前行的 field 字段取值 count: $.add(row.count, 1), })) ``` 你可以在 [这里](../../api/database/evaluation.md) 看到完整的求值表达式 API。 ## `upsert`:修改或插入数据 `database.upsert()` 的逻辑稍微有些不同,需要你传入一个数组: ```ts // 用一个数组来对数据进行更新,你需要确保每一个元素都拥有这个数据表的主键 // 修改时只会用每一行中出现的键进行覆盖,不会影响未定义的字段 await ctx.database.upsert('foo', [ { id: 1, foo: 'hello' }, { id: 2, foo: 'world' }, // 这里同样支持求值表达式,$concat 可用于连接字符串 { id: 3, bar: { $concat: ['koi', 'shi'] } }, ]) ``` 如果初始的数据库是这样的: | id | foo | bar | | ----- | ---- | --- | | (默认值) | null | bar | | 1 | foo | baz | 那么进行上述操作后的数据库将是这样的: | id | foo | bar | 说明 | | -- | ----- | ------ | ---------------------------------- | | 1 | hello | baz | 该行已经存在,只更新了 foo 字段 | | 2 | world | bar | 插入了新行,其中 foo 字段取自传入的数据,bar 字段取自默认值 | | 3 | null | koishi | 插入了新行,其中 bar 字段取自传入的数据,foo 字段取自默认值 | 如果想以非主键来索引要修改的数据,可以使用第三个参数: ```ts // @errors: 2304 // 以非主键为基准对数据表进行更新,你需要确保每一个元素都拥有 telegram 属性 await ctx.database.upsert('user', rows, 'telegram') // 以复合键为基准对数据表进行更新,你需要确保每一个元素都拥有 platform 和 id 属性 await ctx.database.upsert('channel', rows, ['platform', 'id']) ``` ## `remove`:删除数据 使用 `database.remove()` 方法以删除特定表中的数据。 ```ts // 从 schedule 表中删除特定 id 的数据行 // 第二个参数也可以使用上面介绍的查询表达式 await ctx.database.remove('schedule', [id]) ``` ## 获取改动行数 `set`, `upsert` 和 `remove` 操作都会返回一个 `WriteResult` 对象,它包含了这次操作的结果。你可以通过 `matched` 属性来获取匹配的数据行数 (注意不是修改的函数),通过 `inserted` 属性来获取插入的数据行数 (仅限 `upsert` 操作)。 ```ts // 对某个用户的余额进行扣除 const result = await ctx.database.set( 'user', { id, money: { $gte: 100 } }, (row) => ({ money: $.sub(row.money, 100) }), ) // 如果用户不存在或余额不足,此时 result.matched 为 0 if (!result.matched) { throw new Error('用户不存在或余额不足!') } ``` ## 对比 set 和 upsert `set` 与 `upsert` 方法都可以用于修改已经存在的数据,它们的区别如下表所示: | | set | upsert | | ---- | ---------------- | ------------- | | 作用范围 | 支持复杂的查询表达式 | 只能限定特定字段的值 | | 插入行为 | 如果数据不存在则不会进行任何操作 | 如果数据不存在则会插入新行 | ## 对比 create 和 upsert `create` 与 `upsert` 方法都可以用于插入新的数据,它们的区别如下表所示: | | create | upsert | | ---- | ----------- | ------------- | | 插入数量 | 只能插入一条数据 | 可以批量插入多条数据 | | 返回值 | 返回经过填充后的数据 | 没有返回值 | | 冲突行为 | 如果数据已存在则会报错 | 如果数据已存在则会执行修改 | --- ## File: de-DE/guide/database/model.md # 数据模型 Koishi 的架构允许任何插件对数据库的结构进行扩展。你就可以在不修改 Koishi 或其他插件源码的情况下,为数据库添加新的字段或者表。这些功能都是通过 `ctx.model` 提供的。 ::: tip 请注意:数据模型的扩展一定要在使用前完成,不然后续数据库操作可能会失败。 ::: ## 扩展表和字段 可以使用 `model.extend()` 方法扩展一个新的数据表,其中的第一个参数是表名,第二个参数包含了各字段的类型声明。下面的代码向数据库中扩展了一个名为 `schedule` 的表: ```ts declare module 'koishi' { interface Tables { schedule: Schedule } } // 这里是新增表的接口类型 export interface Schedule { id: number assignee: string time: Date interval: number command: string session: Session.Payload } ctx.model.extend('schedule', { // 各字段的类型声明 id: 'unsigned', assignee: 'string', time: 'timestamp', interval: 'integer', command: 'text', session: 'json', }) ``` `model.extend()` 同样也可以向已经存在的表中注入新的字段,使用方法与上面完全一致。例如,下面的代码向内置的 `User` 表中注入了 `foo` 字段: ```ts declare module 'koishi' { interface User { foo: string } } ctx.model.extend('user', { // 向用户表中注入字符串字段 foo foo: 'string', }) ``` ## 数据类型 上面的数据类型均直接使用字符串来定义。对于更复杂的需求,你也可以选择传入一个对象: ```ts ctx.model.extend('user', { foo: { type: 'string', // 占据的字节长度 length: 65535, // 该字段的默认值 initial: 'bar', // 是否允许为空 nullable: false, }, }) ``` 当你直接使用 `string` 作为类型时,其默认字节长度为 255,默认初始值为 `''`。不同字段的默认值也有所区别,你可以在 [这里](../../api/database/model.md) 查看完整的数据类型列表。 ## 字段迁移 如果你想要修改一个已有的字段 (只修改名称,不修改逻辑),你并不能单纯地将源码中的字段名改成新名称。如果这样做,数据仍然会停留在旧的字段中,它们实质上已经丢失了,却仍然占据的数据库的空间。此时你需要将旧的字段一并声明到表中: ```ts ctx.model.extend('user', { foo: { type: 'string', legacy: ['bar', 'baz'], }, }) ``` 这样一来,Koishi 就知道 `foo`, `bar`, `baz` 这三个字段实际上对应是同一列数据,并在启动时自动将旧字段中的数据迁移到 `foo` 字段中。 ## 嵌套字段 实验性 数据模型中的字段也可以是一个对象。有两种方式可以实现这一点: 1. 使用 `json` 类型,适用于对象内部属性不固定的情况 2. 为每个属性单独声明嵌套类型,这种做法在查询时更加高效 下面是第二种方式的声明示例: ```ts declare module 'koishi' { interface User { foo: { bar: string baz: number } } } // 声明嵌套类型时,对象的多级属性被拼接为一个字符串 ctx.model.extend('user', { 'foo.bar': 'string', 'foo.baz': 'integer', }) ``` 无论是哪一种情况,在查询时 `foo` 都会被视为一个独立的字段。 我们甚至还可以把上述两种方式相结合起来,例如指定 `foo.bar` 的类型为 `json`。 ## 声明索引 实验性 `model.extend()` 还接受一个可选的三参数,在这里你可以对表的索引进行设置: ```ts // 注意这里配置的是第三个参数,也就是之前 autoInc 所在的参数 ctx.model.extend('foo', {}, { // 主键,默认为 'id' // 主键将会被用于 Query 的简写形式,如果传入的是原始类型或数组则会自行理解成主键的值 primary: 'name', // 自增主键值 autoInc: true, // 唯一键,这应该是一个列表 // 这个列表中的字段对应的值在创建和修改的时候都不允许与其他行重复 unique: ['bar', 'baz'], // 外键,这应该是一个键值对 foreign: { // 相当于约束了 foo.uid 必须是某一个 user.id uid: ['user', 'id'], }, }) ``` ## 整表迁移 实验性 ::: warning 整表迁移的性能较差,建议谨慎设计数据库结构而不是依赖迁移。 ::: 前面介绍的 [字段迁移](#字段迁移) 仅仅适用于修改字段名称的情况。如果你的插件需要重构表的数据结构,这种方法就不适用了。此时你可以使用 `model.migrate()` 方法来进行整表迁移: ```ts ctx.model.extend('qux', { id: 'unsigned', text: 'string', }) ctx.model.extend('qux2', { id: 'unsigned', flag: 'boolean', }) // 如果 qux 中存在 flag 列,则对这部分数据进行迁移 ctx.model.migrate('qux', { flag: 'boolean', }, async (database) => { const data = await database.get('qux', {}, ['id', 'flag']) await database.upsert('qux2', data) }) ``` 上面的例子展示了如何将 `qux` 表中的 `flag` 数据迁移到 `qux2` 表中。迁移完成后,`qux` 表中的 `flag` 列将会被删除,而其他列则会保留。如果你希望删除旧表,可以在回调函数的最后加上一句 `database.drop('qux')`。 --- ## File: de-DE/guide/database/observer.md # 按需加载与自动更新 上面介绍了一些 Koishi 内置的权限管理行为,而接下来将介绍的是开发者如何读取和更新数据。通常来说,中间件、插件的设计可以让机器人的开发变得更加模块化,但是这也带来了数据流向的问题。如果每个中间件分别从数据库中读取和更新自己所需的字段,那会造成大量重复的请求,导致严重的资源浪费;将所有可能请求的数据都在中间件的一开始就请求完成,并不会解决问题,因为一条信息的解读可能只需要少数几个字段,而大部分都是不需要的;更严重的是,后一种做法将导致资源单次请求,多次更新,从而产生种种数据安全性问题。那么针对这些问题,Koishi 又是如何解决的呢? ## 观察者对象 之前我们已经提到过,你可以在 `session.user` 上获得本次事件相关的用户数据,但实际上 `session.user` 能做的远远不止这些。它的本质其实是一个**观察者**对象。假如我们有下面的代码: ```ts declare function getLotteryItem(): string // ---cut--- // 定义一个 items 字段,用于存放物品列表 declare module 'koishi' { interface User { items: string[] } } ctx.model.extend('user', { items: 'list', }) ctx.command('lottery') .userFields(['items']) .action(({ session }) => { // 这里假设 item 是一个字符串,表示抽到的物品 const item = getLotteryItem() // 将抽到的物品存放到 user.items 中 session.user.items.push(item) return `恭喜您获得了 ${item}!` }) ``` 上面的代码看起来完全无法工作,因为我们都知道将数据写入数据库是一个异步的操作,但是在上面的中间件中我们没有调用任何异步操作。然而如果你运行这段代码,你会发现用户数据被成功地更新了。这就归功于观察者机制。`session.user` 的本质是一个 **观察者对象**,它检测在其上面做的一切更改并缓存下来。当任务进行完毕后,Koishi 又会自动将变化的部分进行更新,同时将缓冲区清空。 这套机制不仅可以将多次更新合并成一次以提高程序性能,更能解决数据竞争的问题。如果两条信息先后被接收到,如果单纯地使用 getUser / setUser 进行处理,可能会发生后一次 getUser 在前一次 setUser 之前完成,导致本应获得 2 件物品,但实际只获得了 1 件的问题。而观察者会随时同步同源数据,数据安全得以保证。 当然,如果你确实需要阻塞式地等待数据写入,我们也提供了 `user.$update()` 方法。顺便一提,一旦成功执行了观察者的 `$update()` 方法,之前的缓冲区将会被清空,因此之后不会重复更新数据;对于缓冲区为空的观察者,`$update()` 方法也会直接返回,不会产生任何的数据库访问。这些都是我们优化的几个细节。 你可以在 [这里](../../api/utils/observer.md) 看到完整的观察者 API。 ## 声明所需字段 如果说观察者机制帮我们解决了多次更新和数据安全的问题的话,那么这一节要介绍的就是如何控制要加载的内容。在上面的例子中我们看到了 `cmd.userFields()` 函数,它通过一个 [可迭代对象](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols) 或者回调函数来添加所需的用户字段。同理我们也有 `cmd.channelFields()` 方法,功能类似。 如果你需要对全体指令添加所需的用户字段,可以使用 `command/before-attach-user` 事件。下面是一个例子: ```ts // 注意这不是实例方法,而是类上的静态方法 ctx.before('command/attach-user', (argv, fields) => { fields.add('name') }) ctx.before('command/execute', ({ session, command }) => { console.log('%s calls command %s', session.user.name, command.name) }) ``` 如果要控制中间件能取得的用户数据,可以监听 before-user 和 before-channel 事件,通过修改传入的 `fields` 参数来添加特定的字段。下面是一个例子: ```ts // 定义一个 msgCount 字段,用于存放收到的信息数量 declare module 'koishi' { interface User { msgCount: number } } ctx.model.extend('user', { msgCount: 'integer', }) // 手动添加要获取的字段,下面会介绍 ctx.before('attach-user', (session, fields) => { fields.add('msgCount') }) ctx.middleware((session: Session<'msgCount'>, next) => { // 这里更新了 msgCount 数据 session.user.msgCount++ return next() }) ``` ## 使用会话 API 对于 Koishi 内部的两个抽象表 User 和 Channel,我们在 [会话对象](../../api/core/session.md) 中封装了几个高级方法: ```ts declare const id: string declare const fields: any[] // ---cut--- // 中间增加了一个第二参数,表示默认情况下的权限等级 // 如果找到该用户,则返回该用户本身 session.getUser(id, fields) // 在当前会话上绑定一个可观测用户实例 // 也就是所谓的 session.user session.observeUser(fields) // 中间增加了一个第二参数,表示默认情况下的 assignee // 如果找到该频道,则不修改任何数据,返回该频道本身 session.getChannel(id, fields) // 在当前会话上绑定一个可观测频道实例 // 也就是所谓的 session.channel session.observeChannel(fields) ``` --- METRICS --- - Files Extracted: 11 - Estimated Token Budget: ~7536 tokens - Recency Window: Active (< 180 days) - Canonical Reference: https://codewiki.google/github.com/koishijs/koishi