From 5e2b2f805e98b5549057630a0571227b78550f4c Mon Sep 17 00:00:00 2001
From: koishi-bot <105629226+koishi-bot@users.noreply.github.com>
Date: Sun, 3 Sep 2023 22:14:07 +0800
Subject: [PATCH] i18n: update translations (#95)
---
.vitepress/config/de-DE.json | 101 +++---
.vitepress/config/en-US.json | 99 +++---
.vitepress/config/fr-FR.json | 123 +++++---
.vitepress/config/ja-JP.json | 113 ++++---
.vitepress/config/ru-RU.json | 99 +++---
.vitepress/config/zh-TW.json | 119 ++++---
de-DE/about/history.md | 4 +-
de-DE/about/releases/v4.12.md | 39 +++
de-DE/about/releases/v4.13.md | 42 +++
de-DE/api/console/index.md | 5 -
de-DE/api/core/adapter.md | 4 +
de-DE/api/core/bot.md | 14 +-
de-DE/api/core/command.md | 8 +-
de-DE/api/core/context.md | 1 +
de-DE/api/core/events.md | 9 +-
de-DE/api/database/built-in.md | 2 +-
de-DE/api/database/database.md | 4 +
de-DE/api/database/model.md | 26 +-
de-DE/api/glossary.md | 10 +-
de-DE/api/service/bots.md | 2 +-
de-DE/api/service/events.md | 6 +-
de-DE/api/service/i18n.md | 2 +-
de-DE/api/service/permissions.md | 21 ++
de-DE/cookbook/message.md | 2 +-
de-DE/cookbook/online.md | 5 +-
de-DE/guide/adapter/adapter.md | 3 +-
de-DE/guide/adapter/binding.md | 1 -
de-DE/guide/adapter/integration.md | 41 ++-
de-DE/guide/adapter/message.md | 298 ++++++++++++++++++
de-DE/guide/adapter/writing.md | 172 ----------
de-DE/guide/basic/advanced.md | 2 +-
de-DE/guide/console/extension.md | 128 --------
de-DE/guide/console/index.md | 2 +-
de-DE/guide/database/builtin.md | 114 +++++++
de-DE/guide/database/index.md | 107 +++----
de-DE/guide/database/model.md | 161 +++++++---
de-DE/guide/database/permission.md | 129 ++++++++
.../guide/database/select.md | 2 +-
de-DE/guide/develop/publish.md | 8 +-
de-DE/guide/develop/setup.md | 44 +--
de-DE/guide/develop/workspace.md | 18 ++
de-DE/guide/i18n/index.md | 2 +-
de-DE/guide/in-depth/message.md | 47 ---
de-DE/guide/in-depth/module.md | 2 -
de-DE/guide/plugin/filter.md | 2 +-
de-DE/guide/plugin/index.md | 2 +-
de-DE/guide/plugin/lifecycle.md | 2 +-
de-DE/guide/plugin/service.md | 85 +++++
de-DE/guide/testing/index.md | 144 ---------
de-DE/manual/recipe/multiple.md | 4 +-
de-DE/manual/starter/boilerplate.md | 20 +-
de-DE/manual/starter/direct.md | 2 +-
de-DE/manual/usage/adapter.md | 56 ++++
de-DE/manual/usage/command.md | 6 +-
de-DE/manual/usage/customize.md | 116 +++++++
de-DE/manual/usage/filter.md | 33 --
de-DE/manual/usage/i18n.md | 30 --
de-DE/manual/usage/market.md | 117 +++++++
de-DE/manual/usage/permission.md | 49 ---
de-DE/manual/usage/platform.md | 2 +-
de-DE/plugins/adapter/dingtalk.md | 44 +++
de-DE/plugins/adapter/kook.md | 9 +-
de-DE/plugins/adapter/slack.md | 84 +++++
de-DE/plugins/adapter/wechat-official.md | 5 +
de-DE/plugins/adapter/wecom.md | 5 +
de-DE/plugins/adapter/whatsapp.md | 41 +++
de-DE/plugins/common/admin.md | 4 +-
de-DE/plugins/common/broadcast.md | 2 +-
de-DE/plugins/console/locales.md | 2 +-
de-DE/plugins/console/sandbox.md | 2 +-
de-DE/plugins/index.md | 5 +
en-US/about/history.md | 4 +-
en-US/about/license.md | 6 +-
en-US/about/releases/v4.12.md | 39 +++
en-US/about/releases/v4.13.md | 42 +++
en-US/api/console/index.md | 5 -
en-US/api/core/adapter.md | 4 +
en-US/api/core/bot.md | 54 ++--
en-US/api/core/command.md | 8 +-
en-US/api/core/context.md | 1 +
en-US/api/core/events.md | 9 +-
en-US/api/core/session.md | 2 +-
en-US/api/database/built-in.md | 2 +-
en-US/api/database/database.md | 4 +
en-US/api/database/model.md | 26 +-
en-US/api/glossary.md | 10 +-
en-US/api/message/api.md | 2 +-
en-US/api/service/bots.md | 4 +-
en-US/api/service/events.md | 6 +-
en-US/api/service/filter.md | 2 +-
en-US/api/service/i18n.md | 2 +-
en-US/api/service/permissions.md | 21 ++
en-US/api/service/registry.md | 2 +-
en-US/cookbook/message.md | 2 +-
en-US/cookbook/online.md | 5 +-
en-US/guide/adapter/adapter.md | 29 +-
en-US/guide/adapter/binding.md | 1 -
en-US/guide/adapter/bot.md | 42 +--
en-US/guide/adapter/integration.md | 41 ++-
en-US/guide/adapter/message.md | 298 ++++++++++++++++++
en-US/guide/adapter/writing.md | 172 ----------
en-US/guide/basic/advanced.md | 2 +-
en-US/guide/console/extension.md | 128 --------
en-US/guide/console/index.md | 2 +-
en-US/guide/database/builtin.md | 114 +++++++
en-US/guide/database/index.md | 107 +++----
en-US/guide/database/model.md | 161 +++++++---
en-US/guide/database/permission.md | 129 ++++++++
.../guide/database/select.md | 2 +-
en-US/guide/develop/publish.md | 58 ++--
en-US/guide/develop/setup.md | 44 +--
en-US/guide/develop/workspace.md | 18 ++
en-US/guide/i18n/index.md | 2 +-
en-US/guide/in-depth/message.md | 47 ---
en-US/guide/in-depth/module.md | 2 -
en-US/guide/plugin/filter.md | 2 +-
en-US/guide/plugin/index.md | 10 +-
en-US/guide/plugin/lifecycle.md | 24 +-
en-US/guide/plugin/selector.md | 163 ----------
en-US/guide/plugin/service.md | 85 +++++
en-US/guide/testing/index.md | 144 ---------
en-US/manual/recipe/multiple.md | 4 +-
en-US/manual/starter/boilerplate.md | 20 +-
en-US/manual/starter/direct.md | 2 +-
en-US/manual/usage/adapter.md | 56 ++++
en-US/manual/usage/command.md | 12 +-
en-US/manual/usage/customize.md | 116 +++++++
en-US/manual/usage/filter.md | 33 --
en-US/manual/usage/i18n.md | 30 --
en-US/manual/usage/market.md | 117 +++++++
en-US/manual/usage/permission.md | 49 ---
en-US/manual/usage/platform.md | 2 +-
en-US/plugins/adapter/dingtalk.md | 44 +++
en-US/plugins/adapter/kook.md | 9 +-
en-US/plugins/adapter/slack.md | 84 +++++
en-US/plugins/adapter/wechat-official.md | 5 +
en-US/plugins/adapter/wecom.md | 5 +
en-US/plugins/adapter/whatsapp.md | 41 +++
en-US/plugins/common/admin.md | 4 +-
en-US/plugins/common/broadcast.md | 2 +-
en-US/plugins/console/locales.md | 2 +-
en-US/plugins/console/sandbox.md | 2 +-
en-US/plugins/index.md | 5 +
fr-FR/about/history.md | 4 +-
fr-FR/about/releases/v4.12.md | 39 +++
fr-FR/about/releases/v4.13.md | 42 +++
fr-FR/api/console/index.md | 5 -
fr-FR/api/core/adapter.md | 4 +
fr-FR/api/core/bot.md | 14 +-
fr-FR/api/core/command.md | 8 +-
fr-FR/api/core/context.md | 1 +
fr-FR/api/core/events.md | 9 +-
fr-FR/api/database/built-in.md | 2 +-
fr-FR/api/database/database.md | 4 +
fr-FR/api/database/model.md | 26 +-
fr-FR/api/glossary.md | 10 +-
fr-FR/api/service/bots.md | 2 +-
fr-FR/api/service/events.md | 6 +-
fr-FR/api/service/i18n.md | 2 +-
fr-FR/api/service/permissions.md | 21 ++
fr-FR/cookbook/message.md | 2 +-
fr-FR/cookbook/online.md | 5 +-
fr-FR/guide/adapter/adapter.md | 3 +-
fr-FR/guide/adapter/binding.md | 1 -
fr-FR/guide/adapter/integration.md | 41 ++-
fr-FR/guide/adapter/message.md | 298 ++++++++++++++++++
fr-FR/guide/adapter/writing.md | 172 ----------
fr-FR/guide/basic/advanced.md | 2 +-
fr-FR/guide/console/extension.md | 128 --------
fr-FR/guide/console/index.md | 2 +-
fr-FR/guide/database/builtin.md | 114 +++++++
fr-FR/guide/database/index.md | 107 +++----
fr-FR/guide/database/model.md | 161 +++++++---
fr-FR/guide/database/permission.md | 129 ++++++++
.../guide/database/select.md | 2 +-
fr-FR/guide/develop/publish.md | 8 +-
fr-FR/guide/develop/setup.md | 44 +--
fr-FR/guide/develop/workspace.md | 18 ++
fr-FR/guide/i18n/index.md | 2 +-
fr-FR/guide/in-depth/message.md | 47 ---
fr-FR/guide/in-depth/module.md | 2 -
fr-FR/guide/plugin/filter.md | 4 +-
fr-FR/guide/plugin/index.md | 2 +-
fr-FR/guide/plugin/lifecycle.md | 2 +-
fr-FR/guide/plugin/service.md | 85 +++++
fr-FR/guide/testing/index.md | 144 ---------
fr-FR/manual/recipe/multiple.md | 4 +-
fr-FR/manual/starter/boilerplate.md | 20 +-
fr-FR/manual/starter/direct.md | 2 +-
fr-FR/manual/usage/adapter.md | 56 ++++
fr-FR/manual/usage/command.md | 6 +-
fr-FR/manual/usage/customize.md | 116 +++++++
fr-FR/manual/usage/filter.md | 33 --
fr-FR/manual/usage/i18n.md | 30 --
fr-FR/manual/usage/market.md | 117 +++++++
fr-FR/manual/usage/permission.md | 49 ---
fr-FR/manual/usage/platform.md | 2 +-
fr-FR/plugins/adapter/dingtalk.md | 44 +++
fr-FR/plugins/adapter/kook.md | 9 +-
fr-FR/plugins/adapter/slack.md | 84 +++++
fr-FR/plugins/adapter/wechat-official.md | 5 +
fr-FR/plugins/adapter/wecom.md | 5 +
fr-FR/plugins/adapter/whatsapp.md | 41 +++
fr-FR/plugins/common/admin.md | 4 +-
fr-FR/plugins/common/broadcast.md | 2 +-
fr-FR/plugins/console/locales.md | 2 +-
fr-FR/plugins/console/sandbox.md | 2 +-
fr-FR/plugins/index.md | 5 +
ja-JP/about/history.md | 4 +-
ja-JP/about/releases/v4.12.md | 39 +++
ja-JP/about/releases/v4.13.md | 42 +++
ja-JP/api/console/index.md | 5 -
ja-JP/api/core/adapter.md | 4 +
ja-JP/api/core/bot.md | 14 +-
ja-JP/api/core/command.md | 8 +-
ja-JP/api/core/context.md | 1 +
ja-JP/api/core/events.md | 9 +-
ja-JP/api/database/built-in.md | 2 +-
ja-JP/api/database/database.md | 4 +
ja-JP/api/database/model.md | 26 +-
ja-JP/api/glossary.md | 10 +-
ja-JP/api/service/bots.md | 2 +-
ja-JP/api/service/events.md | 6 +-
ja-JP/api/service/i18n.md | 2 +-
ja-JP/api/service/permissions.md | 21 ++
ja-JP/cookbook/message.md | 2 +-
ja-JP/cookbook/online.md | 5 +-
ja-JP/guide/adapter/adapter.md | 3 +-
ja-JP/guide/adapter/binding.md | 1 -
ja-JP/guide/adapter/integration.md | 41 ++-
ja-JP/guide/adapter/message.md | 298 ++++++++++++++++++
ja-JP/guide/adapter/writing.md | 172 ----------
ja-JP/guide/basic/advanced.md | 2 +-
ja-JP/guide/console/extension.md | 128 --------
ja-JP/guide/console/index.md | 2 +-
ja-JP/guide/database/builtin.md | 114 +++++++
ja-JP/guide/database/index.md | 107 +++----
ja-JP/guide/database/model.md | 161 +++++++---
ja-JP/guide/database/permission.md | 129 ++++++++
.../guide/database/select.md | 2 +-
ja-JP/guide/develop/publish.md | 8 +-
ja-JP/guide/develop/setup.md | 44 +--
ja-JP/guide/develop/workspace.md | 18 ++
ja-JP/guide/i18n/index.md | 2 +-
ja-JP/guide/in-depth/message.md | 47 ---
ja-JP/guide/in-depth/module.md | 2 -
ja-JP/guide/plugin/filter.md | 2 +-
ja-JP/guide/plugin/index.md | 2 +-
ja-JP/guide/plugin/lifecycle.md | 2 +-
ja-JP/guide/plugin/service.md | 85 +++++
ja-JP/guide/testing/index.md | 144 ---------
ja-JP/manual/recipe/multiple.md | 4 +-
ja-JP/manual/starter/boilerplate.md | 20 +-
ja-JP/manual/starter/direct.md | 4 +-
ja-JP/manual/starter/macos.md | 4 +-
ja-JP/manual/usage/adapter.md | 56 ++++
ja-JP/manual/usage/command.md | 6 +-
ja-JP/manual/usage/customize.md | 116 +++++++
ja-JP/manual/usage/filter.md | 33 --
ja-JP/manual/usage/i18n.md | 30 --
ja-JP/manual/usage/market.md | 117 +++++++
ja-JP/manual/usage/permission.md | 49 ---
ja-JP/manual/usage/platform.md | 2 +-
ja-JP/plugins/adapter/dingtalk.md | 44 +++
ja-JP/plugins/adapter/kook.md | 9 +-
ja-JP/plugins/adapter/slack.md | 84 +++++
ja-JP/plugins/adapter/wechat-official.md | 5 +
ja-JP/plugins/adapter/wecom.md | 5 +
ja-JP/plugins/adapter/whatsapp.md | 41 +++
ja-JP/plugins/common/admin.md | 4 +-
ja-JP/plugins/common/broadcast.md | 2 +-
ja-JP/plugins/console/locales.md | 2 +-
ja-JP/plugins/console/sandbox.md | 2 +-
ja-JP/plugins/index.md | 5 +
ru-RU/about/history.md | 4 +-
ru-RU/about/releases/v4.12.md | 39 +++
ru-RU/about/releases/v4.13.md | 42 +++
ru-RU/api/console/index.md | 5 -
ru-RU/api/core/adapter.md | 4 +
ru-RU/api/core/bot.md | 14 +-
ru-RU/api/core/command.md | 8 +-
ru-RU/api/core/context.md | 1 +
ru-RU/api/core/events.md | 9 +-
ru-RU/api/database/built-in.md | 2 +-
ru-RU/api/database/database.md | 4 +
ru-RU/api/database/model.md | 26 +-
ru-RU/api/glossary.md | 10 +-
ru-RU/api/service/bots.md | 2 +-
ru-RU/api/service/events.md | 6 +-
ru-RU/api/service/i18n.md | 2 +-
ru-RU/api/service/permissions.md | 21 ++
ru-RU/cookbook/message.md | 2 +-
ru-RU/cookbook/online.md | 5 +-
ru-RU/guide/adapter/adapter.md | 3 +-
ru-RU/guide/adapter/binding.md | 1 -
ru-RU/guide/adapter/encoder.md | 5 -
ru-RU/guide/adapter/integration.md | 41 ++-
ru-RU/guide/adapter/message.md | 298 ++++++++++++++++++
ru-RU/guide/adapter/writing.md | 172 ----------
ru-RU/guide/basic/advanced.md | 2 +-
ru-RU/guide/console/extension.md | 128 --------
ru-RU/guide/console/index.md | 2 +-
ru-RU/guide/database/builtin.md | 114 +++++++
ru-RU/guide/database/index.md | 107 +++----
ru-RU/guide/database/model.md | 161 +++++++---
ru-RU/guide/database/permission.md | 129 ++++++++
ru-RU/guide/database/select.md | 5 +
ru-RU/guide/develop/publish.md | 8 +-
ru-RU/guide/develop/setup.md | 44 +--
ru-RU/guide/develop/workspace.md | 18 ++
ru-RU/guide/i18n/index.md | 2 +-
ru-RU/guide/in-depth/message.md | 47 ---
ru-RU/guide/in-depth/module.md | 2 -
ru-RU/guide/plugin/filter.md | 2 +-
ru-RU/guide/plugin/index.md | 2 +-
ru-RU/guide/plugin/lifecycle.md | 2 +-
ru-RU/guide/plugin/service.md | 85 +++++
ru-RU/guide/testing/index.md | 144 ---------
ru-RU/manual/recipe/multiple.md | 4 +-
ru-RU/manual/starter/boilerplate.md | 20 +-
ru-RU/manual/starter/direct.md | 2 +-
ru-RU/manual/usage/adapter.md | 56 ++++
ru-RU/manual/usage/command.md | 8 +-
ru-RU/manual/usage/customize.md | 116 +++++++
ru-RU/manual/usage/filter.md | 33 --
ru-RU/manual/usage/i18n.md | 30 --
ru-RU/manual/usage/market.md | 117 +++++++
ru-RU/manual/usage/permission.md | 49 ---
ru-RU/manual/usage/platform.md | 2 +-
ru-RU/plugins/adapter/dingtalk.md | 44 +++
ru-RU/plugins/adapter/kook.md | 9 +-
ru-RU/plugins/adapter/slack.md | 84 +++++
ru-RU/plugins/adapter/wechat-official.md | 5 +
ru-RU/plugins/adapter/wecom.md | 5 +
ru-RU/plugins/adapter/whatsapp.md | 41 +++
ru-RU/plugins/common/admin.md | 4 +-
ru-RU/plugins/common/broadcast.md | 2 +-
ru-RU/plugins/console/locales.md | 2 +-
ru-RU/plugins/console/sandbox.md | 2 +-
ru-RU/plugins/index.md | 5 +
zh-TW/about/history.md | 4 +-
zh-TW/about/releases/v4.12.md | 39 +++
zh-TW/about/releases/v4.13.md | 42 +++
zh-TW/api/console/index.md | 5 -
zh-TW/api/core/adapter.md | 4 +
zh-TW/api/core/bot.md | 14 +-
zh-TW/api/core/command.md | 8 +-
zh-TW/api/core/context.md | 1 +
zh-TW/api/core/events.md | 9 +-
zh-TW/api/database/built-in.md | 2 +-
zh-TW/api/database/database.md | 4 +
zh-TW/api/database/model.md | 26 +-
zh-TW/api/glossary.md | 10 +-
zh-TW/api/service/bots.md | 2 +-
zh-TW/api/service/events.md | 6 +-
zh-TW/api/service/i18n.md | 2 +-
zh-TW/api/service/permissions.md | 21 ++
zh-TW/cookbook/message.md | 2 +-
zh-TW/cookbook/online.md | 5 +-
zh-TW/guide/adapter/adapter.md | 3 +-
zh-TW/guide/adapter/binding.md | 1 -
zh-TW/guide/adapter/encoder.md | 5 -
zh-TW/guide/adapter/integration.md | 41 ++-
zh-TW/guide/adapter/message.md | 298 ++++++++++++++++++
zh-TW/guide/adapter/writing.md | 172 ----------
zh-TW/guide/basic/advanced.md | 2 +-
zh-TW/guide/console/index.md | 2 +-
zh-TW/guide/database/builtin.md | 114 +++++++
zh-TW/guide/database/index.md | 107 +++----
zh-TW/guide/database/model.md | 161 +++++++---
zh-TW/guide/database/permission.md | 129 ++++++++
zh-TW/guide/database/select.md | 5 +
zh-TW/guide/develop/publish.md | 8 +-
zh-TW/guide/develop/setup.md | 44 +--
zh-TW/guide/develop/workspace.md | 18 ++
zh-TW/guide/i18n/index.md | 2 +-
zh-TW/guide/in-depth/message.md | 47 ---
zh-TW/guide/in-depth/module.md | 2 -
zh-TW/guide/plugin/filter.md | 4 +-
zh-TW/guide/plugin/index.md | 2 +-
zh-TW/guide/plugin/lifecycle.md | 2 +-
zh-TW/guide/plugin/service.md | 85 +++++
zh-TW/guide/testing/index.md | 144 ---------
zh-TW/manual/recipe/multiple.md | 4 +-
zh-TW/manual/starter/boilerplate.md | 20 +-
zh-TW/manual/starter/direct.md | 2 +-
zh-TW/manual/usage/adapter.md | 56 ++++
zh-TW/manual/usage/command.md | 6 +-
zh-TW/manual/usage/customize.md | 116 +++++++
zh-TW/manual/usage/filter.md | 33 --
zh-TW/manual/usage/i18n.md | 30 --
zh-TW/manual/usage/market.md | 117 +++++++
zh-TW/manual/usage/permission.md | 49 ---
zh-TW/manual/usage/platform.md | 2 +-
zh-TW/plugins/adapter/dingtalk.md | 44 +++
zh-TW/plugins/adapter/kook.md | 9 +-
zh-TW/plugins/adapter/slack.md | 84 +++++
zh-TW/plugins/adapter/wechat-official.md | 5 +
zh-TW/plugins/adapter/wecom.md | 5 +
zh-TW/plugins/adapter/whatsapp.md | 41 +++
zh-TW/plugins/common/admin.md | 4 +-
zh-TW/plugins/common/broadcast.md | 2 +-
zh-TW/plugins/console/locales.md | 2 +-
zh-TW/plugins/console/sandbox.md | 2 +-
zh-TW/plugins/index.md | 5 +
405 files changed, 9737 insertions(+), 5350 deletions(-)
create mode 100644 de-DE/about/releases/v4.13.md
delete mode 100644 de-DE/api/console/index.md
create mode 100644 de-DE/api/service/permissions.md
delete mode 100644 de-DE/guide/adapter/binding.md
create mode 100644 de-DE/guide/adapter/message.md
delete mode 100644 de-DE/guide/adapter/writing.md
delete mode 100644 de-DE/guide/console/extension.md
create mode 100644 de-DE/guide/database/builtin.md
create mode 100644 de-DE/guide/database/permission.md
rename fr-FR/guide/adapter/encoder.md => de-DE/guide/database/select.md (80%)
delete mode 100644 de-DE/guide/in-depth/message.md
delete mode 100644 de-DE/guide/in-depth/module.md
delete mode 100644 de-DE/guide/testing/index.md
create mode 100644 de-DE/manual/usage/adapter.md
create mode 100644 de-DE/manual/usage/customize.md
delete mode 100644 de-DE/manual/usage/filter.md
delete mode 100644 de-DE/manual/usage/i18n.md
create mode 100644 de-DE/manual/usage/market.md
delete mode 100644 de-DE/manual/usage/permission.md
create mode 100644 de-DE/plugins/adapter/dingtalk.md
create mode 100644 de-DE/plugins/adapter/slack.md
create mode 100644 de-DE/plugins/adapter/wechat-official.md
create mode 100644 de-DE/plugins/adapter/wecom.md
create mode 100644 de-DE/plugins/adapter/whatsapp.md
create mode 100644 en-US/about/releases/v4.13.md
delete mode 100644 en-US/api/console/index.md
create mode 100644 en-US/api/service/permissions.md
delete mode 100644 en-US/guide/adapter/binding.md
create mode 100644 en-US/guide/adapter/message.md
delete mode 100644 en-US/guide/adapter/writing.md
delete mode 100644 en-US/guide/console/extension.md
create mode 100644 en-US/guide/database/builtin.md
create mode 100644 en-US/guide/database/permission.md
rename ja-JP/guide/adapter/encoder.md => en-US/guide/database/select.md (80%)
delete mode 100644 en-US/guide/in-depth/message.md
delete mode 100644 en-US/guide/in-depth/module.md
delete mode 100644 en-US/guide/plugin/selector.md
delete mode 100644 en-US/guide/testing/index.md
create mode 100644 en-US/manual/usage/adapter.md
create mode 100644 en-US/manual/usage/customize.md
delete mode 100644 en-US/manual/usage/filter.md
delete mode 100644 en-US/manual/usage/i18n.md
create mode 100644 en-US/manual/usage/market.md
delete mode 100644 en-US/manual/usage/permission.md
create mode 100644 en-US/plugins/adapter/dingtalk.md
create mode 100644 en-US/plugins/adapter/slack.md
create mode 100644 en-US/plugins/adapter/wechat-official.md
create mode 100644 en-US/plugins/adapter/wecom.md
create mode 100644 en-US/plugins/adapter/whatsapp.md
create mode 100644 fr-FR/about/releases/v4.13.md
delete mode 100644 fr-FR/api/console/index.md
create mode 100644 fr-FR/api/service/permissions.md
delete mode 100644 fr-FR/guide/adapter/binding.md
create mode 100644 fr-FR/guide/adapter/message.md
delete mode 100644 fr-FR/guide/adapter/writing.md
delete mode 100644 fr-FR/guide/console/extension.md
create mode 100644 fr-FR/guide/database/builtin.md
create mode 100644 fr-FR/guide/database/permission.md
rename de-DE/guide/adapter/encoder.md => fr-FR/guide/database/select.md (80%)
delete mode 100644 fr-FR/guide/in-depth/message.md
delete mode 100644 fr-FR/guide/in-depth/module.md
delete mode 100644 fr-FR/guide/testing/index.md
create mode 100644 fr-FR/manual/usage/adapter.md
create mode 100644 fr-FR/manual/usage/customize.md
delete mode 100644 fr-FR/manual/usage/filter.md
delete mode 100644 fr-FR/manual/usage/i18n.md
create mode 100644 fr-FR/manual/usage/market.md
delete mode 100644 fr-FR/manual/usage/permission.md
create mode 100644 fr-FR/plugins/adapter/dingtalk.md
create mode 100644 fr-FR/plugins/adapter/slack.md
create mode 100644 fr-FR/plugins/adapter/wechat-official.md
create mode 100644 fr-FR/plugins/adapter/wecom.md
create mode 100644 fr-FR/plugins/adapter/whatsapp.md
create mode 100644 ja-JP/about/releases/v4.13.md
delete mode 100644 ja-JP/api/console/index.md
create mode 100644 ja-JP/api/service/permissions.md
delete mode 100644 ja-JP/guide/adapter/binding.md
create mode 100644 ja-JP/guide/adapter/message.md
delete mode 100644 ja-JP/guide/adapter/writing.md
delete mode 100644 ja-JP/guide/console/extension.md
create mode 100644 ja-JP/guide/database/builtin.md
create mode 100644 ja-JP/guide/database/permission.md
rename en-US/guide/adapter/encoder.md => ja-JP/guide/database/select.md (80%)
delete mode 100644 ja-JP/guide/in-depth/message.md
delete mode 100644 ja-JP/guide/in-depth/module.md
delete mode 100644 ja-JP/guide/testing/index.md
create mode 100644 ja-JP/manual/usage/adapter.md
create mode 100644 ja-JP/manual/usage/customize.md
delete mode 100644 ja-JP/manual/usage/filter.md
delete mode 100644 ja-JP/manual/usage/i18n.md
create mode 100644 ja-JP/manual/usage/market.md
delete mode 100644 ja-JP/manual/usage/permission.md
create mode 100644 ja-JP/plugins/adapter/dingtalk.md
create mode 100644 ja-JP/plugins/adapter/slack.md
create mode 100644 ja-JP/plugins/adapter/wechat-official.md
create mode 100644 ja-JP/plugins/adapter/wecom.md
create mode 100644 ja-JP/plugins/adapter/whatsapp.md
create mode 100644 ru-RU/about/releases/v4.13.md
delete mode 100644 ru-RU/api/console/index.md
create mode 100644 ru-RU/api/service/permissions.md
delete mode 100644 ru-RU/guide/adapter/binding.md
delete mode 100644 ru-RU/guide/adapter/encoder.md
create mode 100644 ru-RU/guide/adapter/message.md
delete mode 100644 ru-RU/guide/adapter/writing.md
delete mode 100644 ru-RU/guide/console/extension.md
create mode 100644 ru-RU/guide/database/builtin.md
create mode 100644 ru-RU/guide/database/permission.md
create mode 100644 ru-RU/guide/database/select.md
delete mode 100644 ru-RU/guide/in-depth/message.md
delete mode 100644 ru-RU/guide/in-depth/module.md
delete mode 100644 ru-RU/guide/testing/index.md
create mode 100644 ru-RU/manual/usage/adapter.md
create mode 100644 ru-RU/manual/usage/customize.md
delete mode 100644 ru-RU/manual/usage/filter.md
delete mode 100644 ru-RU/manual/usage/i18n.md
create mode 100644 ru-RU/manual/usage/market.md
delete mode 100644 ru-RU/manual/usage/permission.md
create mode 100644 ru-RU/plugins/adapter/dingtalk.md
create mode 100644 ru-RU/plugins/adapter/slack.md
create mode 100644 ru-RU/plugins/adapter/wechat-official.md
create mode 100644 ru-RU/plugins/adapter/wecom.md
create mode 100644 ru-RU/plugins/adapter/whatsapp.md
create mode 100644 zh-TW/about/releases/v4.13.md
delete mode 100644 zh-TW/api/console/index.md
create mode 100644 zh-TW/api/service/permissions.md
delete mode 100644 zh-TW/guide/adapter/binding.md
delete mode 100644 zh-TW/guide/adapter/encoder.md
create mode 100644 zh-TW/guide/adapter/message.md
delete mode 100644 zh-TW/guide/adapter/writing.md
create mode 100644 zh-TW/guide/database/builtin.md
create mode 100644 zh-TW/guide/database/permission.md
create mode 100644 zh-TW/guide/database/select.md
delete mode 100644 zh-TW/guide/in-depth/message.md
delete mode 100644 zh-TW/guide/in-depth/module.md
delete mode 100644 zh-TW/guide/testing/index.md
create mode 100644 zh-TW/manual/usage/adapter.md
create mode 100644 zh-TW/manual/usage/customize.md
delete mode 100644 zh-TW/manual/usage/filter.md
delete mode 100644 zh-TW/manual/usage/i18n.md
create mode 100644 zh-TW/manual/usage/market.md
delete mode 100644 zh-TW/manual/usage/permission.md
create mode 100644 zh-TW/plugins/adapter/dingtalk.md
create mode 100644 zh-TW/plugins/adapter/slack.md
create mode 100644 zh-TW/plugins/adapter/wechat-official.md
create mode 100644 zh-TW/plugins/adapter/wecom.md
create mode 100644 zh-TW/plugins/adapter/whatsapp.md
diff --git a/.vitepress/config/de-DE.json b/.vitepress/config/de-DE.json
index 7988c718628a..f6c644b34949 100644
--- a/.vitepress/config/de-DE.json
+++ b/.vitepress/config/de-DE.json
@@ -130,48 +130,27 @@
]
},
{
- "text": "Konsole",
+ "text": "使用",
"items": [
{
- "text": "Über die Konsole",
- "link": "/manual/console/index.md"
- },
- {
- "text": "Installation und Konfiguration der Plugins",
- "link": "/manual/console/market.md"
+ "text": "安装和配置插件",
+ "link": "/manual/usage/market.md"
},
{
- "text": "Chatten in der Sandbox",
- "link": "/manual/console/sandbox.md"
+ "text": "第一次对话",
+ "link": "/manual/usage/adapter.md"
},
- {
- "text": "接入聊天平台",
- "link": "/manual/console/adapter.md"
- }
- ]
- },
- {
- "text": "使用",
- "items": [
{
"text": "指令系统",
"link": "/manual/usage/command.md"
},
{
- "text": "账号系统",
+ "text": "账号登录与绑定",
"link": "/manual/usage/platform.md"
},
{
- "text": "权限管理",
- "link": "/manual/usage/permission.md"
- },
- {
- "text": "过滤器",
- "link": "/manual/usage/filter.md"
- },
- {
- "text": "国际化",
- "link": "/manual/usage/i18n.md"
+ "text": "深入定制机器人",
+ "link": "/manual/usage/customize.md"
}
]
},
@@ -303,20 +282,24 @@
"text": "数据库",
"items": [
{
- "text": "使用数据库",
+ "text": "基本用法",
"link": "/guide/database/"
},
{
- "text": "扩展数据模型",
+ "text": "数据模型",
"link": "/guide/database/model.md"
},
{
- "text": "按需加载和自动更新",
- "link": "/guide/database/observer.md"
+ "text": "进阶查询技巧",
+ "link": "/guide/database/select.md"
},
{
- "text": "编写数据库插件",
- "link": "/guide/database/writing.md"
+ "text": "内置数据结构",
+ "link": "/guide/database/builtin.md"
+ },
+ {
+ "text": "权限管理",
+ "link": "/guide/database/permission.md"
}
]
},
@@ -337,7 +320,11 @@
},
{
"text": "消息编码",
- "link": "/guide/adapter/encoder.md"
+ "link": "/guide/adapter/message.md"
+ },
+ {
+ "text": "平台集成",
+ "link": "/guide/adapter/integration.md"
}
]
},
@@ -460,6 +447,10 @@
"text": "国际化 (I18n)",
"link": "/api/service/i18n.md"
},
+ {
+ "text": "权限管理 (Permissions)",
+ "link": "/api/service/permissions.md"
+ },
{
"text": "插件系统 (Registry)",
"link": "/api/service/registry.md"
@@ -603,6 +594,10 @@
{
"text": "适配器支持",
"items": [
+ {
+ "text": "钉钉",
+ "link": "/plugins/adapter/dingtalk.md"
+ },
{
"text": "Discord",
"link": "/plugins/adapter/discord.md"
@@ -612,7 +607,7 @@
"link": "/plugins/adapter/kook.md"
},
{
- "text": "Lark",
+ "text": "飞书",
"link": "/plugins/adapter/lark.md"
},
{
@@ -620,20 +615,40 @@
"link": "/plugins/adapter/line.md"
},
{
- "text": "Mail",
+ "text": "邮件",
"link": "/plugins/adapter/mail.md"
},
+ {
+ "text": "Matrix",
+ "link": "/plugins/adapter/matrix.md"
+ },
{
"text": "OneBot",
"link": "/plugins/adapter/onebot.md"
},
{
- "text": "QQGuild",
+ "text": "QQ 频道",
"link": "/plugins/adapter/qqguild.md"
},
+ {
+ "text": "Slack",
+ "link": "/plugins/adapter/slack.md"
+ },
{
"text": "Telegram",
"link": "/plugins/adapter/telegram.md"
+ },
+ {
+ "text": "微信公众号",
+ "link": "/plugins/adapter/wechat-official.md"
+ },
+ {
+ "text": "企业微信",
+ "link": "/plugins/adapter/wecom.md"
+ },
+ {
+ "text": "WhatsApp",
+ "link": "/plugins/adapter/whatsapp.md"
}
]
},
@@ -979,6 +994,14 @@
{
"text": "v4.11 版本介绍",
"link": "/about/releases/v4.11.md"
+ },
+ {
+ "text": "v4.12 版本介绍",
+ "link": "/about/releases/v4.12.md"
+ },
+ {
+ "text": "v4.13 版本介绍",
+ "link": "/about/releases/v4.13.md"
}
]
},
diff --git a/.vitepress/config/en-US.json b/.vitepress/config/en-US.json
index 8338c1c7e66f..57ae12ac5850 100644
--- a/.vitepress/config/en-US.json
+++ b/.vitepress/config/en-US.json
@@ -130,53 +130,32 @@
]
},
{
- "text": "Koishi Console",
+ "text": "使用",
"items": [
- {
- "text": "About Koishi Console",
- "link": "/manual/console/index.md"
- },
{
"text": "Install and Configure Plugins",
- "link": "/manual/console/market.md"
+ "link": "/manual/usage/market.md"
},
{
- "text": "Chat in Sandbox",
- "link": "/manual/console/sandbox.md"
+ "text": "第一次对话",
+ "link": "/manual/usage/adapter.md"
},
- {
- "text": "Collaborate with Chat Platforms",
- "link": "/manual/console/adapter.md"
- }
- ]
- },
- {
- "text": "Usage",
- "items": [
{
"text": "Command System",
"link": "/manual/usage/command.md"
},
{
- "text": "账号系统",
+ "text": "账号登录与绑定",
"link": "/manual/usage/platform.md"
},
{
- "text": "Permission Management",
- "link": "/manual/usage/permission.md"
- },
- {
- "text": "Filters",
- "link": "/manual/usage/filter.md"
- },
- {
- "text": "Internationalization",
- "link": "/manual/usage/i18n.md"
+ "text": "深入定制机器人",
+ "link": "/manual/usage/customize.md"
}
]
},
{
- "text": "Recipe",
+ "text": "配方",
"items": [
{
"text": "Advanced Command Tricks",
@@ -201,7 +180,7 @@
]
},
{
- "text": "Launcher",
+ "text": "启动器",
"items": [
{
"text": "System Requirements",
@@ -303,20 +282,24 @@
"text": "Database",
"items": [
{
- "text": "Work with Databases",
+ "text": "基本用法",
"link": "/guide/database/"
},
{
- "text": "Extend Data Model",
+ "text": "数据模型",
"link": "/guide/database/model.md"
},
{
- "text": "Lazy Initialization & Auto-update",
- "link": "/guide/database/observer.md"
+ "text": "进阶查询技巧",
+ "link": "/guide/database/select.md"
},
{
- "text": "Database Plugin",
- "link": "/guide/database/writing.md"
+ "text": "Built-in Data Structure",
+ "link": "/guide/database/builtin.md"
+ },
+ {
+ "text": "Permission Management",
+ "link": "/guide/database/permission.md"
}
]
},
@@ -337,7 +320,11 @@
},
{
"text": "Message Encoding",
- "link": "/guide/adapter/encoder.md"
+ "link": "/guide/adapter/message.md"
+ },
+ {
+ "text": "平台集成",
+ "link": "/guide/adapter/integration.md"
}
]
},
@@ -460,6 +447,10 @@
"text": "I18n",
"link": "/api/service/i18n.md"
},
+ {
+ "text": "权限管理 (Permissions)",
+ "link": "/api/service/permissions.md"
+ },
{
"text": "Registry",
"link": "/api/service/registry.md"
@@ -603,6 +594,10 @@
{
"text": "Adapter",
"items": [
+ {
+ "text": "DingTalk",
+ "link": "/plugins/adapter/dingtalk.md"
+ },
{
"text": "Discord",
"link": "/plugins/adapter/discord.md"
@@ -623,17 +618,37 @@
"text": "Mail",
"link": "/plugins/adapter/mail.md"
},
+ {
+ "text": "Matrix",
+ "link": "/plugins/adapter/matrix.md"
+ },
{
"text": "OneBot",
"link": "/plugins/adapter/onebot.md"
},
{
- "text": "QQGuild",
+ "text": "QQ Guild",
"link": "/plugins/adapter/qqguild.md"
},
+ {
+ "text": "Slack",
+ "link": "/plugins/adapter/slack.md"
+ },
{
"text": "Telegram",
"link": "/plugins/adapter/telegram.md"
+ },
+ {
+ "text": "微信公众号",
+ "link": "/plugins/adapter/wechat-official.md"
+ },
+ {
+ "text": "企业微信",
+ "link": "/plugins/adapter/wecom.md"
+ },
+ {
+ "text": "WhatsApp",
+ "link": "/plugins/adapter/whatsapp.md"
}
]
},
@@ -979,6 +994,14 @@
{
"text": "Release Notes: v4.11",
"link": "/about/releases/v4.11.md"
+ },
+ {
+ "text": "Release Notes: v4.12",
+ "link": "/about/releases/v4.12.md"
+ },
+ {
+ "text": "v4.13 版本介绍",
+ "link": "/about/releases/v4.13.md"
}
]
},
diff --git a/.vitepress/config/fr-FR.json b/.vitepress/config/fr-FR.json
index f40ffc41527d..5326b10d8143 100644
--- a/.vitepress/config/fr-FR.json
+++ b/.vitepress/config/fr-FR.json
@@ -130,85 +130,64 @@
]
},
{
- "text": "Console",
+ "text": "使用",
"items": [
{
- "text": "À propos de la console Koishi",
- "link": "/manual/console/index.md"
- },
- {
- "text": "Installation et configuration des plugins",
- "link": "/manual/console/market.md"
+ "text": "安装和配置插件",
+ "link": "/manual/usage/market.md"
},
{
- "text": "Chat dans le bac à sable",
- "link": "/manual/console/sandbox.md"
+ "text": "第一次对话",
+ "link": "/manual/usage/adapter.md"
},
{
- "text": "Connexion avec les plateformes conversationnelles",
- "link": "/manual/console/adapter.md"
- }
- ]
- },
- {
- "text": "Utilisation",
- "items": [
- {
- "text": "Système commande",
+ "text": "指令系统",
"link": "/manual/usage/command.md"
},
{
- "text": "Système des comptes",
+ "text": "账号登录与绑定",
"link": "/manual/usage/platform.md"
},
{
- "text": "Gestion des permissions",
- "link": "/manual/usage/permission.md"
- },
- {
- "text": "Filtres",
- "link": "/manual/usage/filter.md"
- },
- {
- "text": "Internationalisation",
- "link": "/manual/usage/i18n.md"
+ "text": "深入定制机器人",
+ "link": "/manual/usage/customize.md"
}
]
},
{
- "text": "Recettes",
+ "text": "配方",
"items": [
{
- "text": "Solutions perfectionnées des commandes",
+ "text": "指令进阶技巧",
"link": "/manual/recipe/execution.md"
},
{
- "text": "Utilisation de la base des données",
+ "text": "访问数据库",
"link": "/manual/recipe/dataview.md"
},
{
- "text": "Maintenance des multiples configurations",
+ "text": "维护多份配置",
"link": "/manual/recipe/multiple.md"
},
{
- "text": "Rechercher sur la place de marché",
+ "text": "搜索插件市场",
"link": "/manual/recipe/search.md"
},
{
- "text": "Déploiement",
+ "text": "公网部署",
"link": "/manual/recipe/server.md"
}
]
},
{
- "text": "Lanceur",
+ "text": "启动器",
"items": [
{
- "text": "Configuration requise",
+ "text": "系统要求",
"link": "/manual/launcher/system.md"
},
{
- "text": "Outil de ligne de commande",
+ "text": "L'outil de ligne de commande",
"link": "/manual/launcher/cli.md"
}
]
@@ -303,20 +282,24 @@
"text": "Base de données",
"items": [
{
- "text": "Utilisation",
+ "text": "基本用法",
"link": "/guide/database/"
},
{
- "text": "Données d'expansion",
+ "text": "数据模型",
"link": "/guide/database/model.md"
},
{
- "text": "Initialisation tardive et mise à jour automatiquement",
- "link": "/guide/database/observer.md"
+ "text": "进阶查询技巧",
+ "link": "/guide/database/select.md"
},
{
- "text": "Écrit du plugin de base des données",
- "link": "/guide/database/writing.md"
+ "text": "内置数据结构",
+ "link": "/guide/database/builtin.md"
+ },
+ {
+ "text": "权限管理",
+ "link": "/guide/database/permission.md"
}
]
},
@@ -337,7 +320,11 @@
},
{
"text": "Encodé d'éléments",
- "link": "/guide/adapter/encoder.md"
+ "link": "/guide/adapter/message.md"
+ },
+ {
+ "text": "平台集成",
+ "link": "/guide/adapter/integration.md"
}
]
},
@@ -461,11 +448,15 @@
"link": "/api/service/i18n.md"
},
{
- "text": "Registre",
+ "text": "权限管理 (Permissions)",
+ "link": "/api/service/permissions.md"
+ },
+ {
+ "text": "插件系统 (Registry)",
"link": "/api/service/registry.md"
},
{
- "text": "Service Network",
+ "text": "网络服务 (Router)",
"link": "/api/service/router.md"
}
]
@@ -603,6 +594,10 @@
{
"text": "Adaptateurs",
"items": [
+ {
+ "text": "钉钉",
+ "link": "/plugins/adapter/dingtalk.md"
+ },
{
"text": "Discord",
"link": "/plugins/adapter/discord.md"
@@ -620,20 +615,40 @@
"link": "/plugins/adapter/line.md"
},
{
- "text": "E-mail",
+ "text": "邮件",
"link": "/plugins/adapter/mail.md"
},
+ {
+ "text": "Matrix",
+ "link": "/plugins/adapter/matrix.md"
+ },
{
"text": "OneBot",
"link": "/plugins/adapter/onebot.md"
},
{
- "text": "Guilde QQ",
+ "text": "QQ 频道",
"link": "/plugins/adapter/qqguild.md"
},
+ {
+ "text": "Slack",
+ "link": "/plugins/adapter/slack.md"
+ },
{
"text": "Telegram",
"link": "/plugins/adapter/telegram.md"
+ },
+ {
+ "text": "微信公众号",
+ "link": "/plugins/adapter/wechat-official.md"
+ },
+ {
+ "text": "企业微信",
+ "link": "/plugins/adapter/wecom.md"
+ },
+ {
+ "text": "WhatsApp",
+ "link": "/plugins/adapter/whatsapp.md"
}
]
},
@@ -979,6 +994,14 @@
{
"text": "Notes de version : v4.11",
"link": "/about/releases/v4.11.md"
+ },
+ {
+ "text": "v4.12 版本介绍",
+ "link": "/about/releases/v4.12.md"
+ },
+ {
+ "text": "v4.13 版本介绍",
+ "link": "/about/releases/v4.13.md"
}
]
},
diff --git a/.vitepress/config/ja-JP.json b/.vitepress/config/ja-JP.json
index 2b137aedbf46..37af72551979 100644
--- a/.vitepress/config/ja-JP.json
+++ b/.vitepress/config/ja-JP.json
@@ -130,64 +130,43 @@
]
},
{
- "text": "コンソール",
+ "text": "使用",
"items": [
{
- "text": "コンソールについて",
- "link": "/manual/console/index.md"
- },
- {
- "text": "プラグインのインストールと設定",
- "link": "/manual/console/market.md"
+ "text": "安装和配置插件",
+ "link": "/manual/usage/market.md"
},
{
- "text": "サンドボックスでのチャット",
- "link": "/manual/console/sandbox.md"
+ "text": "第一次对话",
+ "link": "/manual/usage/adapter.md"
},
{
- "text": "チャットアプリに接続",
- "link": "/manual/console/adapter.md"
- }
- ]
- },
- {
- "text": "使い方",
- "items": [
- {
- "text": "コマンドシステム",
+ "text": "指令系统",
"link": "/manual/usage/command.md"
},
{
- "text": "账号系统",
+ "text": "账号登录与绑定",
"link": "/manual/usage/platform.md"
},
{
- "text": "権限管理",
- "link": "/manual/usage/permission.md"
- },
- {
- "text": "フィルター",
- "link": "/manual/usage/filter.md"
- },
- {
- "text": "国際化",
- "link": "/manual/usage/i18n.md"
+ "text": "深入定制机器人",
+ "link": "/manual/usage/customize.md"
}
]
},
{
- "text": "レシピ",
+ "text": "配方",
"items": [
{
- "text": "コマンド (アドバンスト)",
+ "text": "指令进阶技巧",
"link": "/manual/recipe/execution.md"
},
{
- "text": "データベースにアクセス",
+ "text": "访问数据库",
"link": "/manual/recipe/dataview.md"
},
{
- "text": "マルチ設定",
+ "text": "维护多份配置",
"link": "/manual/recipe/multiple.md"
},
{
@@ -195,13 +174,13 @@
"link": "/manual/recipe/search.md"
},
{
- "text": "デプロイメント",
+ "text": "ウェブ上で公開する",
"link": "/manual/recipe/server.md"
}
]
},
{
- "text": "ランチャー",
+ "text": "启动器",
"items": [
{
"text": "システム要件",
@@ -303,20 +282,24 @@
"text": "データベース",
"items": [
{
- "text": "データベースを使用する",
+ "text": "基本用法",
"link": "/guide/database/"
},
{
- "text": "データモデルを拡張する",
+ "text": "数据模型",
"link": "/guide/database/model.md"
},
{
- "text": "遅延初期化と自動更新",
- "link": "/guide/database/observer.md"
+ "text": "进阶查询技巧",
+ "link": "/guide/database/select.md"
},
{
- "text": "データベースプラグインの作成",
- "link": "/guide/database/writing.md"
+ "text": "組み込みデータ構造",
+ "link": "/guide/database/builtin.md"
+ },
+ {
+ "text": "权限管理",
+ "link": "/guide/database/permission.md"
}
]
},
@@ -337,7 +320,11 @@
},
{
"text": "消息编码",
- "link": "/guide/adapter/encoder.md"
+ "link": "/guide/adapter/message.md"
+ },
+ {
+ "text": "平台集成",
+ "link": "/guide/adapter/integration.md"
}
]
},
@@ -460,6 +447,10 @@
"text": "国际化 (I18n)",
"link": "/api/service/i18n.md"
},
+ {
+ "text": "权限管理 (Permissions)",
+ "link": "/api/service/permissions.md"
+ },
{
"text": "插件系统 (Registry)",
"link": "/api/service/registry.md"
@@ -603,6 +594,10 @@
{
"text": "アダプター",
"items": [
+ {
+ "text": "钉钉",
+ "link": "/plugins/adapter/dingtalk.md"
+ },
{
"text": "Discord",
"link": "/plugins/adapter/discord.md"
@@ -620,20 +615,40 @@
"link": "/plugins/adapter/line.md"
},
{
- "text": "Mail",
+ "text": "邮件",
"link": "/plugins/adapter/mail.md"
},
+ {
+ "text": "Matrix",
+ "link": "/plugins/adapter/matrix.md"
+ },
{
"text": "OneBot",
"link": "/plugins/adapter/onebot.md"
},
{
- "text": "QQGuild",
+ "text": "QQ 频道",
"link": "/plugins/adapter/qqguild.md"
},
+ {
+ "text": "Slack",
+ "link": "/plugins/adapter/slack.md"
+ },
{
"text": "Telegram",
"link": "/plugins/adapter/telegram.md"
+ },
+ {
+ "text": "微信公众号",
+ "link": "/plugins/adapter/wechat-official.md"
+ },
+ {
+ "text": "企业微信",
+ "link": "/plugins/adapter/wecom.md"
+ },
+ {
+ "text": "WhatsApp",
+ "link": "/plugins/adapter/whatsapp.md"
}
]
},
@@ -979,6 +994,14 @@
{
"text": "v4.11 のバージョン情報",
"link": "/about/releases/v4.11.md"
+ },
+ {
+ "text": "v4.12 版本介绍",
+ "link": "/about/releases/v4.12.md"
+ },
+ {
+ "text": "v4.13 版本介绍",
+ "link": "/about/releases/v4.13.md"
}
]
},
diff --git a/.vitepress/config/ru-RU.json b/.vitepress/config/ru-RU.json
index e2929f4497a1..013b4683e7a1 100644
--- a/.vitepress/config/ru-RU.json
+++ b/.vitepress/config/ru-RU.json
@@ -130,48 +130,27 @@
]
},
{
- "text": "Консоль",
+ "text": "使用",
"items": [
- {
- "text": "认识控制台",
- "link": "/manual/console/index.md"
- },
{
"text": "安装和配置插件",
- "link": "/manual/console/market.md"
+ "link": "/manual/usage/market.md"
},
{
- "text": "在沙盒中聊天",
- "link": "/manual/console/sandbox.md"
+ "text": "第一次对话",
+ "link": "/manual/usage/adapter.md"
},
- {
- "text": "接入聊天平台",
- "link": "/manual/console/adapter.md"
- }
- ]
- },
- {
- "text": "使用",
- "items": [
{
"text": "指令系统",
"link": "/manual/usage/command.md"
},
{
- "text": "账号系统",
+ "text": "账号登录与绑定",
"link": "/manual/usage/platform.md"
},
{
- "text": "权限管理",
- "link": "/manual/usage/permission.md"
- },
- {
- "text": "过滤器",
- "link": "/manual/usage/filter.md"
- },
- {
- "text": "国际化",
- "link": "/manual/usage/i18n.md"
+ "text": "深入定制机器人",
+ "link": "/manual/usage/customize.md"
}
]
},
@@ -303,20 +282,24 @@
"text": "База данных",
"items": [
{
- "text": "使用数据库",
+ "text": "基本用法",
"link": "/guide/database/"
},
{
- "text": "扩展数据模型",
+ "text": "数据模型",
"link": "/guide/database/model.md"
},
{
- "text": "按需加载和自动更新",
- "link": "/guide/database/observer.md"
+ "text": "进阶查询技巧",
+ "link": "/guide/database/select.md"
},
{
- "text": "编写数据库插件",
- "link": "/guide/database/writing.md"
+ "text": "内置数据结构",
+ "link": "/guide/database/builtin.md"
+ },
+ {
+ "text": "权限管理",
+ "link": "/guide/database/permission.md"
}
]
},
@@ -337,7 +320,11 @@
},
{
"text": "消息编码",
- "link": "/guide/adapter/encoder.md"
+ "link": "/guide/adapter/message.md"
+ },
+ {
+ "text": "平台集成",
+ "link": "/guide/adapter/integration.md"
}
]
},
@@ -460,6 +447,10 @@
"text": "国际化 (I18n)",
"link": "/api/service/i18n.md"
},
+ {
+ "text": "权限管理 (Permissions)",
+ "link": "/api/service/permissions.md"
+ },
{
"text": "插件系统 (Registry)",
"link": "/api/service/registry.md"
@@ -603,6 +594,10 @@
{
"text": "适配器支持",
"items": [
+ {
+ "text": "钉钉",
+ "link": "/plugins/adapter/dingtalk.md"
+ },
{
"text": "Discord",
"link": "/plugins/adapter/discord.md"
@@ -612,7 +607,7 @@
"link": "/plugins/adapter/kook.md"
},
{
- "text": "Lark",
+ "text": "飞书",
"link": "/plugins/adapter/lark.md"
},
{
@@ -620,20 +615,40 @@
"link": "/plugins/adapter/line.md"
},
{
- "text": "Mail",
+ "text": "邮件",
"link": "/plugins/adapter/mail.md"
},
+ {
+ "text": "Matrix",
+ "link": "/plugins/adapter/matrix.md"
+ },
{
"text": "OneBot",
"link": "/plugins/adapter/onebot.md"
},
{
- "text": "QQGuild",
+ "text": "QQ 频道",
"link": "/plugins/adapter/qqguild.md"
},
+ {
+ "text": "Slack",
+ "link": "/plugins/adapter/slack.md"
+ },
{
"text": "Telegram",
"link": "/plugins/adapter/telegram.md"
+ },
+ {
+ "text": "微信公众号",
+ "link": "/plugins/adapter/wechat-official.md"
+ },
+ {
+ "text": "企业微信",
+ "link": "/plugins/adapter/wecom.md"
+ },
+ {
+ "text": "WhatsApp",
+ "link": "/plugins/adapter/whatsapp.md"
}
]
},
@@ -979,6 +994,14 @@
{
"text": "v4.11 版本介绍",
"link": "/about/releases/v4.11.md"
+ },
+ {
+ "text": "v4.12 版本介绍",
+ "link": "/about/releases/v4.12.md"
+ },
+ {
+ "text": "v4.13 版本介绍",
+ "link": "/about/releases/v4.13.md"
}
]
},
diff --git a/.vitepress/config/zh-TW.json b/.vitepress/config/zh-TW.json
index bf459d7f60b7..560193540b52 100644
--- a/.vitepress/config/zh-TW.json
+++ b/.vitepress/config/zh-TW.json
@@ -130,48 +130,27 @@
]
},
{
- "text": "控制檯",
+ "text": "使用",
"items": [
- {
- "text": "認識控制檯",
- "link": "/manual/console/index.md"
- },
{
"text": "安裝和配置外掛",
- "link": "/manual/console/market.md"
+ "link": "/manual/usage/market.md"
},
{
- "text": "在沙盒中聊天",
- "link": "/manual/console/sandbox.md"
+ "text": "第一次对话",
+ "link": "/manual/usage/adapter.md"
},
- {
- "text": "接入聊天平臺",
- "link": "/manual/console/adapter.md"
- }
- ]
- },
- {
- "text": "使用",
- "items": [
{
"text": "指令系統",
"link": "/manual/usage/command.md"
},
{
- "text": "账号系统",
+ "text": "账号登录与绑定",
"link": "/manual/usage/platform.md"
},
{
- "text": "權限管理",
- "link": "/manual/usage/permission.md"
- },
- {
- "text": "過濾器",
- "link": "/manual/usage/filter.md"
- },
- {
- "text": "國際化",
- "link": "/manual/usage/i18n.md"
+ "text": "深入定制机器人",
+ "link": "/manual/usage/customize.md"
}
]
},
@@ -179,36 +158,36 @@
"text": "配方",
"items": [
{
- "text": "指令進階技巧",
+ "text": "指令进阶技巧",
"link": "/manual/recipe/execution.md"
},
{
- "text": "訪問資料庫",
+ "text": "访问数据库",
"link": "/manual/recipe/dataview.md"
},
{
- "text": "維護多份配置",
+ "text": "维护多份配置",
"link": "/manual/recipe/multiple.md"
},
{
- "text": "搜尋外掛市場",
+ "text": "搜索插件市场",
"link": "/manual/recipe/search.md"
},
{
- "text": "公網部署",
+ "text": "公网部署",
"link": "/manual/recipe/server.md"
}
]
},
{
- "text": "啟動器",
+ "text": "启动器",
"items": [
{
- "text": "系統要求",
+ "text": "系统要求",
"link": "/manual/launcher/system.md"
},
{
- "text": "命令列工具",
+ "text": "命令行工具",
"link": "/manual/launcher/cli.md"
}
]
@@ -303,20 +282,24 @@
"text": "資料庫",
"items": [
{
- "text": "使用資料庫",
+ "text": "基本用法",
"link": "/guide/database/"
},
{
- "text": "擴充資料模型",
+ "text": "数据模型",
"link": "/guide/database/model.md"
},
{
- "text": "按需載入和自動更新",
- "link": "/guide/database/observer.md"
+ "text": "进阶查询技巧",
+ "link": "/guide/database/select.md"
},
{
- "text": "編寫資料庫外掛",
- "link": "/guide/database/writing.md"
+ "text": "內建資料結構",
+ "link": "/guide/database/builtin.md"
+ },
+ {
+ "text": "权限管理",
+ "link": "/guide/database/permission.md"
}
]
},
@@ -337,7 +320,11 @@
},
{
"text": "訊息編碼",
- "link": "/guide/adapter/encoder.md"
+ "link": "/guide/adapter/message.md"
+ },
+ {
+ "text": "平台集成",
+ "link": "/guide/adapter/integration.md"
}
]
},
@@ -461,11 +448,15 @@
"link": "/api/service/i18n.md"
},
{
- "text": "外掛系統 (Registry)",
+ "text": "权限管理 (Permissions)",
+ "link": "/api/service/permissions.md"
+ },
+ {
+ "text": "插件系统 (Registry)",
"link": "/api/service/registry.md"
},
{
- "text": "網路服務 (Router)",
+ "text": "网络服务 (Router)",
"link": "/api/service/router.md"
}
]
@@ -603,6 +594,10 @@
{
"text": "配接器支援",
"items": [
+ {
+ "text": "钉钉",
+ "link": "/plugins/adapter/dingtalk.md"
+ },
{
"text": "Discord",
"link": "/plugins/adapter/discord.md"
@@ -612,7 +607,7 @@
"link": "/plugins/adapter/kook.md"
},
{
- "text": "Lark",
+ "text": "飞书",
"link": "/plugins/adapter/lark.md"
},
{
@@ -620,20 +615,40 @@
"link": "/plugins/adapter/line.md"
},
{
- "text": "電子郵件",
+ "text": "郵件",
"link": "/plugins/adapter/mail.md"
},
+ {
+ "text": "Matrix",
+ "link": "/plugins/adapter/matrix.md"
+ },
{
"text": "OneBot",
"link": "/plugins/adapter/onebot.md"
},
{
- "text": "QQ 頻道",
+ "text": "QQ 频道",
"link": "/plugins/adapter/qqguild.md"
},
+ {
+ "text": "Slack",
+ "link": "/plugins/adapter/slack.md"
+ },
{
"text": "Telegram",
"link": "/plugins/adapter/telegram.md"
+ },
+ {
+ "text": "微信公众号",
+ "link": "/plugins/adapter/wechat-official.md"
+ },
+ {
+ "text": "企业微信",
+ "link": "/plugins/adapter/wecom.md"
+ },
+ {
+ "text": "WhatsApp",
+ "link": "/plugins/adapter/whatsapp.md"
}
]
},
@@ -979,6 +994,14 @@
{
"text": "v4.11 版本介紹",
"link": "/about/releases/v4.11.md"
+ },
+ {
+ "text": "v4.12 版本介绍",
+ "link": "/about/releases/v4.12.md"
+ },
+ {
+ "text": "v4.13 版本介绍",
+ "link": "/about/releases/v4.13.md"
}
]
},
diff --git a/de-DE/about/history.md b/de-DE/about/history.md
index d9004a45b305..dc106fc4ec72 100644
--- a/de-DE/about/history.md
+++ b/de-DE/about/history.md
@@ -2,13 +2,13 @@
2019 年 8 月,我开始编写我的第一个基于 Node.js 的聊天机器人,名为四季酱。当时我浏览各种聊天机器人框架,发现并没有自己真正想要的,遂决定从零开始编写。一开始这个机器人只包含了很少的功能,但随着其更多功能的加入,我开始调整起底层架构,并计划逐步将其开源出来。
-到目前为止 Koishi 大约每 8 个月发布一个大版本。可以说 Koishi 的发展完全是由需求推动的,在迭代中形成了一套聊天机器人开发的最佳实践。
+直到 v4 发布之前,Koishi 大约每 8 个月发布一个大版本。可以说 Koishi 的发展完全是由需求推动的,在迭代中形成了一套聊天机器人开发的最佳实践。
## v1 时期
Koishi v1 发布于 2020 年 1 月。此时的 Koishi 虽然体量尚小,但已具备了许多一直沿袭至今的特征:通过插件系统实现了功能的模块化,通过事件模型和中间件处理各种输入,上下文负责对输入的事件进行过滤,以及一个通过链式调用进行开发的指令系统等等。
-最初的 v1 官方插件只有 common, schedule 和 teach,其他在这个时期开发的插件大都已经弃用了。
+目前可追溯的 v1 官方插件只有 common, schedule 和 teach,其他在这个时期开发的插件大都已经弃用了。
## v2 时期
diff --git a/de-DE/about/releases/v4.12.md b/de-DE/about/releases/v4.12.md
index a31a1d106f47..72def250c3ed 100644
--- a/de-DE/about/releases/v4.12.md
+++ b/de-DE/about/releases/v4.12.md
@@ -1,3 +1,42 @@
# v4.12 版本介绍
- [Roadmap](https://github.com/koishijs/koishi/issues/1000)
+- [v4.12.0](https://github.com/koishijs/koishi/releases/tag/4.12.0)
+- [v4.12.1](https://github.com/koishijs/koishi/releases/tag/4.12.1)
+- [v4.12.2](https://github.com/koishijs/koishi/releases/tag/4.12.2)
+- [v4.12.3](https://github.com/koishijs/koishi/releases/tag/4.12.3)
+- [v4.12.4](https://github.com/koishijs/koishi/releases/tag/4.12.4)
+- [v4.12.5](https://github.com/koishijs/koishi/releases/tag/4.12.5)
+- [v4.12.6](https://github.com/koishijs/koishi/releases/tag/4.12.6)
+- [v4.12.7](https://github.com/koishijs/koishi/releases/tag/4.12.7)
+- [v4.12.8](https://github.com/koishijs/koishi/releases/tag/4.12.8)
+- [v4.12.9](https://github.com/koishijs/koishi/releases/tag/4.12.9)
+
+## 用户绑定
+
+在这个版本中,我们引入了新的内置数据表 `binding`,用于存储账号绑定信息。在新的架构下,我们更新了 `auth` 和 `bind` 插件,支持了同平台的用户绑定和解绑等功能。
+
+为了能让旧版本的用户平滑升级,我们还引入了数据库迁移技术,支持插件在升级时将已有的数据迁移到新的表中。
+
+## 资源管理器
+
+新增了官方插件 @koishijs/plugin-explorer,可用于在控制台中查看和编辑实例目录内的文件。该插件除了能够方便云端部署的用户进行文件管理外,还为路径类型的配置项提供了便捷的选择界面。至此,Koishi 的控制台生态已经趋于完善。
+
+## 配置界面优化
+
+v4.12 版本期间,我们对配置 UI 库 schemastery-vue 进行了全面的重构:
+
+- 提供了更好的扩展性
+- 修复了一些边界情况下的已知问题,同时提高了性能
+- 对于 `array` 和 `dict` 等类型支持了折叠
+- 支持了配置项国际化
+
+## 适配器更新
+
+- 优化了多个适配器的消息元素支持,包括 onebot, telegram, kook 等
+- 将 feishu 适配器更名为 lark,并同时支持了飞书的国内与国际版本
+- 新增了 mail 适配器,允许用户通过邮件与机器人交互
+
+## 热重载优化
+
+将热重载相关逻辑从 CLI 内置迁移到了独立的插件 @koishijs/plugin-hmr 中,并提供了更多的配置项与功能。例如,如果当前保存的文件存在编译错误,将会显示色彩丰富的提示信息。
diff --git a/de-DE/about/releases/v4.13.md b/de-DE/about/releases/v4.13.md
new file mode 100644
index 000000000000..20d33caf1be6
--- /dev/null
+++ b/de-DE/about/releases/v4.13.md
@@ -0,0 +1,42 @@
+# v4.13 版本介绍
+
+- [Roadmap](https://github.com/koishijs/koishi/issues/1085)
+- [v4.13.0](https://github.com/koishijs/koishi/releases/tag/4.13.0)
+- [v4.13.1](https://github.com/koishijs/koishi/releases/tag/4.13.1)
+- [v4.13.2](https://github.com/koishijs/koishi/releases/tag/4.13.2)
+- [v4.13.3](https://github.com/koishijs/koishi/releases/tag/4.13.3)
+- [v4.13.4](https://github.com/koishijs/koishi/releases/tag/4.13.4)
+- [v4.13.5](https://github.com/koishijs/koishi/releases/tag/4.13.5)
+- [v4.13.6](https://github.com/koishijs/koishi/releases/tag/4.13.6)
+- [v4.13.7](https://github.com/koishijs/koishi/releases/tag/4.13.7)
+- [v4.13.8](https://github.com/koishijs/koishi/releases/tag/4.13.8)
+- [v4.13.9](https://github.com/koishijs/koishi/releases/tag/4.13.9)
+
+## 控制台优化
+
+v4.12 版本的主要改动是新增了一批控制台 API:
+
+- 引入了主题系统,允许插件定义新的主题
+- 引入了用户配置 API,并允许插件扩展配置项
+- 引入了菜单 API,允许插件定义和触发上下文菜单
+- 支持了配置漫游功能,用户可以在多个客户端同步配置
+
+伴随着新的 API,已有的控制台插件也迎来了各种优化,这里就不一一列举了。
+
+## 插件配置优化
+
+在这个版本中,我们进一步将 @koishijs/plugin-market 拆分为了 market 和 config 两个独立的插件。前者负责插件的安装和更新,后者负责插件的配置。同时,我们支持了插件信息的按需读取,大幅提高了启动速度和插件市场加载速度。
+
+新版本的 market 和 config 插件也带来了一些新功能:
+
+- 支持了启用、停用、重载插件分组
+- 支持了插件的快速预览功能
+- 支持了对插件配置项的热重载
+- 支持了以指令形式安装、卸载、更新插件
+
+## 适配器更新
+
+- 支持了更多标准消息元素
+- 支持了斜线指令,允许用户快速与机器人交互
+- 新增了与表态和角色相关的 Bot API
+- 新增了 matrix 和 line 适配器
diff --git a/de-DE/api/console/index.md b/de-DE/api/console/index.md
deleted file mode 100644
index fa60bfd67599..000000000000
--- a/de-DE/api/console/index.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# 控制台架构
-
-::: warning
-此页文档正在施工,内容尚未完成。
-:::
diff --git a/de-DE/api/core/adapter.md b/de-DE/api/core/adapter.md
index 4c49143ff920..f678785cd1c7 100644
--- a/de-DE/api/core/adapter.md
+++ b/de-DE/api/core/adapter.md
@@ -1,5 +1,9 @@
# 适配器 (Adapter)
+::: tip
+参见:[开发 > 跨平台 > 实现适配器](../../guide/adapter/adapter.md)
+:::
+
本章将介绍与适配器相关的内容,这是一个相当底层的概念,因此如果你并不打算编写一个平台实现,你完全可以跳过本章节。
标有 抽象 的方法需要平台自行实现。
diff --git a/de-DE/api/core/bot.md b/de-DE/api/core/bot.md
index 2f2936ff4bc2..ed5cc52bd50e 100644
--- a/de-DE/api/core/bot.md
+++ b/de-DE/api/core/bot.md
@@ -1,5 +1,9 @@
# 机器人 (Bot)
+::: tip
+参见:[开发 > 跨平台 > 实现机器人](../../guide/adapter/bot.md)
+:::
+
**机器人 (Bot)** 是适配器的核心,它将不同平台的 API 封装成统一的格式供 Koishi 使用。而不同的适配器也可以自行扩展 Bot 实例上的属性和方法。
标有 内置 的 API 已经由 Koishi 提供,适配器可以覆盖对应的方法,但是无需自行实现。
@@ -215,7 +219,7 @@ export interface UserInfo {
获取用户信息。
-### bot.getFriendList()
+### bot.getFriendList() 实验性
- 返回值: `Promise` 好友列表
@@ -235,7 +239,7 @@ export interface GuildInfo {
}
```
-### bot.getGuildList()
+### bot.getGuildList() 实验性
- 返回值: `Promise` 群组列表
@@ -262,10 +266,10 @@ export interface GuildMemberInfo extends UserInfo {
}
```
-### bot.getGuildMemberList(guildId)
+### bot.getGuildMemberList(guildId) 实验性
- **guildId:** `string` 群组 ID
-- 返回值: `Promise` 群成员列表
+- 返回值: `Promise>` 群成员列表
获取群成员列表。
@@ -290,7 +294,7 @@ export interface ChannelInfo {
}
```
-### bot.getChannelList(guildId)
+### bot.getChannelList(guildId) 实验性
- **guildId:** `string` 群组 ID
- 返回值: `Promise` 频道列表
diff --git a/de-DE/api/core/command.md b/de-DE/api/core/command.md
index b34c71840a86..37ba86ff9e44 100644
--- a/de-DE/api/core/command.md
+++ b/de-DE/api/core/command.md
@@ -1,5 +1,9 @@
# 指令 (Command)
+::: tip
+参见:[开发 > 交互基础 > 指令开发](../../guide/basic/command.md)
+:::
+
指令系统是 Koishi 的核心功能之一。通过 `ctx.command()` 方法获得的是指令的实例,它含有下面的方法:
## Argv 对象
@@ -79,7 +83,7 @@ type CommandAction = (argv: Argv, ...args: any[]) => Awaitable
- **fields:** `FieldCollector` 要请求的用户字段
- 返回值: `this`
-如果指令需要用到用户数据,你可以提前声明,这样有助于合并多次请求,从而提高性能。 参见[按需加载](../../guide/database/observer.md#声明所需字段)章节。
+如果指令需要用到用户数据,你可以提前声明,这样有助于合并多次请求,从而提高性能。 参见[按需加载](../../guide/database/builtin.md#声明所需字段)章节。
```ts
type FieldCollector =
@@ -92,7 +96,7 @@ type FieldCollector =
- **fields:** `FieldCollector` 要请求的频道字段
- 返回值: `this`
-如果指令需要用到频道数据,你可以提前声明,这样有助于合并多次请求,从而提高性能。 参见[按需加载](../../guide/database/observer.md#声明所需字段)章节。
+如果指令需要用到频道数据,你可以提前声明,这样有助于合并多次请求,从而提高性能。 参见[按需加载](../../guide/database/builtin.md#声明所需字段)章节。
### cmd.alias(...names)
diff --git a/de-DE/api/core/context.md b/de-DE/api/core/context.md
index ec6c790b5112..f0d4058ebf76 100644
--- a/de-DE/api/core/context.md
+++ b/de-DE/api/core/context.md
@@ -24,6 +24,7 @@ Koishi 使用了面向切面编程 (AOP) 的开发方式,绝大部分上下文
- [ctx.on](../service/events.md#ctx-on)
- [ctx.once](../service/events.md#ctx-once)
- [ctx.parallel](../service/events.md#ctx-parallel)
+- [ctx.permissions](../service/permissions.md)
- [ctx.plugin](../service/registry.md#ctx-plugin)
- [ctx.router](../service/router.md)
- [ctx.scope](../service/registry.md#ctx-scope)
diff --git a/de-DE/api/core/events.md b/de-DE/api/core/events.md
index 7b8000df9793..8dc2cbe01d99 100644
--- a/de-DE/api/core/events.md
+++ b/de-DE/api/core/events.md
@@ -1,9 +1,12 @@
# 事件 (Events)
-Koishi 封装了一套事件系统。其基本用法与 Node.js 自带的 [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) 类似,但支持更多的功能,比如多达 6 种的触发形式以及会话事件等。在了解下面的内容之前,建议你先阅读下面的章节:
+::: tip
+参见:[开发 > 交互基础 > 事件系统](../../guide/basic/events.md)
参见:[开发 > 模块化 > 生命周期](../../guide/plugin/lifecycle.md)
+:::
-- [事件系统](../../guide/basic/events.md)
-- [生命周期](../../guide/plugin/lifecycle.md)
+::: tip
+本节介绍 Koishi 的内置事件。如果想了解事件 API,请前往 [API > 内置服务 > 事件](../service/events.md)。
+:::
## 通用会话事件
diff --git a/de-DE/api/database/built-in.md b/de-DE/api/database/built-in.md
index 21c58b62956e..ca09afd7cc42 100644
--- a/de-DE/api/database/built-in.md
+++ b/de-DE/api/database/built-in.md
@@ -27,7 +27,7 @@ Koishi 的数据库 API 实际上分为两部分:
- **platform:** `string` 平台名
- **id:** `string` 频道账号
-- **assignee:** `string` [受理人](../../manual/usage/permission.md#受理人机制)
+- **assignee:** `string` [受理人](../../manual/usage/customize.md#受理人机制)
- **permissions:** `string[]` 权限列表
- **locales:** `string[]` 语言列表
diff --git a/de-DE/api/database/database.md b/de-DE/api/database/database.md
index afbe5b16e667..59d08bc42352 100644
--- a/de-DE/api/database/database.md
+++ b/de-DE/api/database/database.md
@@ -1,5 +1,9 @@
# 数据库操作 (Database)
+::: tip
+参见:[开发 > 数据库 > 基本用法](../../guide/database/)
+:::
+
一个 Database 对象代理了 Koishi 上下文绑定的应用实例有关的所有数据库访问。同时它具有注入特性,任何插件都可以自己定义数据库上的方法。本章主要介绍数据库的官方接口。注意:**它们并不由 Koishi 自身实现,而是由每个数据库分别实现的**。Koishi 只是提供了一套标准。
## database.drop()
diff --git a/de-DE/api/database/model.md b/de-DE/api/database/model.md
index 621f5ef6ec8a..cee8c091b9ae 100644
--- a/de-DE/api/database/model.md
+++ b/de-DE/api/database/model.md
@@ -1,8 +1,12 @@
# 数据模型 (Model)
+::: tip
+参见:[开发 > 数据库 > 数据模型](../../guide/database/model.md)
+:::
+
## 数据类型
-数据类型会被用于 [`model.extend()`](#model-extend-name-fields-config) 方法中,其定义如下:
+数据类型会被用于 [`model.extend()`](#model-extend) 方法中,其定义如下:
```ts
export interface Field {
@@ -11,6 +15,7 @@ export interface Field {
nullable?: boolean
initial?: T
comment?: string
+ legacy?: string[]
}
```
@@ -28,7 +33,7 @@ export interface Field {
| 名称 | TS 类型 | 默认长度 | 默认初始值 | 说明 |
|:------:|:--------:|:-----:|:-----:|:------:|
| char | `string` | 64 | `''` | 定长的字符串 |
-| string | `string` | 256 | `''` | 变长的字符串 |
+| string | `string` | 255 | `''` | 变长的字符串 |
| text | `string` | 65535 | `''` | 变长的字符串 |
### 时间类型
@@ -55,13 +60,22 @@ export interface Field {
- **config:** `Table.Meta` 表的基本配置
- **config.primary:** `string | string[]` 主键名,默认为 `'id'`
- **config.unique:** `(string | string[])[]` 值唯一的键名列表
- - **config.foreign:** `Dict<[string, string]>` 外键列表
+ - **config.foreign:** `Dict<[string, string]>` 外键列表 实验性
- **config.autoInc:** `boolean` 是否使用自增主键
扩展一个新的数据表。
-### model.create(name)
+### model.create(name, data)
+
+- **name:** `string` 数据表名
+- **data:** `any` 数据
+
+创建一条新的数据,折叠嵌套属性,并填充必要的默认值。
+
+### model.migrate(name, fields, callback) 实验性
-### model.resolveQuery(query)
+- **name:** `string` 数据表名
+- **fields:** `Field.Config` 要迁移的字段信息
+- **callback:** `(db: Database) => Promise` 迁移的回调函数
-### model.resolveModifier(modifier)
+设置 [整表迁移](../../guide/database/model.md#整表迁移) 的操作。
diff --git a/de-DE/api/glossary.md b/de-DE/api/glossary.md
index a206a6190e1e..394cc071cb77 100644
--- a/de-DE/api/glossary.md
+++ b/de-DE/api/glossary.md
@@ -6,7 +6,7 @@
适配器是指实现了平台协议,能够让机器人接入平台的插件。通常来说一个适配器实例对应了一个机器人用户,同时启用多个适配器就实现了多个机器人的同时接入。
-- [入门 > 接入聊天平台](../manual/console/adapter.md)
+- [入门 > 接入聊天平台](../manual/usage/adapter.md)
- [开发 > 跨平台 > 实现适配器](../guide/adapter/adapter.md)
- [API > 核心模块 > 适配器](./core/adapter.md)
@@ -38,7 +38,7 @@
## 控制台 (Console)
-- [入门 > 认识控制台](../manual/console/index.md)
+- [入门 > 认识控制台](../manual/usage/market.md#认识控制台)
- [开发 > 控制台](../guide/console/index.md)
- [API > 控制台](./console/server.md)
@@ -58,7 +58,7 @@
消息元素类似于 HTML 元素,它是组成消息的基本单位。一个元素可以表示具有特定语义的内容,如文本、表情、图片、引用、元信息等。Koishi 会将这些元素转换为平台所支持的格式,以便在不同平台之间发送和接收消息。
- [开发 > 交互基础 > 消息元素](../guide/basic/element.md)
-- [开发 > 跨平台 > 消息编码](../guide/adapter/encoder.md)
+- [开发 > 跨平台 > 消息编码](../guide/adapter/message.md)
- [API > 消息元素](./message/syntax.md)
## 事件 (Events)
@@ -69,7 +69,7 @@
## 过滤器 (Filter)
-- [入门 > 过滤器](../manual/usage/filter.md)
+- [入门 > 过滤器](../manual/usage/customize.md#过滤器)
- [开发 > 模块化 > 过滤器](../guide/plugin/filter.md)
- [API > 内置服务 > 过滤器](./service/filter.md)
@@ -91,7 +91,7 @@
## 数据模型 (Model)
-- [开发 > 数据库 > 扩展数据模型](../guide/database/model.md#扩展数据模型)
+- [开发 > 数据库 > 数据模型](../guide/database/model.md)
- [API > 数据库 > 数据模型](./database/model.md)
## 平台 (Platform)
diff --git a/de-DE/api/service/bots.md b/de-DE/api/service/bots.md
index 7e2a3bb5e5e2..ea447291d373 100644
--- a/de-DE/api/service/bots.md
+++ b/de-DE/api/service/bots.md
@@ -1,7 +1,7 @@
# 机器人 (Bots)
::: warning
-本节中介绍的 API 均为实验性功能,可能在后续版本中发生变化。
+本节中介绍的 API 目前属于实验性功能,可能在后续版本中发生变化。
:::
`ctx.bots` 保存了当前全部 [Bot](../core/bot.md) 实例。它继承了 Array 类,因此你可以使用诸如 `ctx.bots.forEach()` 的写法。除此以外,我们还提供了一些与机器人相关的实用方法。
diff --git a/de-DE/api/service/events.md b/de-DE/api/service/events.md
index d573a91d190d..08f5f45b6e52 100644
--- a/de-DE/api/service/events.md
+++ b/de-DE/api/service/events.md
@@ -1,7 +1,11 @@
# 事件系统 (Events)
::: tip
-相关指南:[事件系统](../../guide/basic/events.md)
+参见:[开发 > 交互基础 > 事件系统](../../guide/basic/events.md)
参见:[开发 > 模块化 > 生命周期](../../guide/plugin/lifecycle.md)
+:::
+
+::: tip
+本节介绍事件 API。如果想了解 Koishi 的内置事件,请前往 [API > 核心模块 > 事件](../core/events.md)。
:::
## 实例方法
diff --git a/de-DE/api/service/i18n.md b/de-DE/api/service/i18n.md
index dfd625de7d04..7b6a07490085 100644
--- a/de-DE/api/service/i18n.md
+++ b/de-DE/api/service/i18n.md
@@ -1,7 +1,7 @@
# 国际化 (I18n)
::: tip
-相关指南:[国际化](../../guide/i18n/index.md)
+参见:[开发 > 国际化](../../guide/i18n/index.md)
:::
## 实例方法
diff --git a/de-DE/api/service/permissions.md b/de-DE/api/service/permissions.md
new file mode 100644
index 000000000000..bddd69fc8771
--- /dev/null
+++ b/de-DE/api/service/permissions.md
@@ -0,0 +1,21 @@
+# 权限管理 (Permissions) 实验性
+
+::: warning
+权限管理目前属于实验性功能,API 可能在后续版本中发生变化。
+:::
+
+::: tip
+参见:[开发 > 数据库 > 权限管理](../../guide/database/permission.md)
+:::
+
+## 实例方法
+
+### ctx.permissions.define(name, inherits)
+
+### ctx.permissions.inherit(a, b)
+
+### ctx.permissions.depend(a, b)
+
+### ctx.permissions.provide(name, callback)
+
+### ctx.permissions.test(list, session)
diff --git a/de-DE/cookbook/message.md b/de-DE/cookbook/message.md
index 23f70ce0596a..a2748349658a 100644
--- a/de-DE/cookbook/message.md
+++ b/de-DE/cookbook/message.md
@@ -14,7 +14,7 @@
- 临时中间件会直接插在当前序列的尾端,并不会影响中间件池本身
- 如果执行中遇到错误或未调用 `next` 函数,会停止后续中间件的执行
4. 触发 [middleware](../api/core/events.md#事件:middleware) 事件
-5. 更新当前用户和群的缓冲数据 (参见 [按需加载与自动更新](../guide/database/observer.md#按需加载与自动更新))
+5. 更新当前用户和群的缓冲数据 (参见 [按需加载与自动更新](../guide/database/builtin.md#按需加载与自动更新))
### 内置中间件
diff --git a/de-DE/cookbook/online.md b/de-DE/cookbook/online.md
index d34603df7e59..31aced599a3b 100644
--- a/de-DE/cookbook/online.md
+++ b/de-DE/cookbook/online.md
@@ -89,11 +89,12 @@ export default plugin
```json title=package.json
{
"main": "lib/node/index.cjs",
- "typings": "lib/index.d.ts",
+ "types": "lib/index.d.ts",
"exports": {
".": {
"node": "./lib/node/index.cjs",
- "browser": "./lib/browser/index.mjs"
+ "browser": "./lib/browser/index.mjs",
+ "types": "./lib/index.d.ts"
},
"./shared": {
"require": "./lib/shared/index.cjs",
diff --git a/de-DE/guide/adapter/adapter.md b/de-DE/guide/adapter/adapter.md
index b8b2655d1af0..6d20b017252c 100644
--- a/de-DE/guide/adapter/adapter.md
+++ b/de-DE/guide/adapter/adapter.md
@@ -166,11 +166,12 @@ for (const event of parsed.events) {
首先调整目录结构,在 `server.ts` 和 `polling.ts` 中分别完成两种通信方式的适配器开发:
-```text{5-6}
+```text{5D,6-7A}
adapter-telegram
├── src
│ ├── bot.ts
│ ├── index.ts
+│ ├── adapter.ts
│ ├── polling.ts
│ └── server.ts
└── package.json
diff --git a/de-DE/guide/adapter/binding.md b/de-DE/guide/adapter/binding.md
deleted file mode 100644
index f2f701207386..000000000000
--- a/de-DE/guide/adapter/binding.md
+++ /dev/null
@@ -1 +0,0 @@
-# 跨平台账号绑定
diff --git a/de-DE/guide/adapter/integration.md b/de-DE/guide/adapter/integration.md
index a64a6dfd9d52..617f8307f6e1 100644
--- a/de-DE/guide/adapter/integration.md
+++ b/de-DE/guide/adapter/integration.md
@@ -1,5 +1,42 @@
# 平台集成
-::: danger 注意
-此页文档正在施工,其中的内容可能不是最新。
+至此,Koishi 的适配器开发已经接近尾声。经过前面的几节内容,我们的适配器已经封装了平台接口,与服务器稳定地进行连接,并能够顺利地接受和发送消息。但除此以外,部分平台还提供了一些额外的能力,允许机器人做得更好。Koishi 当然也要把这些能力集成到机器人中。
+
+## 斜线指令
+
+::: tip
+相关章节:[指令开发](../basic/command.md)
+:::
+
+部分平台为机器人提供了斜线指令功能,用于在聊天框中快速输入指令。在 Discord 中差不多是这个效果:
+
+![slash command](/adapter/slash.png)
+
+适配器可以通过 `bot.updateCommands()` 方法,将 Koishi 的指令注册为平台的斜线指令:
+
+```ts
+class DiscordBot {
+ async updateCommands(commands: Universal.Command[]) {
+ // 这里忽略了部分细节,仅供参考
+ const updates = commands.map(Discord.encodeCommand)
+ await this.internal.bulkOverwriteGlobalApplicationCommands(this.selfId, updates)
+ }
+}
+```
+
+## 用户语言偏好
+
+::: tip
+相关章节:[多语言支持](../i18n/index.md)
:::
+
+部分平台本身支持多种语言。在这样的平台中,用户可以自行设置自己的语言偏好。当用户向机器人发送消息时,Koishi 就可以根据用户的语言偏好,做出相应语言的回复。
+
+而适配器所需要做的,就只有设置 `session.locales` 属性 (以 Telegram 平台为例):
+
+```ts
+if (from.language_code) {
+ // 这里为了简化逻辑,只取语言码的前两位
+ session.locales = [from.language_code.slice(0, 2)]
+}
+```
diff --git a/de-DE/guide/adapter/message.md b/de-DE/guide/adapter/message.md
new file mode 100644
index 000000000000..5207677b7109
--- /dev/null
+++ b/de-DE/guide/adapter/message.md
@@ -0,0 +1,298 @@
+# 消息编码
+
+在 [实现机器人](./bot.md#在适配器中访问) 一节中,我们其实已经涉及了格式转换的概念:
+
+```ts
+// 将 Discord 的数据结构转换为通用数据结构
+const decodeGuild = (data: Discord.Guild): Universal.Guild => ({
+ guildId: data.id,
+ guildName: data.name,
+})
+
+// 将通用数据结构转换为 Discord 的数据结构
+const encodeGuild = (data: Universal.Guild): Discord.Guild => ({
+ id: data.guildId,
+ name: data.guildName,
+})
+```
+
+不同平台对于同一个概念的接口会存在或多或少的差异。为了抹平这些差异,Koishi 引入了一套通用接口,用来描述这些跨平台的概念。在实现机器人和适配器时,通常都需要编写如上的函数,来对具体平台的数据进行转化。而这其中最复杂的则是对消息的处理。
+
+Koishi 使用 [消息元素](../basic/element.md) 表达任何聊天平台的消息。这是一种类似于 HTML 的格式。消息元素作为组成消息的基本单位,可以表示具有特定语义的内容,如文本、表情、图片、引用、元信息等。本节将介绍如何在消息元素与平台消息之间互相转换。
+
+## 接收消息
+
+在会话对象上存在两个属性与消息的内容有关:`content` 和 `elements`,它们分别对应着字符串形式和消息元素形式的消息内容。它们之间会自动转换,因此下面的两种写法是等价的:
+
+```ts
+session.content = '欢迎 '
+```
+
+```ts
+session.elements = [
+ h('text', { content: '欢迎 ' }),
+ h('at', { id: '1234567' }),
+]
+```
+
+在接收消息时,只需根据平台的格式对消息进行解码,将结果赋值到上述两个属性之一即可。下面是一个最简单的例子,假设平台的消息均以文本形式接收,并且使用 `@id` 的语法表达提及用户,那么你可以这么写:
+
+```ts
+session.content = input.replace(/@(\d+)/g, '')
+```
+
+## 发送消息
+
+### 兼容性原则
+
+在具体介绍消息发送之前,不知道你是否有这样的疑问:Koishi 提供了一整套标准的消息元素,但并非所有平台都支持这些元素。对于那些不支持的元素,应该如何处理呢?
+
+Koishi 的建议是**尽量兼容实现**。对于平台不支持的元素,可以根据元素的类型和用户的配置进行转化与回退。大致可以分为两种情况:
+
+- 修饰型的元素可以选择只渲染内部的元素,或以适当的方式进行文本修饰。
例如:在不支持粗体的平台上渲染 `` 时,可以改为只渲染粗体的内容。
例如:在不支持列表的平台上渲染 `` 时,可以在每个列表项前面渲染一个 `-`。
+
+- 占位型的元素尽量转换为可渲染的元素;如果实在无法渲染则抛出错误。
例如:如果平台不支持发送网络图片,可以先将图片下载到本地再发送。
例如:如果平台不支持发送语音,可以改为发送文件,或抛出错误。
+
+对于更加复杂的元素,适配器也可以发挥自主性,设计最适合的交互形式。例如,如果用户的需求是「从若干个选项中选择一个」,那么平台 A 可以渲染出多个按钮供用户点击;平台 B 则可以发送一条带有表态的消息,点击表态对应选择选项;实在不行,平台 C 也可以直接发送选项列表和文本提示语,并将用户的下一次输入作为选项。
+
+### 消息编码器
+
+之前介绍过的 REPL 适配器为了简化写法,并未包含消息的编码过程。对于一般的适配器,我们建议通过继承 `MessageEncoder` 类来实现消息的发送逻辑。
+
+这里我们以 Telegram 平台为例,首先在源码目录下创建 `message.ts`:
+
+```text{6A}
+adapter-example
+├── src
+│ ├── adapter.ts
+│ ├── bot.ts
+│ ├── index.ts
+│ └── message.ts
+└── package.json
+```
+
+在这个文件中我们定义 `TelegramMessageEncoder`:
+
+```ts title=message.ts
+class TelegramMessageEncoder extends MessageEncoder {
+ // 使用 payload 存储待发送的消息
+ private payload: Dict
+
+ constructor(bot: TelegramBot, channelId: string, guildId?: string, options?: SendOptions) {
+ super(bot, channelId, guildId, options)
+ const chat_id = guildId || channelId
+ this.payload = { chat_id, parse_mode: 'html', text: '' }
+ }
+
+ // 将发送好的消息添加到 results 中
+ async addResult(message: Telegram.Message) {
+ const session = this.bot.session()
+ await adaptMessage(message, session)
+ this.results.push(session)
+ session.app.emit(session, 'send', session)
+ }
+
+ // 发送缓冲区内的消息
+ async flush() {
+ let message: Telegram.Message
+ if (this.payload.text) {
+ message = await this.bot.internal.sendMessage(this.payload)
+ }
+ await this.addResult(message)
+ this.payload.text = ''
+ }
+
+ // 遍历消息元素
+ async visit(element: h) {
+ const { type, attrs, children } = element
+ if (type === 'text') {
+ this.payload.text += h.escape(attrs.content)
+ } else {
+ await this.render(children)
+ }
+ }
+}
+```
+
+一个 `MessageEncoder` 类需要提供 `flush` 和 `visit` 两个方法。前者用于发送缓冲区内的消息,后者用于遍历消息元素。消息发送完成后,还需要构造相应的 `Session`,用于触发 `send` 会话事件并存储于 `results` 数组中。
+
+与此同时,我们还需要修改 `TelegramBot` 类,为其添加静态属性。实现了 `MessageEncoder` 静态属性后,就无需手动实现 `bot.sendMessage()` 和 `bot.sendPrivateMessage()` 方法了:
+
+```ts title=bot.ts
+export class TelegramBot extends Bot {
+ static MessageEncoder = TelegramMessageEncoder
+}
+```
+
+### 行内元素
+
+上面的例子仅仅包含了消息编码器的基本结构,并未实现除了文本外的任何消息元素。对于任何非文本元素,上面的代码都会回退到其内部的文本。要添加更多消息元素的支持,只需在 `visit` 方法中添加更多的判断分支,就像这样:
+
+```ts
+if (type === 'text') {
+ this.payload.text += h.escape(attrs.content)
+} else if (['b', 'strong', 'i', 'em', 'u', 'ins', 's', 'del'].includes(type)) {
+ // 这些元素都是 Telegram 已经支持的,直接渲染成 HTML 即可
+ this.payload.text += `<${type}>`
+ await this.render(children)
+ this.payload.text += `${type}>`
+} else if (type === 'at') {
+ // 将 at 渲染为用户链接
+ this.payload.text += `@${attrs.name || attrs.id}`
+} else {
+ await this.render(children)
+}
+```
+
+### 消息分片
+
+在 Koishi 中,一次消息发送可能在目标平台产生多条独立的消息,称为消息分片。这也是为什么上面的 `results` 是一个数组。消息分片产生的原因是多样的:
+
+- 某些元素的语义就是发送独立的消息 (例如 ``)
+- 部分平台不支持某些消息元素的组合 (例如图文混合发送),此时必须对消息进行拆分
+- 待发送的消息长度超出平台限制,此时必须对消息进行拆分
+
+在需要对消息进行分片的场合,我们可以手动调用 `flush()` 方法。下面的代码展示了如何实现 `` 元素:
+
+```ts
+// 忽略前面的部分
+} else if (type === 'message') {
+ // 在解析内部元素之前先清空缓冲区
+ await this.flush()
+ await this.render(children)
+ await this.flush()
+} else ...
+```
+
+### 资源元素
+
+由于不同平台对于媒体资源的支持类型、发送方式、渲染形式有所不同,因此资源元素的情况会更加复杂。可以大致将各种平台规定的发送方式分为以下几类:
+
+1. 通过不同的 API 发送不同类型的资源 (例如 Telegram)
+2. 使用统一的 API,但通过不同的字段区分资源类型 (例如 Discord)
+3. 先上传资源获得链接或资源 ID,再调用发送 API (例如 Lark)
+
+这里我们还是以 Telegram 平台为例。首先照例修改 `visit` 方法。由于 Telegram 仅支持资源 + 文本的组合 (文本显示在资源下方),因此我们需要进行消息分片:
+
+```ts
+// 忽略前面的部分
+} else if (['image', 'audio', 'video', 'file'].includes(type)) {
+ await this.flush()
+ this.asset = element
+} else ...
+```
+
+接着,我们需要在 `flush` 方法中处理资源元素。Telegram 的资源上传接口是 `sendPhoto`、`sendAudio` 等,与文本所用的 `sendMessage` 不同,因此我们需要根据资源类型进行判断:
+
+```ts
+class TelegramMessageEncoder extends MessageEncoder {
+ async flush() {
+ let message: Telegram.Message
+ if (this.asset) {
+ const form = new FormData()
+ for (const key in this.payload) {
+ form.append(key, this.payload[key].toString())
+ }
+ const { type, attrs } = this.asset
+ const { filename, data } = await this.bot.ctx.http.file(attrs.url, attrs)
+ if (type === 'image') {
+ form.append('photo', data, filename)
+ message = await this.bot.internal.sendPhoto(form)
+ } else if (type === 'audio') {
+ form.append('audio', data, filename)
+ message = await this.bot.internal.sendAudio(form)
+ } else if (type === 'video') {
+ form.append('video', data, filename)
+ message = await this.bot.internal.sendVideo(form)
+ } else if (type === 'file') {
+ form.append('document', data, filename)
+ message = await this.bot.internal.sendDocument(form)
+ }
+ this.asset = null
+ } else if (this.payload.text) {
+ message = await this.bot.internal.sendMessage(this.payload)
+ }
+ await this.addResult(message)
+ this.payload.text = ''
+ }
+}
+```
+
+差不多这样就实现了资源元素的发送。值得一提的是,这里的代码使用了 `http.file()` 方法。它可以自动为我们处理 `http:`、`file:`、`data:` 等各种协议的资源链接,并将它们统一转换为 `ArrayBuffer`。这可以省去适配器解析资源链接的步骤,对于适配器开发是非常方便的。
+
+## 进阶知识
+
+下面的知识并非适用于所有适配器。但对于一些特殊的平台,你可能会用到它们。
+
+### 被动型平台
+
+我们通常将机器人做出的交互行为分为两种:主动交互和被动交互。
+
+- 主动交互是指机器人主动进行某些操作,例如定时任务、通知推送。
+- 被动交互是指机器人接收到特定事件后做出的响应,例如消息回复、入群欢迎。
+
+遗憾的是,部分平台会限制机器人的主动交互能力。例如,在 QQ 频道中,机器人每天只能发送极少量的主动消息;而对于被动消息,则必须在用户发送消息后的短时间内回复。这种平台被称为**被动型平台**。
+
+被动型平台要求适配器在发送消息时尽可能带有回复目标。当然 Koishi 也提供了解决方案:
+
+```ts{5}
+class QQGuildMessageEncoder {
+ async flush() {
+ await this.bot.internal.sendMessages(this.channelId, {
+ content: this.content,
+ msgId: this.options?.session?.messageId,
+ })
+ }
+}
+```
+
+在这一段代码中使用了 `this.options`,它存储了一些额外的发送选项。其中 `session` 正好对应着接收到消息的会话对象。当我们调用 `session.send()` 时,Koishi 会把当前的会话对象传递给 `MessageEncoder`。这样一来,我们就可以在发送消息时带上回复目标了。
+
+### 资源反向代理
+
+一些平台会使用 ID 标识资源文件 (例如 Lark)。当你接收到来自平台的消息时,拿到的是资源 ID 而非资源链接。此时你需要将资源 ID 转换为资源链接,才能构造合法的资源元素。
+
+::: tip
+Telegram 是另一种特殊情况。尽管其提供的资源链接是可用的,但这个链接中会明文包含机器人令牌,并非可以公开使用的链接。因此 Telegram 和其他类似平台也适用于这一节的内容。
+:::
+
+对于这种情况,一种**不推荐**的做法是直接下载资源,并转存为 `data:` 链接放入消息中。之所以不推荐,是因为这种做法有两大致命缺点:
+
+1. 这些图片本来可以按需加载,但现在却被强制下载到本地,造成额外的带宽消耗。
+2. 编码为 `data:` 会导致消息体积大幅增加,极大影响消息处理的性能。
+
+那么,有没有更好的解决方案呢?答案便是资源反向代理。我们要做的,是在本地提供一个路由,将资源 ID 映射到资源链接。这样一来,上面提到的两个问题也就都解决了。
+
+下面是 Lark 适配器的一部分代码,用于实现资源反向代理 (位于 `adapter.ts`):
+
+```ts
+class LarkAdapter {
+ constructor(ctx: Context) {
+ ctx.router.get('/lark/assets/:message_id/:key', async (ctx) => {
+ const key = ctx.params.key
+ const messageId = ctx.params.message_id
+ const selfId = ctx.request.query.self_id
+ const bot = this.bots.find((bot) => bot.selfId === selfId)
+ if (!bot) return ctx.status = 404
+ const response = await bot.http(`/im/v1/messages/${messageId}/resources/${key}`, {
+ method: 'GET',
+ params: { type: 'image' },
+ responseType: 'stream',
+ })
+ ctx.status = 200
+ ctx.response.headers['content-type'] = response.headers['content-type']
+ ctx.response.body = response.data
+ })
+ }
+}
+```
+
+然后在接收消息的逻辑中,我们只需要将资源 ID 转换为资源链接即可:
+
+```ts
+h.image(`http://${host}/image/${message_id}/${image_key}?self_id=${selfId}`)
+```
+
+::: tip
+反向代理同时也带来了一个新的问题,那就是当这个链接被原样发送时,外网可能无法访问到这个链接。但无需担心,上面提到的 `http.file()` 方法恰好可以解决这个问题。因此,即使经过了反向代理,Koishi 也可以确保消息的跨平台转发插件能够正常工作。
+:::
diff --git a/de-DE/guide/adapter/writing.md b/de-DE/guide/adapter/writing.md
deleted file mode 100644
index f654a4d2590c..000000000000
--- a/de-DE/guide/adapter/writing.md
+++ /dev/null
@@ -1,172 +0,0 @@
-# 编写适配器插件
-
-::: danger 注意
-此页文档正在施工,其中的内容可能不是最新。
-:::
-
-Koishi 通过 **适配器 (Adapter)** 实现对多账户和跨平台的实现。在我们开始之前,首先你需要了解多机器人在 Koishi 中究竟是以何种方式进行组织的。这个问题用一句话来说就是:一个应用可以有多个平台,每个平台可以有多个适配器,每个适配器可以有多个机器人。在上面的例子中,形如 onebot 的是 **平台名称**,形如 onebot:http 的是**适配器名称**。
-
-当应用被创建时,它会按照配置创建所有的适配器和机器人实例。如果多个机器人使用了同一种适配器,那么会创建一个适配器实例绑定多个机器人。它们的关系用代码表示就是这样:
-
-```ts no-extra-header
-class App {
- // 可以使用 adapters[type] 找到对应的适配器
- adapters: Record
- // 可以使用 Array 方法遍历全部 bots
- // 也可以使用 bots[`${platform}:${selfId}`] 找到具体的某一个
- bots: Bot[] & Record
-}
-
-class Adapter {
- // 可以使用 Array 方法遍历全部 bots
- // 也可以使用 bots[selfId] 找到具体的某一个
- bots: Bot[] & Record
- // 所在的 App 实例
- app: App
-}
-
-class Bot {
- // 所在的 App 实例
- app: App
- // 所在的 Adapter 实例
- adapter: Adapter
-}
-```
-
-当适配器收到一个上报事件时,它会首先对事件进行鉴权,并处理好改事件的响应值。接着这个适配器将按照事件的内容生成一个会话对象,并使用 `adapter.dispatch` 将其在对应的上下文触发事件。因此,如果你需要编写一个平台支持,你只需要做三件事:
-
-- 编写这个平台的 Bot 类,实现 Koishi 所需的方法
-- 编写这个平台的 Adapter 类,实现 start() 和 stop() 方法
-- 注册这个 Adapter
-
-### 一个 Webhook 例子
-
-下面是一个使用 Webhook 的例子。适配器通过 http post 请求接受事件推送。
-
-::: tabs code
-```js no-extra-header
-const { Adapter, Bot, Session } = require('koishi')
-
-class MyBot extends Bot {
- async sendMessage(channelId, content) {
- // 这里应该执行发送操作
- this.logger.debug('send:', content)
- return []
- }
-}
-
-class MyAdapter extends Adapter {
- constructor(ctx) {
- // 请注意这里的第二个参数是应该是一个构造函数而非实例
- super(ctx, MyBot)
- }
-
- start() {
- // 收到 http post 请求时,生成会话对象并触发事件
- this.ctx.router.post('/', (ctx) => {
- const session = new Session(this.app, ctx.request.body)
- this.dispatch(session)
- })
- }
-
- stop() {}
-}
-
-// 注册适配器
-Adapter.types['my-adapter'] = MyAdapter
-```
-```ts no-extra-header
-import { App, Adapter, Bot, Session } from 'koishi'
-
-class MyBot extends Bot {
- async sendMessage(channelId: string, content: string) {
- // 这里应该执行发送操作
- this.logger.debug('send:', content)
- return []
- }
-}
-
-class MyAdapter extends Adapter {
- constructor(app: App) {
- // 请注意这里的第二个参数是应该是一个构造函数而非实例
- super(app, MyBot)
- }
-
- start() {
- // 收到 http post 请求时,生成会话对象并触发事件
- this.app.router.post('/', (ctx) => {
- const session = new Session(this.app, ctx.request.body)
- this.dispatch(session)
- })
- }
-
- stop() {}
-}
-
-// 注册适配器
-Adapter.types['my-adapter'] = MyAdapter
-```
-:::
-
-### 一个 WebSocket 例子
-
-WebSocket 的逻辑相比 Webhook 要稍微复杂一些,因此我们提供了一个工具类:
-
-::: tabs code
-```js no-extra-header
-const { Adapter, Bot, Session } = require('koishi')
-const WebSocket = require('ws')
-
-class MyAdapter2 extends Adapter.WsClient {
- constructor(app) {
- // MyBot 跟上面一样,我就不写了
- super(app, MyBot)
- }
-
- // prepare 方法要求你返回一个 WebSocket 实例
- prepare(bot) {
- return new WebSocket('ws://websocket-endpoint')
- }
-
- // connect 方法将作为 socket.on('open') 的回调函数
- connect(bot) {
- bot.socket.on('message', (data) => {
- const body = JSON.parse(data.toString())
- const session = new Session(this.app, body)
- this.dispatch(session)
- })
- }
-}
-
-// 注册适配器
-Adapter.types['another-adapter'] = MyAdapter2
-```
-```ts no-extra-header
-import { Adapter, Bot, Session } from 'koishi'
-import WebSocket from 'ws'
-
-class MyAdapter2 extends Adapter.WsClient {
- constructor(app: App) {
- // MyBot 跟上面一样,我就不写了
- super(app, MyBot)
- }
-
- // prepare 方法要求你返回一个 WebSocket 实例
- prepare(bot: MyBot) {
- return new WebSocket('ws://websocket-endpoint')
- }
-
- // connect 方法将作为 socket.on('open') 的回调函数
- connect(bot: MyBot) {
- bot.socket.on('message', (data) => {
- const body = JSON.parse(data.toString())
- const session = new Session(this.app, body)
- this.dispatch(session)
- })
- }
-}
-
-// 注册适配器
-Adapter.types['another-adapter'] = MyAdapter2
-```
-:::
diff --git a/de-DE/guide/basic/advanced.md b/de-DE/guide/basic/advanced.md
index 931d15d2fa59..3d78045d7ff0 100644
--- a/de-DE/guide/basic/advanced.md
+++ b/de-DE/guide/basic/advanced.md
@@ -27,7 +27,7 @@ ctx.bots[`${platform}:${selfId}`]
await bot.broadcast(['123456', '456789'], '全体目光向我看齐')
```
-但这样写需要知道每一个频道对应哪个机器人。对于启用了多个机器人的场景下,这么写就有点不方便了。幸运的是,Koishi 还有另一个方法:`ctx.broadcast()`。在启用了数据库的情况下,此方法会自动获取每个频道的 [受理人](../../manual/usage/permission.md#受理人机制),并以对应的账号发送消息:
+但这样写需要知道每一个频道对应哪个机器人。对于启用了多个机器人的场景下,这么写就有点不方便了。幸运的是,Koishi 还有另一个方法:`ctx.broadcast()`。在启用了数据库的情况下,此方法会自动获取每个频道的 [受理人](../../manual/usage/customize.md#受理人机制),并以对应的账号发送消息:
```ts
await ctx.broadcast(['onebot:123456', 'discord:456789'], '全体目光向我看齐')
diff --git a/de-DE/guide/console/extension.md b/de-DE/guide/console/extension.md
deleted file mode 100644
index af9d2b88daea..000000000000
--- a/de-DE/guide/console/extension.md
+++ /dev/null
@@ -1,128 +0,0 @@
-# 编写扩展
-
-## 创建扩展
-
-::: tabs code
-```npm
-npm i @koishijs/client @koishijs/plugin-console -D
-```
-```yarn
-yarn add @koishijs/client @koishijs/plugin-console -D
-```
-:::
-
-在项目中新建这几个文件:
-
-```diff
-└── my-plugin
-+ ├── client
-+ │ ├── index.ts
-+ │ ├── custom-page.vue
-+ │ └── tsconfig.json
- ├── src
- │ └── index.ts
- ├── package.json
- └── tsconfig.json
-```
-
-```ts title=client/index.ts no-extra-header
-import { Context } from '@koishijs/client'
-import Page from './custom-page.vue'
-
-export default (ctx: Context) => {
- // 此 Context 非彼 Context
- // 我们只是在前端同样实现了一套插件逻辑
- ctx.page({
- name: '页面标题',
- path: '/custom-page',
- component: Page,
- })
-}
-```
-
-```vue title=client/custom-page.vue
-
-
- 扩展内容
-
-
-```
-
-```json title=client/tsconfig.json
-{
- "compilerOptions": {
- "rootDir": ".",
- "module": "esnext",
- "moduleResolution": "node",
- "types": [
- // 这一行的作用是导入相关全局类型
- // 以便于在编辑器中显示更好的代码提示
- "@koishijs/client/global",
- ],
- },
- "include": ["."],
-}
-```
-
-接着修改你的入口文件:
-
-```ts title=src/index.ts
-import { Context } from 'koishi'
-// 此处需要导入 @koishijs/plugin-console 以获取类型
-import {} from '@koishijs/plugin-console'
-import { resolve } from 'path'
-
-export const name = 'my-plugin'
-
-export function apply(ctx: Context) {
- // 在已有插件逻辑的基础上,添加下面这段
- ctx.using(['console'], (ctx) => {
- ctx.console.addEntry({
- dev: resolve(__dirname, '../client/index.ts'),
- prod: resolve(__dirname, '../dist'),
- })
- })
-}
-```
-
-## 调试模式
-
-启动应用,并配置 console 插件进入调试模式:
-
-```yaml
-plugins:
- console:
- devMode: true
- my-plugin:
-```
-
-你就可以在网页中看到自己刚刚创建的页面了。
-
-## 构建代码
-
-调试好你的扩展后,下一步就是构建了。修改你的 package.json:
-
-```json title=package.json
-{
- "files": [
- "lib", // 我们假设 src 目录编译到 lib 目录
- "dist", // 这里的 dist 目录就是留给 client 的
- ],
- "scripts": {
- // @koishijs/client 提供了一个指令 koishi-console build
- // 它可以用来构建 client 目录中的扩展台扩展到 dist 目录
- "build:console": "koishi-console build",
- },
-}
-```
-
-然后运行上面的脚本就大功告成啦:
-
-::: tabs code
-```npm
-npm run build:console
-```
-```yarn
-yarn build:console
-```
-:::
diff --git a/de-DE/guide/console/index.md b/de-DE/guide/console/index.md
index 7983d7ee6391..9b9bd48751fd 100644
--- a/de-DE/guide/console/index.md
+++ b/de-DE/guide/console/index.md
@@ -1,7 +1,7 @@
# 扩展控制台
::: tip
-在学习本章之前,建议先完整阅读 [入门 > 认识控制台](../../manual/console/index.md)。
+在学习本章之前,建议先完整阅读 [入门 > 认识控制台](../../manual/usage/market.md#认识控制台)。
:::
## 创建扩展
diff --git a/de-DE/guide/database/builtin.md b/de-DE/guide/database/builtin.md
new file mode 100644
index 000000000000..4125caa6409e
--- /dev/null
+++ b/de-DE/guide/database/builtin.md
@@ -0,0 +1,114 @@
+# 内置数据结构
+
+::: danger 注意
+此页文档正在施工,其中的内容可能不是最新。
+:::
+
+上面介绍了一些 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)
+```
diff --git a/de-DE/guide/database/index.md b/de-DE/guide/database/index.md
index 6bbf5a37d01c..a639755ad30d 100644
--- a/de-DE/guide/database/index.md
+++ b/de-DE/guide/database/index.md
@@ -1,52 +1,12 @@
-# 使用数据库
+# 基本用法
::: tip
-本章所介绍的内容需要你安装一个数据库支持。如果你暂时不打算使用数据库,那么可以略过。
+`ctx.database` 并非内置服务,因此如果你的插件需要使用数据库功能,需要[声明依赖](../plugin/service.md#using-属性)。
:::
-对于几乎所有大型机器人项目,数据库的使用都是不可或缺的。但如果每个插件都使用了自己的数据库,这将导致插件之间的兼容性非常差——用户要么选择同时安装多个数据库,要么只能放弃一些功能或者重复造轮子。为此,Koishi 设计了一整套对象关系映射 (ORM) 接口,它易于扩展并广泛地运用于各种插件中。同时,我们也提供了一些常用数据库的官方插件,足以应对绝大部分使用场景。
+对于几乎所有大型机器人项目,数据库的使用都是不可或缺的。但如果每个插件都独立处理与数据库的交互,这将导致插件之间的兼容性非常差——用户要么选择同时安装多个数据库,要么只能放弃一些功能。为此,Koishi 设计了一整套对象关系映射 (ORM) 接口,它易于扩展并广泛地运用于各种插件中。同时,我们也提供了一些常用数据库的官方插件,足以应对绝大部分使用场景。
-## 安装数据库
-
-如果你是插件开发者,你并不需要关心具体的数据库实现。但是如果你是 Koishi 的使用者,只有当安装了数据库你才能正常使用所有的特性。首先你需要安装数据库依赖:
-
-::: tabs code
-```npm
-# 我们以 mysql 数据库为例
-npm i @koishijs/plugin-database-mysql -D
-```
-```yarn
-# 我们以 mysql 数据库为例
-yarn add @koishijs/plugin-database-mysql -D
-```
-:::
-
-然后与你添加插件同样的方法配置你的数据库:
-
-```yaml title=koishi.yml
-plugins:
- database-mysql:
- host: host
- port: 3306
- user: root
- password: password
- database: database
-```
-
-运行程序后,你就可以通过访问 `ctx.database` 来调用数据库接口了:
-
-```ts
-// @errors: 2304
-// 获取用户数据
-const user = await ctx.database.getUser(platform, id)
-
-// 修改频道数据
-await ctx.database.setChannel(platform, id, { assignee: '123456789' })
-```
-
-你可以在后面的 API 文档中看到全部内置的 [数据库方法](../../api/database/database.md)。
-
-## 获取数据
+## `get`:查询数据
使用 `database.get()` 方法以获取特定表中的数据。下面是一个最基本的形式:
@@ -61,8 +21,8 @@ await ctx.database.get('schedule', [1234, 5678])
对于复杂的数据表,如果你只需要获取少数字段,你可以通过第三个参数手动指定要获取的字段:
```ts
-// 返回的数组中每个元素只会包含 command, lastCall 属性
-await ctx.database.get('schedule', [1234], ['command', 'lastCall'])
+// 返回的数组中每个元素只会包含 command, time 属性
+await ctx.database.get('schedule', [1234], ['command', 'time'])
```
你还可以向第二个参数传入一个对象,用来查询非主键上的数据或者同时指定多列的值:
@@ -97,31 +57,19 @@ await ctx.database.get('schedule', {
你可以在 [这里](../../api/database/query.md) 看到完整的查询表达式 API。
-## 添加和删除数据
+## `create`:插入数据
-添加和删除数据的语法也非常简单:
+使用 `database.create()` 方法以插入数据。
```ts
-// @errors: 2304
-// 从 schedule 表中删除特定 id 的数据行
-// 第二个参数也可以使用上面介绍的查询表达式
-await ctx.database.remove('schedule', [id])
-
// 向 schedule 表中添加一行数据,data 是要添加的数据行
-// 返回值是添加的行的完整数据(包括自动生成的 id 和默认属性等)
+// 返回值是添加的行的完整数据 (包括自动填充的 id 和默认属性等)
await ctx.database.create('schedule', row)
```
-## 修改数据
-
-Koishi 提供了两种修改数据的方法。我们将逐一介绍。
+如果你想要批量插入数据,可以使用下面介绍的 `database.upsert()` 方法。
-| | set | upsert |
-| ---- | -------------- | ----------- |
-| 作用范围 | 支持复杂的查询表达式 | 只能限定特定字段的值 |
-| 插入行为 | 如果不存在则不会进行任何操作 | 如果不存在则会插入新行 |
-
-### 使用 set 修改数据
+## `set`:修改数据
`database.set()` 方法需要传入三个参数:表名、查询条件和要修改的数据。
@@ -129,7 +77,7 @@ Koishi 提供了两种修改数据的方法。我们将逐一介绍。
// 第二个参数也可以使用上面介绍的查询表达式
await ctx.database.set('schedule', 1234, {
assignee: 'onebot:123456',
- lastCall: new Date(),
+ time: new Date(),
})
```
@@ -146,7 +94,7 @@ await ctx.database.set('foo', { date: new Date() }, {
你可以在 [这里](../../api/database/evaluation.md) 看到完整的求值表达式 API。
-### 使用 upsert 修改数据
+## `upsert`:修改或插入数据
`database.upsert()` 的逻辑稍微有些不同,需要你传入一个数组:
@@ -186,3 +134,32 @@ await ctx.database.upsert('user', rows, 'onebot')
// 以复合键为基准对数据表进行更新,你需要确保每一个元素都拥有 platform 和 id 属性
await ctx.database.upsert('channel', rows, ['platform', 'id'])
```
+
+## `remove`:删除数据
+
+使用 `database.remove()` 方法以删除特定表中的数据。
+
+```ts
+// 从 schedule 表中删除特定 id 的数据行
+// 第二个参数也可以使用上面介绍的查询表达式
+await ctx.database.remove('schedule', [id])
+```
+
+## 对比 set 和 upsert
+
+`set` 与 `upsert` 方法都可以用于修改已经存在的数据,它们的区别如下表所示:
+
+| | set | upsert |
+| ---- | ---------------- | ------------- |
+| 作用范围 | 支持复杂的查询表达式 | 只能限定特定字段的值 |
+| 插入行为 | 如果数据不存在则不会进行任何操作 | 如果数据不存在则会插入新行 |
+
+## 对比 create 和 upsert
+
+`create` 与 `upsert` 方法都可以用于插入新的数据,它们的区别如下表所示:
+
+| | create | upsert |
+| ---- | ----------- | ------------- |
+| 插入数量 | 只能插入一条数据 | 可以批量插入多条数据 |
+| 返回值 | 返回经过填充后的数据 | 没有返回值 |
+| 冲突行为 | 如果数据已存在则会报错 | 如果数据已存在则会执行修改 |
diff --git a/de-DE/guide/database/model.md b/de-DE/guide/database/model.md
index 22fe3aa36185..6618dacc3c34 100644
--- a/de-DE/guide/database/model.md
+++ b/de-DE/guide/database/model.md
@@ -1,92 +1,133 @@
-# 扩展数据模型
+# 数据模型
-如果你的插件需要声明新的字段或者表,你可以通过 `ctx.model` 来对数据模型进行扩展。请注意,数据模型的扩展一定要在使用前完成,不然后续数据库操作可能会失败。
+Koishi 的架构允许任何插件对数据库的结构进行扩展。你就可以在不修改 Koishi 或其他插件源码的情况下,为数据库添加新的字段或者表。这些功能都是通过 `ctx.model` 提供的。
-## 扩展字段
+请注意:数据模型的扩展一定要在使用前完成,不然后续数据库操作可能会失败。
-向内置的 User 表中注入字段的方式如下:
+## 扩展表和字段
-```ts
-// @errors: 1117
-// TypeScript 用户需要进行类型合并
-declare module 'koishi' {
- interface User {
- foo: string
- }
-}
-
-ctx.model.extend('user', {
- // 向用户表中注入字符串字段 foo
- foo: 'string',
- // 你还可以配置默认值为 'bar'
- foo: { type: 'string', initial: 'bar' },
-})
-```
-
-向 Channel 注入字段同理。
-
-## 扩展表
-
-利用 `ctx.model.extend()` 的第三个参数,我们就可以定义新的数据表了:
+可以使用 `model.extend()` 方法扩展一个新的数据表,其中的第一个参数是表名,第二个参数包含了各字段的类型声明。下面的代码向数据库中扩展了一个名为 `schedule` 的表:
```ts
-// @errors: 2440
-// TypeScript 用户需要进行类型合并
declare module 'koishi' {
interface Tables {
schedule: Schedule
}
}
+// 这里是新增表的接口类型
export interface Schedule {
id: number
assignee: string
time: Date
- lastCall: Date
interval: number
command: string
session: Session.Payload
}
ctx.model.extend('schedule', {
- // 各字段类型
+ // 各字段的类型声明
id: 'unsigned',
assignee: 'string',
time: 'timestamp',
- lastCall: 'timestamp',
interval: 'integer',
command: 'text',
session: 'json',
-}, {
- // 使用自增的主键值
- autoInc: true,
})
```
-## 创建索引
-
-我们还可以为数据库声明索引:
+`model.extend()` 同样也可以向已经存在的表中注入新的字段,使用方法与上面完全一致。例如,下面的代码向内置的 `User` 表中注入了 `foo` 字段:
```ts
declare module 'koishi' {
- interface Tables {
- foo: Foo
+ interface User {
+ foo: string
}
}
-interface Foo {
- name: string
- bar: string
- baz: string
- uid: 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
+ }
+ }
}
-// ---cut---
+// 声明嵌套类型时,对象的多级属性被拼接为一个字符串
+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'],
@@ -97,3 +138,33 @@ ctx.model.extend('foo', {}, {
},
})
```
+
+## 整表迁移 实验性
+
+::: 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')`。
diff --git a/de-DE/guide/database/permission.md b/de-DE/guide/database/permission.md
new file mode 100644
index 000000000000..246a44b7bb38
--- /dev/null
+++ b/de-DE/guide/database/permission.md
@@ -0,0 +1,129 @@
+# 权限管理 实验性
+
+::: warning
+权限管理目前属于实验性功能,API 可能随时会发生变化。
+:::
+
+## 权限是什么?
+
+权限其实就是执行某些操作的能力。权限具有唯一的标识符,通常由小写英文字母、数字、短横线和点构成。下面是一些你可能会见到的权限名称:
+
+- `user.514`:ID 为 514 的用户的权限
+- `group.114`:ID 为 114 的用户组的权限
+- `authority.3`:权限等级 3 的权限
+- `command.foo`:指令 foo 的权限
+- `command.foo.option.bar`:指令 foo 选项 bar 的权限
+- `onebot.admin`:onebot 平台下群管理员的权限
+- `bot.channel.mute`:能够禁言频道的机器人的权限
+- `config.write`:能够写入配置文件的权限
+
+权限之间存在两种关系:继承和依赖。Koishi 不区分权限与权限组的概念,权限组只是继承了其他权限的权限。你可以将用户和用户组也都视为一种权限组。
+
+## 权限的继承
+
+权限可以继承其他权限。它的基本写法如下:
+
+```ts
+ctx.permissions.inherit(A, B)
+```
+
+如果权限 A 继承了权限 B,那么拥有权限 A 的主体将被视为同时拥有权限 B。
+
+例:ID 为 514 的用户拥有权限等级 3,指令 foo 所需要的权限等级是 2。这种情况下该用户显然应该可以调用该指令。那么这种调用关系具体是如何体现的呢?
+
+```text
+user.514 > authority.3 > authority.2 > command.foo
+```
+
+这里出现了三个继承关系:
+
+- `user.114 > authority.3`,因为 ID 为 514 的用户拥有权限等级 3
+- `authority.3 > authority.2`,因为权限等级 3 天生比权限等级 2 大(内置逻辑)
+- `authority.2 > command.foo`,因为指令 foo 被权限等级 2 继承
+
+由此,权限的继承关系能够顺利表达已有的权限等级机制,并且具备更强的表达能力。
+
+### 权限的多继承
+
+权限继承除了不能循环外,几乎没有任何限制。因此,任何一个权限既可以被多个权限继承,也可以继承多个权限。下面分别展示一些使用例。
+
+例:ID 为 514 的用户同时继承了权限等级 1,而指令 foo 所需权限等级 2,此时该用户并不能调用 foo 指令。但如果现在我们让该用户直接继承 foo 指令的调用权限,会发生什么呢?
+
+```text
+user.514 > authority.1
+ > command.foo
+```
+
+在这张图中,`user.514` 并不能经由 `authority.1` 到达 `command.foo`,但是添加第二条边后又可以直接到达 `command.foo` 了。因此该用户此时就又可以调用 foo 指令了。
+
+可以看到,权限的继承为我们提供了一种能力,可以允许特定低等级用户去调用高级权限的指令,这种能力是过去的权限等级所不具有的。
+
+例:我们希望某个管理型指令 foo 既可以被权限等级 2 的用户调用,又可以被 QQ 群的管理员调用。此时我们可以对 foo 指令进行以下配置:
+
+```text
+authority.2 >
+onebot.admin > command.foo
+```
+
+这样一来,一个用户只需满足上述两个条件之一就可以调用此指令了。
+
+可以看到,判断指令是否可以被用户调用,本质上是判断用户自身的权限是否可以顺着权限继承的关系到达指令的权限。同时,权限的继承允许我们给指令的调用设置多个不同条件。
+
+## 权限的依赖
+
+Koishi 中的权限不仅存在继承关系,还存在依赖关系。它的基本写法如下:
+
+```ts
+ctx.permissions.depend(A, B)
+```
+
+如果权限 A 依赖了权限 B,那么要执行权限 A 的操作时必须同时检查权限 B。
+
+例:foo 指令的代码中调用了 bar 指令,因此 foo 指令依赖 bar 指令。
+
+```text
+command.foo -> command.bar
+```
+
+如果用户只拥有 foo 的权限,没有调用 bar 的权限,他依然无法调用 foo 指令。
+
+例:foo 指令的代码中使用了 `bot.muteChannel()`。
+
+```text
+command.foo -> bot.channel.mute
+```
+
+如果机器人没有实现此 API,用户就无法在该机器人上调用 foo 指令。
+
+由此可见,权限的依赖与继承不同。继承更多的是一种管理上的考虑,而依赖则关乎具体的功能实现。
+
+## 权限访问器
+
+在上面的介绍中,如果要定义新的权限,就必须手动分配给用户或用户组后才能使用。有没有方法自动为一个会话分配权限呢?这就是权限访问器的功能。
+
+```ts
+ctx.permissions.provide('onebot.admin', async (name, session) => {
+ return session.onebot?.sender?.role === 'admin'
+})
+```
+
+上面的代码的作用是:当某个会话处于 onebot 平台,并且发送者是群内管理员时,自动附加一个 onebot.admin 权限。利用这种技术,我们就可以为特定平台提供权限能力了。
+
+每个权限可以定义多个访问器函数。在运行时必须通过每一个访问器函数的检查才能视为拥有权限。
+
+一个权限要么是普通权限,要么是访问器权限。下面是一些区别:
+
+| 普通权限 | 访问器权限 |
+| ----------- | --------- |
+| 手动分配给用户 (组) | 自动分配给会话 |
+| 可以被其他权限继承 | 不能被其他权限继承 |
+
+## 权限国际化
+
+普通权限要被用于指令和控制台中显示,因此需要做国际化。具体的做法也很简单:
+
+- 通过 API 定义:使用 `permission.{name}` 提供翻译文本
+- 通过指令定义:定义时提供文本 (自动视为当前用户语言),或通过 `--locale` 指定特定语言的文本
+- 通过控制台定义:可以在控制台「用户管理」界面中配置用户组文本
+
+访问器权限由于其不能被其他权限继承,因此不需要做国际化。
diff --git a/fr-FR/guide/adapter/encoder.md b/de-DE/guide/database/select.md
similarity index 80%
rename from fr-FR/guide/adapter/encoder.md
rename to de-DE/guide/database/select.md
index d06c29fff73c..87b80776b7d7 100644
--- a/fr-FR/guide/adapter/encoder.md
+++ b/de-DE/guide/database/select.md
@@ -1,4 +1,4 @@
-# 消息编码
+# 进阶查询技巧
::: danger 注意
此页文档正在施工,其中的内容可能不是最新。
diff --git a/de-DE/guide/develop/publish.md b/de-DE/guide/develop/publish.md
index ee7ab64849db..d9471818171c 100644
--- a/de-DE/guide/develop/publish.md
+++ b/de-DE/guide/develop/publish.md
@@ -158,6 +158,11 @@ yarn pub [...name]
这将发布所有版本号发生变动的插件。
+::: tip
+从插件成功发布到进插件市场需要一定的时间 (通常在 15 分钟内),请耐心等待。
+:::
+
+:::: tip
如果你配置了国内镜像,你可能会遇到以下的错误提示:
```text
@@ -177,7 +182,8 @@ yarn login
```
:::
-发布成功后,你可以将镜像重新设置为国内镜像,以保证后续的下载速度。
+发布成功后,你可以将镜像重新设置为国内镜像,以保证后续的下载速度。 :
+:::
## 更新插件版本
diff --git a/de-DE/guide/develop/setup.md b/de-DE/guide/develop/setup.md
index d5e4f75f7964..075dc0119062 100644
--- a/de-DE/guide/develop/setup.md
+++ b/de-DE/guide/develop/setup.md
@@ -2,46 +2,7 @@
本节将介绍推荐的开发环境搭建流程。如果某些软件已经安装完成,可以跳过对应的步骤。
-## 安装 Node.js
-
-Koishi 需要 [Node.js](https://nodejs.org/) (最低 v14,推荐使用 LTS) 运行环境,你需要自己安装它。
-
-### 下载安装包
-
-首先我们前往 [Node.js](https://nodejs.org/) 的官方网站:
-
-![home](/manual/nodejs/home-dark.webp) {.dark-only}
-
-![home](/manual/nodejs/home-light.webp) {.light-only}
-
-在这里可以看到两个巨大的按钮,分别对应着 **LTS (长期维护版)** 和 **Current (最新版本)**。我们建议你选择更加稳定的 LTS 版本,点击按钮即可下载安装包。
-
-随后,运行下载好的安装包,根据提示完成整个安装流程即可。
-
-### 安装包管理器
-
-Node.js 自带名为 [npm](https://www.npmjs.com/) 的包管理器,你可以直接使用它。我们同时也推荐功能更强大的 [yarn](https://classic.yarnpkg.com/) 作为包管理器。它的安装非常简单,只需打开命令行输入下面的命令:
-
-```sh
-# 安装 yarn
-npm i -g yarn
-
-# 查看版本
-yarn -v
-```
-
-### 配置镜像源
-
-如果你是国内用户,从 npm 或 yarn 上下载依赖可能非常慢。因此,我们推荐你配置一下镜像源,以提升安装速度。
-
-::: tabs code
-```npm
-npm config set registry https://registry.npmmirror.com
-```
-```yarn
-yarn config set registry https://registry.npmmirror.com
-```
-:::
+
### 注册 npm
@@ -63,7 +24,7 @@ Git 是最普遍使用的版本控制工具。前往 [官网](https://git-scm.co
![downloads](/manual/git/downloads.webp)
-国内的 Windows 用户也可以选择从 [镜像](https://registry.npmmirror.com/binary.html?path=git-for-windows/) 下载。如果不知道下载哪个版本,可以在上面的官网中看到 (比如现在是 2.39.1)。
+国内的 Windows 用户也可以选择从 [镜像](https://registry.npmmirror.com/binary.html?path=git-for-windows/) 下载。如果不知道下载哪个版本,可以在上面的官网中看到 (比如图中就是 2.39.1)。
获取到安装包后,双击运行。安装过程无需手动配置,一直点击下一步即可完成安装。
@@ -111,7 +72,6 @@ yarn create koishi
如果你顺利完成了上述操作,你的应用此时应该已经是启动状态,并弹出了控制台界面。接下来的几节中我们将学习更多的命令行用法,因此我们可以先关闭 Koishi。在命令行中按下 `Ctrl+C` 组合键即可停止 Koishi 的运行。
-
### 注册 npm
@@ -63,7 +24,7 @@ Git 是最普遍使用的版本控制工具。前往 [官网](https://git-scm.co
![downloads](/manual/git/downloads.webp)
-国内的 Windows 用户也可以选择从 [镜像](https://registry.npmmirror.com/binary.html?path=git-for-windows/) 下载。如果不知道下载哪个版本,可以在上面的官网中看到 (比如现在是 2.39.1)。
+国内的 Windows 用户也可以选择从 [镜像](https://registry.npmmirror.com/binary.html?path=git-for-windows/) 下载。如果不知道下载哪个版本,可以在上面的官网中看到 (比如图中就是 2.39.1)。
获取到安装包后,双击运行。安装过程无需手动配置,一直点击下一步即可完成安装。
@@ -111,7 +72,6 @@ Follow the prompts and finalize the initialization process.
If you have successfully finalized the operations above, your application should be already launched, the Koishi Console Web UI should be also opened. 接下来的几节中我们将学习更多的命令行用法,因此我们可以先关闭 Koishi。在命令行中按下 `Ctrl+C` 组合键即可停止 Koishi 的运行。
-
diff --git a/en-US/guide/plugin/service.md b/en-US/guide/plugin/service.md
index cc0a68027e1d..d631d3959d03 100644
--- a/en-US/guide/plugin/service.md
+++ b/en-US/guide/plugin/service.md
@@ -301,3 +301,88 @@ class Console extends Service {
}
```
:::
+
+## 在 `package.json` 中声明依赖
+
+如果你打算将插件发布到插件市场,我们建议在插件的 [`package.json`](../develop/publish.md#koishi-字段) 中对其所提供和使用的服务进行声明。这些字段将显示在控制台中插件的详情页中,帮助使用者更好地理解插件的功能。
+
+```json title=package.json
+{
+ "koishi": {
+ "service": {
+ "required": ["database"],
+ "optional": ["assets", "console"],
+ "implements": ["dialogue"]
+ }
+ }
+}
+```
+
+在这里,`required` 对应于必需依赖,`optional` 对应于可选依赖,`implements` 对应于提供的服务。如果你的插件没有使用或提供服务,那么对应的字段可以省略。
+
+### 关于 `peerDependencies`
+
+一个很容易混淆的概念是 `package.json` 自带的 `peerDependencies` 字段。这个字段用于声明一个 npm 包的依赖,但声明的依赖需要由用户安装 (或由包管理器自动安装到依赖树顶层)。是不是跟服务的概念非常像?它们之间的区别如下:
+
+1. `peerDependencies` 描述的是 npm 包的运行时行为。如果对应的依赖不存在,那么程序预期无法正常运行 (除非在 `peerDependenciesMeta` 中指明可选性)。而对于 Koishi 插件来说,由于有了 `using` 机制,即使依赖的服务不存在,程序也不会崩溃。
+
+2. `peerDependencies` 是一对一的关系,即依赖的只能是另一个确定的包。而 Koishi 中的服务则是一对多的关系,即依赖的服务可以被多个插件所提供。
+
+基于以上两点,关于是否要在插件的 `package.json` 中为服务声明 `peerDependencies`,我们可以根据插件从依赖中导入的内容来判断:
+
+#### 情况一:插件仅导入了类型声明
+
+这是绝大部分的情况。在这种情况下,我们无需声明 `peerDependencies`,但建议把依赖加入 `devDependencies` 中。下面将以 puppeteer 插件提供的服务为例介绍:
+
+```ts
+// import {} 的意思是,我们只需要类型声明,而不需要导入任何内容
+// 在编译后,这个语句会被移除,不会引入任何副作用
+import {} from '@koishijs/plugin-puppeteer'
+
+// 通过 using 属性声明依赖,并通过 ctx 来访问服务
+export const using = ['puppeteer']
+export function apply(ctx: Context) {
+ ctx.puppeteer.render()
+}
+```
+
+此时插件的 `package.json` 可以这样写:
+
+```json title=package.json
+{
+ "service": {
+ "required": ["puppeteer"]
+ },
+ "devDependencies": {
+ "@koishijs/plugin-puppeteer": "^2.0.0"
+ }
+}
+```
+
+#### 情况二:插件导入了类型声明以外的内容
+
+一个典型的例子是 console 服务。一些控制台扩展需要从 `@koishijs/plugin-console` 中导入 `DataService` 基类。此时你的代码应该是这样的:
+
+```ts
+import { DataService } from '@koishijs/plugin-console'
+
+export class ExamplePlugin extends DataService {
+ // ...
+}
+```
+
+此时你需要同时声明 `peerDependencies` 和 `devDependencies`:
+
+```json
+{
+ "service": {
+ "required": ["console"]
+ },
+ "peerDependencies": {
+ "@koishijs/plugin-console": "^5.13.0"
+ },
+ "devDependencies": {
+ "@koishijs/plugin-console": "^5.13.0"
+ }
+}
+```
diff --git a/en-US/guide/testing/index.md b/en-US/guide/testing/index.md
deleted file mode 100644
index e9fa3941161a..000000000000
--- a/en-US/guide/testing/index.md
+++ /dev/null
@@ -1,144 +0,0 @@
-# Unit Testing
-
-如果你是一位插件开发者,比起让机器人真正运行起来,你或许会更希望使用**单元测试**,因为它具有许多前者所不具有的优点:
-
-- 可以在无网络的情况下运行
-- 可以模拟出多用户交互等复杂情况
-- 可以在内存中模拟你想要的数据库
-- 能够有效避免风控带来的损失
-- 便于调试与错误定位
-
-本章将介绍官方插件 `@koishijs/plugin-mock`。你可以用它来快速检验你编写的 Koishi 插件。
-
-::: tip
-本节中介绍的样例用到了 [Mocha](https://mochajs.org/) 和 [Chai](https://www.chaijs.com/)。它们都是比较通用的测试库和断言库,但并非绑定 @koishijs/plugin-mock 一同使用。你也可以根据你的喜好选择其他工具,比如 [Jest](https://jestjs.io/) 等等。
-:::
-
-## Prerequisite
-
-安装所需的测试工具以及 @koishijs/plugin-mock:
-
-::: tabs code
-```npm
-npm i mocha chai @koishijs/plugin-mock -D
-```
-```yarn
-yarn add mocha chai @koishijs/plugin-mock -D
-```
-:::
-
-接着创建存放测试文件的 `tests` 目录,并在其中新建一个 `index.spec.js` 文件,开始编写你的单元测试:
-
-```ts title=tests/index.spec.js no-extra-header
-import { Context } from 'koishi'
-import mock from '@koishijs/plugin-mock'
-
-const app = new Context()
-
-app.plugin(mock)
-```
-
-### 使用 TypeScript
-
-如果你使用 TypeScript 进行开发,你可能还需要下面这些依赖 (当然你可能已经安装了它们):
-
-::: tabs code
-```npm
-npm i typescript esbuild-register @types/node @types/mocha @types/chai -D
-```
-```yarn
-yarn add typescript esbuild-register @types/node @types/mocha @types/chai -D
-```
-:::
-
-接着编辑你的 `.mocharc.js` 文件:
-
-```js title=.mocharc.js
-module.exports = {
- extension: ['ts'],
- require: [
- 'esbuild-register',
- ],
-}
-```
-
-## 模拟会话消息
-
-对于聊天机器人来说最常见的需求是处理用户的消息。为此,我们提供了 **客户端 (Client)** 对象,用于模拟特定频道和用户的输入:
-
-```ts no-extra-header
-///
-// ---cut---
-import { Context } from 'koishi'
-import mock from '@koishijs/plugin-mock'
-
-const app = new Context()
-app.plugin(mock)
-
-// 创建一个 userId 为 123 的私聊客户端
-const client = app.mock.client('123')
-
-// 这是一个简单的中间件例子,下面将测试这个中间件
-app.middleware(({ content }, next) => {
- if (content === '天王盖地虎') {
- return '宝塔镇河妖'
- } else {
- return next()
- }
-})
-
-// 这一句不能少,要等待 app 启动完成
-before(() => app.start())
-after(() => app.stop())
-
-it('example 1', async () => {
- // 将“天王盖地虎”发送给机器人将会获得“宝塔镇河妖”的回复
- await client.shouldReply('天王盖地虎', '宝塔镇河妖')
-
- // 将“天王盖地虎”发送给机器人将会获得某些回复
- await client.shouldReply('天王盖地虎')
-
- // 将“宫廷玉液酒”发送给机器人将不会获得任何回复
- await client.shouldNotReply('宫廷玉液酒')
-})
-```
-
-## 模拟数据库
-
-@koishijs/plugin-database-memory 是 Koishi 的一个基于内存的数据库实现,非常适合用于单元测试。
-
-```ts no-extra-header
-import { Context } from 'koishi'
-import mock from '@koishijs/plugin-mock'
-import memory from '@koishijs/plugin-database-memory'
-
-const app = new Context()
-app.plugin(mock)
-app.plugin(memory)
-
-// 这次我们来测试一下这个指令
-app.command('foo', { authority: 2 }).action(() => 'bar')
-
-// 创建两个来自不同用户的客户端对象
-const client1 = app.mock.client('123')
-const client2 = app.mock.client('456')
-
-before(async () => {
- await app.start()
-
- // 在数据库中初始化两个用户,userId 分别为 123 和 456,权限等级分别为 1 和 2
- // app.mock.initUser() 方法本质上只是 app.database.createUser() 的语法糖
- await app.mock.initUser('123', 1)
- await app.mock.initUser('456', 2)
-})
-
-after(() => app.stop())
-
-it('example 2', async () => {
- // 用户 123 尝试调用 foo 指令,但是权限不足
- await client1.shouldReply('foo', '权限不足。')
-
- // 用户 456 得以正常调用 foo 指令
- await client2.shouldReply('foo', 'bar')
-})
-```
diff --git a/en-US/manual/recipe/multiple.md b/en-US/manual/recipe/multiple.md
index 5e05f12779f9..eb863ea63499 100644
--- a/en-US/manual/recipe/multiple.md
+++ b/en-US/manual/recipe/multiple.md
@@ -12,11 +12,11 @@ Solutions can be varied for different demands, so there is no a unique answer. F
In each Koishi application, some plugins can be enabled multiple instances, others can't. It is not the defects of the plugin, but the expected behavior.In fact, the author of the plugin can specify which features can be enabled independently.This is reflected in two different types of plugins: those that can enable multiple configurations at the same time are called reusable plugins and instead are non-reusable plugins.
-Typical reusable plugins are [adapter plugins](../console/adapter.md). Each adapter corresponds to a running bot, and bots on different platforms are configured by different adapters. So, if you want to configure multiple bots on the same platform, just follow the method in previous section to add multiple adapter plugins.
+Typical reusable plugins are [Adapter Plugins](../usage/adapter.md).Each adapter corresponds to a running bot, and bots on different platforms are configured by different adapters. So, if you want to configure multiple bots on the same platform, just follow the method in previous section to add multiple adapter plugins.
At the same time, the vast majority of plugins are not reusable. For such plugins, you can only have one running configuration at a time. If there's already a running configuration, you'll see a line prompting "This plugin is already running and cannot be reused" in other configurations. Of course, you can still prepare multiple configurations, then disable one configuration and enable another at the right time.
-For those plugins that are not reusable, if you want to switch to different configurations in different scenarios, plugin authors are required to provide configurations with [filters](../usage/filter.md). If the configuration you want to modify does not support filters, then you may consider making suggestions to the plugin author, or using [multiple instances](#多实例) described below.
+For those plugins that are not reusable, if you want to switch to different configurations in different cases, plugin authors are required to provide configurations with [filters](../usage/customize.md#过滤器).If the configuration you want to modify does not support filters, then you may consider making suggestions to the plugin author, or using [multiple instances](#多实例) described below.
## Multiple Instances
diff --git a/en-US/manual/starter/boilerplate.md b/en-US/manual/starter/boilerplate.md
index 53c11bcab4b8..834665895edc 100644
--- a/en-US/manual/starter/boilerplate.md
+++ b/en-US/manual/starter/boilerplate.md
@@ -24,7 +24,7 @@ Of course, you could also use the template project in production. While it might
## Install Node.js
-Koishi requires [Node.js](https://nodejs.org/) (minimum v14, LTS is recommended) as the runtime environment. You should install it manually.
+Koishi requires [Node.js](https://nodejs.org/) (at least v16, suggested to use LTS versions) Runtime, you need to install it.
### Download Installer
@@ -50,6 +50,22 @@ npm i -g yarn
yarn -v
```
+::: tip
+Some Windows users may get errors like below ([Reference Link](https://learn.microsoft.com/zh-cn/powershell/module/microsoft.powershell.core/about/about_execution_policies)):
+
+```text
+yarn: File yarn.ps1 cannot be loaded because running scripts is disabled on this system.
+```
+
+Now start a (PowerShell) terminal with elevated permission (as Administrator), run the command below:
+
+```sh
+Set-ExecutionPolicy RemoteSigned
+```
+
+Then you can use Yarn normally.
+:::
+
### Configure Registry Mirror
If you live in Chinese mainland, it might be very slow when you download dependencies from npm or yarn. Therefore, it is recommended to configure a registry mirror to speed up the installation process.
@@ -105,5 +121,5 @@ yarn start
Congratulations on mastering the basic of Koishi! Moving forward:
-- If you're interested in learning how to use the Koishi Console, please proceed to [About Koishi Console](../console/index.md).
+- If you want to know more features about Koishi, please refer to [Install and Configure Plugins](../usage/market.md)
- If you're interested in developing your own plugin immediately, please proceed to the [Developing Guide](../../guide/index.md).
diff --git a/en-US/manual/starter/direct.md b/en-US/manual/starter/direct.md
index deb9da0fa213..dd2e9e83a926 100644
--- a/en-US/manual/starter/direct.md
+++ b/en-US/manual/starter/direct.md
@@ -25,7 +25,7 @@ While it is recommended to use the [boilerplate project](./boilerplate.md) for m
Koishi is written with TypeScript, so TypeScript would be the first class programming language when you are developing Koishi. In the following section of documents, we will use TypeScript as example language. If you are writing vanilla JavaScript or other JavaScript dialects, you could modify your own code based on the example code.
:::
-Koishi requires [Node.js](https://nodejs.org/) (minimum v14, LTS is recommended) as the runtime environment. You should install it manually.Here we suppose you have it installed already.
+Koishi requires [Node.js](https://nodejs.org/) (at least v16, suggested to use LTS versions) Runtime, you need to install it.Here we suppose you have it installed already.
Initialize a directly as your bot, install Koishi and common plugins, here we would install several official plugins as example: console, sandbox and echo.
diff --git a/en-US/manual/usage/adapter.md b/en-US/manual/usage/adapter.md
new file mode 100644
index 000000000000..76342aacb985
--- /dev/null
+++ b/en-US/manual/usage/adapter.md
@@ -0,0 +1,56 @@
+# 第一次对话
+
+安装完了 Koishi 并体验了插件市场,想必你已经等不及体验 Koishi 的功能了。现在就让我们立即开始与机器人的第一次对话吧!
+
+## 在沙盒中模拟对话
+
+在控制台中前往「沙盒」页面,在这里我们可以模拟与机器人的对话。
+
+First, click the "Add User" button in the upper left corner to create a virtual user, which is named Alice for example. Then, you would see a blank chat UI on the right side. Click the input box at the bottom of the chat UI, enter "help" (without quotes) and then press Enter key. You will see a reply message from bot immediately. "help" is a built-in command that lists all available commands at the moment. Therefore, we could see "echo" and "help" commands in the response.
+
+The "echo" here is the plugin we have just installed, which displays a line of text that the user just entered. Let's try with entering "echo Bonjour", then press Enter to commit it. You will see the response from bot with "Bonjour".
+
+![sandbox](/manual/console/sandbox.light.webp) {.light-only}
+
+![sandbox](/manual/console/sandbox.dark.webp) {.dark-only}
+
+If you want to simulate a group chat, then we could click "Add User" again to create more virtual users. Then, switch to "Group Chat" at the top of the screen. At this moment, you could control multiple virtual users to chat with the bot. This would be useful when you want to test a multiplayer plugin like chess.
+
+除此以外,如果某些指令需要一定的 [权限等级](../usage/customize.md#权限管理),你也可以切换到「用户设置」中进行调整。
+
+## 接入真实聊天平台
+
+仅仅是在沙盒中对话是远远不够的。我们需要将机器人接入到真实的聊天平台中,才能让它真正地为我们服务。Koishi uses adapter plugins to support various chat platforms. 下面是官方维护的适配器列表:
+
+- [DingTalk](../../plugins/adapter/dingtalk.md)
+- [Discord](../../plugins/adapter/discord.md)
+- [KOOK](../../plugins/adapter/kook.md)
+- [Lark](../../plugins/adapter/lark.md)
+- [LINE](../../plugins/adapter/line.md)
+- [Mail](../../plugins/adapter/mail.md)
+- [Matrix](../../plugins/adapter/matrix.md)
+- [OneBot](../../plugins/adapter/onebot.md)
+- [QQ Guild](../../plugins/adapter/qqguild.md)
+- [Slack](../../plugins/adapter/slack.md)
+- [Telegram](../../plugins/adapter/telegram.md)
+- [微信公众号](../../plugins/adapter/wechat-official.md)
+- [企业微信](../../plugins/adapter/wecom.md)
+- [WhatsApp](../../plugins/adapter/whatsapp.md)
+
+其中,常用的适配器插件已经预装在了 Koishi 中,你可以在插件配置中的 adapter 分组中找到它们。如果没有看到你想要的平台,你也可以在插件市场中搜索并安装更多适配器插件。
+
+Koishi is able to maintain multiple bot accounts of different chat platforms in the same process. Each configuration copy of the corresponding adapter plugin maintains the bot account instance, you could add new configurations of adapter plugin according to [Add More Plugins](./market.md#添加更多插件). User data is shared by bots that connected to the same chat platform. So it is available to achieve a load balance by changing the current active bot account between them.
+
+不同平台的接入方式与难度存在较大的差异。There are different configuration work that you need to do with different platforms. 这些工作可能包括在平台内注册开发者账号、准备一台部署到公网的服务器等等。你可以在各个适配器插件的文档中找到详细的指引。
+
+好消息是,Koishi 的大部分功能都不依赖特定的聊天平台。因此在进行准备工作的同时,你完全可以阅读本文档的后续部分,并在沙盒中体验并学习 Koishi 的功能。
+
+## 对比沙盒与真实环境
+
+事实上,大多数机器人框架都没有提供沙盒功能,或是倾向于用户在真实环境中进行体验。你可能会有疑惑:为什么 Koishi 要推荐使用沙盒功能呢?因此我们列出了沙盒的几点优势。
+
+首先,沙盒可以让你快速地了解插件的效果,而不需要在真实环境中进行大量的测试。想象一下,你刚刚安装了一个陌生的插件,你并不知道应该如何使用它,也不知道它的效果是什么。最糟糕的情况下,一旦插件的某些功能触发了机器人的敏感行为,你的真实账号还存在被封禁的风险。而沙盒则可以让你在不用担心这些问题的情况下,快速地了解插件的功能。
+
+其次,如果你是插件的开发者而非使用者,沙盒功能的意义则更大了:得益于 Koishi 的热重载机制,每次修改插件源码后,你只需要按下保存,即可立即在沙盒中体验修改后的效果。这允许你在任何设备上进行快速的迭代开发,而根本不需要准备真实环境的账号。
+
+当然,沙盒并不能代替真实的聊天环境,有些插件的效果可能无法在沙盒中体验。因此,我们推荐你在沙盒中体验插件的基础功能,而在真实环境中进行更加深入的测试。
diff --git a/en-US/manual/usage/command.md b/en-US/manual/usage/command.md
index 60edeb852f71..bf2013e77fc1 100644
--- a/en-US/manual/usage/command.md
+++ b/en-US/manual/usage/command.md
@@ -121,7 +121,7 @@ For groups with lots of people or more than one bot, we strongly recommend that
1. `prefix` is a list with default value `['']` for triggering a prefix without prefix; empty the list will not trigger all instructions via `prefix` (but can still be triggered by private chat or `nickname` or @Bot)
2. If you set multiple values in `prefix` such as `['.', '/', '']`, then `.`, `/` or no prefix can all trigger the command; but empty string `'` must be written in the last one because Koishi matches each prefix in order
-3. You can set different `prefix`for different sessions, refer to [filter](./filter.md) section
+3. You can set different `prefix` for different sessions, learn more in [Filters](./customize.md#过滤器) section
:::
## Subcommands
@@ -148,7 +148,7 @@ In the example above, we can also find Koishi has two different types of subcomm
User data updated.
-如果父指令本身没有功能,那么 `user` 和 `user -h` 的效果是一样的。此时,我们也可以使用空格代替小数点进行派生式子指令的调用:
+If the parent command didn't have a feature itself, then the effect of `user` and `user -h` are the same.In that situation, we can use spaces instead of dots to call derivative subcommands:
user locale zh
@@ -156,15 +156,15 @@ In the example above, we can also find Koishi has two different types of subcomm
用户数据已修改。
-熟悉 Git 的用户可能会发现,这种设计正是借鉴了 Git 的二级指令:当一个指令的功能过于复杂时,我们可以将其拆分为多个子指令,从而使得指令的功能更加清晰。
+Users who are familiar with Git may find out, this design draws on the 2-level command of Git: When features of a command are too complex, we can split them into several subcommands, to make the feature of command clear.
::: tip
-至于 user.locale 是干什么的,想必大家也已经猜出来了。我们留到 [国际化](./i18n.md) 一节再详细介绍。
+You may already guess out what does user.locale do.We will talk more about it in [Internationalization](./customize.md#国际化) section.
:::
## Command Management
-打开控制台,我们会在活动栏中找到名为「指令管理」的页面。你可以在这里查看当前所有指令的列表,并对指令的行为进行设置。
+Open the Console, we can find the page named 'Command Management' on the activity bar.You can view a list of all current commands here, and set behaviors of these commands.
### Set Aliases
@@ -184,4 +184,4 @@ In the example above, we can also find Koishi has two different types of subcomm
我们甚至还可以单独设置每一个指令选项的权限等级。例如,我们可以单独给 `-E, --unescape` 选项设置 `authority` 为 3。这样一来,只有 3 级以上权限的用户才能使用 `echo -E` 的功能。
-For user permissions, refer to [permissions management](./permission.md) section.
+For user permissions, please refer to [Permission Management](./customize.md#权限管理) section.
diff --git a/en-US/manual/usage/customize.md b/en-US/manual/usage/customize.md
new file mode 100644
index 000000000000..0599967c164e
--- /dev/null
+++ b/en-US/manual/usage/customize.md
@@ -0,0 +1,116 @@
+# 深入定制机器人
+
+## Permission Management
+
+Now that a user system is in place, the next natural requirement is permission management.
+
+### User Permissions
+
+Koishi 内部有一套默认的权限系统,它为每个用户赋予了一个权限等级,遵循以下的 **核心规则**:
+
+- 数据库中没有的用户默认拥有 0 级权限
+- 高权限者能够执行一切低权限者的操作
+
+在此基础上,我们还扩充出了这样的一套 **设计准则**:
+
+- Level 0: non-existing users
+- Level 1: All users, limited access to functionality
+- Level 2: Advanced user with access to almost all functionality
+- Level 3: Admin, able to directly manipulate bot configurations
+- Level 4: Senior admin, able to manage other accounts
+
+你可以基于这套准则对指令进行 [权限管理](./command.md#权限管理),也可以用于部分 [计算属性](#计算属性) 的配置项中。
+
+通过 [配置登录插件](./platform.md#配置登录插件) 的方式,你可以快速拥有一个 5 级权限的管理员账号。接下来,要做的就是为其他用户赋予权限了。
+
+安装 [admin](../../plugins/common/admin.md) 插件。该插件提供了名为 `authorize` 的指令,可以设置其他用户的权限等级:
+
+
+authorize -u @Bob 2
+User data updated.
+
+
+任何用户只能对权限等级低于自己的用户进行操作,且操作后的权限等级同样必须低于自己。
+
+### Assignment Mechanism
+
+默认情况下,同一个 Koishi 应用接入的多个机器人账号在同一个频道内,只有一个机器人会响应用户的消息。这是为了防止消息重复发送和循环触发等问题。这个负责响应消息的机器人被称为该频道的「受理人」。默认情况下,第一个收到该频道的消息的机器人会自动成为受理人。
+
+当受理人账号离线时,即便频道内仍有其他机器人,它们也不会响应你的消息。此时如果想要切换受理人,同样请安装 [admin](../../plugins/common/admin.md) 插件,并使用 `@bot assign`,其中 @bot 是你所希望的受理人。
+
+反过来,如果你希望你的 Koishi 实例不主动响应某个频道的消息,可以通过 `assign -r` 的方式来移除该频道的受理人。其他频道不会受到影响。
+
+::: tip
+某个频道处于无受理人状态与该频道被过滤器排除的区别在于:前者仍然可以被通过 @机器人 的方式触发指令,而后者无论如何都无法触发指令。具体请参考 [触发前缀](./command.md#触发前缀) 章节。
+:::
+
+### Auto-assign
+
+默认情况下,对于每一条接收到的消息,机器人都会自动向数据库中注册其用户和频道。新注册的用户将默认获得 1 级权限,而新注册的频道会自动以收到消息的机器人为其受理者。如果你不希望有此行为,可以在全局设置中手动配置 `autoAuthorize` 和 `autoAssign`。
+
+上述两个配置项都支持 [计算属性](#计算属性),这也意味着你可以在不同的聊天环境中配置不同的行为。
+
+## Filters
+
+Many times, we hope that certain features can only be used for certain group or private chats.Using permission management means introducing a database, and a lighter approach is to directly affect the plugin's functional diagram through **filters**.
+
+### Plugin Filters
+
+::: tip
+A few plugins are unrelated to chat platforms, such as console and database plugins.These plugins therefore have no filter settings.
+:::
+
+Most of the plugins provide filter settings at the top of the plugin details page.Click the 'Add Condition' button to create a filter condition.Can include or exclude any platform/user/group/channel/robot by setting filters.
+
+::: tip
+如果不知道这些 ID 是什么,可以使用 [获取账号信息](./platform.md#获取账号信息) 中介绍的 [inspect](../../plugins/common/inspect.md) 插件。
+:::
+
+After adding a condition, you will find that the button below has changed to two options: "Add AND Condition" and "Add OR Condition".Koishi's filter supports a Protein secondary structure. A series of conditions in the inner layer form a condition group with a logical relationship of "and", and a series of bars in the outer layer form the final filter condition with a recursive relationship of "or".
+
+### Conditional Properties
+
+Koishi not only supports setting up filters at the plugin level, but certain configuration items also support taking different values in different sessions.For example, we can see `prefix`, `autopsying` on the right side of configuration items with a "…" button
+
+![computed](/manual/console/computed.dark.webp) {.dark-only}
+
+![computed](/manual/console/computed.light.webp) {.light-only}
+
+Click this [..] button to turn a regular configuration item into a calculationWe can configure a series of values that meet a filter, and a default value that does not meet anything.
+
+By utilizing this feature, we can achieve some complex functions, such as:
+
+- Block all messages from certain groups
+- Using different command prefixes on different platforms
+- Open extra limit for a specific user
+
+## Internationalization
+
+Koishi supports internationalization natively, which means that a Koishi bot could switch languages according to different context. This might no matter with you at all...but wait a second! This section might show you some very useful features. Internationalization is not as simple as you think.
+
+### Set Language Preferences
+
+In Koishi, each user and channel managed by the application itself can independently set language preferences.其中,应用级别的默认语言通过全局配置项 `locale` 来设置,而用户、频道的语言偏好则通过 [admin](../../plugins/common/admin.md) 插件提供的 `user.locale` 和 `channel.locale` 指令来管理 (还记得指令系统中的 [这个例子](./command.md#子指令) 吧)。
+
+By default, the priority of preferred language is Channel > User > GlobalA group may have a large number of users who use different native languages participating in discussions, and different language channels are usually set up to facilitate communication.而对于此类情况,频道优先策略就可以让机器人在这些频道内始终使用预设好的语言来回答,而对于未设置语言偏好的频道,机器人仍然会遵循用户的偏好设置。Of course, you can also change this behavior through the global configuration item`i18n. output`.
+
+### Localize Text
+
+Of course, most plugins only support Chinese.If you want to make your Koishi bot support other languages, you can modify texts of plugins locally, instead of sending Pull Requests to their authors.
+
+Click 'Localization' on the activity bar, you will see page like below:
+
+![locales](/manual/console/locales.dark.webp) {.dark-only}
+
+![locales](/manual/console/locales.light.webp) {.light-only}
+
+Select categories on the left, and select the language to display in the top right corner, and the zone to edit texts is in the middle.其中,文本框里的占位符对应于插件本身提供的文本,可以在翻译到其他语言时用做参考。
+
+然而,这个页面还有另一个用法,就是修改已有的文本!例如在上面的截图中,`low-authority` 原本对应到的文本是 `权限不足`,我们可以将它修改为 `哼,不给用`。现在让我们找个带权限等级的指令试试看有什么效果:
+
+
+authorize -u @Bob 5
+哼,不给用!
+
+
+If you do things like above, you can get fully customized texts of your bot, and make your bot very different.
diff --git a/en-US/manual/usage/filter.md b/en-US/manual/usage/filter.md
deleted file mode 100644
index f0e7ca9f6302..000000000000
--- a/en-US/manual/usage/filter.md
+++ /dev/null
@@ -1,33 +0,0 @@
-# Filters
-
-Many times, we hope that certain features can only be used for certain group or private chats.Using permission management means introducing a database, and a lighter approach is to directly affect the plugin's functional diagram through**filters**.
-
-## Plugin Filters
-
-::: tip
-A few plugins are unrelated to chat platforms, such as console and database plugins.These plugins therefore have no filter settings.
-:::
-
-Most of the plugins provide filter settings at the top of the plugin details page.Click the 'Add Condition' button to create a filter condition.Can include or exclude any platform/user/group/channel/robot by setting filters.
-
-::: tip
-如果不知道这些 ID 是什么,可以使用 [获取账号信息](./platform.md#获取账号信息) 中介绍的 [inspect](../../plugins/common/inspect.md) 插件。
-:::
-
-After adding a condition, you will find that the button below has changed to two options: "Add AND Condition" and "Add OR Condition".Koishi's filter supports a Protein secondary structure. A series of conditions in the inner layer form a condition group with a logical relationship of "and", and a series of bars in the outer layer form the final filter condition with a recursive relationship of "or".
-
-## Conditional Properties
-
-Koishi not only supports setting up filters at the plugin level, but certain configuration items also support taking different values in different sessions.For example, we can see `prefix`, `autopsying` on the right side of configuration items with a "…" button
-
-![computed](/manual/console/computed.dark.webp) {.dark-only}
-
-![computed](/manual/console/computed.light.webp) {.light-only}
-
-Click this [..] button to turn a regular configuration item into a calculationWe can configure a series of values that meet a filter, and a default value that does not meet anything.
-
-By utilizing this feature, we can achieve some complex functions, such as:
-
-- Block all messages from certain groups
-- Using different command prefixes on different platforms
-- Open extra limit for a specific user
diff --git a/en-US/manual/usage/i18n.md b/en-US/manual/usage/i18n.md
deleted file mode 100644
index 54e0e98ce53e..000000000000
--- a/en-US/manual/usage/i18n.md
+++ /dev/null
@@ -1,30 +0,0 @@
-# Internationalization
-
-Koishi supports internationalization natively, which means that a Koishi bot could switch languages according to different context. This might no matter with you at all...but wait a second! This section might show you some very useful features. Internationalization is not as simple as you think.
-
-## Set Language Preferences
-
-In Koishi, each user and channel managed by the application itself can independently set language preferences.其中,应用级别的默认语言通过全局配置项 `locale` 来设置,而用户、频道的语言偏好则通过 [admin](../../plugins/common/admin.md) 插件提供的 `user.locale` 和 `channel.locale` 指令来管理 (还记得指令系统中的 [这个例子](./command.md#子指令) 吧)。
-
-默认情况下,语言偏好的优先级是 频道 > 用户 > 全局。A group may have a large number of users who use different native languages participating in discussions, and different language channels are usually set up to facilitate communication.而对于此类情况,频道优先策略就可以让机器人在这些频道内始终使用预设好的语言来回答,而对于未设置语言偏好的频道,机器人仍然会遵循用户的偏好设置。Of course, you can also change this behavior through the global configuration item`i18n. output`.
-
-## Localize Text
-
-当然,绝大部分插件都仅仅支持了中文。如果你希望你的机器人支持其他语言,除了向这些插件的作者提交 Pull Request 外,还可以本地修改插件的文本。
-
-在活动栏中点击「本地化」,你将看到如下的界面:
-
-![locales](/manual/console/locales.dark.webp) {.dark-only}
-
-![locales](/manual/console/locales.light.webp) {.light-only}
-
-左侧可以选择类别,右上角可以选择要显示的语言,而中间则是编辑文本的区域。其中,文本框里的占位符对应于插件本身提供的文本,可以在翻译到其他语言时用做参考。
-
-然而,这个页面还有另一个用法,就是修改已有的文本!例如在上面的截图中,`low-authority` 原本对应到的文本是 `权限不足`,我们可以将它修改为 `哼,不给用`。现在让我们找个带权限等级的指令试试看有什么效果:
-
-
-authorize -u @Bob 5
-哼,不给用!
-
-
-只要你如法炮制,就可以配置出一套完全属于你的机器人文案,让你的机器人与众不同。
diff --git a/en-US/manual/usage/market.md b/en-US/manual/usage/market.md
new file mode 100644
index 000000000000..77fd8b45e1c2
--- /dev/null
+++ b/en-US/manual/usage/market.md
@@ -0,0 +1,117 @@
+- - -
+prev: text: 选择安装方式 link: /zh-CN/manual/starter/
+- - -
+
+# Install and Configure Plugins
+
+::: tip
+This section covers the usage of pages such as "Marketplace", "Plugin Configuration" and "Dependency Management".
+:::
+
+As the key feature, 控制台是一个对用户友好的图形界面,封装了 Koishi 的绝大多数功能:
+
+- Running status monitoring and statistics
+- Plugins configurations
+- Plugin installation, updating and uninstallation
+- Management of commands, database and locale text
+- Chat simulation
+- Log management
+
+本节中我们将以 [echo](../../plugins/common/echo.md) 插件为例来演示插件的安装与配置。The echo plugin registered a command named `echo`. Use this command can output the input to the user originally.
+
+## About Koishi Console
+
+在你成功安装了模板项目或启动器后,控制台将自动打开。
+
+In the left section of the Console UI, you can see a sidebar that is used to toggle the interfaces on the right section. The dashboard page would be shown by default. There is also a status bar which is used to show the running status of bots at the bottom when you are using a PC or a tablet.
+
+![home](/manual/console/home.light.webp) {.light-only}
+
+![home](/manual/console/home.dark.webp) {.dark-only}
+
+在之后的几节里,我们会逐一介绍各界面的功能和使用。
+
+## Install Plugins
+
+::: warning
+We Koishi team doesn't warrant the availability of third party plugins. Plugins from unknown sources may break Koishi to crash, or have very serious consequences. If you have problems after downloading plugins, you can go to the user group or forum to provide feedback. In addition, some plugins are marked as "unsafe" and install such plugins will not be supported by the official group.
+:::
+
+Go to the "Marketplace" page, where you will see all downloadable plugins here. Enter `echo` in the search box to find the plugin we want, click the "Add" button, and then click "Installation" in the popup dialog.Wait for a moment, and the plugin will be installed successfully.
+
+![select-version](/manual/console/select-version.light.webp) {.light-only}
+
+![select-version](/manual/console/select-version.dark.webp) {.dark-only}
+
+## Enable and Disable Plugins
+
+Koishi will not enable the plugin you just installed. You need to manually configure and enable it.Go to the "Plugin Configuration" page, where various configured plugins are listed in the left column. Among these, blackwhite fonts show plugins that are running, while gray fonts show plugins that are not running yet.
+
+![plugins](/manual/console/plugins.light.webp) {.light-only}
+
+![plugins](/manual/console/plugins.dark.webp) {.dark-only}
+
+We can see that the name of the echo plugin is grey, indicating that it is not running.The echo plugin does not have any configurable items, so the details page on the right side is empty.We can directly click on the "Enable Plugin" button in the upper right corner and see the "Enable success" reminder that the echo plugin is already running.
+
+It is also easy to disable the "echo" plugin. Click the "Disable Plugin" button in the upper right corner, then the plugin will stop running.Disabling a plugin will neither delete the plugin code nor delete the plugin configuration, so you can re-enable it at any time.
+
+## Plugins configurations
+
+::: warning
+When configuring plugins, please remember this principle: **Don't change any configuration unless necessary**.Koishi 在设计上兼顾了扩展性和实用性,许多基础功能是以预装插件的形式提供的。The "Marketplace" and "Plugin Configuration" pages that we are already using are also provided by the "market" plugin and the "config" plugin preloaded.It is because all preloaded plugins are well configured, so you do not usually need to modify the preloaded plugins' configuration.Changing the preloaded plugins' configuration or delete the preloaded plugins may cause Koishi to run improperly.
+:::
+
+While the "echo" plugin does not require configuration, more complex plugins often provide configurations that allow users to control the behavior of plugins. The picture below shows the configuration page of the "novelai" plugin.
+
+![settings](/manual/console/settings.light.webp) {.light-only}
+
+![settings](/manual/console/settings.dark.webp) {.dark-only}
+
+In this page, we can see many configurations, where you need to take note of:
+
+- Required but unfilled configurations will display a red tooltip on the left, and they must be filled in correctly to enable the plugin.
+- Modified but unsaved configurations will display a purple tooltip on the left, and they will be saved after you click "Enable Plugin" or "Save Configuration" button. If you want to discard these changes, you can call the menu at the small triangle next to the configuration name, select "Undo Changes" to restore the configuration to the status last saved.
+
+## Manage Plugins
+
+### Manage Groups
+
+Koishi provides a mechanism to group the plugins. You can add plugins into the groups to manage them at a time.
+
+Koishi pre-configured some groups during the installation, while newly installed plugins will be placed at the bottom of the plugin list, indicating that it does not belong to any group.Both plugins and groups can change the order or move between groups by clicking and dragging.Believe you also find that the groups can be nested.
+
+Creating a new group is also simple.In "Global Configuration" or in any group page, click the "Create Group" button in the top right corner to create a new group.The name of the new group is randomly generated, but you can change it by clicking on the name to the name you like.The groups can unfold and fold by clicking on the small triangle in the left bar.
+
+此外,[过滤器](../usage/customize.md#过滤器) 机制也可用于分组,便于控制一系列插件的行为。
+
+### Add More Plugins
+
+::: tip
+Normally, a plugin can only run one configuration at once. Please refer to the [Maintaining Multiple Configurations](../recipe/multiple.md) section.
+:::
+
+If an installed plugin is not shown in the plugin list, you can also add it manually. In "Global Configuration" or in any group page, click the "Add Plugin" button in the top right corner will eject a dialog box. Click on the plugin to be added in the dialog box to create a plugin configuration which is not enabled.
+
+![select-plugin](/manual/console/select-plugin.light.webp) {.light-only}
+
+![select-plugin](/manual/console/select-plugin.dark.webp) {.dark-only}
+
+### Remove Plugin or Group
+
+::: warning
+Warning: this action cannot be undone. If you want to restore the previous configuration, you can only manually add it again. Please be careful.
+:::
+
+Click Remove Plugin button in the top right corner in the configuration page of any plugin to remove the plugin configuration. Similarly, you can remove a plugin group by clicking "Remove Group" in the top right corner of its configuration page. When removing groups, all plugins in the group will also be deleted.
+
+## Update and Uninstall Plugins
+
+Go to the "Dependency Management" page. You can see the dependency list here.Dependencies may include Koishi properties, various plugins, and packages that support plugins to run, etc.
+
+当依赖的状态显示为「可更新」时,点击其右侧的「修改」按钮,在弹出的窗口左上角选择你需要的版本,点击右下角的「更新」按钮即可完成更新。
+
+You can also update multiple plugins. Select the version you need by relying on the dropdown menu on the right side of the dependency name. Then press the "Apply changes" button in the upper right corner.In addition, the "Update All" button in the top right corner can update all dependencies at a time.
+
+![dependencies](/manual/console/dependencies.light.webp) {.light-only}
+
+![dependencies](/manual/console/dependencies.dark.webp) {.dark-only}
diff --git a/en-US/manual/usage/permission.md b/en-US/manual/usage/permission.md
deleted file mode 100644
index e85615876dd2..000000000000
--- a/en-US/manual/usage/permission.md
+++ /dev/null
@@ -1,49 +0,0 @@
-# Permission Management
-
-Now that a user system is in place, the next natural requirement is permission management.
-
-## User Permissions
-
-Koishi 内部有一套默认的权限系统,它为每个用户赋予了一个权限等级,遵循以下的 **核心规则**:
-
-- 数据库中没有的用户默认拥有 0 级权限
-- 高权限者能够执行一切低权限者的操作
-
-在此基础上,我们还扩充出了这样的一套 **设计准则**:
-
-- Level 0: non-existing users
-- Level 1: All users, limited access to functionality
-- Level 2: Advanced user with access to almost all functionality
-- Level 3: Admin, able to directly manipulate bot configurations
-- Level 4: Senior admin, able to manage other accounts
-
-你可以基于这套准则对指令进行 [权限管理](./command.md#权限管理),也可以用于部分 [计算属性](./filter.md#计算属性) 的配置项中。
-
-通过 [配置登录插件](./platform.md#配置登录插件) 的方式,你可以快速拥有一个 5 级权限的管理员账号。接下来,要做的就是为其他用户赋予权限了。
-
-安装 [admin](../../plugins/common/admin.md) 插件。该插件提供了名为 `authorize` 的指令,可以设置其他用户的权限等级:
-
-
-authorize -u @Bob 2
-用户数据已修改。
-
-
-任何用户只能对权限等级低于自己的用户进行操作,且操作后的权限等级同样必须低于自己。
-
-## Assignment Mechanism
-
-默认情况下,同一个 Koishi 应用接入的多个机器人账号在同一个频道内,只有一个机器人会响应用户的消息。这是为了防止消息重复发送和循环触发等问题。这个负责响应消息的机器人被称为该频道的「受理人」。默认情况下,第一个收到该频道的消息的机器人会自动成为受理人。
-
-当受理人账号离线时,即便频道内仍有其他机器人,它们也不会响应你的消息。此时如果想要切换受理人,同样请安装 [admin](../../plugins/common/admin.md) 插件,并使用 `@bot assign`,其中 @bot 是你所希望的受理人。
-
-反过来,如果你希望你的 Koishi 实例不主动响应某个频道的消息,可以通过 `assign -r` 的方式来移除该频道的受理人。其他频道不会受到影响。
-
-::: tip
-某个频道处于无受理人状态与该频道被过滤器排除的区别在于:前者仍然可以被通过 @机器人 的方式触发指令,而后者无论如何都无法触发指令。具体请参考 [触发前缀](./command.md#触发前缀) 章节。
-:::
-
-## Auto-assign
-
-默认情况下,对于每一条接收到的消息,机器人都会自动向数据库中注册其用户和频道。新注册的用户将默认获得 1 级权限,而新注册的频道会自动以收到消息的机器人为其受理者。如果你不希望有此行为,可以在全局设置中手动配置 `autoAuthorize` 和 `autoAssign`。
-
-上述两个配置项都支持 [计算属性](./filter.md#计算属性),这也意味着你可以在不同的聊天环境中配置不同的行为。
diff --git a/en-US/manual/usage/platform.md b/en-US/manual/usage/platform.md
index 3e5ebb037ae5..9bc22890e1c4 100644
--- a/en-US/manual/usage/platform.md
+++ b/en-US/manual/usage/platform.md
@@ -1,4 +1,4 @@
-# 账号系统
+# 账号登录与绑定
Koishi describes itself as a "cross-platform" framework, but what does this "cross-platform" mean?This doesn't just mean that Koishi supports multiple running platforms, but that Koishi can access multiple chat platforms simultaneously and provide the most native experience possible:
diff --git a/en-US/plugins/adapter/dingtalk.md b/en-US/plugins/adapter/dingtalk.md
new file mode 100644
index 000000000000..fd799322393d
--- /dev/null
+++ b/en-US/plugins/adapter/dingtalk.md
@@ -0,0 +1,44 @@
+# @koishijs/plugin-adapter-dingtalk
+
+## 接入方式
+
+1. 前往 [开放平台 > 应用开发 > 钉钉应用](https://open-dev.dingtalk.com/fe/app#/corp/app) 并点击「创建应用」,输入相关信息,选择「企业自主开发」并确定创建
+2. 在跳转至的应用信息页面,将 `AppKey` 填入插件的 `appkey` 字段,将 `AppSecret` 填入插件的 `secret` 字段,将 `AgentId` 填入插件的 `agentId` 字段
+3. 在左侧打开「机器人与消息推送」页面,勾选「机器人配置」,输入相关信息;消息接收模式选择 `Stream 模式` 时,插件的 protocol 填写 ws,选择 `HTTP 模式` 时,将机器人的 `selfUrl` 值后连接 `/dingtalk`(如 `https://example.com/dingtalk`)填入钉钉平台的消息接收地址,插件的 protocol 填写 http。最后点击发布按钮
+
+## 配置项
+
+### config.protocol
+
+- 可选值: http, ws
+
+要使用的协议类型。
+
+### config.secret
+
+- 类型: `string`
+- 必需选项
+
+机器人密钥。
+
+### config.appkey
+
+- 类型: `string`
+- 必需选项
+
+机器人 AppKey。
+
+### config.agentId
+
+- 类型: `string`
+- 必需选项
+
+机器人 AgentId。
+
+## HTTP 配置项
+
+无。
+
+## WebSocket 配置项
+
+包括全部的 [`WsClient`](../../api/core/adapter.md#类:adapter-websocketclient) 选项。
diff --git a/en-US/plugins/adapter/kook.md b/en-US/plugins/adapter/kook.md
index 9f7ef99e7514..03a0582024dd 100644
--- a/en-US/plugins/adapter/kook.md
+++ b/en-US/plugins/adapter/kook.md
@@ -3,12 +3,11 @@
## 接入方法
1. 前往 [开发者平台](https://developer.kookapp.cn/),选择「机器人」并点击「新建」
-2. 在机器人连接模式中配置 Webhook 或 WebSocket 中的一种:
- - 如果是 Webhook,请记下页面中的 token 和 verify_token,并作为机器人的配置项,同时让 Koishi 暴露一个 URL,填入下方的 Callback URL 中,启动 Koishi 后点击「机器人上线」
- - 如果是 WebSocket,则只需记录 token 并作为机器人的配置项即可,你可以在任何时候启动 Koishi
- - 页面中的其他值不用管,但请注意 token 不要泄露
+2. 根据自身需要,在「机器人连接模式」中选择 Webhook 或 WebSocket 中的一种:
+ - 如果是 Webhook,请记下页面中的 `token` 和 `verify_token` (请注意不要泄露),并作为机器人的配置项,同时让 Koishi 暴露一个 URL,填入下方的 Callback URL 中,启动 Koishi 后点击「机器人上线」
+ - 如果是 WebSocket,则只需记录 `token` (请注意不要泄露) 并作为机器人的配置项即可,你可以在任何时候启动 Koishi
-## 配置项
+## 基础配置项
### options.protocol
diff --git a/en-US/plugins/adapter/slack.md b/en-US/plugins/adapter/slack.md
new file mode 100644
index 000000000000..5f9b0ee377e0
--- /dev/null
+++ b/en-US/plugins/adapter/slack.md
@@ -0,0 +1,84 @@
+# @koishijs/plugin-adapter-slack
+
+## 接入方式
+
+1. 在 [应用后台](https://api.slack.com/apps) 点击「Create New App」,选择「From Scratch」,填入应用名称和所添加的工作区 (目前适配器只支持一个应用处理一个工作区的事件),点击「Create App」
+2. 在跳转至的应用信息页面,在底部复制 `Signing Secret` 备用 (将用作 [`signing`](#config-signing) 配置项)
+3. 在「App-Level Tokens」一栏,点击带有 Generate 字样的按钮,填写 Token 名称,在下方下拉框中选择 `connections:write`,点击绿色 Generate 按钮,在弹出的对话框中点击 Copy 按钮,填入插件的 [`token`](#config-token) 字段
+4. 按照使用需求,决定由 Slack 服务器推送 Webhook 至 Koishi 的公网地址(`Webhook` 模式)或是连接至 Slack 服务器接收推送的消息(`Socket Mode` 模式),参照下方说明配置
+5. Socket Mode:在左侧打开「Socket Mode」页面,勾选页面内的单选框
+6. Webhook:在左侧打开 Event Subscriptions 页面,勾选单选框。将机器人的 `selfUrl` 值后连接 `/slack`(如 `https://example.com/slack`),在 Request URL 中填写
+7. 在 Event Subscriptions 页面勾选事件 (参见下方的推荐列表),填写完整后点击右下角绿色 Save Changes 保存
+8. 在左侧 OAuth & Permissions 页面,在下方 Bot Token Scopes 中,点击 Add 添加权限 (参见下方的推荐列表)
+9. 返回页面上方,点击 Install to Workspace,点击 Allow 授权,复制 Bot User OAuth Token 填入插件的 [`botToken`](#config-bottoken) 字段
+10. 在相应工作区 @ 机器人名称或右键频道详情,选择 集成-添加应用 添加机器人到频道中
+
+### 推荐勾选的事件
+
+- channel_archive
+- channel_created
+- channel_deleted
+- channel_left
+- channel_rename
+- member_joined_channel
+- member_left_channel
+- message.channels
+- message.groups
+- message.im
+- reaction_added
+- reaction_removed
+- team_join
+
+### 推荐添加的权限
+
+- channels:history
+- channels:read
+- channels:write.invites
+- chat:write
+- chat:write.customize
+- chat:write.public
+- files:read
+- files:write
+- groups:history
+- groups:read
+- groups:write
+- im:history
+- im:write
+- reactions:read
+- reactions:write
+- users:read
+
+## 配置项
+
+### config.protocol
+
+- 可选值: http, ws
+
+要使用的协议类型。
+
+### config.token
+
+- 类型: `string`
+- 必需选项
+
+应用令牌。
+
+### config.botToken
+
+- 类型: `string`
+- 必需选项
+
+机器人令牌。
+
+## HTTP 配置项
+
+### config.signing
+
+- 类型: `string`
+- 必需选项
+
+用于验证请求来源的签名密钥。
+
+## WebSocket 配置项
+
+包括全部的 [`WsClient`](../../api/core/adapter.md#类:adapter-websocketclient) 选项。
diff --git a/en-US/plugins/adapter/wechat-official.md b/en-US/plugins/adapter/wechat-official.md
new file mode 100644
index 000000000000..07c7cec5a005
--- /dev/null
+++ b/en-US/plugins/adapter/wechat-official.md
@@ -0,0 +1,5 @@
+# @koishijs/plugin-adapter-wechat-official
+
+## 接入方法
+
+## 机器人选项
diff --git a/en-US/plugins/adapter/wecom.md b/en-US/plugins/adapter/wecom.md
new file mode 100644
index 000000000000..14b469365502
--- /dev/null
+++ b/en-US/plugins/adapter/wecom.md
@@ -0,0 +1,5 @@
+# @koishijs/plugin-adapter-wecom
+
+## 接入方法
+
+## 机器人选项
diff --git a/en-US/plugins/adapter/whatsapp.md b/en-US/plugins/adapter/whatsapp.md
new file mode 100644
index 000000000000..e33dbab03ddb
--- /dev/null
+++ b/en-US/plugins/adapter/whatsapp.md
@@ -0,0 +1,41 @@
+# @koishijs/plugin-adapter-whatsapp
+
+## 接入方式
+
+1. 前往 [商务平台](https://business.facebook.com/) 创建业务账户,并在 [业务设置](https://business.facebook.com/settings/security) 完成组织验证
+2. 参照 [官方入门指南](https://developers.facebook.com/docs/whatsapp/cloud-api/get-started) 进行操作
+3. 阅读 [Business API 概览](https://developers.facebook.com/docs/whatsapp/business-management-api/get-started),创建系统用户访问口令,在 Available Permissions 中勾选 `whatsapp_business_messaging` 和 `whatsapp_business_management`,填入插件的 `systemToken` 字段。在当前页面,点击 Add assets,在左侧 Apps 菜单选中创建的 App,勾选 Develop app,点击 Save changes
+4. 在 [应用面板](https://developers.facebook.com),左侧切换至 设置-基本 页面,点击应用密钥右侧的显示按钮,复制密钥填入插件的 `systemToken` 字段。
+5. 在 [商业账号](https://business.facebook.com/settings/whatsapp-business-accounts/) 页面选择生产或开发环境的账号集合,复制页面上方高亮的 ID,填入插件的 `id` 字段
+6. 在官方入门指南第三步配置 Webhooks 中,Callback URL 填写机器人的 `selfUrl` 值后连接 `/whatsapp`,Verify token 可填写随机的字符串,与插件的 verifyToken 配置保持一致,启用插件后点击 Verify and save
+7. 点击 Webhook 字段右侧的管理,在 messages 一行打勾
+
+## 配置项
+
+### config.id
+
+- 类型:`string`
+- 必需选项
+
+商户 ID。
+
+### config.secret
+
+- 类型:`string`
+- 必需选项
+
+应用密钥。
+
+### config.systemToken
+
+- 类型:`string`
+- 必需选项
+
+系统用户访问令牌。
+
+### config.verifyToken
+
+- 类型:`string`
+- 必需选项
+
+Webhook 验证令牌。
diff --git a/en-US/plugins/common/admin.md b/en-US/plugins/common/admin.md
index 4dc8a116d107..b37c2b3db34e 100644
--- a/en-US/plugins/common/admin.md
+++ b/en-US/plugins/common/admin.md
@@ -5,7 +5,7 @@
:::
::: tip
-建议配合阅读 [入门 > 权限管理](../../manual/usage/permission.md) 章节。
+建议配合阅读 [入门 > 权限管理](../../manual/usage/customize.md#权限管理) 章节。
:::
## 指令:authorize
@@ -40,7 +40,7 @@ authorize 3 -u @onebot:123456789 # 指定具体的平台和用户名
- `-c, --channel` 指定目标频道(不在群组内使用时必须指定)
- `-r, --remove` 重置设置
-assign 指令可用于设置频道的 [受理人](../../manual/usage/permission.md#受理人机制)。该指令 4 级权限才能调用。
+assign 指令可用于设置频道的 [受理人](../../manual/usage/customize.md#受理人机制)。该指令 4 级权限才能调用。
如果 `-c [channel]` 缺省,则表示目标频道为当前频道(因此私聊状态下不能缺省);如果 `bot` 缺省,则表示当前接收消息的机器人账号。举个例子,如果要设定一个频道 A 的代理者为 B,下面的两种做法是等价的:
diff --git a/en-US/plugins/common/broadcast.md b/en-US/plugins/common/broadcast.md
index 4d37799b5218..f1fb9e8ef989 100644
--- a/en-US/plugins/common/broadcast.md
+++ b/en-US/plugins/common/broadcast.md
@@ -12,7 +12,7 @@
- `-o, --only` 仅向当前账号负责的群进行广播
- `-f, --forced` 无视 silent 标签进行广播
-broadcast 指令用于按照 [受理人](../../manual/usage/permission.md#受理人机制) 向所有机器人所负责的频道发送一段文本(默认情况下有 silent 标签的群不发送)。你可以这样调用它:
+broadcast 指令用于按照 [受理人](../../manual/usage/customize.md#受理人机制) 向所有机器人所负责的频道发送一段文本(默认情况下有 silent 标签的群不发送)。你可以这样调用它:
```sh
broadcast foo bar baz # 向所有频道发送 foo bar baz
diff --git a/en-US/plugins/console/locales.md b/en-US/plugins/console/locales.md
index 9d3c3411c5dc..0c8a274259df 100644
--- a/en-US/plugins/console/locales.md
+++ b/en-US/plugins/console/locales.md
@@ -1,7 +1,7 @@
# Localization Management (Locales)
::: tip
-使用方法请参见 [入门 > 国际化 > 本地化文本](../../manual/usage/i18n.md#本地化文本) 一节。
+使用方法请参见 [入门 > 国际化](../../manual/usage/customize.md#本地化文本) 一节。
:::
@koishijs/plugin-locales 允许你在本地覆盖和扩展 Koishi 本体和其他插件的翻译文本。
diff --git a/en-US/plugins/console/sandbox.md b/en-US/plugins/console/sandbox.md
index 593dd5f1a69f..9873fe87d718 100644
--- a/en-US/plugins/console/sandbox.md
+++ b/en-US/plugins/console/sandbox.md
@@ -1,5 +1,5 @@
# 沙盒调试 (Sandbox)
::: tip
-使用方法请参见 [入门 > 在沙盒中聊天](../../manual/console/sandbox.md) 一节。
+使用方法请参见 [入门 > 第一次聊天](../../manual/usage/adapter.md#在沙盒中模拟对话) 一节。
:::
diff --git a/en-US/plugins/index.md b/en-US/plugins/index.md
index 085e74ab8190..678649ba975e 100644
--- a/en-US/plugins/index.md
+++ b/en-US/plugins/index.md
@@ -4,6 +4,7 @@ Koishi 官方提供了许多插件。为了更好地模块化开发,它们被
## Adapter
+- [@koishijs/plugin-adapter-dingtalk](./adapter/dingtalk.md)
- [@koishijs/plugin-adapter-discord](./adapter/discord.md)
- [@koishijs/plugin-adapter-kook](./adapter/kook.md)
- [@koishijs/plugin-adapter-lark](./adapter/lark.md)
@@ -12,7 +13,11 @@ Koishi 官方提供了许多插件。为了更好地模块化开发,它们被
- [@koishijs/plugin-adapter-matrix](./adapter/matrix.md)
- [@koishijs/plugin-adapter-onebot](./adapter/onebot.md)
- [@koishijs/plugin-adapter-qqguild](./adapter/qqguild.md)
+- [@koishijs/plugin-adapter-slack](./adapter/slack.md)
- [@koishijs/plugin-adapter-telegram](./adapter/telegram.md)
+- [@koishijs/plugin-adapter-wechat-official](./adapter/wechat-official.md)
+- [@koishijs/plugin-adapter-wecom](./adapter/wecom.md)
+- [@koishijs/plugin-adapter-whatsapp](./adapter/whatsapp.md)
## Database
diff --git a/fr-FR/about/history.md b/fr-FR/about/history.md
index d9004a45b305..dc106fc4ec72 100644
--- a/fr-FR/about/history.md
+++ b/fr-FR/about/history.md
@@ -2,13 +2,13 @@
2019 年 8 月,我开始编写我的第一个基于 Node.js 的聊天机器人,名为四季酱。当时我浏览各种聊天机器人框架,发现并没有自己真正想要的,遂决定从零开始编写。一开始这个机器人只包含了很少的功能,但随着其更多功能的加入,我开始调整起底层架构,并计划逐步将其开源出来。
-到目前为止 Koishi 大约每 8 个月发布一个大版本。可以说 Koishi 的发展完全是由需求推动的,在迭代中形成了一套聊天机器人开发的最佳实践。
+直到 v4 发布之前,Koishi 大约每 8 个月发布一个大版本。可以说 Koishi 的发展完全是由需求推动的,在迭代中形成了一套聊天机器人开发的最佳实践。
## v1 时期
Koishi v1 发布于 2020 年 1 月。此时的 Koishi 虽然体量尚小,但已具备了许多一直沿袭至今的特征:通过插件系统实现了功能的模块化,通过事件模型和中间件处理各种输入,上下文负责对输入的事件进行过滤,以及一个通过链式调用进行开发的指令系统等等。
-最初的 v1 官方插件只有 common, schedule 和 teach,其他在这个时期开发的插件大都已经弃用了。
+目前可追溯的 v1 官方插件只有 common, schedule 和 teach,其他在这个时期开发的插件大都已经弃用了。
## v2 时期
diff --git a/fr-FR/about/releases/v4.12.md b/fr-FR/about/releases/v4.12.md
index a31a1d106f47..72def250c3ed 100644
--- a/fr-FR/about/releases/v4.12.md
+++ b/fr-FR/about/releases/v4.12.md
@@ -1,3 +1,42 @@
# v4.12 版本介绍
- [Roadmap](https://github.com/koishijs/koishi/issues/1000)
+- [v4.12.0](https://github.com/koishijs/koishi/releases/tag/4.12.0)
+- [v4.12.1](https://github.com/koishijs/koishi/releases/tag/4.12.1)
+- [v4.12.2](https://github.com/koishijs/koishi/releases/tag/4.12.2)
+- [v4.12.3](https://github.com/koishijs/koishi/releases/tag/4.12.3)
+- [v4.12.4](https://github.com/koishijs/koishi/releases/tag/4.12.4)
+- [v4.12.5](https://github.com/koishijs/koishi/releases/tag/4.12.5)
+- [v4.12.6](https://github.com/koishijs/koishi/releases/tag/4.12.6)
+- [v4.12.7](https://github.com/koishijs/koishi/releases/tag/4.12.7)
+- [v4.12.8](https://github.com/koishijs/koishi/releases/tag/4.12.8)
+- [v4.12.9](https://github.com/koishijs/koishi/releases/tag/4.12.9)
+
+## 用户绑定
+
+在这个版本中,我们引入了新的内置数据表 `binding`,用于存储账号绑定信息。在新的架构下,我们更新了 `auth` 和 `bind` 插件,支持了同平台的用户绑定和解绑等功能。
+
+为了能让旧版本的用户平滑升级,我们还引入了数据库迁移技术,支持插件在升级时将已有的数据迁移到新的表中。
+
+## 资源管理器
+
+新增了官方插件 @koishijs/plugin-explorer,可用于在控制台中查看和编辑实例目录内的文件。该插件除了能够方便云端部署的用户进行文件管理外,还为路径类型的配置项提供了便捷的选择界面。至此,Koishi 的控制台生态已经趋于完善。
+
+## 配置界面优化
+
+v4.12 版本期间,我们对配置 UI 库 schemastery-vue 进行了全面的重构:
+
+- 提供了更好的扩展性
+- 修复了一些边界情况下的已知问题,同时提高了性能
+- 对于 `array` 和 `dict` 等类型支持了折叠
+- 支持了配置项国际化
+
+## 适配器更新
+
+- 优化了多个适配器的消息元素支持,包括 onebot, telegram, kook 等
+- 将 feishu 适配器更名为 lark,并同时支持了飞书的国内与国际版本
+- 新增了 mail 适配器,允许用户通过邮件与机器人交互
+
+## 热重载优化
+
+将热重载相关逻辑从 CLI 内置迁移到了独立的插件 @koishijs/plugin-hmr 中,并提供了更多的配置项与功能。例如,如果当前保存的文件存在编译错误,将会显示色彩丰富的提示信息。
diff --git a/fr-FR/about/releases/v4.13.md b/fr-FR/about/releases/v4.13.md
new file mode 100644
index 000000000000..20d33caf1be6
--- /dev/null
+++ b/fr-FR/about/releases/v4.13.md
@@ -0,0 +1,42 @@
+# v4.13 版本介绍
+
+- [Roadmap](https://github.com/koishijs/koishi/issues/1085)
+- [v4.13.0](https://github.com/koishijs/koishi/releases/tag/4.13.0)
+- [v4.13.1](https://github.com/koishijs/koishi/releases/tag/4.13.1)
+- [v4.13.2](https://github.com/koishijs/koishi/releases/tag/4.13.2)
+- [v4.13.3](https://github.com/koishijs/koishi/releases/tag/4.13.3)
+- [v4.13.4](https://github.com/koishijs/koishi/releases/tag/4.13.4)
+- [v4.13.5](https://github.com/koishijs/koishi/releases/tag/4.13.5)
+- [v4.13.6](https://github.com/koishijs/koishi/releases/tag/4.13.6)
+- [v4.13.7](https://github.com/koishijs/koishi/releases/tag/4.13.7)
+- [v4.13.8](https://github.com/koishijs/koishi/releases/tag/4.13.8)
+- [v4.13.9](https://github.com/koishijs/koishi/releases/tag/4.13.9)
+
+## 控制台优化
+
+v4.12 版本的主要改动是新增了一批控制台 API:
+
+- 引入了主题系统,允许插件定义新的主题
+- 引入了用户配置 API,并允许插件扩展配置项
+- 引入了菜单 API,允许插件定义和触发上下文菜单
+- 支持了配置漫游功能,用户可以在多个客户端同步配置
+
+伴随着新的 API,已有的控制台插件也迎来了各种优化,这里就不一一列举了。
+
+## 插件配置优化
+
+在这个版本中,我们进一步将 @koishijs/plugin-market 拆分为了 market 和 config 两个独立的插件。前者负责插件的安装和更新,后者负责插件的配置。同时,我们支持了插件信息的按需读取,大幅提高了启动速度和插件市场加载速度。
+
+新版本的 market 和 config 插件也带来了一些新功能:
+
+- 支持了启用、停用、重载插件分组
+- 支持了插件的快速预览功能
+- 支持了对插件配置项的热重载
+- 支持了以指令形式安装、卸载、更新插件
+
+## 适配器更新
+
+- 支持了更多标准消息元素
+- 支持了斜线指令,允许用户快速与机器人交互
+- 新增了与表态和角色相关的 Bot API
+- 新增了 matrix 和 line 适配器
diff --git a/fr-FR/api/console/index.md b/fr-FR/api/console/index.md
deleted file mode 100644
index fa60bfd67599..000000000000
--- a/fr-FR/api/console/index.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# 控制台架构
-
-::: warning
-此页文档正在施工,内容尚未完成。
-:::
diff --git a/fr-FR/api/core/adapter.md b/fr-FR/api/core/adapter.md
index 4c49143ff920..f678785cd1c7 100644
--- a/fr-FR/api/core/adapter.md
+++ b/fr-FR/api/core/adapter.md
@@ -1,5 +1,9 @@
# 适配器 (Adapter)
+::: tip
+参见:[开发 > 跨平台 > 实现适配器](../../guide/adapter/adapter.md)
+:::
+
本章将介绍与适配器相关的内容,这是一个相当底层的概念,因此如果你并不打算编写一个平台实现,你完全可以跳过本章节。
标有 抽象 的方法需要平台自行实现。
diff --git a/fr-FR/api/core/bot.md b/fr-FR/api/core/bot.md
index 2f2936ff4bc2..ed5cc52bd50e 100644
--- a/fr-FR/api/core/bot.md
+++ b/fr-FR/api/core/bot.md
@@ -1,5 +1,9 @@
# 机器人 (Bot)
+::: tip
+参见:[开发 > 跨平台 > 实现机器人](../../guide/adapter/bot.md)
+:::
+
**机器人 (Bot)** 是适配器的核心,它将不同平台的 API 封装成统一的格式供 Koishi 使用。而不同的适配器也可以自行扩展 Bot 实例上的属性和方法。
标有 内置 的 API 已经由 Koishi 提供,适配器可以覆盖对应的方法,但是无需自行实现。
@@ -215,7 +219,7 @@ export interface UserInfo {
获取用户信息。
-### bot.getFriendList()
+### bot.getFriendList() 实验性
- 返回值: `Promise` 好友列表
@@ -235,7 +239,7 @@ export interface GuildInfo {
}
```
-### bot.getGuildList()
+### bot.getGuildList() 实验性
- 返回值: `Promise` 群组列表
@@ -262,10 +266,10 @@ export interface GuildMemberInfo extends UserInfo {
}
```
-### bot.getGuildMemberList(guildId)
+### bot.getGuildMemberList(guildId) 实验性
- **guildId:** `string` 群组 ID
-- 返回值: `Promise` 群成员列表
+- 返回值: `Promise>` 群成员列表
获取群成员列表。
@@ -290,7 +294,7 @@ export interface ChannelInfo {
}
```
-### bot.getChannelList(guildId)
+### bot.getChannelList(guildId) 实验性
- **guildId:** `string` 群组 ID
- 返回值: `Promise` 频道列表
diff --git a/fr-FR/api/core/command.md b/fr-FR/api/core/command.md
index b34c71840a86..37ba86ff9e44 100644
--- a/fr-FR/api/core/command.md
+++ b/fr-FR/api/core/command.md
@@ -1,5 +1,9 @@
# 指令 (Command)
+::: tip
+参见:[开发 > 交互基础 > 指令开发](../../guide/basic/command.md)
+:::
+
指令系统是 Koishi 的核心功能之一。通过 `ctx.command()` 方法获得的是指令的实例,它含有下面的方法:
## Argv 对象
@@ -79,7 +83,7 @@ type CommandAction = (argv: Argv, ...args: any[]) => Awaitable
- **fields:** `FieldCollector` 要请求的用户字段
- 返回值: `this`
-如果指令需要用到用户数据,你可以提前声明,这样有助于合并多次请求,从而提高性能。 参见[按需加载](../../guide/database/observer.md#声明所需字段)章节。
+如果指令需要用到用户数据,你可以提前声明,这样有助于合并多次请求,从而提高性能。 参见[按需加载](../../guide/database/builtin.md#声明所需字段)章节。
```ts
type FieldCollector =
@@ -92,7 +96,7 @@ type FieldCollector =
- **fields:** `FieldCollector` 要请求的频道字段
- 返回值: `this`
-如果指令需要用到频道数据,你可以提前声明,这样有助于合并多次请求,从而提高性能。 参见[按需加载](../../guide/database/observer.md#声明所需字段)章节。
+如果指令需要用到频道数据,你可以提前声明,这样有助于合并多次请求,从而提高性能。 参见[按需加载](../../guide/database/builtin.md#声明所需字段)章节。
### cmd.alias(...names)
diff --git a/fr-FR/api/core/context.md b/fr-FR/api/core/context.md
index ec6c790b5112..f0d4058ebf76 100644
--- a/fr-FR/api/core/context.md
+++ b/fr-FR/api/core/context.md
@@ -24,6 +24,7 @@ Koishi 使用了面向切面编程 (AOP) 的开发方式,绝大部分上下文
- [ctx.on](../service/events.md#ctx-on)
- [ctx.once](../service/events.md#ctx-once)
- [ctx.parallel](../service/events.md#ctx-parallel)
+- [ctx.permissions](../service/permissions.md)
- [ctx.plugin](../service/registry.md#ctx-plugin)
- [ctx.router](../service/router.md)
- [ctx.scope](../service/registry.md#ctx-scope)
diff --git a/fr-FR/api/core/events.md b/fr-FR/api/core/events.md
index 7b8000df9793..8dc2cbe01d99 100644
--- a/fr-FR/api/core/events.md
+++ b/fr-FR/api/core/events.md
@@ -1,9 +1,12 @@
# 事件 (Events)
-Koishi 封装了一套事件系统。其基本用法与 Node.js 自带的 [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) 类似,但支持更多的功能,比如多达 6 种的触发形式以及会话事件等。在了解下面的内容之前,建议你先阅读下面的章节:
+::: tip
+参见:[开发 > 交互基础 > 事件系统](../../guide/basic/events.md)
参见:[开发 > 模块化 > 生命周期](../../guide/plugin/lifecycle.md)
+:::
-- [事件系统](../../guide/basic/events.md)
-- [生命周期](../../guide/plugin/lifecycle.md)
+::: tip
+本节介绍 Koishi 的内置事件。如果想了解事件 API,请前往 [API > 内置服务 > 事件](../service/events.md)。
+:::
## 通用会话事件
diff --git a/fr-FR/api/database/built-in.md b/fr-FR/api/database/built-in.md
index f568a0fd4527..b598f865a275 100644
--- a/fr-FR/api/database/built-in.md
+++ b/fr-FR/api/database/built-in.md
@@ -27,7 +27,7 @@ Koishi 的数据库 API 实际上分为两部分:
- **platform:** `string` 平台名
- **id:** `string` 频道账号
-- **assignee:** `string` [受理人](../../manual/usage/permission.md#受理人机制)
+- **assignee:** `string` [受理人](../../manual/usage/customize.md#受理人机制)
- **permissions:** `string[]` 权限列表
- **locales:** `string[]` 语言列表
diff --git a/fr-FR/api/database/database.md b/fr-FR/api/database/database.md
index afbe5b16e667..59d08bc42352 100644
--- a/fr-FR/api/database/database.md
+++ b/fr-FR/api/database/database.md
@@ -1,5 +1,9 @@
# 数据库操作 (Database)
+::: tip
+参见:[开发 > 数据库 > 基本用法](../../guide/database/)
+:::
+
一个 Database 对象代理了 Koishi 上下文绑定的应用实例有关的所有数据库访问。同时它具有注入特性,任何插件都可以自己定义数据库上的方法。本章主要介绍数据库的官方接口。注意:**它们并不由 Koishi 自身实现,而是由每个数据库分别实现的**。Koishi 只是提供了一套标准。
## database.drop()
diff --git a/fr-FR/api/database/model.md b/fr-FR/api/database/model.md
index 621f5ef6ec8a..cee8c091b9ae 100644
--- a/fr-FR/api/database/model.md
+++ b/fr-FR/api/database/model.md
@@ -1,8 +1,12 @@
# 数据模型 (Model)
+::: tip
+参见:[开发 > 数据库 > 数据模型](../../guide/database/model.md)
+:::
+
## 数据类型
-数据类型会被用于 [`model.extend()`](#model-extend-name-fields-config) 方法中,其定义如下:
+数据类型会被用于 [`model.extend()`](#model-extend) 方法中,其定义如下:
```ts
export interface Field {
@@ -11,6 +15,7 @@ export interface Field {
nullable?: boolean
initial?: T
comment?: string
+ legacy?: string[]
}
```
@@ -28,7 +33,7 @@ export interface Field {
| 名称 | TS 类型 | 默认长度 | 默认初始值 | 说明 |
|:------:|:--------:|:-----:|:-----:|:------:|
| char | `string` | 64 | `''` | 定长的字符串 |
-| string | `string` | 256 | `''` | 变长的字符串 |
+| string | `string` | 255 | `''` | 变长的字符串 |
| text | `string` | 65535 | `''` | 变长的字符串 |
### 时间类型
@@ -55,13 +60,22 @@ export interface Field {
- **config:** `Table.Meta` 表的基本配置
- **config.primary:** `string | string[]` 主键名,默认为 `'id'`
- **config.unique:** `(string | string[])[]` 值唯一的键名列表
- - **config.foreign:** `Dict<[string, string]>` 外键列表
+ - **config.foreign:** `Dict<[string, string]>` 外键列表 实验性
- **config.autoInc:** `boolean` 是否使用自增主键
扩展一个新的数据表。
-### model.create(name)
+### model.create(name, data)
+
+- **name:** `string` 数据表名
+- **data:** `any` 数据
+
+创建一条新的数据,折叠嵌套属性,并填充必要的默认值。
+
+### model.migrate(name, fields, callback) 实验性
-### model.resolveQuery(query)
+- **name:** `string` 数据表名
+- **fields:** `Field.Config` 要迁移的字段信息
+- **callback:** `(db: Database) => Promise` 迁移的回调函数
-### model.resolveModifier(modifier)
+设置 [整表迁移](../../guide/database/model.md#整表迁移) 的操作。
diff --git a/fr-FR/api/glossary.md b/fr-FR/api/glossary.md
index a206a6190e1e..394cc071cb77 100644
--- a/fr-FR/api/glossary.md
+++ b/fr-FR/api/glossary.md
@@ -6,7 +6,7 @@
适配器是指实现了平台协议,能够让机器人接入平台的插件。通常来说一个适配器实例对应了一个机器人用户,同时启用多个适配器就实现了多个机器人的同时接入。
-- [入门 > 接入聊天平台](../manual/console/adapter.md)
+- [入门 > 接入聊天平台](../manual/usage/adapter.md)
- [开发 > 跨平台 > 实现适配器](../guide/adapter/adapter.md)
- [API > 核心模块 > 适配器](./core/adapter.md)
@@ -38,7 +38,7 @@
## 控制台 (Console)
-- [入门 > 认识控制台](../manual/console/index.md)
+- [入门 > 认识控制台](../manual/usage/market.md#认识控制台)
- [开发 > 控制台](../guide/console/index.md)
- [API > 控制台](./console/server.md)
@@ -58,7 +58,7 @@
消息元素类似于 HTML 元素,它是组成消息的基本单位。一个元素可以表示具有特定语义的内容,如文本、表情、图片、引用、元信息等。Koishi 会将这些元素转换为平台所支持的格式,以便在不同平台之间发送和接收消息。
- [开发 > 交互基础 > 消息元素](../guide/basic/element.md)
-- [开发 > 跨平台 > 消息编码](../guide/adapter/encoder.md)
+- [开发 > 跨平台 > 消息编码](../guide/adapter/message.md)
- [API > 消息元素](./message/syntax.md)
## 事件 (Events)
@@ -69,7 +69,7 @@
## 过滤器 (Filter)
-- [入门 > 过滤器](../manual/usage/filter.md)
+- [入门 > 过滤器](../manual/usage/customize.md#过滤器)
- [开发 > 模块化 > 过滤器](../guide/plugin/filter.md)
- [API > 内置服务 > 过滤器](./service/filter.md)
@@ -91,7 +91,7 @@
## 数据模型 (Model)
-- [开发 > 数据库 > 扩展数据模型](../guide/database/model.md#扩展数据模型)
+- [开发 > 数据库 > 数据模型](../guide/database/model.md)
- [API > 数据库 > 数据模型](./database/model.md)
## 平台 (Platform)
diff --git a/fr-FR/api/service/bots.md b/fr-FR/api/service/bots.md
index 8a4353b1099b..bcfaab93ee1a 100644
--- a/fr-FR/api/service/bots.md
+++ b/fr-FR/api/service/bots.md
@@ -1,7 +1,7 @@
# Bots
::: warning
-本节中介绍的 API 均为实验性功能,可能在后续版本中发生变化。
+本节中介绍的 API 目前属于实验性功能,可能在后续版本中发生变化。
:::
`ctx.bots` 保存了当前全部 [Bot](../core/bot.md) 实例。它继承了 Array 类,因此你可以使用诸如 `ctx.bots.forEach()` 的写法。除此以外,我们还提供了一些与机器人相关的实用方法。
diff --git a/fr-FR/api/service/events.md b/fr-FR/api/service/events.md
index 94b835fd7126..63c5c37299dc 100644
--- a/fr-FR/api/service/events.md
+++ b/fr-FR/api/service/events.md
@@ -1,7 +1,11 @@
# Événements
::: tip
-相关指南:[事件系统](../../guide/basic/events.md)
+参见:[开发 > 交互基础 > 事件系统](../../guide/basic/events.md)
参见:[开发 > 模块化 > 生命周期](../../guide/plugin/lifecycle.md)
+:::
+
+::: tip
+本节介绍事件 API。如果想了解 Koishi 的内置事件,请前往 [API > 核心模块 > 事件](../core/events.md)。
:::
## 实例方法
diff --git a/fr-FR/api/service/i18n.md b/fr-FR/api/service/i18n.md
index dfd625de7d04..7b6a07490085 100644
--- a/fr-FR/api/service/i18n.md
+++ b/fr-FR/api/service/i18n.md
@@ -1,7 +1,7 @@
# 国际化 (I18n)
::: tip
-相关指南:[国际化](../../guide/i18n/index.md)
+参见:[开发 > 国际化](../../guide/i18n/index.md)
:::
## 实例方法
diff --git a/fr-FR/api/service/permissions.md b/fr-FR/api/service/permissions.md
new file mode 100644
index 000000000000..bddd69fc8771
--- /dev/null
+++ b/fr-FR/api/service/permissions.md
@@ -0,0 +1,21 @@
+# 权限管理 (Permissions) 实验性
+
+::: warning
+权限管理目前属于实验性功能,API 可能在后续版本中发生变化。
+:::
+
+::: tip
+参见:[开发 > 数据库 > 权限管理](../../guide/database/permission.md)
+:::
+
+## 实例方法
+
+### ctx.permissions.define(name, inherits)
+
+### ctx.permissions.inherit(a, b)
+
+### ctx.permissions.depend(a, b)
+
+### ctx.permissions.provide(name, callback)
+
+### ctx.permissions.test(list, session)
diff --git a/fr-FR/cookbook/message.md b/fr-FR/cookbook/message.md
index 23f70ce0596a..a2748349658a 100644
--- a/fr-FR/cookbook/message.md
+++ b/fr-FR/cookbook/message.md
@@ -14,7 +14,7 @@
- 临时中间件会直接插在当前序列的尾端,并不会影响中间件池本身
- 如果执行中遇到错误或未调用 `next` 函数,会停止后续中间件的执行
4. 触发 [middleware](../api/core/events.md#事件:middleware) 事件
-5. 更新当前用户和群的缓冲数据 (参见 [按需加载与自动更新](../guide/database/observer.md#按需加载与自动更新))
+5. 更新当前用户和群的缓冲数据 (参见 [按需加载与自动更新](../guide/database/builtin.md#按需加载与自动更新))
### 内置中间件
diff --git a/fr-FR/cookbook/online.md b/fr-FR/cookbook/online.md
index d34603df7e59..31aced599a3b 100644
--- a/fr-FR/cookbook/online.md
+++ b/fr-FR/cookbook/online.md
@@ -89,11 +89,12 @@ export default plugin
```json title=package.json
{
"main": "lib/node/index.cjs",
- "typings": "lib/index.d.ts",
+ "types": "lib/index.d.ts",
"exports": {
".": {
"node": "./lib/node/index.cjs",
- "browser": "./lib/browser/index.mjs"
+ "browser": "./lib/browser/index.mjs",
+ "types": "./lib/index.d.ts"
},
"./shared": {
"require": "./lib/shared/index.cjs",
diff --git a/fr-FR/guide/adapter/adapter.md b/fr-FR/guide/adapter/adapter.md
index b8b2655d1af0..6d20b017252c 100644
--- a/fr-FR/guide/adapter/adapter.md
+++ b/fr-FR/guide/adapter/adapter.md
@@ -166,11 +166,12 @@ for (const event of parsed.events) {
首先调整目录结构,在 `server.ts` 和 `polling.ts` 中分别完成两种通信方式的适配器开发:
-```text{5-6}
+```text{5D,6-7A}
adapter-telegram
├── src
│ ├── bot.ts
│ ├── index.ts
+│ ├── adapter.ts
│ ├── polling.ts
│ └── server.ts
└── package.json
diff --git a/fr-FR/guide/adapter/binding.md b/fr-FR/guide/adapter/binding.md
deleted file mode 100644
index f2f701207386..000000000000
--- a/fr-FR/guide/adapter/binding.md
+++ /dev/null
@@ -1 +0,0 @@
-# 跨平台账号绑定
diff --git a/fr-FR/guide/adapter/integration.md b/fr-FR/guide/adapter/integration.md
index a64a6dfd9d52..617f8307f6e1 100644
--- a/fr-FR/guide/adapter/integration.md
+++ b/fr-FR/guide/adapter/integration.md
@@ -1,5 +1,42 @@
# 平台集成
-::: danger 注意
-此页文档正在施工,其中的内容可能不是最新。
+至此,Koishi 的适配器开发已经接近尾声。经过前面的几节内容,我们的适配器已经封装了平台接口,与服务器稳定地进行连接,并能够顺利地接受和发送消息。但除此以外,部分平台还提供了一些额外的能力,允许机器人做得更好。Koishi 当然也要把这些能力集成到机器人中。
+
+## 斜线指令
+
+::: tip
+相关章节:[指令开发](../basic/command.md)
+:::
+
+部分平台为机器人提供了斜线指令功能,用于在聊天框中快速输入指令。在 Discord 中差不多是这个效果:
+
+![slash command](/adapter/slash.png)
+
+适配器可以通过 `bot.updateCommands()` 方法,将 Koishi 的指令注册为平台的斜线指令:
+
+```ts
+class DiscordBot {
+ async updateCommands(commands: Universal.Command[]) {
+ // 这里忽略了部分细节,仅供参考
+ const updates = commands.map(Discord.encodeCommand)
+ await this.internal.bulkOverwriteGlobalApplicationCommands(this.selfId, updates)
+ }
+}
+```
+
+## 用户语言偏好
+
+::: tip
+相关章节:[多语言支持](../i18n/index.md)
:::
+
+部分平台本身支持多种语言。在这样的平台中,用户可以自行设置自己的语言偏好。当用户向机器人发送消息时,Koishi 就可以根据用户的语言偏好,做出相应语言的回复。
+
+而适配器所需要做的,就只有设置 `session.locales` 属性 (以 Telegram 平台为例):
+
+```ts
+if (from.language_code) {
+ // 这里为了简化逻辑,只取语言码的前两位
+ session.locales = [from.language_code.slice(0, 2)]
+}
+```
diff --git a/fr-FR/guide/adapter/message.md b/fr-FR/guide/adapter/message.md
new file mode 100644
index 000000000000..2cde4ccd0937
--- /dev/null
+++ b/fr-FR/guide/adapter/message.md
@@ -0,0 +1,298 @@
+# Encodé d'éléments
+
+在 [实现机器人](./bot.md#在适配器中访问) 一节中,我们其实已经涉及了格式转换的概念:
+
+```ts
+// 将 Discord 的数据结构转换为通用数据结构
+const decodeGuild = (data: Discord.Guild): Universal.Guild => ({
+ guildId: data.id,
+ guildName: data.name,
+})
+
+// 将通用数据结构转换为 Discord 的数据结构
+const encodeGuild = (data: Universal.Guild): Discord.Guild => ({
+ id: data.guildId,
+ name: data.guildName,
+})
+```
+
+不同平台对于同一个概念的接口会存在或多或少的差异。为了抹平这些差异,Koishi 引入了一套通用接口,用来描述这些跨平台的概念。在实现机器人和适配器时,通常都需要编写如上的函数,来对具体平台的数据进行转化。而这其中最复杂的则是对消息的处理。
+
+Koishi 使用 [消息元素](../basic/element.md) 表达任何聊天平台的消息。这是一种类似于 HTML 的格式。消息元素作为组成消息的基本单位,可以表示具有特定语义的内容,如文本、表情、图片、引用、元信息等。本节将介绍如何在消息元素与平台消息之间互相转换。
+
+## 接收消息
+
+在会话对象上存在两个属性与消息的内容有关:`content` 和 `elements`,它们分别对应着字符串形式和消息元素形式的消息内容。它们之间会自动转换,因此下面的两种写法是等价的:
+
+```ts
+session.content = '欢迎 '
+```
+
+```ts
+session.elements = [
+ h('text', { content: '欢迎 ' }),
+ h('at', { id: '1234567' }),
+]
+```
+
+在接收消息时,只需根据平台的格式对消息进行解码,将结果赋值到上述两个属性之一即可。下面是一个最简单的例子,假设平台的消息均以文本形式接收,并且使用 `@id` 的语法表达提及用户,那么你可以这么写:
+
+```ts
+session.content = input.replace(/@(\d+)/g, '')
+```
+
+## 发送消息
+
+### 兼容性原则
+
+在具体介绍消息发送之前,不知道你是否有这样的疑问:Koishi 提供了一整套标准的消息元素,但并非所有平台都支持这些元素。对于那些不支持的元素,应该如何处理呢?
+
+Koishi 的建议是**尽量兼容实现**。对于平台不支持的元素,可以根据元素的类型和用户的配置进行转化与回退。大致可以分为两种情况:
+
+- 修饰型的元素可以选择只渲染内部的元素,或以适当的方式进行文本修饰。
例如:在不支持粗体的平台上渲染 `` 时,可以改为只渲染粗体的内容。
例如:在不支持列表的平台上渲染 `` 时,可以在每个列表项前面渲染一个 `-`。
+
+- 占位型的元素尽量转换为可渲染的元素;如果实在无法渲染则抛出错误。
例如:如果平台不支持发送网络图片,可以先将图片下载到本地再发送。
例如:如果平台不支持发送语音,可以改为发送文件,或抛出错误。
+
+对于更加复杂的元素,适配器也可以发挥自主性,设计最适合的交互形式。例如,如果用户的需求是「从若干个选项中选择一个」,那么平台 A 可以渲染出多个按钮供用户点击;平台 B 则可以发送一条带有表态的消息,点击表态对应选择选项;实在不行,平台 C 也可以直接发送选项列表和文本提示语,并将用户的下一次输入作为选项。
+
+### 消息编码器
+
+之前介绍过的 REPL 适配器为了简化写法,并未包含消息的编码过程。对于一般的适配器,我们建议通过继承 `MessageEncoder` 类来实现消息的发送逻辑。
+
+这里我们以 Telegram 平台为例,首先在源码目录下创建 `message.ts`:
+
+```text{6A}
+adapter-example
+├── src
+│ ├── adapter.ts
+│ ├── bot.ts
+│ ├── index.ts
+│ └── message.ts
+└── package.json
+```
+
+在这个文件中我们定义 `TelegramMessageEncoder`:
+
+```ts title=message.ts
+class TelegramMessageEncoder extends MessageEncoder {
+ // 使用 payload 存储待发送的消息
+ private payload: Dict
+
+ constructor(bot: TelegramBot, channelId: string, guildId?: string, options?: SendOptions) {
+ super(bot, channelId, guildId, options)
+ const chat_id = guildId || channelId
+ this.payload = { chat_id, parse_mode: 'html', text: '' }
+ }
+
+ // 将发送好的消息添加到 results 中
+ async addResult(message: Telegram.Message) {
+ const session = this.bot.session()
+ await adaptMessage(message, session)
+ this.results.push(session)
+ session.app.emit(session, 'send', session)
+ }
+
+ // 发送缓冲区内的消息
+ async flush() {
+ let message: Telegram.Message
+ if (this.payload.text) {
+ message = await this.bot.internal.sendMessage(this.payload)
+ }
+ await this.addResult(message)
+ this.payload.text = ''
+ }
+
+ // 遍历消息元素
+ async visit(element: h) {
+ const { type, attrs, children } = element
+ if (type === 'text') {
+ this.payload.text += h.escape(attrs.content)
+ } else {
+ await this.render(children)
+ }
+ }
+}
+```
+
+一个 `MessageEncoder` 类需要提供 `flush` 和 `visit` 两个方法。前者用于发送缓冲区内的消息,后者用于遍历消息元素。消息发送完成后,还需要构造相应的 `Session`,用于触发 `send` 会话事件并存储于 `results` 数组中。
+
+与此同时,我们还需要修改 `TelegramBot` 类,为其添加静态属性。实现了 `MessageEncoder` 静态属性后,就无需手动实现 `bot.sendMessage()` 和 `bot.sendPrivateMessage()` 方法了:
+
+```ts title=bot.ts
+export class TelegramBot extends Bot {
+ static MessageEncoder = TelegramMessageEncoder
+}
+```
+
+### 行内元素
+
+上面的例子仅仅包含了消息编码器的基本结构,并未实现除了文本外的任何消息元素。对于任何非文本元素,上面的代码都会回退到其内部的文本。要添加更多消息元素的支持,只需在 `visit` 方法中添加更多的判断分支,就像这样:
+
+```ts
+if (type === 'text') {
+ this.payload.text += h.escape(attrs.content)
+} else if (['b', 'strong', 'i', 'em', 'u', 'ins', 's', 'del'].includes(type)) {
+ // 这些元素都是 Telegram 已经支持的,直接渲染成 HTML 即可
+ this.payload.text += `<${type}>`
+ await this.render(children)
+ this.payload.text += `${type}>`
+} else if (type === 'at') {
+ // 将 at 渲染为用户链接
+ this.payload.text += `@${attrs.name || attrs.id}`
+} else {
+ await this.render(children)
+}
+```
+
+### 消息分片
+
+在 Koishi 中,一次消息发送可能在目标平台产生多条独立的消息,称为消息分片。这也是为什么上面的 `results` 是一个数组。消息分片产生的原因是多样的:
+
+- 某些元素的语义就是发送独立的消息 (例如 ``)
+- 部分平台不支持某些消息元素的组合 (例如图文混合发送),此时必须对消息进行拆分
+- 待发送的消息长度超出平台限制,此时必须对消息进行拆分
+
+在需要对消息进行分片的场合,我们可以手动调用 `flush()` 方法。下面的代码展示了如何实现 `` 元素:
+
+```ts
+// 忽略前面的部分
+} else if (type === 'message') {
+ // 在解析内部元素之前先清空缓冲区
+ await this.flush()
+ await this.render(children)
+ await this.flush()
+} else ...
+```
+
+### 资源元素
+
+由于不同平台对于媒体资源的支持类型、发送方式、渲染形式有所不同,因此资源元素的情况会更加复杂。可以大致将各种平台规定的发送方式分为以下几类:
+
+1. 通过不同的 API 发送不同类型的资源 (例如 Telegram)
+2. 使用统一的 API,但通过不同的字段区分资源类型 (例如 Discord)
+3. 先上传资源获得链接或资源 ID,再调用发送 API (例如 Lark)
+
+这里我们还是以 Telegram 平台为例。首先照例修改 `visit` 方法。由于 Telegram 仅支持资源 + 文本的组合 (文本显示在资源下方),因此我们需要进行消息分片:
+
+```ts
+// 忽略前面的部分
+} else if (['image', 'audio', 'video', 'file'].includes(type)) {
+ await this.flush()
+ this.asset = element
+} else ...
+```
+
+接着,我们需要在 `flush` 方法中处理资源元素。Telegram 的资源上传接口是 `sendPhoto`、`sendAudio` 等,与文本所用的 `sendMessage` 不同,因此我们需要根据资源类型进行判断:
+
+```ts
+class TelegramMessageEncoder extends MessageEncoder {
+ async flush() {
+ let message: Telegram.Message
+ if (this.asset) {
+ const form = new FormData()
+ for (const key in this.payload) {
+ form.append(key, this.payload[key].toString())
+ }
+ const { type, attrs } = this.asset
+ const { filename, data } = await this.bot.ctx.http.file(attrs.url, attrs)
+ if (type === 'image') {
+ form.append('photo', data, filename)
+ message = await this.bot.internal.sendPhoto(form)
+ } else if (type === 'audio') {
+ form.append('audio', data, filename)
+ message = await this.bot.internal.sendAudio(form)
+ } else if (type === 'video') {
+ form.append('video', data, filename)
+ message = await this.bot.internal.sendVideo(form)
+ } else if (type === 'file') {
+ form.append('document', data, filename)
+ message = await this.bot.internal.sendDocument(form)
+ }
+ this.asset = null
+ } else if (this.payload.text) {
+ message = await this.bot.internal.sendMessage(this.payload)
+ }
+ await this.addResult(message)
+ this.payload.text = ''
+ }
+}
+```
+
+差不多这样就实现了资源元素的发送。值得一提的是,这里的代码使用了 `http.file()` 方法。它可以自动为我们处理 `http:`、`file:`、`data:` 等各种协议的资源链接,并将它们统一转换为 `ArrayBuffer`。这可以省去适配器解析资源链接的步骤,对于适配器开发是非常方便的。
+
+## 进阶知识
+
+下面的知识并非适用于所有适配器。但对于一些特殊的平台,你可能会用到它们。
+
+### 被动型平台
+
+我们通常将机器人做出的交互行为分为两种:主动交互和被动交互。
+
+- 主动交互是指机器人主动进行某些操作,例如定时任务、通知推送。
+- 被动交互是指机器人接收到特定事件后做出的响应,例如消息回复、入群欢迎。
+
+遗憾的是,部分平台会限制机器人的主动交互能力。例如,在 QQ 频道中,机器人每天只能发送极少量的主动消息;而对于被动消息,则必须在用户发送消息后的短时间内回复。这种平台被称为**被动型平台**。
+
+被动型平台要求适配器在发送消息时尽可能带有回复目标。当然 Koishi 也提供了解决方案:
+
+```ts{5}
+class QQGuildMessageEncoder {
+ async flush() {
+ await this.bot.internal.sendMessages(this.channelId, {
+ content: this.content,
+ msgId: this.options?.session?.messageId,
+ })
+ }
+}
+```
+
+在这一段代码中使用了 `this.options`,它存储了一些额外的发送选项。其中 `session` 正好对应着接收到消息的会话对象。当我们调用 `session.send()` 时,Koishi 会把当前的会话对象传递给 `MessageEncoder`。这样一来,我们就可以在发送消息时带上回复目标了。
+
+### 资源反向代理
+
+一些平台会使用 ID 标识资源文件 (例如 Lark)。当你接收到来自平台的消息时,拿到的是资源 ID 而非资源链接。此时你需要将资源 ID 转换为资源链接,才能构造合法的资源元素。
+
+::: tip
+Telegram 是另一种特殊情况。尽管其提供的资源链接是可用的,但这个链接中会明文包含机器人令牌,并非可以公开使用的链接。因此 Telegram 和其他类似平台也适用于这一节的内容。
+:::
+
+对于这种情况,一种**不推荐**的做法是直接下载资源,并转存为 `data:` 链接放入消息中。之所以不推荐,是因为这种做法有两大致命缺点:
+
+1. 这些图片本来可以按需加载,但现在却被强制下载到本地,造成额外的带宽消耗。
+2. 编码为 `data:` 会导致消息体积大幅增加,极大影响消息处理的性能。
+
+那么,有没有更好的解决方案呢?答案便是资源反向代理。我们要做的,是在本地提供一个路由,将资源 ID 映射到资源链接。这样一来,上面提到的两个问题也就都解决了。
+
+下面是 Lark 适配器的一部分代码,用于实现资源反向代理 (位于 `adapter.ts`):
+
+```ts
+class LarkAdapter {
+ constructor(ctx: Context) {
+ ctx.router.get('/lark/assets/:message_id/:key', async (ctx) => {
+ const key = ctx.params.key
+ const messageId = ctx.params.message_id
+ const selfId = ctx.request.query.self_id
+ const bot = this.bots.find((bot) => bot.selfId === selfId)
+ if (!bot) return ctx.status = 404
+ const response = await bot.http(`/im/v1/messages/${messageId}/resources/${key}`, {
+ method: 'GET',
+ params: { type: 'image' },
+ responseType: 'stream',
+ })
+ ctx.status = 200
+ ctx.response.headers['content-type'] = response.headers['content-type']
+ ctx.response.body = response.data
+ })
+ }
+}
+```
+
+然后在接收消息的逻辑中,我们只需要将资源 ID 转换为资源链接即可:
+
+```ts
+h.image(`http://${host}/image/${message_id}/${image_key}?self_id=${selfId}`)
+```
+
+::: tip
+反向代理同时也带来了一个新的问题,那就是当这个链接被原样发送时,外网可能无法访问到这个链接。但无需担心,上面提到的 `http.file()` 方法恰好可以解决这个问题。因此,即使经过了反向代理,Koishi 也可以确保消息的跨平台转发插件能够正常工作。
+:::
diff --git a/fr-FR/guide/adapter/writing.md b/fr-FR/guide/adapter/writing.md
deleted file mode 100644
index f654a4d2590c..000000000000
--- a/fr-FR/guide/adapter/writing.md
+++ /dev/null
@@ -1,172 +0,0 @@
-# 编写适配器插件
-
-::: danger 注意
-此页文档正在施工,其中的内容可能不是最新。
-:::
-
-Koishi 通过 **适配器 (Adapter)** 实现对多账户和跨平台的实现。在我们开始之前,首先你需要了解多机器人在 Koishi 中究竟是以何种方式进行组织的。这个问题用一句话来说就是:一个应用可以有多个平台,每个平台可以有多个适配器,每个适配器可以有多个机器人。在上面的例子中,形如 onebot 的是 **平台名称**,形如 onebot:http 的是**适配器名称**。
-
-当应用被创建时,它会按照配置创建所有的适配器和机器人实例。如果多个机器人使用了同一种适配器,那么会创建一个适配器实例绑定多个机器人。它们的关系用代码表示就是这样:
-
-```ts no-extra-header
-class App {
- // 可以使用 adapters[type] 找到对应的适配器
- adapters: Record
- // 可以使用 Array 方法遍历全部 bots
- // 也可以使用 bots[`${platform}:${selfId}`] 找到具体的某一个
- bots: Bot[] & Record
-}
-
-class Adapter {
- // 可以使用 Array 方法遍历全部 bots
- // 也可以使用 bots[selfId] 找到具体的某一个
- bots: Bot[] & Record
- // 所在的 App 实例
- app: App
-}
-
-class Bot {
- // 所在的 App 实例
- app: App
- // 所在的 Adapter 实例
- adapter: Adapter
-}
-```
-
-当适配器收到一个上报事件时,它会首先对事件进行鉴权,并处理好改事件的响应值。接着这个适配器将按照事件的内容生成一个会话对象,并使用 `adapter.dispatch` 将其在对应的上下文触发事件。因此,如果你需要编写一个平台支持,你只需要做三件事:
-
-- 编写这个平台的 Bot 类,实现 Koishi 所需的方法
-- 编写这个平台的 Adapter 类,实现 start() 和 stop() 方法
-- 注册这个 Adapter
-
-### 一个 Webhook 例子
-
-下面是一个使用 Webhook 的例子。适配器通过 http post 请求接受事件推送。
-
-::: tabs code
-```js no-extra-header
-const { Adapter, Bot, Session } = require('koishi')
-
-class MyBot extends Bot {
- async sendMessage(channelId, content) {
- // 这里应该执行发送操作
- this.logger.debug('send:', content)
- return []
- }
-}
-
-class MyAdapter extends Adapter {
- constructor(ctx) {
- // 请注意这里的第二个参数是应该是一个构造函数而非实例
- super(ctx, MyBot)
- }
-
- start() {
- // 收到 http post 请求时,生成会话对象并触发事件
- this.ctx.router.post('/', (ctx) => {
- const session = new Session(this.app, ctx.request.body)
- this.dispatch(session)
- })
- }
-
- stop() {}
-}
-
-// 注册适配器
-Adapter.types['my-adapter'] = MyAdapter
-```
-```ts no-extra-header
-import { App, Adapter, Bot, Session } from 'koishi'
-
-class MyBot extends Bot {
- async sendMessage(channelId: string, content: string) {
- // 这里应该执行发送操作
- this.logger.debug('send:', content)
- return []
- }
-}
-
-class MyAdapter extends Adapter {
- constructor(app: App) {
- // 请注意这里的第二个参数是应该是一个构造函数而非实例
- super(app, MyBot)
- }
-
- start() {
- // 收到 http post 请求时,生成会话对象并触发事件
- this.app.router.post('/', (ctx) => {
- const session = new Session(this.app, ctx.request.body)
- this.dispatch(session)
- })
- }
-
- stop() {}
-}
-
-// 注册适配器
-Adapter.types['my-adapter'] = MyAdapter
-```
-:::
-
-### 一个 WebSocket 例子
-
-WebSocket 的逻辑相比 Webhook 要稍微复杂一些,因此我们提供了一个工具类:
-
-::: tabs code
-```js no-extra-header
-const { Adapter, Bot, Session } = require('koishi')
-const WebSocket = require('ws')
-
-class MyAdapter2 extends Adapter.WsClient {
- constructor(app) {
- // MyBot 跟上面一样,我就不写了
- super(app, MyBot)
- }
-
- // prepare 方法要求你返回一个 WebSocket 实例
- prepare(bot) {
- return new WebSocket('ws://websocket-endpoint')
- }
-
- // connect 方法将作为 socket.on('open') 的回调函数
- connect(bot) {
- bot.socket.on('message', (data) => {
- const body = JSON.parse(data.toString())
- const session = new Session(this.app, body)
- this.dispatch(session)
- })
- }
-}
-
-// 注册适配器
-Adapter.types['another-adapter'] = MyAdapter2
-```
-```ts no-extra-header
-import { Adapter, Bot, Session } from 'koishi'
-import WebSocket from 'ws'
-
-class MyAdapter2 extends Adapter.WsClient {
- constructor(app: App) {
- // MyBot 跟上面一样,我就不写了
- super(app, MyBot)
- }
-
- // prepare 方法要求你返回一个 WebSocket 实例
- prepare(bot: MyBot) {
- return new WebSocket('ws://websocket-endpoint')
- }
-
- // connect 方法将作为 socket.on('open') 的回调函数
- connect(bot: MyBot) {
- bot.socket.on('message', (data) => {
- const body = JSON.parse(data.toString())
- const session = new Session(this.app, body)
- this.dispatch(session)
- })
- }
-}
-
-// 注册适配器
-Adapter.types['another-adapter'] = MyAdapter2
-```
-:::
diff --git a/fr-FR/guide/basic/advanced.md b/fr-FR/guide/basic/advanced.md
index 931d15d2fa59..3d78045d7ff0 100644
--- a/fr-FR/guide/basic/advanced.md
+++ b/fr-FR/guide/basic/advanced.md
@@ -27,7 +27,7 @@ ctx.bots[`${platform}:${selfId}`]
await bot.broadcast(['123456', '456789'], '全体目光向我看齐')
```
-但这样写需要知道每一个频道对应哪个机器人。对于启用了多个机器人的场景下,这么写就有点不方便了。幸运的是,Koishi 还有另一个方法:`ctx.broadcast()`。在启用了数据库的情况下,此方法会自动获取每个频道的 [受理人](../../manual/usage/permission.md#受理人机制),并以对应的账号发送消息:
+但这样写需要知道每一个频道对应哪个机器人。对于启用了多个机器人的场景下,这么写就有点不方便了。幸运的是,Koishi 还有另一个方法:`ctx.broadcast()`。在启用了数据库的情况下,此方法会自动获取每个频道的 [受理人](../../manual/usage/customize.md#受理人机制),并以对应的账号发送消息:
```ts
await ctx.broadcast(['onebot:123456', 'discord:456789'], '全体目光向我看齐')
diff --git a/fr-FR/guide/console/extension.md b/fr-FR/guide/console/extension.md
deleted file mode 100644
index af9d2b88daea..000000000000
--- a/fr-FR/guide/console/extension.md
+++ /dev/null
@@ -1,128 +0,0 @@
-# 编写扩展
-
-## 创建扩展
-
-::: tabs code
-```npm
-npm i @koishijs/client @koishijs/plugin-console -D
-```
-```yarn
-yarn add @koishijs/client @koishijs/plugin-console -D
-```
-:::
-
-在项目中新建这几个文件:
-
-```diff
-└── my-plugin
-+ ├── client
-+ │ ├── index.ts
-+ │ ├── custom-page.vue
-+ │ └── tsconfig.json
- ├── src
- │ └── index.ts
- ├── package.json
- └── tsconfig.json
-```
-
-```ts title=client/index.ts no-extra-header
-import { Context } from '@koishijs/client'
-import Page from './custom-page.vue'
-
-export default (ctx: Context) => {
- // 此 Context 非彼 Context
- // 我们只是在前端同样实现了一套插件逻辑
- ctx.page({
- name: '页面标题',
- path: '/custom-page',
- component: Page,
- })
-}
-```
-
-```vue title=client/custom-page.vue
-
-
- 扩展内容
-
-
-```
-
-```json title=client/tsconfig.json
-{
- "compilerOptions": {
- "rootDir": ".",
- "module": "esnext",
- "moduleResolution": "node",
- "types": [
- // 这一行的作用是导入相关全局类型
- // 以便于在编辑器中显示更好的代码提示
- "@koishijs/client/global",
- ],
- },
- "include": ["."],
-}
-```
-
-接着修改你的入口文件:
-
-```ts title=src/index.ts
-import { Context } from 'koishi'
-// 此处需要导入 @koishijs/plugin-console 以获取类型
-import {} from '@koishijs/plugin-console'
-import { resolve } from 'path'
-
-export const name = 'my-plugin'
-
-export function apply(ctx: Context) {
- // 在已有插件逻辑的基础上,添加下面这段
- ctx.using(['console'], (ctx) => {
- ctx.console.addEntry({
- dev: resolve(__dirname, '../client/index.ts'),
- prod: resolve(__dirname, '../dist'),
- })
- })
-}
-```
-
-## 调试模式
-
-启动应用,并配置 console 插件进入调试模式:
-
-```yaml
-plugins:
- console:
- devMode: true
- my-plugin:
-```
-
-你就可以在网页中看到自己刚刚创建的页面了。
-
-## 构建代码
-
-调试好你的扩展后,下一步就是构建了。修改你的 package.json:
-
-```json title=package.json
-{
- "files": [
- "lib", // 我们假设 src 目录编译到 lib 目录
- "dist", // 这里的 dist 目录就是留给 client 的
- ],
- "scripts": {
- // @koishijs/client 提供了一个指令 koishi-console build
- // 它可以用来构建 client 目录中的扩展台扩展到 dist 目录
- "build:console": "koishi-console build",
- },
-}
-```
-
-然后运行上面的脚本就大功告成啦:
-
-::: tabs code
-```npm
-npm run build:console
-```
-```yarn
-yarn build:console
-```
-:::
diff --git a/fr-FR/guide/console/index.md b/fr-FR/guide/console/index.md
index 866bab6e8714..fb8af0328354 100644
--- a/fr-FR/guide/console/index.md
+++ b/fr-FR/guide/console/index.md
@@ -1,7 +1,7 @@
# Extension de la console
::: tip
-在学习本章之前,建议先完整阅读 [入门 > 认识控制台](../../manual/console/index.md)。
+在学习本章之前,建议先完整阅读 [入门 > 认识控制台](../../manual/usage/market.md#认识控制台)。
:::
## 创建扩展
diff --git a/fr-FR/guide/database/builtin.md b/fr-FR/guide/database/builtin.md
new file mode 100644
index 000000000000..4125caa6409e
--- /dev/null
+++ b/fr-FR/guide/database/builtin.md
@@ -0,0 +1,114 @@
+# 内置数据结构
+
+::: danger 注意
+此页文档正在施工,其中的内容可能不是最新。
+:::
+
+上面介绍了一些 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)
+```
diff --git a/fr-FR/guide/database/index.md b/fr-FR/guide/database/index.md
index 6bbf5a37d01c..a9e62f03a973 100644
--- a/fr-FR/guide/database/index.md
+++ b/fr-FR/guide/database/index.md
@@ -1,52 +1,12 @@
-# 使用数据库
+# 基本用法
::: tip
-本章所介绍的内容需要你安装一个数据库支持。如果你暂时不打算使用数据库,那么可以略过。
+`ctx.database` 并非内置服务,因此如果你的插件需要使用数据库功能,需要[声明依赖](../plugin/service.md#using-属性)。
:::
-对于几乎所有大型机器人项目,数据库的使用都是不可或缺的。但如果每个插件都使用了自己的数据库,这将导致插件之间的兼容性非常差——用户要么选择同时安装多个数据库,要么只能放弃一些功能或者重复造轮子。为此,Koishi 设计了一整套对象关系映射 (ORM) 接口,它易于扩展并广泛地运用于各种插件中。同时,我们也提供了一些常用数据库的官方插件,足以应对绝大部分使用场景。
+对于几乎所有大型机器人项目,数据库的使用都是不可或缺的。但如果每个插件都独立处理与数据库的交互,这将导致插件之间的兼容性非常差——用户要么选择同时安装多个数据库,要么只能放弃一些功能。为此,Koishi 设计了一整套对象关系映射 (ORM) 接口,它易于扩展并广泛地运用于各种插件中。同时,我们也提供了一些常用数据库的官方插件,足以应对绝大部分使用场景。
-## 安装数据库
-
-如果你是插件开发者,你并不需要关心具体的数据库实现。但是如果你是 Koishi 的使用者,只有当安装了数据库你才能正常使用所有的特性。首先你需要安装数据库依赖:
-
-::: tabs code
-```npm
-# 我们以 mysql 数据库为例
-npm i @koishijs/plugin-database-mysql -D
-```
-```yarn
-# 我们以 mysql 数据库为例
-yarn add @koishijs/plugin-database-mysql -D
-```
-:::
-
-然后与你添加插件同样的方法配置你的数据库:
-
-```yaml title=koishi.yml
-plugins:
- database-mysql:
- host: host
- port: 3306
- user: root
- password: password
- database: database
-```
-
-运行程序后,你就可以通过访问 `ctx.database` 来调用数据库接口了:
-
-```ts
-// @errors: 2304
-// 获取用户数据
-const user = await ctx.database.getUser(platform, id)
-
-// 修改频道数据
-await ctx.database.setChannel(platform, id, { assignee: '123456789' })
-```
-
-你可以在后面的 API 文档中看到全部内置的 [数据库方法](../../api/database/database.md)。
-
-## 获取数据
+## `get`:查询数据
使用 `database.get()` 方法以获取特定表中的数据。下面是一个最基本的形式:
@@ -61,8 +21,8 @@ await ctx.database.get('schedule', [1234, 5678])
对于复杂的数据表,如果你只需要获取少数字段,你可以通过第三个参数手动指定要获取的字段:
```ts
-// 返回的数组中每个元素只会包含 command, lastCall 属性
-await ctx.database.get('schedule', [1234], ['command', 'lastCall'])
+// 返回的数组中每个元素只会包含 command, time 属性
+await ctx.database.get('schedule', [1234], ['command', 'time'])
```
你还可以向第二个参数传入一个对象,用来查询非主键上的数据或者同时指定多列的值:
@@ -97,31 +57,19 @@ await ctx.database.get('schedule', {
你可以在 [这里](../../api/database/query.md) 看到完整的查询表达式 API。
-## 添加和删除数据
+## `create`:插入数据
-添加和删除数据的语法也非常简单:
+使用 `database.create()` 方法以插入数据。
```ts
-// @errors: 2304
-// 从 schedule 表中删除特定 id 的数据行
-// 第二个参数也可以使用上面介绍的查询表达式
-await ctx.database.remove('schedule', [id])
-
// 向 schedule 表中添加一行数据,data 是要添加的数据行
-// 返回值是添加的行的完整数据(包括自动生成的 id 和默认属性等)
+// 返回值是添加的行的完整数据 (包括自动填充的 id 和默认属性等)
await ctx.database.create('schedule', row)
```
-## 修改数据
-
-Koishi 提供了两种修改数据的方法。我们将逐一介绍。
+如果你想要批量插入数据,可以使用下面介绍的 `database.upsert()` 方法。
-| | set | upsert |
-| ---- | -------------- | ----------- |
-| 作用范围 | 支持复杂的查询表达式 | 只能限定特定字段的值 |
-| 插入行为 | 如果不存在则不会进行任何操作 | 如果不存在则会插入新行 |
-
-### 使用 set 修改数据
+## `set`:修改数据
`database.set()` 方法需要传入三个参数:表名、查询条件和要修改的数据。
@@ -129,7 +77,7 @@ Koishi 提供了两种修改数据的方法。我们将逐一介绍。
// 第二个参数也可以使用上面介绍的查询表达式
await ctx.database.set('schedule', 1234, {
assignee: 'onebot:123456',
- lastCall: new Date(),
+ time: new Date(),
})
```
@@ -146,7 +94,7 @@ await ctx.database.set('foo', { date: new Date() }, {
你可以在 [这里](../../api/database/evaluation.md) 看到完整的求值表达式 API。
-### 使用 upsert 修改数据
+## `upsert`:修改或插入数据
`database.upsert()` 的逻辑稍微有些不同,需要你传入一个数组:
@@ -186,3 +134,32 @@ await ctx.database.upsert('user', rows, 'onebot')
// 以复合键为基准对数据表进行更新,你需要确保每一个元素都拥有 platform 和 id 属性
await ctx.database.upsert('channel', rows, ['platform', 'id'])
```
+
+## `remove`:删除数据
+
+使用 `database.remove()` 方法以删除特定表中的数据。
+
+```ts
+// 从 schedule 表中删除特定 id 的数据行
+// 第二个参数也可以使用上面介绍的查询表达式
+await ctx.database.remove('schedule', [id])
+```
+
+## 对比 set 和 upsert
+
+`set` 与 `upsert` 方法都可以用于修改已经存在的数据,它们的区别如下表所示:
+
+| | set | upsert |
+| ---- | ---------------- | ------------- |
+| 作用范围 | 支持复杂的查询表达式 | 只能限定特定字段的值 |
+| 插入行为 | 如果数据不存在则不会进行任何操作 | 如果数据不存在则会插入新行 |
+
+## 对比 create 和 upsert
+
+`create` 与 `upsert` 方法都可以用于插入新的数据,它们的区别如下表所示:
+
+| | create | upsert |
+| ---------------- | ----------- | ------------- |
+| 插入数量 | 只能插入一条数据 | 可以批量插入多条数据 |
+| valeur de retour | 返回经过填充后的数据 | 没有返回值 |
+| 冲突行为 | 如果数据已存在则会报错 | 如果数据已存在则会执行修改 |
diff --git a/fr-FR/guide/database/model.md b/fr-FR/guide/database/model.md
index 22fe3aa36185..6618dacc3c34 100644
--- a/fr-FR/guide/database/model.md
+++ b/fr-FR/guide/database/model.md
@@ -1,92 +1,133 @@
-# 扩展数据模型
+# 数据模型
-如果你的插件需要声明新的字段或者表,你可以通过 `ctx.model` 来对数据模型进行扩展。请注意,数据模型的扩展一定要在使用前完成,不然后续数据库操作可能会失败。
+Koishi 的架构允许任何插件对数据库的结构进行扩展。你就可以在不修改 Koishi 或其他插件源码的情况下,为数据库添加新的字段或者表。这些功能都是通过 `ctx.model` 提供的。
-## 扩展字段
+请注意:数据模型的扩展一定要在使用前完成,不然后续数据库操作可能会失败。
-向内置的 User 表中注入字段的方式如下:
+## 扩展表和字段
-```ts
-// @errors: 1117
-// TypeScript 用户需要进行类型合并
-declare module 'koishi' {
- interface User {
- foo: string
- }
-}
-
-ctx.model.extend('user', {
- // 向用户表中注入字符串字段 foo
- foo: 'string',
- // 你还可以配置默认值为 'bar'
- foo: { type: 'string', initial: 'bar' },
-})
-```
-
-向 Channel 注入字段同理。
-
-## 扩展表
-
-利用 `ctx.model.extend()` 的第三个参数,我们就可以定义新的数据表了:
+可以使用 `model.extend()` 方法扩展一个新的数据表,其中的第一个参数是表名,第二个参数包含了各字段的类型声明。下面的代码向数据库中扩展了一个名为 `schedule` 的表:
```ts
-// @errors: 2440
-// TypeScript 用户需要进行类型合并
declare module 'koishi' {
interface Tables {
schedule: Schedule
}
}
+// 这里是新增表的接口类型
export interface Schedule {
id: number
assignee: string
time: Date
- lastCall: Date
interval: number
command: string
session: Session.Payload
}
ctx.model.extend('schedule', {
- // 各字段类型
+ // 各字段的类型声明
id: 'unsigned',
assignee: 'string',
time: 'timestamp',
- lastCall: 'timestamp',
interval: 'integer',
command: 'text',
session: 'json',
-}, {
- // 使用自增的主键值
- autoInc: true,
})
```
-## 创建索引
-
-我们还可以为数据库声明索引:
+`model.extend()` 同样也可以向已经存在的表中注入新的字段,使用方法与上面完全一致。例如,下面的代码向内置的 `User` 表中注入了 `foo` 字段:
```ts
declare module 'koishi' {
- interface Tables {
- foo: Foo
+ interface User {
+ foo: string
}
}
-interface Foo {
- name: string
- bar: string
- baz: string
- uid: 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
+ }
+ }
}
-// ---cut---
+// 声明嵌套类型时,对象的多级属性被拼接为一个字符串
+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'],
@@ -97,3 +138,33 @@ ctx.model.extend('foo', {}, {
},
})
```
+
+## 整表迁移 实验性
+
+::: 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')`。
diff --git a/fr-FR/guide/database/permission.md b/fr-FR/guide/database/permission.md
new file mode 100644
index 000000000000..246a44b7bb38
--- /dev/null
+++ b/fr-FR/guide/database/permission.md
@@ -0,0 +1,129 @@
+# 权限管理 实验性
+
+::: warning
+权限管理目前属于实验性功能,API 可能随时会发生变化。
+:::
+
+## 权限是什么?
+
+权限其实就是执行某些操作的能力。权限具有唯一的标识符,通常由小写英文字母、数字、短横线和点构成。下面是一些你可能会见到的权限名称:
+
+- `user.514`:ID 为 514 的用户的权限
+- `group.114`:ID 为 114 的用户组的权限
+- `authority.3`:权限等级 3 的权限
+- `command.foo`:指令 foo 的权限
+- `command.foo.option.bar`:指令 foo 选项 bar 的权限
+- `onebot.admin`:onebot 平台下群管理员的权限
+- `bot.channel.mute`:能够禁言频道的机器人的权限
+- `config.write`:能够写入配置文件的权限
+
+权限之间存在两种关系:继承和依赖。Koishi 不区分权限与权限组的概念,权限组只是继承了其他权限的权限。你可以将用户和用户组也都视为一种权限组。
+
+## 权限的继承
+
+权限可以继承其他权限。它的基本写法如下:
+
+```ts
+ctx.permissions.inherit(A, B)
+```
+
+如果权限 A 继承了权限 B,那么拥有权限 A 的主体将被视为同时拥有权限 B。
+
+例:ID 为 514 的用户拥有权限等级 3,指令 foo 所需要的权限等级是 2。这种情况下该用户显然应该可以调用该指令。那么这种调用关系具体是如何体现的呢?
+
+```text
+user.514 > authority.3 > authority.2 > command.foo
+```
+
+这里出现了三个继承关系:
+
+- `user.114 > authority.3`,因为 ID 为 514 的用户拥有权限等级 3
+- `authority.3 > authority.2`,因为权限等级 3 天生比权限等级 2 大(内置逻辑)
+- `authority.2 > command.foo`,因为指令 foo 被权限等级 2 继承
+
+由此,权限的继承关系能够顺利表达已有的权限等级机制,并且具备更强的表达能力。
+
+### 权限的多继承
+
+权限继承除了不能循环外,几乎没有任何限制。因此,任何一个权限既可以被多个权限继承,也可以继承多个权限。下面分别展示一些使用例。
+
+例:ID 为 514 的用户同时继承了权限等级 1,而指令 foo 所需权限等级 2,此时该用户并不能调用 foo 指令。但如果现在我们让该用户直接继承 foo 指令的调用权限,会发生什么呢?
+
+```text
+user.514 > authority.1
+ > command.foo
+```
+
+在这张图中,`user.514` 并不能经由 `authority.1` 到达 `command.foo`,但是添加第二条边后又可以直接到达 `command.foo` 了。因此该用户此时就又可以调用 foo 指令了。
+
+可以看到,权限的继承为我们提供了一种能力,可以允许特定低等级用户去调用高级权限的指令,这种能力是过去的权限等级所不具有的。
+
+例:我们希望某个管理型指令 foo 既可以被权限等级 2 的用户调用,又可以被 QQ 群的管理员调用。此时我们可以对 foo 指令进行以下配置:
+
+```text
+authority.2 >
+onebot.admin > command.foo
+```
+
+这样一来,一个用户只需满足上述两个条件之一就可以调用此指令了。
+
+可以看到,判断指令是否可以被用户调用,本质上是判断用户自身的权限是否可以顺着权限继承的关系到达指令的权限。同时,权限的继承允许我们给指令的调用设置多个不同条件。
+
+## 权限的依赖
+
+Koishi 中的权限不仅存在继承关系,还存在依赖关系。它的基本写法如下:
+
+```ts
+ctx.permissions.depend(A, B)
+```
+
+如果权限 A 依赖了权限 B,那么要执行权限 A 的操作时必须同时检查权限 B。
+
+例:foo 指令的代码中调用了 bar 指令,因此 foo 指令依赖 bar 指令。
+
+```text
+command.foo -> command.bar
+```
+
+如果用户只拥有 foo 的权限,没有调用 bar 的权限,他依然无法调用 foo 指令。
+
+例:foo 指令的代码中使用了 `bot.muteChannel()`。
+
+```text
+command.foo -> bot.channel.mute
+```
+
+如果机器人没有实现此 API,用户就无法在该机器人上调用 foo 指令。
+
+由此可见,权限的依赖与继承不同。继承更多的是一种管理上的考虑,而依赖则关乎具体的功能实现。
+
+## 权限访问器
+
+在上面的介绍中,如果要定义新的权限,就必须手动分配给用户或用户组后才能使用。有没有方法自动为一个会话分配权限呢?这就是权限访问器的功能。
+
+```ts
+ctx.permissions.provide('onebot.admin', async (name, session) => {
+ return session.onebot?.sender?.role === 'admin'
+})
+```
+
+上面的代码的作用是:当某个会话处于 onebot 平台,并且发送者是群内管理员时,自动附加一个 onebot.admin 权限。利用这种技术,我们就可以为特定平台提供权限能力了。
+
+每个权限可以定义多个访问器函数。在运行时必须通过每一个访问器函数的检查才能视为拥有权限。
+
+一个权限要么是普通权限,要么是访问器权限。下面是一些区别:
+
+| 普通权限 | 访问器权限 |
+| ----------- | --------- |
+| 手动分配给用户 (组) | 自动分配给会话 |
+| 可以被其他权限继承 | 不能被其他权限继承 |
+
+## 权限国际化
+
+普通权限要被用于指令和控制台中显示,因此需要做国际化。具体的做法也很简单:
+
+- 通过 API 定义:使用 `permission.{name}` 提供翻译文本
+- 通过指令定义:定义时提供文本 (自动视为当前用户语言),或通过 `--locale` 指定特定语言的文本
+- 通过控制台定义:可以在控制台「用户管理」界面中配置用户组文本
+
+访问器权限由于其不能被其他权限继承,因此不需要做国际化。
diff --git a/de-DE/guide/adapter/encoder.md b/fr-FR/guide/database/select.md
similarity index 80%
rename from de-DE/guide/adapter/encoder.md
rename to fr-FR/guide/database/select.md
index d06c29fff73c..87b80776b7d7 100644
--- a/de-DE/guide/adapter/encoder.md
+++ b/fr-FR/guide/database/select.md
@@ -1,4 +1,4 @@
-# 消息编码
+# 进阶查询技巧
::: danger 注意
此页文档正在施工,其中的内容可能不是最新。
diff --git a/fr-FR/guide/develop/publish.md b/fr-FR/guide/develop/publish.md
index ee7ab64849db..d9471818171c 100644
--- a/fr-FR/guide/develop/publish.md
+++ b/fr-FR/guide/develop/publish.md
@@ -158,6 +158,11 @@ yarn pub [...name]
这将发布所有版本号发生变动的插件。
+::: tip
+从插件成功发布到进插件市场需要一定的时间 (通常在 15 分钟内),请耐心等待。
+:::
+
+:::: tip
如果你配置了国内镜像,你可能会遇到以下的错误提示:
```text
@@ -177,7 +182,8 @@ yarn login
```
:::
-发布成功后,你可以将镜像重新设置为国内镜像,以保证后续的下载速度。
+发布成功后,你可以将镜像重新设置为国内镜像,以保证后续的下载速度。 :
+:::
## 更新插件版本
diff --git a/fr-FR/guide/develop/setup.md b/fr-FR/guide/develop/setup.md
index d5e4f75f7964..075dc0119062 100644
--- a/fr-FR/guide/develop/setup.md
+++ b/fr-FR/guide/develop/setup.md
@@ -2,46 +2,7 @@
本节将介绍推荐的开发环境搭建流程。如果某些软件已经安装完成,可以跳过对应的步骤。
-## 安装 Node.js
-
-Koishi 需要 [Node.js](https://nodejs.org/) (最低 v14,推荐使用 LTS) 运行环境,你需要自己安装它。
-
-### 下载安装包
-
-首先我们前往 [Node.js](https://nodejs.org/) 的官方网站:
-
-![home](/manual/nodejs/home-dark.webp) {.dark-only}
-
-![home](/manual/nodejs/home-light.webp) {.light-only}
-
-在这里可以看到两个巨大的按钮,分别对应着 **LTS (长期维护版)** 和 **Current (最新版本)**。我们建议你选择更加稳定的 LTS 版本,点击按钮即可下载安装包。
-
-随后,运行下载好的安装包,根据提示完成整个安装流程即可。
-
-### 安装包管理器
-
-Node.js 自带名为 [npm](https://www.npmjs.com/) 的包管理器,你可以直接使用它。我们同时也推荐功能更强大的 [yarn](https://classic.yarnpkg.com/) 作为包管理器。它的安装非常简单,只需打开命令行输入下面的命令:
-
-```sh
-# 安装 yarn
-npm i -g yarn
-
-# 查看版本
-yarn -v
-```
-
-### 配置镜像源
-
-如果你是国内用户,从 npm 或 yarn 上下载依赖可能非常慢。因此,我们推荐你配置一下镜像源,以提升安装速度。
-
-::: tabs code
-```npm
-npm config set registry https://registry.npmmirror.com
-```
-```yarn
-yarn config set registry https://registry.npmmirror.com
-```
-:::
+
### 注册 npm
@@ -63,7 +24,7 @@ Git 是最普遍使用的版本控制工具。前往 [官网](https://git-scm.co
![downloads](/manual/git/downloads.webp)
-国内的 Windows 用户也可以选择从 [镜像](https://registry.npmmirror.com/binary.html?path=git-for-windows/) 下载。如果不知道下载哪个版本,可以在上面的官网中看到 (比如现在是 2.39.1)。
+国内的 Windows 用户也可以选择从 [镜像](https://registry.npmmirror.com/binary.html?path=git-for-windows/) 下载。如果不知道下载哪个版本,可以在上面的官网中看到 (比如图中就是 2.39.1)。
获取到安装包后,双击运行。安装过程无需手动配置,一直点击下一步即可完成安装。
@@ -111,7 +72,6 @@ yarn create koishi
如果你顺利完成了上述操作,你的应用此时应该已经是启动状态,并弹出了控制台界面。接下来的几节中我们将学习更多的命令行用法,因此我们可以先关闭 Koishi。在命令行中按下 `Ctrl+C` 组合键即可停止 Koishi 的运行。
-
### 注册 npm
@@ -63,7 +24,7 @@ Git 是最普遍使用的版本控制工具。前往 [官网](https://git-scm.co
![downloads](/manual/git/downloads.webp)
-国内的 Windows 用户也可以选择从 [镜像](https://registry.npmmirror.com/binary.html?path=git-for-windows/) 下载。如果不知道下载哪个版本,可以在上面的官网中看到 (比如现在是 2.39.1)。
+国内的 Windows 用户也可以选择从 [镜像](https://registry.npmmirror.com/binary.html?path=git-for-windows/) 下载。如果不知道下载哪个版本,可以在上面的官网中看到 (比如图中就是 2.39.1)。
获取到安装包后,双击运行。安装过程无需手动配置,一直点击下一步即可完成安装。
@@ -111,7 +72,6 @@ yarn create koishi
如果你顺利完成了上述操作,你的应用此时应该已经是启动状态,并弹出了控制台界面。接下来的几节中我们将学习更多的命令行用法,因此我们可以先关闭 Koishi。在命令行中按下 `Ctrl+C` 组合键即可停止 Koishi 的运行。
-
### 注册 npm
@@ -63,7 +24,7 @@ Git 是最普遍使用的版本控制工具。前往 [官网](https://git-scm.co
![downloads](/manual/git/downloads.webp)
-国内的 Windows 用户也可以选择从 [镜像](https://registry.npmmirror.com/binary.html?path=git-for-windows/) 下载。如果不知道下载哪个版本,可以在上面的官网中看到 (比如现在是 2.39.1)。
+国内的 Windows 用户也可以选择从 [镜像](https://registry.npmmirror.com/binary.html?path=git-for-windows/) 下载。如果不知道下载哪个版本,可以在上面的官网中看到 (比如图中就是 2.39.1)。
获取到安装包后,双击运行。安装过程无需手动配置,一直点击下一步即可完成安装。
@@ -111,7 +72,6 @@ yarn create koishi
如果你顺利完成了上述操作,你的应用此时应该已经是启动状态,并弹出了控制台界面。接下来的几节中我们将学习更多的命令行用法,因此我们可以先关闭 Koishi。在命令行中按下 `Ctrl+C` 组合键即可停止 Koishi 的运行。
-
### 注册 npm
@@ -63,7 +24,7 @@ Git 是最普遍使用的版本控制工具。前往 [官网](https://git-scm.co
![downloads](/manual/git/downloads.webp)
-国内的 Windows 用户也可以选择从 [镜像](https://registry.npmmirror.com/binary.html?path=git-for-windows/) 下载。如果不知道下载哪个版本,可以在上面的官网中看到 (比如现在是 2.39.1)。
+国内的 Windows 用户也可以选择从 [镜像](https://registry.npmmirror.com/binary.html?path=git-for-windows/) 下载。如果不知道下载哪个版本,可以在上面的官网中看到 (比如图中就是 2.39.1)。
获取到安装包后,双击运行。安装过程无需手动配置,一直点击下一步即可完成安装。
@@ -111,7 +72,6 @@ yarn create koishi
如果你顺利完成了上述操作,你的应用此时应该已经是启动状态,并弹出了控制台界面。接下来的几节中我们将学习更多的命令行用法,因此我们可以先关闭 Koishi。在命令行中按下 `Ctrl+C` 组合键即可停止 Koishi 的运行。
-