diff --git a/core/src/adapter.ts b/core/src/adapter.ts
index 33679c9a9..7d2cd1e3c 100644
--- a/core/src/adapter.ts
+++ b/core/src/adapter.ts
@@ -14,24 +14,13 @@ export type Element = {
export class Adapter
extends EventEmitter {
elements: Element[] = [];
private [adapterKey] = true;
+ bots: Adapter.Bot
[] = [];
#is_started: boolean = false;
app: App | null = null;
#configs: Adapter.BotConfig
[] = [];
static isAdapter(obj: any): obj is Adapter {
return typeof obj === 'object' && !!obj[adapterKey];
}
- get bots(): Adapter.Bot
[] {
- return (this.app?.bots.filter(bot => bot.adapter === this) || []) as unknown as Adapter.Bot
[];
- }
- set bots(bots: Adapter.Bot
[]) {
- for (const bot of bots) {
- if (bot.adapter !== this) throw new Error(`bot ${bot.unique_id} not belongs to adapter ${this.name}`);
- const hasBot = (bot: Adapter.Bot
) => {
- return this.app?.bots.some(b => b.unique_id === bot.unique_id);
- };
- if (!hasBot(bot)) this.app?.bots.push(bot as any);
- }
- }
start(app: App, config: Adapter.BotConfig
[]) {
this.app = app;
this.emit('start', config);
diff --git a/core/src/app.ts b/core/src/app.ts
index fa15143bd..6770d0c48 100644
--- a/core/src/app.ts
+++ b/core/src/app.ts
@@ -29,10 +29,15 @@ export class App extends EventEmitter {
middlewares: Middleware[] = [];
plugins: PluginMap = new PluginMap();
renders: Message.Render[] = [];
- bots: Adapter.Bot[] = [];
get adapters() {
return App.adapters;
}
+ get bots() {
+ return Array.from(App.adapters.values()).reduce((result, adapter) => {
+ result.push(...adapter.bots);
+ return result;
+ }, [] as Adapter.Bot[]);
+ }
constructor() {
super();
this.handleMessage = this.handleMessage.bind(this);
diff --git a/core/src/message.ts b/core/src/message.ts
index ef1b9f69b..a50c4f614 100644
--- a/core/src/message.ts
+++ b/core/src/message.ts
@@ -2,6 +2,8 @@ import { Dict, escape, unescape } from '@zhinjs/shared';
import { Prompt } from './prompt';
import { Adapter } from './adapter';
import { Adapters, App } from './app';
+import path from 'path';
+import * as fs from 'node:fs';
export interface MessageBase {
message_id?: string;
channel: Message.Channel;
@@ -65,7 +67,7 @@ export class Message {
export function parseFromTemplate(template: string | MessageElem): MessageElem[] {
if (typeof template !== 'string') return [template];
const result: MessageElem[] = [];
- const closingReg = /^<(\S+)(\s[^>]+)?\/>/;
+ const closingReg = /^<(\S+)(\s[^>]+)?>/;
const twinningReg = /^<(\S+)(\s[^>]+)?>([\s\S]*?)<\/\1>/;
while (template.length) {
const [_, type, attrStr = '', child = ''] = template.match(twinningReg) || template.match(closingReg) || [];
@@ -123,9 +125,18 @@ export namespace Message {
(type: string, data: Dict): string;
text(text?: string): string;
face(id: number): string;
+ image(path: string, type?: string): string;
+ image(url: string, type?: string): string;
+ image(data: Uint8Array, type?: string): string;
image(base64: string, type?: string): string;
- video(file: string, type?: string): string;
- audio(file: string, type?: string): string;
+ video(path: string, type?: string): string;
+ video(url: string, type?: string): string;
+ video(data: Uint8Array, type?: string): string;
+ video(base64: string, type?: string): string;
+ audio(path: string, type?: string): string;
+ audio(url: string, type?: string): string;
+ audio(data: Uint8Array, type?: string): string;
+ audio(base64: string, type?: string): string;
at(user_id: string | number): string;
};
export type Type = 'private' | 'group' | 'guild' | 'direct';
@@ -149,9 +160,23 @@ export const segment: Message.DefineSegment = function (type, data) {
})
.join(' ')}/>`;
} as Message.DefineSegment;
+function formatSourceDataTostring(data: T): string {
+ const result: string = typeof data === 'string' ? data : `base64://${Buffer.from(data).toString('base64')}`;
+ if (fs.existsSync(result)) return `base64://${fs.readFileSync(result).toString('base64')}`;
+ return result;
+}
segment.text = text => escape(text || '');
segment.face = (id: number) => ` `;
-segment.image = (file: string, type = 'png') => ` `;
-segment.video = (file: string, type = 'mp4') => ``;
-segment.audio = (file: string, type = 'mp3') => ``;
+segment.image = (file: string | Uint8Array, type = 'png') => {
+ file = formatSourceDataTostring(file);
+ return ` `;
+};
+segment.video = (file: string | Uint8Array, type = 'mp4') => {
+ file = formatSourceDataTostring(file);
+ return ` `;
+};
+segment.audio = (file: string | Uint8Array, type = 'mp3') => {
+ file = formatSourceDataTostring(file);
+ return ` `;
+};
segment.at = user_id => ` `;
diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts
index 2f544d93d..bab16e5c3 100644
--- a/docs/.vitepress/config.mts
+++ b/docs/.vitepress/config.mts
@@ -1,4 +1,5 @@
import { defineConfigWithTheme } from 'vitepress';
+
const pkg = require('../../zhin/package.json');
export default defineConfigWithTheme({
title: '知音(Zhin)',
@@ -63,9 +64,9 @@ export default defineConfigWithTheme({
},
},
nav: [
- { text: '开始', link: '/guide/start', activeMatch: '/guide/' },
- { text: '配置', link: '/config/common', activeMatch: '/config/' },
- { text: 'API', link: '/api/zhin', activeMatch: '/api/' },
+ { text: '开始', link: '/guide/intro', activeMatch: '/guide/' },
+ { text: '进阶', link: '/advance/plugin', activeMatch: '/advance/' },
+ { text: '开发', link: '/dev/intro', activeMatch: '/dev/' },
{ text: '插件商店', link: '/store', activeMatch: '/store' },
// { text: 'Playground', link: 'https://playground.zhin.icu', activeMatch: '/playground/' },
{
@@ -86,80 +87,53 @@ export default defineConfigWithTheme({
'/guide/': [
{
text: '介绍',
- collapsible: true,
- items: [
- { text: `准备工作`, link: '/guide/prepare' },
- { text: `安装`, link: '/guide/start' },
- { text: `编写第一个插件`, link: '/guide/plugin-guide' },
- ],
- },
- {
- text: '深入了解',
- collapsible: true,
- items: [
- { text: `插件 - Plugin`, link: '/guide/plugin-introduce' },
- { text: `指令 - Command`, link: '/guide/command' },
- { text: `可交互输入 - Prompt`, link: '/guide/prompt' },
- { text: `组件 - Component`, link: '/guide/component' },
- { text: `Bot API`, link: '/guide/bot' },
- { text: `装饰器(实验性)`, link: '/guide/decorator' },
- ],
+ link: '/guide/intro',
},
{
- text: '部署',
- link: '/guide/deploy',
- },
- ],
- '/api/': [
- { text: `目录`, link: '/api/' },
- {
- text: '核心模块',
+ text: '安装',
collapsible: true,
items: [
- { text: `知音`, link: '/api/zhin' },
- { text: `服务`, link: '/api/service' },
- { text: `适配器`, link: '/api/adapter' },
- { text: `机器人`, link: '/api/bot' },
- { text: `指令`, link: '/api/command' },
- { text: `上下文`, link: '/api/context' },
- { text: `会话`, link: '/api/session' },
+ { text: `Android Phone`, link: '/guide/android' },
+ { text: `Windows PC`, link: '/guide/windows' },
+ { text: `Linux`, link: '/guide/linux' },
],
},
{
- text: '消息定义',
- link: '/api/message',
- },
- {
- text: '内置服务',
+ text: '接入平台',
collapsible: true,
items: [
- { text: `server`, link: '/api/service-server' },
- { text: `router`, link: '/api/service-router' },
- { text: `koa`, link: '/api/service-koa' },
+ { text: `QQ`, link: '/guide/qq' },
+ { text: `Icqq`, link: '/guide/icqq' },
+ { text: `Discord`, link: '/guide/discord' },
+ { text: `钉钉`, link: '/guide/dingtalk' },
+ { text: `微信(Web)`, link: '/guide/web-wechat' },
+ { text: `OneBot 11/12`, link: '/guide/onebot' },
+ { text: `Email`, link: '/guide/email' },
],
},
{
- text: `事件系统`,
- collapsible: true,
- items: [{ text: `事件地图`, link: '/api/event/map' }],
+ text: '配置文件',
+ link: '/guide/config',
},
],
- '/config': [
+ '/advance/': [
{
- text: '通用配置',
- link: '/config/common',
+ text: '插件开发',
+ link: '/advance/plugin',
},
{
- text: '适配器',
+ text: '核心模块',
collapsible: true,
items: [
- { text: `icqq`, link: '/config/adapter-icqq' },
- { text: `onebot`, link: '/config/adapter-onebot' },
+ { text: `服务`, link: '/advance/service' },
+ { text: `适配器`, link: '/advance/adapter' },
+ { text: `中间件`, link: '/advance/middleware' },
+ { text: `指令`, link: '/advance/command' },
],
},
{
- text: '内置插件',
- link: '/config/built-plugin',
+ text: '消息',
+ link: '/advance/message',
},
],
},
diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts
index 05cd1dcc5..b39c4565c 100644
--- a/docs/.vitepress/theme/index.ts
+++ b/docs/.vitepress/theme/index.ts
@@ -4,6 +4,7 @@ import zhCn from 'element-plus/es/locale/lang/zh-cn';
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
import 'element-plus/dist/index.css';
import 'element-plus/theme-chalk/dark/css-vars.css';
+import './custom.css';
import ChatHistory from './components/ChatHistory.vue';
import UserAvatar from './components/UserAvatar.vue';
import ChatMsg from './components/ChatMsg.vue';
diff --git a/docs/src/advance/plugin.md b/docs/src/advance/plugin.md
new file mode 100644
index 000000000..7bb2d734c
--- /dev/null
+++ b/docs/src/advance/plugin.md
@@ -0,0 +1,76 @@
+# 插件
+- 在知音的设计理念中,插件是实现一切业务逻辑的载体,小到指令定义,大到服务定义、中间件定义,都可以通过插件来实现。插件是知音的核心,也是知音的灵魂。
+- 知音同时支持使用 `javascript` 和 `typescript` 进行插件开发,开发者可以根据自己的喜好选择开发语言。
+- 插件分为 `本地插件` 和` 模块插件`。
+- 无论是本地插件还是模块插件,都需要在 `zhin.config.yml` 中配置启用才能生效。
+## 本地插件
+- 本地插件是指在本地开发的插件,可以是一个普通的 `js` 或 `ts` 文件,也可以是一个目录,目录下包含一个 `index.js` 或 `index.ts` 文件,或者通过 `package.json` 管理的插件包。
+
+## 模块插件
+- 模块插件是指通过 `npm` 发布的插件,可以通过 `npm` 安装到项目中。
+## 插件开发
+### 1. 创建插件
+- 在项目的 `plugins` 目录下创建一个插件目录,插件目录下创建一个 `index.js` 或 `index.ts` 文件。
+- 也可以通过 `npm init` 创建一个插件包。
+- 一个插件的目录结构如下:
+```text
+plugins
+└── my-plugin
+ ├── index.js
+ └── package.json
+```
+### 2. 编写插件
+- 知音通过实例化 `Plugin` 类来创建一个插件。
+- 通过实例化对象 你可以往插件实例上挂载指令、服务、中间件等。
+- 你可以通过 `mounted` 声明周期,指定插件挂载时的操作。
+- 你需要将实例化对象作为默认导出,zhin才会视为有效插件。
+- 一个简单的插件示例:
+::: code-group
+
+```javascript [index.js]
+import { Plugin } from 'zhin';
+const plugin = new Plugin('my-plugin');
+// 定义指令
+plugin.command('hello', 'hello world');
+// 定义服务
+plugin.service('foo',{
+ async bar(){
+ return 'bar';
+ }
+})
+// 定义中间件
+plugin.middleware(async (message, next) => {
+ console.log('before');
+ await next();
+});
+// 挂载周期
+plugin.mounted(() => {
+ console.log('my-plugin is mounted');
+ // 使用服务
+ console.log(plugin.hello.bar()) // bar
+});
+export default plugin;
+```
+```json [package.json]
+{
+ "name": "my-plugin",
+ "version": "1.0.0",
+ "main": "index.js",
+ "peerDependencies": {
+ "zhin": "latest"
+ }
+}
+```
+:::
+### 3. 启用插件
+- 在 `zhin.config.yml` 中的 `plugins` 下添加插件名称即可启用插件。
+```yaml
+plugins:
+ - my-plugin
+```
+### 4. 插件发布
+- 如果你的插件是一个模块插件,你可以通过 `npm publish` 发布到 `npm` 上。
+- 其他开发者可以通过 `npm install my-plugin` 安装你的插件。
+## 更多
+- 本篇章节只是简单介绍了插件的基本使用,更多插件实例方法,请参考当前页面左侧 `核心模块` 功能介绍。
+
diff --git a/docs/src/api/adapter.md b/docs/src/api/adapter.md
deleted file mode 100644
index 33439a6ae..000000000
--- a/docs/src/api/adapter.md
+++ /dev/null
@@ -1,92 +0,0 @@
-# 适配器(Adapter)
-
-::: tip
-继承自 EventEmitter
-
-此处介绍仅为基础介绍,已足够普通开发这使用,如需进阶(自行开发机器人适配器),可联
-系开发者
-:::
-
-## 属性(Attrs)
-
-### bots:BotList
-
-- 存放机器人的数组
-
-### logger:Logger
-
-- Log4js的Logger,使用方法自行搜索`log4js`
-
-### status:Record
-
-获取该适配器下所有机器人的状态
-
-## 构造函数 constructor(zhin:Zhin,protocol:string,options:AdapterOptions)
-
-## 方法(Methods)
-
-### botStatus(self_id:string|number):BotStatus
-
-获取该适配器下指定机器人的状态
-
-### getLogger(sub_type:string):Logger
-
-获取一个Log4js的Logger,使用方法自行搜索`log4js`
-
-### dispatch(event:string|symbol,session:Session):void
-
-向`Zhin`推送[会话](/api/session)
-
-### start
-
-启动适配器
-
-### stop
-
-停止适配器
-
-### startBot(options:[BotOptions](/api/bot#options)):void
-
-启动一个机器人
-
-## 配置文件 AdapterOptions
-
-```typescript
-export type AdapterOptions = {
- bots?: BotOptions[]; // 机器人配置文件数组
-} & AO;
-```
-
-## 命名空间(Namespace)
-
-```typescript
-export const adapterConstructs: AdapterConstructs = {};
-// 用于定义适配器,普通开发者可忽略
-export function define(
- key: K,
- protocolConstruct: AdapterConstruct,
- botConstruct: BotConstruct,
-) {
- adapterConstructs[key] = protocolConstruct;
- Bot.define(key, botConstruct);
-}
-// 适配器模块的类型声明,普通开发者可忽略
-export interface Install {
- install(ctx: Context, config?: T);
-}
-export interface BotStatus {
- start_time: number;
- lost_times: number;
- recv_msg_cnt: number;
- sent_msg_cnt: number;
- msg_cnt_per_min: number;
- online: boolean;
-}
-// 获取指定平台的适配器
-export function get(protocol: K) {
- return {
- Adapter: adapterConstructs[protocol],
- Bot: Bot.botConstructors[protocol],
- };
-}
-```
diff --git a/docs/src/api/bot.md b/docs/src/api/bot.md
deleted file mode 100644
index 52613a6bb..000000000
--- a/docs/src/api/bot.md
+++ /dev/null
@@ -1,109 +0,0 @@
-# 机器人(Bot)
-
-::: tip 继承自 EventEmitter
-
-此处介绍仅为基础介绍,已足够普通开发这使用,如需进阶(自行开发机器人适配器),可联
-系开发者
-:::
-
-## 属性(Attrs)
-
-### internal
-
-- 不同平台的机器人实例
-
-### get status():BotStatus
-
-- 机器人状态
-
-### options:BotOptions
-
-- 机器人配置
-
-### self_id:string}number
-
-- 机器人唯一标识
-
-## 构造函数 constructor(public zhin:Zhin,public adapter:Adapter,options:BotOptions)
-
-## 方法(Methods)
-
-### isOnline():boolean
-
-- 获取机器人是否在线状态
-
-### enable(plugin?:Plugin):this|boolean
-
-- 传plugin时,代表该机器人启用指定插件(默认会启用所有插件)
-- 不传任何参数时,代表启用该机器人
-
-### disable(plugin:Plugin):this:boolean
-
-- 传plugin时,代表该机器人禁用指定插件
-- 不传任何参数时,代表禁用该机器人
-
-### match(plugin:Plugin):boolean
-
-- 判断当前机器人是否启用指定插件
-
-### isMaster(session:Session):boolean
-
-- 会话发起者是否为zhin主人
-
-### isAdmin(session:Session):boolean
-
-- 会话发起者是否为zhin管理员
-
-### reply(session:Session,message:Fragment,quote?:boolean):MessageRet
-
-- 回复一个指定会话一条消息(quote为true会引用该会话)
-
-### sendMsg(target_id:string:number,target_type:MessageType,message:Fragment):MessageRet
-
-- 发送一条消息给指定类型的用户
-- MessageType可为`gorup`、`private`、`discuss`(仅qq支持)、`guild`
-
-### start
-
-启动机器人
-
-## 配置文件 BotOptions
-
-```typescript
-export type BotOptions = {
- quote_self?: boolean; // 回复消息是否自动引用
- self_id?: string | number; // 机器人唯一标识
- prefix?: string; // 指令调用时的前缀
- enable?: boolean; // 机器人是否启用
- master?: string | number; // 机器人主人账号
- disable_plugins?: string[]; // 禁言的插件名称数组
- admins?: (string | number)[]; // 管理员账号数组
-} & O;
-```
-
-## 命名空间(Namespace)
-
-```typescript
-export namespace Bot {
- // 默认配置文件
- export const defaultOptions: BotOptions = {
- quote_self: false,
- enable: true,
- disable_plugins: [],
- admins: [],
- };
- interface MsgBase {
- user_id: string | number;
- elements: Element[];
- }
- export interface GroupMsg extends MsgBase {
- group_id: string | number;
- }
- export interface PrivateMsg extends MsgBase {}
- export interface GuildMsg extends MsgBase {
- guild_id: string | number;
- channel_id: string | number;
- }
- export type Message = PrivateMsg | GroupMsg | GuildMsg;
-}
-```
diff --git a/docs/src/api/command.md b/docs/src/api/command.md
deleted file mode 100644
index 4fafcec1d..000000000
--- a/docs/src/api/command.md
+++ /dev/null
@@ -1,6 +0,0 @@
-# 指令(Command)
-
-## 属性
-
-
-
diff --git a/docs/src/api/context.md b/docs/src/api/context.md
deleted file mode 100644
index d0fe68973..000000000
--- a/docs/src/api/context.md
+++ /dev/null
@@ -1,174 +0,0 @@
-# 上下文(Context)
-
-::: tip 继承自 EventEmitter :::
-
-## 属性(Attrs)
-
-### plugins:Map
-
-- **当前**上下文安装的插件Map
-
-### middlewares:Middleware[]
-
-- **当前**上下文产生的所有中间件
-
-### components:Record
-
-- **当前**上下文产生的组件键值对
-
-### commands:Map
-
-- **当前**上下文产生的指令Map
-
-### disposes:Dispose[]
-
-- 卸载**当前**上下文时,需要执行的函数列表
-
-### get pluginList:Plugin[]
-
-- 获取**当前**上下文及**其下级**上下文安装的插件列表
-
-### get middlewareList:Middleware[]
-
-- 获取**当前**上下文及**其下级**上下文产生的的中间件列表
-
-### get commandList:Command[]
-
-- 获取**当前**上下文及**其下级**上下文产生的的指令列表
-
-### [Context.childKey]:Context[]
-
-- **当前**上下文产生的**子**上下文列表
-
-### [Context.plugin]:Plugin
-
-- 产生**当前**上下文的插件
-
-### [keyof Zhin.Services]:Service
-
-- 知音安装的服务
-
-## 构造函数 constructor(public parent:Context,filter?:Filter)
-
-## 方法(Methods)
-
-### extend(ctx:Context):Context
-
-- 将**其他**上下文继承到**当前**上下文
-
-### pick(key: K, ...values: Session[K][]):Context
-
-- 产生一个新的上下文,该上下文仅在传入会话`session[key]`的值存在于`values`中并且
- 满足构造函数的filter时有效
-
-### union(filter:Filter):Context
-
-- 产生一个新的上下文,该上下文仅在满足传入filter并且满足构造函数的filter时有效
-
-### except(filter:Filter):Context
-
-- 产生一个新的上下文,该上下文仅在不满足传入filter并且满足构造函数的filter时有效
-
-### user(...user_ids:(string|number)[]):Context
-
-- 产生一个新的上下文,该上下文传入会话`session.user_id`的值存在于`user_ids`中并
- 且满足构造函数的filter时有效
-
-### group(...group_ids:(string|number)[]):Context
-
-- 产生一个新的上下文,该上下文传入会话`session.group_id`的值存在于`group_ids`中
- 并且满足构造函数的filter时有效
-
-### discuss(...discuss_ids:(string|number)[]):Context
-
-- 产生一个新的上下文,该上下文传入会话`session.discuss_id`的值存在
- 于`discuss_ids`中并且满足构造函数的filter时有效
-
-### guild(...guild_ids:string[]):Context
-
-- 产生一个新的上下文,该上下文传入会话`session.guild_id`的值存在于`guild_ids`中
- 并且满足构造函数的filter时有效
-
-### channel(...channel_ids:string[]):Context
-
-- 产生一个新的上下文,该上下文传入会话`session.channel_id`的值存在
- 于`channel_ids`中并且满足构造函数的filter时有效
-
-### platform(...platforms:(keyof Zhin.Adapters)[]):Context
-
-- 产生一个新的上下文,该上下文传入会话`session.platform`的值存在于`platforms`中
- 并且满足构造函数的filter时有效
-
-### private(...user_ids:(string|number)[]):Context
-
-- 产生一个新的上下文,该上下文传入会话`session.user_id`的值存在于`user_ids`中并
- 且会话事件为私聊并且满足构造函数的filter时有效
-
-### getMatchedContextList(session:Session):Context[]
-
-- 根据传入会话获取匹配到的上下文列表
-
-### plugin(entry:string:Plugin.Install,setup?:boolean):this|Plugin
-
-- 传入string时,若当前上下文已安装name为entry的插件,则返回插件,否则,将尝试从
- 已加载的模块中加载插件
-- 传入Plugin.Install时,将在当前上下文安装对应插件
-
-### use(plugin:Plugin):this
-
-- 根据Plugin.Install在当前上下文安装对应插件
-
-### middleware(middleware:Middleware):this
-
-- 为当前上下文添加一个中间件
-
-### command(def:string,initial?:string):Command
-### command(def:string,config?:Command.Config):Command
-### command(def:string,initial?:string,config?:Command.Config):Command
-
-- 为当前上下文添加一个指令,并返回指令本身
-
-### dispatch(session:Session):void
-
-- 如果session满足当前上下文的filter,则将session继续想下分发
-
-### sendMsg(channel: Context.MsgChannel, msg: Element.Fragment):MessageRet
-
-- 发送一条消息给指定类型的用户
-
-### broadcast(channelIds: ChannelId | ChannelId[], content: Element.Fragment):Promise
-
-- 广播一条消息给指定类型的用户
-
-## 命名空间(Namespace)
-
-```typescript
-export namespace Context {
- export const plugin = Symbol("plugin");
- export const childKey = Symbol("children");
- export type MsgChannel = {
- protocol: keyof Zhin.Adapters;
- bot_id: string | number;
- target_id: string | number;
- target_type: "private" | "group" | "discuss" | "guild";
- };
-
- export function from(parent: Context, filter: Filter) {
- const ctx = new Context(parent, filter);
- ctx[plugin] = parent ? parent[plugin] : null;
- return ctx;
- }
-
- export type Filter = (session: Session) => boolean;
- export const defaultFilter: Filter = () => true;
- export const or = (ctx: Context, filter: Filter) => {
- return ((session: Session) => ctx.filter(session) || filter(session)) as Filter;
- };
- export const not = (ctx: Context, filter: Filter) => {
- return ((session: Session) => ctx.filter(session) && !filter(session)) as Filter;
- };
- export const and = (ctx: Context, filter: Filter) => {
- return ((session: Session) => ctx.filter(session) && filter(session)) as Filter;
- };
-}
-```
diff --git a/docs/src/api/event/map.md b/docs/src/api/event/map.md
deleted file mode 100644
index 2ec784714..000000000
--- a/docs/src/api/event/map.md
+++ /dev/null
@@ -1,44 +0,0 @@
-# 事件地图
-
-- 知音底层基于EventEmitter驱动,因此支持所有EventEmitter的事件,同时也支持自定义事件。
-
-- 事件地图如下:
-- [Zhin](#session)
-- [Adapter](#adapter)
-- [Bot](#bot)
-
-## Zhin
-
-| 事件名 | 说明 | 参数 |
-|:---------------|:----------------------------------------------------|:-----------|
-| message | 消息事件 | Session |
-| before-message | 触发消息事件之前触发 | Session |
-| message.send | 消息发送成功后触发 | MessageRet |
-| before-start | 在before-ready之前触发 | void |
-| before-ready | 在ready之前触发 | void |
-| ready | 机器人准备就绪 | void |
-| after-ready | 在ready之后触发 | void |
-| start | 机器人开始运行 | void |
-| after-start | 在start之后触发 | void |
-| command-add | 添加指令时触发 | Command |
-| command-remove | 移除指令时触发 | Command |
-| plugin-add | 添加插件时触发 | Plugin |
-| plugin-remove | 移除插件时触发 | Plugin |
-| service-add | 添加服务时触发 | Service |
-| service-remove | 移除服务时触发 | Service |
-| \[protocol\].\[event\] | 协议事件,例如适配器`icqq`的事件`notice.group.increase`,则为`icqq.notice.group.increase` | Session |
-
-## Adapter
-
-| 事件名 | 说明 | 参数 |
-|:----------------|:--------|:-----------|
-| message.receive | 收到消息 | Session |
-| message.send | 发送消息 | MessageRet |
-| bot.online | 某个机器人上线 | Bot |
-| bot.offline | 某个机器人下线 | Bot |
-| bot.error | 机器人出错 | Bot |
-
-## Bot【Icqq适配器】
-- 参见[Icqq事件地图](https://icqqjs.github.io/icqq/docs/interfaces/EventMap.html)
-
-
diff --git a/docs/src/api/index.md b/docs/src/api/index.md
deleted file mode 100644
index 7132fd915..000000000
--- a/docs/src/api/index.md
+++ /dev/null
@@ -1,49 +0,0 @@
-# 核心模块
-
-## [知音(Zhin)](/api/zhin)
-
-- 框架的统筹管理者,[适配器](#适配器--adapter-)、[服务](#服务--service-)的载体
-
-## [适配器(Adapter)](/api/adapter)
-
-- 连接[知音](#知音--zhin-)和机器人的桥梁(上传下达)
-- 适配对应平台机器人连接到知音
-- [机器人](#机器人--bot-)的载体
-
-## [机器人(Bot)](/api/bot)
-
-- 将对应平台推送的内容封装成[会话](#会话--session-),并提交给上一级(适配器)
-
-## [会话(Session)](/api/session)
-
-- 描述平台推送内容、发出该内容的机器人、所使用的适配器的对象
-- 提供一系列快捷功能
-
-## [上下文(Context)](/api/context)
-
-- [中间件](#中间件--middleware-)、[指令](#指令--command-)、[组件](#组件--component-)的
- 载体
-
-## [插件(Plugin)](/api/plugin)
-
-- 使用[知音](#知音--zhin-) 开发自定义功能的**入口**
-- 访问[上下文](#上下文--context-)的**入口**
-
-# 普通开发者该关心的
-
-## [服务(Service)](/api/service)
-
-- 为[知音](#知音--zhin-)添加的**任何**[上下文](#上下文--context-)都可以访问的属
- 性
-
-## [指令(Command)](/api/command)
-
-- 处理消息[会话](#会话--session-)的特殊对象
-
-## [组件(Component)](/api/component)
-
-- 处理消息[会话](#会话--session-)的特殊对象
-
-## [中间件(Middleware)](/api/middleware)
-
-- 处理消息[会话](#会话--session-)的回调函数,处理顺序与`Koa`的洋葱模型相同
diff --git a/docs/src/api/message.md b/docs/src/api/message.md
deleted file mode 100644
index 40dcb4bda..000000000
--- a/docs/src/api/message.md
+++ /dev/null
@@ -1,68 +0,0 @@
-# 消息定义
-
-zhin中的消息分为普通文本消息、消息元素、消息模板和消息组件构成。
-
-## 普通文本消息
-
-普通文本消息是指不包含任何消息元素的字符串。你可以通过session.reply方法来发送普
-通文本消息。
-
-```typescript
-session.reply("Hello World!");
-```
-
-## 消息元素
-
-消息元素是指消息中的一些特殊元素,例如图片、链接、at等。你可以通过导入zhin提供
-的`h`来构造,通过session.reply方法来发送消息元素。
-
-```typescript
-import { h } from "zhin";
-
-session.reply(h("mention", { user_id: 123456789 })); // 提及某人
-session.reply(h("image", { url: "https://example.com/image.png" })); // 发送图片
-session.reply(h("face", { id: 123 })); // 发送表情
-session.reply(h("rps", { id: 1 })); // 发送猜拳
-session.reply(h("dice", { id: 1 })); // 发送骰子
-session.reply(
- h("node", {
- message: ["Hello World!", h("mention", { user_id: 123456789 })],
- }),
-); // 发送节点
-```
-
-## 消息模板
-
-消息模板是一种特殊的文本消息,在其中你可以通过类html的方式来构造消息元素。你可以
-通过session.reply方法来发送消息模板。
-
-```typescript
-session.reply(
- 'Hello World! ',
-);
-```
-
-## 消息组件
-
-消息组件是由开发者自定义的消息元素,你可以通过ctx.component来注册消息组件,通过
-session.reply方法来发送消息组件。
-
-```typescript
-import { defineComponent } from "zhin";
-
-ctx.component(
- "my-component",
- defineComponent({
- props: {
- who: String,
- },
- render(props) {
- return `Hello ${props.who}!`;
- },
- }),
-);
-ctx.middleware((session, next) => {
- session.reply(` `);
- next();
-});
-```
diff --git a/docs/src/api/render-component.md b/docs/src/api/render-component.md
deleted file mode 100644
index 870e652de..000000000
--- a/docs/src/api/render-component.md
+++ /dev/null
@@ -1 +0,0 @@
-# 待完善
diff --git a/docs/src/api/render-element.md b/docs/src/api/render-element.md
deleted file mode 100644
index 870e652de..000000000
--- a/docs/src/api/render-element.md
+++ /dev/null
@@ -1 +0,0 @@
-# 待完善
diff --git a/docs/src/api/render-template.md b/docs/src/api/render-template.md
deleted file mode 100644
index 870e652de..000000000
--- a/docs/src/api/render-template.md
+++ /dev/null
@@ -1 +0,0 @@
-# 待完善
diff --git a/docs/src/api/service-koa.md b/docs/src/api/service-koa.md
deleted file mode 100644
index 870e652de..000000000
--- a/docs/src/api/service-koa.md
+++ /dev/null
@@ -1 +0,0 @@
-# 待完善
diff --git a/docs/src/api/service-router.md b/docs/src/api/service-router.md
deleted file mode 100644
index 870e652de..000000000
--- a/docs/src/api/service-router.md
+++ /dev/null
@@ -1 +0,0 @@
-# 待完善
diff --git a/docs/src/api/service-server.md b/docs/src/api/service-server.md
deleted file mode 100644
index 870e652de..000000000
--- a/docs/src/api/service-server.md
+++ /dev/null
@@ -1 +0,0 @@
-# 待完善
diff --git a/docs/src/api/service.md b/docs/src/api/service.md
deleted file mode 100644
index 84c3f9275..000000000
--- a/docs/src/api/service.md
+++ /dev/null
@@ -1,95 +0,0 @@
-# 服务(Service)
-
-::: tip
-服务只是zhin的一个概念
-:::
-
-## 介绍
-
-- 为[知音](#知音--zhin-)添加的**任何**[上下文](#上下文--context-)都可以访问的属
- 性
-
-## 如何定义?
-
-- 在插件中,通过上下文定义 ::: code-group
-
-```typescript
-import { Context } from "zhin";
-
-class CustomeService {
- constructor(public config: any) {}
- getConfig() {
- return this.config;
- }
- setconfig(config: any) {
- this.config = config;
- }
-}
-// 定义类型声明合并
-declare module "zhin" {
- namespace Zhin {
- interface Services {
- custom: CustomeService;
- }
- }
-}
-
-export function install(ctx: Context) {
- // 如果上面没定义类型声明合并,这儿会报错
- ctx.service("custom", new CustomeService("hello"));
-}
-```
-
-```javascript
-
-class CustomeService {
- constructor(config) {
- this.config=config
- }
-
- getConfig() {
- return this.config
- }
-
- setconfig(config: any) {
- this.config = config
- }
-}
-
-module.exports = {
- install(ctx) {
- ctx.service('custom',new CustomeService('hello'))
- }
-}
-```
-
-:::
-
-## 如何使用?
-
-- 在其他插件中,直接使用ctx[serviceName]使用 ::: code-group
-
-```typescript
-import { Context } from "zhin";
-export const use = ["custom"]; // 定义这个,可以确保只有在custom服务正常时,插件才启用
-export function install(ctx: Context) {
- const oldConfig = ctx.custom.getConfig();
- ctx.custom.setConfig("hi");
- const newConfig = ctx.custom.getConfig();
- console.log(oldConfig, newConfig);
-}
-```
-
-```javascript
-module.exports = {
- use: ["custom"], // 定义这个,可以确保只有在custom服务正常时,插件才启用
- install(ctx) {
- const oldConfig = ctx.custom.getConfig();
- ctx.custom.setConfig("hi");
- const newConfig = ctx.custom.getConfig();
- console.log(oldConfig, newConfig);
- },
-};
-```
-
-:::
diff --git a/docs/src/api/session.md b/docs/src/api/session.md
deleted file mode 100644
index b6aa659ca..000000000
--- a/docs/src/api/session.md
+++ /dev/null
@@ -1,64 +0,0 @@
-# 会话
-
-在zhin中,机器人发出的任何事件,都会被封装为一个统一格式的会话对象,开发者可以通
-过访问会话对象的属性,来获取对应事件产生的信息。
-
-下面是会话中的一些常用属性及其类型:
-
-```typescript
-interface Session<
- P extends keyof Zhin.Adapters = keyof Zhin.Adapters,
- E extends keyof Zhin.BotEventMaps[P] = keyof Zhin.BotEventMaps[P],
-> {
- protocol: P; // 所使用的适配器
- type?: string; // 事件类型
- user_id?: string | number; // 用户id
- user_name?: string; // 用户名
- group_id?: string | number; // 群组id 仅在detail_type为group时存在
- group_name?: string; // 群组名 仅在detail_type为group时存在
- discuss_id?: string | number; // 讨论组id 仅在detail_type为discuss时存在
- discuss_name?: string; // 讨论组名 仅在detail_type为discuss时存在
- channel_id?: string; // 频道id 仅在detail_type为guild时存在
- channel_name?: string; // 频道名 仅在detail_type为guild时存在
- guild_id?: string; // 服务器id 仅在detail_type为guild时存在
- guild_name?: string; // 服务器名 仅在detail_type为guild时存在
- detail_type?: string; // 事件详细类型
- zhin: Zhin; // 当前zhin实例
- context: Context; // 当前上下文
- adapter: Zhin.Adapters[P]; // 当前适配器实例
- prompt: Prompt; // 当前会话的提示输入器
- content: string; // 消息内容
- bot: Zhin.Bots[P]; // 当前机器人实例
- event: E; // 事件完整名
- quote?: QuoteMessage; // 引用消息
- message_id?: string; // 消息id 仅在type为message时存在
-}
-```
-
-除此以外,你还可以访问到会话的一些方法和getter,通过这些方法和getter,你可以获取
-到更多的信息,或者对会话进行一些操作。
-
-```typescript
-import { Bot } from "./bot";
-interface Session {
- middleware(middleware: Middleware): void; // 在当前会话上添加一个中间件
- reply(element: Element.Fragment): Promise; // 回复当前会话
- intercept(
- tip: Element.Fragment,
- runFunc: (
- session: NSession,
- ) => Element.Fragment | void,
- free:
- | Element.Fragment
- | ((session: NSession) => boolean),
- filter?: (session: NSession) => boolean,
- ): void; // 拦截当前会话
- get isMaster(): boolean; // 当前会话发起者是否为主人
- get isAdmins(): boolean; // 当前会话发起者是否为zhin管理员
- get isOwner(): boolean; // 当前会话发起者是否为群主
- get isAdmin(): boolean; // 当前会话发起者是否为群组管理
- get isAtMe(): boolean; // 当前会话是否at了机器人
- get isPrivate(): boolean; // 当前会话是否为私聊
- get isGroup(): boolean; // 当前会话是否为群聊
-}
-```
diff --git a/docs/src/api/zhin.md b/docs/src/api/zhin.md
deleted file mode 100644
index 21a17b238..000000000
--- a/docs/src/api/zhin.md
+++ /dev/null
@@ -1,82 +0,0 @@
-# 知音(Zhin)
-
-::: tip
-继承自 [Context](/api/context)
-:::
-
-## 属性(Attrs)
-
-### isReady:boolean
-
-- 标识zhin是否启动
-
-### options:Zhin.Options(见命名空间)
-
-- zhin的配置文件
-
-### adapters:Map
-
-- zhin加载的适配器Map
-
-### services:Map
-
-- zhin加载的服务Map
-
-## 方法(Methods)
-
-### changeOptions(options:Zhin.Options):void
-
-- 更改知音的配置文件
-
-### pickBot(protocol:string,self_id:string|number):[Bot](/api/bot)|undefined
-
-- 根据条件选取一个已存在的机器人
-
-### getLogger(protocol:string,self_id:string|number):Logger
-
-- 获取logger
-
-### getInstalledModules(moduleType:string):Modules[]
-
-- 扫描项目依赖中的已安装的模块
-
-### hasMounted(name:string):boolean
-
-- 检查知音是否安装指定插件
-
-### sendMsg(channelId: ChannelId, message: Fragment):MessageRet
-
-- 发送消息到指定通道
-
-### load(name: string, moduleType: T,setup?:boolean):Zhin.Modules[T]
-
-- 加载指定名称,指定类型的模块
-
-### findCommand(argv:Argv):[Command](/api/command)
-
-- 获取匹配出来的指令
-
-### start
-
-- 启动知音
-
-### stop
-
-- 停止知音
-
-## 命名空间(Namespace)
-
-```typescript
-export interface Options {
- self_url?: string; // 公网访问url,可不填
- port: number; // 监听端口
- log_level: LogLevel; // 日志输出等级
- logConfig?: Partial; // Configuration请自行参阅log4js
- delay: Record; // 超时时间
- plugins?: Record; // 规定用来存放不同插件的配置
- services?: Record; // 规定用来存放不同服务的配置
- adapters?: Record; // 规定用来存放不同适配器的配置
- plugin_dir?: string; // 存放插件的目录路径
- data_dir?: string; // 存放数据的目录路径
-}
-```
diff --git a/docs/src/config/adapter-icqq.md b/docs/src/config/adapter-icqq.md
deleted file mode 100644
index e05d74be1..000000000
--- a/docs/src/config/adapter-icqq.md
+++ /dev/null
@@ -1,42 +0,0 @@
-# 内置适配器(adapter-icqq)
-
-## icqq由来
-
-在介绍该适配器之前,我想先让你了解一下什么是 `icqq`,相信知道 `QQ` `NodeJS` 机器人生态的都知道 `oicq`,他是由`takayama-lily` 大佬维护的 qq 机器人的 SDK。奈何近一年来,大佬似乎琐事缠身,没空更新了,我们便自发开始维护起来,而 `icqq` 就是所有维护分支中的其中之一,他在保留原有 `oicq` api 的同时增加了**频道**、**加精/取消加精群消息**的 API,并优化了登录流程(createClient 不再传 uin,在 login 时才传递),更改了底层发布订阅的 EventEmitter为TripTrap,使得使用过滤器监听事件得以实现。
-
-## adapter-icqq的优势
-
-而 `adapter-icqq` 则是能让你直接在 zhin 中登录使用 icqq 登录个人账号,来作为 qq 机器人的适配器,它不像 `go-cqhhtp` 和 `miral-go` 那样,需要你重新启动一个进程而是和 `zhin`使用同一个进程工作,并且,你可以使用 zhin 去调用 `icqq` 底层的api,来实现更多功能。
-
-说了这么多,那怎么配置呢?
-
-## 接入到zhin
-
-- `adapter-icqq`作为内置适配器,接入到zhin十分的简单
-- 你只需要在配置文件`zhin.yaml`的`adapters`中增加如下配置,即可接入一个qq账号:
-
-```yaml
-
-adapters:
- icqq: # 指定使用icqq适配器 // [!code ++]
- bots: // [!code ++]
- - self_id: 147258369 # 机器人账号 // [!code ++]
- platform: 5 # 指定qq登录平台为iPad(可不配置 1:安卓 2:安卓平板 3:手表 4:苹果电脑 5:苹果平板) // [!code ++]
- password: 123456789 # 机器人密码 // [!code ++]
- ver: 0 # 指定登录协议版本(可不配置,默认取签名支持的最新版本) // [!code ++]
- sign_api_addr: 'zhin' # 签名接口地址 // [!code ++]
- [...]: '' # 其他icqq配置 // [!code ++]
-```
-
-- 其中 `self_id` 对应`icqq` 的 uin,作为一个机器人的唯一标识
-- `platform` 代表你要登录的平台,默认为 1
-## 关于签名
-
-- 由于 `qq` 版本升级,现在登录需要签名,而 `icqq` 也提供了签名的接口,你可以 开源项目 [qsign](https://github.com/fuqiuluo/unidbg-fetch-qsign) 自行搭建签名服务,也使用他人提供的签名服务,只需要在配置文件中配置 `sign_api_addr` 即可。
-- 需要注意的是,当使用 `qsign` 自行部署时,请确保 `sign_api_addr` 中必须携带 `?key=${KEY}` (${KEY} 替换为你qsign配置中的key),否则将无法正常使用。
-
-::: tip
-具体更多的配置,请参考 icqq 的[Config](https://icqqjs.github.io/icqq/interfaces/Config.html)
-:::
-
-完成配置后,重启 zhin,将自动开始启动 icqq,当遇到验证时,内置的 `login` 插件,将提供命令行辅助你完成登录的功能。
diff --git a/docs/src/config/adapter-onebot.md b/docs/src/config/adapter-onebot.md
deleted file mode 100644
index f141a7934..000000000
--- a/docs/src/config/adapter-onebot.md
+++ /dev/null
@@ -1,74 +0,0 @@
-# 官方适配器(onebot)
-
-## 介绍
-
-[OneBot](https://www.npmjs.com/package/@zhinjs/adapter-onebot) 适配器是一个支持 [OneBot12](https://12.onebot.dev/) 标准的适配器,可以连接到任何支持 OneBot12 标准的机器人平台。
-
-::: tip
-
-你可以使用[onebots](https://icqqjs.github.io/onebots/)来快速部署一个符合 `OneBot12` 标准的QQ机器人服务。
-
-:::
-## 接入到zhin
-### 1.安装适配器
-
-- 在项目根目录下执行以下命令安装适配器
-
-```bash
-npm i @zhinjs/adapter-onebot
-```
-
-### 2.配置适配器
-
-- 在配置文件`zhin.yaml`的`adapters`中增加如下配置,即可接入一个 `onebot` 机器人:
-
-::: code-group
-```yaml [HTTP]
-adapters:
- onebot:
- bots:
- - self_id: 147258369
- type: http
- url: http://host:port/path # oneBot http api 地址
- access_token: 123456789 # oneBot http api token
- get_events_interval: 1000 # 获取事件间隔
- events_buffer_size: 100 # 事件缓冲区大小
- timeout: 10000 # 请求超时时间
-```
-```yaml [Webhook]
-adapters:
- onebot:
- bots:
- - self_id: 147258369
- type: webhook
- path: /path # oneBot webhook挂载路径
- get_actions_path: /path # 获取动作缓存路径
- access_token: 123456789 # oneBot http api token
- timeout: 10000 # 请求超时时间
-```
-```yaml [WebSocket]
-adapters:
- onebot:
- bots:
- - self_id: 147258369
- type: ws
- url: ws://host:port/path # oneBot ws api 地址
- access_token: 123456789 # oneBot ws api token
- reconnect_interval: 1000 # 重连间隔
- max_reconnect_times: 10 # 最大重连次数
-```
-```yaml [WebSocket Reverse]
-adapters:
- onebot:
- bots:
- - self_id: 147258369
- type: ws_reverse
- path: /path # oneBot ws_reverse挂载路径
- access_token: 123456789 # oneBot ws api token
-```
-- 其中 `self_id` 对应`onebot` 的 `self_id`,作为一个机器人的唯一标识
-- `type` 代表你要连接的方式,目前支持 `http`、`webhook`、`ws`、`ws_reverse`
-
-### 3.启动
-
-配置完成后,重启 `zhin`,将自动开始连接 对应的 `onebot` 机器人
diff --git a/docs/src/config/built-plugin.md b/docs/src/config/built-plugin.md
deleted file mode 100644
index 3f7b00686..000000000
--- a/docs/src/config/built-plugin.md
+++ /dev/null
@@ -1,106 +0,0 @@
-# 内置插件
-
-zhin 内置了`7`个插件,作为协助开发者管理 zhin 的基础,让我们来认识下这 `七个葫芦娃`
-
-## 帮助(help)
-
-用户使用 zhin 的 `command` 定义了指令,使用 `help` 可以获取对应帮助文本
-
-### 功能描述
-
-- 1.聊天中输入 `help` 会获得到**当前可用**指令的帮助
-- 2.聊天中输入 `help [pluginName:string]` 可获取**对应指令名**的帮助文本
-
-### 配置项
-
-无
-
-## 辅助登录 (login)
-
-用户在登录 icqq 的过程中如果触发相关登录验证,可通过命令行完成验证
-
-### 功能描述
-
-- 1.当触发 `system.login.slider` 事件时,可通过命令行输入对应 ticket
-- 2.当触发 `system.login.qrcode` 事件时,可在扫码后回车继续当前流程
-- 3.当触发 `system.login.device` 事件时,可通过命令行选择验证方式和接收验证方式参
- 数
-
-### 配置项
-
-无
-
-## 配置文件管理 (config)
-
-可通过聊天的形式更改 zhin 的配置文件
-
-### 功能描述
-
-- 1.聊天中输入`config` 可以查看当前 zhin 的**所有**配置
-- 2.聊天中输入`config `可以**查看**当前 zhin 的**对应 keyPath** 的配置
-- 3.聊天中输入`config -d `可以**删除**当前 zhin 的**对应 keyPath** 的配置
-- 4.聊天中输入`config `可以**修改**(没有则添加)当前 zhin 的**对应keyPath**的配置为对应值
-
-### 配置项
-
-无
-
-## 插件管理(plugin)
-
-可通过聊天的形式管理 zhin 的插件
-
-### 功能描述
-
-- 1.聊天中输入`plugin.list` 可以查看当前 zhin 的**所有**插件
-- 2.聊天中输入`plugin.detail `可以**查看**当前 zhin 的\*\*对应名称的插件
-- 3.聊天中输入`plugin.mount `可以**挂载**指定名称的插件到zhin
-- 4.聊天中输入`plugin.unmount `可以**取消挂载** zhin 中指定名称的插件
-- 5.聊天中输入`plugin.enable `可以**启用**指定名称的插件
-- 6.聊天中输入`plugin.disable `可以**禁用**指定名称的插件
-
-### 配置项
-
-无
-
-## 热更新(watcher)
-
-提供 zhin 插件开发过程中热更新的功能
-
-### 功能描述
-
-- 1.在插件代码变化是,自动重载对应插件
-- 2.在配置文件中添加或删除插件时,自动加载或取消加载对应插件
-
-### 配置项
-
-可传入一个文件夹地址作为监听目录,默认为项目文件夹,建议不要更改,否则可能会造成
-第二个功能无法使用
-
-## 进程守护(daemon)
-
-提供 zhin 进程守护的能力和手动重启的能力
-
-### 功能描述
-
-- 1.在意外意外中断时,自动重启 zhin
-- 2.在聊天中,可使用指定的命令重启 zhin
-
-### 配置项
-
-| 配置名 | 值类型 | 默认值 | 描述 |
-| :---------- | :------------------ | :----- | :------------------------------------------------------------------- |
-| exitCommand | stirng|boolean | true | 是否启用退出指令,传字符串时,则自定义退出指令,默认退出指令为`exit` |
-| autoRestart | boolean | true | 是否自动重启 |
-
-## 系统信息(systemInfo)
-
-提供日志查看和状态查看指令
-
-### 功能描述
-
-- 1.在聊天中,可使用`logs [lines:number]`查看 zhin 指定行数的日志,(默认为10行)
-- 2.在聊天中,可使用`status`查看 zhin 当前的运行状态
-
-### 配置项
-
-无
diff --git a/docs/src/config/common.md b/docs/src/config/common.md
deleted file mode 100644
index 8d582390c..000000000
--- a/docs/src/config/common.md
+++ /dev/null
@@ -1,78 +0,0 @@
-# 配置文件
-
-- 在项目初始化完成后,项目根目录会生成一个名为`zhin.yaml`的文件,该文件为zhin核
- 心配置文件,内容大致如下。现在,让我们来了解下配置文件每一项的意义
-
-```yaml
-adapters:
- icqq:
- bots:
- - self_id: 147258369
- platform: 5
-plugins:
- config: null
- daemon: null
- help: null
- login: null
- systemInfo: null
- plugin: null
- watcher: plugins
-log_level: info
-plugin_dir: plugins
-data_dir: data
-delay:
- prompt: 60000
-```
-
-## adapters
-
-- 存放适配器的配置文件,每一个key对应一个适配器,每一个适配器可以启动多个机器
- 人,每个机器人的配置存在`bots`中
-- 不同适配器的机器人配置不尽相同,zhin在每一个bot配置基础上增加了一些zhin专有的
- 配置项,大致含义如下:
-
-### bot通用配置项
-
-| 参数名 | 参数类型 | 默认值 | 描述 |
-| :-------------- | :----------------------- | :----- |:-----------------------|
-| self_id | string|number | - | 必填参数 当前机器人唯一表示 |
-| master | string | number | - | 主人账号 |
-| admins | (string | number)[] | [] | 管理员账号列表 |
-| prefix | string | - | 指令调用前缀 |
-| text_limit | number | 100 | 消息长度限制,发送消息时超出该长度将自动转发 |
-| rate_limit | number | 1000 | 指令调用频率限制,单位毫秒 |
-| quote_self | boolean | false | 触发指令时,是否自动引用触发消息 |
-| enable | boolean | - | 当前机器人是否启用 |
-| enable_plugins | stirng[] | - | 启用的插件列表 |
-| disable_plugins | string[] | - | 禁用的插件列表 |
-
-::: tip
-适配器需安装后方能使用,(icqq为内置适配器,无需安装,相应配置请查
-看[adapter-icqq](/config/adapter-icqq))
-:::
-
-## plugins
-
-- 存放插件的配置文件,每一个key对应一个插件,只有在此处定义的插件才会被加载到
- zhin中
-
-::: tip
-插件需安装后方能使用,(样例配置文件中的插件均为内置插件,无需安装即可使
-用,相应配置请查看[内置插件](/config/built-plugin))
-:::
-
-## log_level
-
-- 日志输出等级:(可选值:`off`,`debug`,`error`,`warn`,`info`,`all`)
-
-## plugin_dir
-
-- 本地插件存放文件夹路径
-
-## data_dir
-
-- 缓存数据文件存放文件夹路径
-
-## delay
-
-- 各种超时时长配置(单位:毫秒)
diff --git a/docs/src/guide/android.md b/docs/src/guide/android.md
new file mode 100644
index 000000000..c120be613
--- /dev/null
+++ b/docs/src/guide/android.md
@@ -0,0 +1,34 @@
+# 在 安卓 手机上使用知音
+## 安装 Termux
+- 下载 [Termux](https://play.google.com/store/apps/details?id=com.termux) 并安装
+## 配置国内镜像
+- 打开 Termux 输入以下命令
+```shell
+sed -i 's@^\(deb.*stable main\)$@#\1\ndeb https://mirrors.aliyun.com/termux/termux-packages-24 stable main@' $PREFIX/etc/apt/sources.list
+sed -i 's@^\(deb.*games stable\)$@#\1\ndeb https://mirrors.aliyun.com/termux/game-packages-24 games stable@' $PREFIX/etc/apt/sources.list.d/game.list
+sed -i 's@^\(deb.*science stable\)$@#\1\ndeb https://mirrors.aliyun.com/termux/science-packages-24 science stable@' $PREFIX/etc/apt/sources.list.d/science.list
+
+pkg update
+```
+## 安装 Node.js
+```shell
+pkg install nodejs
+```
+## 安装知音
+- 新建一个文件夹,打开 `cmd` 输入 `cd 文件夹路径` 进入文件夹,执行以下命令
+```shell
+npm init -y # 初始化一个新的项目
+npm install zhin # 安装 zhin
+```
+- 如果速度慢,可考虑使用国内镜像
+```shell
+npm install zhin --registy https://registry.npmmirror.com
+```
+## 初始化知音
+```shell
+npx zhin init
+```
+## 启动知音
+```shell
+npx zhin
+```
diff --git a/docs/src/guide/bot.md b/docs/src/guide/bot.md
deleted file mode 100644
index 870e652de..000000000
--- a/docs/src/guide/bot.md
+++ /dev/null
@@ -1 +0,0 @@
-# 待完善
diff --git a/docs/src/guide/command.md b/docs/src/guide/command.md
deleted file mode 100644
index cc2bf5031..000000000
--- a/docs/src/guide/command.md
+++ /dev/null
@@ -1,136 +0,0 @@
-# 指令(Command)
-
-## 引言
-
-- 指令是当一条消息满足一定条件时,约定机器人执行指定一个函数函数
-- 在大多数机器人中,都是这样实现这个功能的
-
-```js
-// ...
-bot.on("message", event => {
- if (event.raw_message === "foo") {
- // 执行foo函数
- foo();
- } else if (event.raw_message.startsWith("bar")) {
- event.raw_message = event.raw_message.replace("bar", "");
- // 根据参数执行bar函数
- bar(...event.raw_message.split(" "));
- // do sth
- } else if (condition) {
- // ...
- } // ...
-});
-```
-
-- 这无疑是及其混乱的,而且还不利于维护。
-- 为此,Zhin 参考市面上的指令实现后,实现了自己的指令系统
-- 上边的代码在 Zhin 中,可以这么优雅的实现
-
-```ts [src/index.ts]
-// ...
-export function install(ctx: Context) {
- ctx.command("foo").action(foo);
- ctx.command("bar <...args>").action((argv, ...args) => bar(...args));
-}
-```
-
-如此定义后: Zhin 会在用户发送的消息为 `foo` 时,自动执行 **foo 函数**。当用户发送的消息为 `bar` 开头时,Zhin 自动执行**bar函数**,并将后续的参数按**空格**分隔,传递给 **bar 函数**。
-
-并且还有更多的使用方式,让我们接着往下看...
-
-## 参数定义
-
-如你所见,使用 ctx.command(desc) 方法可以定义一个指令,其中 desc 是一个字符串,包含了**指令名**和**参数列表**。
-
-- 指令名可以包含数字、字母、下划线、短横线甚至中文字符,但不应该包含空格、小数点 `.` 或斜杠 `/`
-- 一个指令可以含有任意个参数。其中 **必选参数** 用**尖括号**包裹,**可选参数**用**方括号**包裹
-- 有时我们需要传入未知数量的参数,这时我们可以使用 **变长参数**,它可以通过在括号中前置**...**来实现。如:
-
-```ts
-ctx.command("echo [...rest]").action((_, arg1, ...rest) => {
- /* do something */
-});
-```
-
-上面一行代码声明了一个`echo`指令,并且该指令接收**1到多个参数**
-
-### 参数类型
-
-知音默认参数类型为消息段,若你需要指定类型,仅需在参数名后跟上 `:type` 即可,Zhin 内置的数据类型有:
-
-- string: string 字符串
-- integer: number 整数
-- number: number 数值
-- boolean: boolean 布尔值
-- user_id: number | string 用户id
-- regexp: RegExp 正则表达式
-- date: Date 日期
-- json: Dict | List JSON对象
-- function: Function 函数用例:
-
-```ts
-ctx.command("send [...rest:number]"); // 声明第一个参数为一个表情,剩下的参数均为数值
-```
-
-上面一行代码声明了一个`send`指令,并且该指令接收**一个表情**和**多个数值**,作为参数
-
-## 可选项定义
-
-使用 cmd.option(name, desc) 函数可以给指令定义参数。这个函数也是可以链式调用的,例如:
-
-```ts
-ctx
- .command("music ")
- .option("-o [origin:boolean]") // 是否原声输出
- .option("-p [platform:string]") // 选用音乐平台
- .option("-s [singer:number]") // 指定歌手id
- .action(({ options }, keyword) => JSON.stringify(options));
-```
-
-
- music 烟雨行舟 -o -p qq -s 82329
- {"options":true,"platform":"qq","singer":82329}
-
-
-同样,可选项的参数也可以声明类型,声明方式同上
-
-## 快捷方式
-
-Zhin 的指令机制虽然能够尽可能避免冲突和误触发,但是也带来了一些麻烦。一方面,一些常用指令的调用会受到指令前缀的限制;另一方面,一些指令可能有较长的选项和参数,但它们调用时却往往是相同的。面对这些情况,**快捷方式 (Shortcut)** 能有效地解决你的问题接下来我们将刚刚上边的 `music` 指令稍微进行一下改造
-
-```ts
-ctx
- .command("music ")
- .option("-o [origin:boolean]") // 是否原声输出
- .option("-p [platform:string]") // 选用音乐平台
- .option("-s [singer:number]") // 指定歌手id
- .sugar("qq点歌", { options: { platform: "qq", origin: true } }) // [!code ++]
- .action(({ options }, keyword) => JSON.stringify(options));
-```
-
-- 这儿的`fuzzy`标识指令可以带参数
-
-
- qq点歌 烟雨行舟
- {"options":true,"platform":"qq"}
-
-
-除此以外,你还可以使用正则表达式作为快捷方式:
-
-```ts
-ctx
- .command("music ")
- .option("-o [origin:boolean]") // 是否原声输出
- .option("-p [platform:string]") // 选用音乐平台
- .option("-s [singer:number]") // 指定歌手id
- .sugar("qq点歌", { options: { platform: "qq", origin: true } })
- .sugar(/^来一首(.+)$/, {
- args: ["$1"],
- options: { platform: "qq", origin: true },
- }) // [!code ++]
- .action(({ options }, keyword) => [keyword, JSON.stringify(options)]);
-```
-
-这样一来,输入**来一首烟雨行舟**就等价于输入`music 烟雨行舟 -p qq -o`了。
-
-不难看出,使用快捷方式会让你的输入方式更加接近自然语言,也会让你的机器人显得更平易近人。
diff --git a/docs/src/guide/component.md b/docs/src/guide/component.md
deleted file mode 100644
index 46a6df94e..000000000
--- a/docs/src/guide/component.md
+++ /dev/null
@@ -1,272 +0,0 @@
-
-# 组件
-
-- zhin 提供了组件以增加代码的复用性,zhin 的组件系统在一定程度上参考了 Vue.js 的语法,从而实现了高易学性和一定的移植性
-- 在组件中,你可以直接获取到当前会话的一些变量,这类似于 vue 的 vuex,是根据会话产生环境自动生成的
-
-## 文本插值
-
-- 首先我们来看数据绑定,最基本形式是使用“Mustache”语法(双花括号)的文本插值:
-
- send {{session.sender.user_id}}
- 1659488338
-
- 可以看到,在使用文本插值后,可以很快速的让机器人输出信息,我们来看在实际运行中的一个 demo
-
-
- send [日志][用户:{{session.sender.nickname}}({{session.sender.user_id}})]是一个来自{{session.sender.area == ""?"未知":sender.area}}的{{session.sender.age}}岁{{session.sender.sex == "unknown"?"人妖":sender.sex}}
- [日志][用户:master(1659488338)]是一个来自四川的26岁male
-
-
-## image 标签
-
-- image 标签提供了一种快速发送照片的方法,请看下面的例子
-
- send <image src="https://maohaoji.com/image标签.gif"/>
-
-
- 可以看到,使用 src 标签可以很快的发送想要发送的图片,下面我们来看一个使用 image 标签获取用户头像的实例
-
- send <image :src="`https://q1.qlogo.cn/g?b=qq&nk=${sender.user_id}&s=100`"/>
-
-
- ps 这里的:src 代表此处使用变量为src赋值,在 zhin 中,不支持v-bind代替这个语法,请注意与vue的区别;session可选字段参考`Session`
-
-## template 标签
-
-- template 标签主要是更加规范和语义化,在 zhin 中可以对元素进行分组
- 下面这个例子可以体现 template 对元素的分组
-
- ps <random>会随机输出内部元素,所以实际输出不一定是图示
-
-
- send 你喜欢<random>
- <template>御姐</template>
- <template>萝莉</template>
- </random>
-
-
- 你喜欢萝莉
-
-
-
- 下面这个例子可以体现使用 template 便签的美观性
-
-
- send <template>
- 今日图片
- <image src="https://maohaoji.com/image标签.gif"/>
- 欢迎您{{session.sender.nickname}}({{session.sender.user_id}})
- <image :src="`https://q1.qlogo.cn/g?b=qq&nk=${sender.user_id}&s=100`"/>
- </template>
-
-
- 今日图片
-
- 欢迎您 master(1659488338)
-
-
-
-
-## random 标签
-
-- 相比于手动使用 Math.random()获取随机数然后输出元素,使用 random 随机输出元素的效率以及代码量、可读性都有不错的改善
-
-ps random 内元素请尽可能使用``标签包装,以免出现奇怪的错误
-
-下来我们来看一个例子
-
-
-send <random>
-<template>我猜你喜欢>image src="https://maohaoji.com/zhindocimage/%E9%BB%91%E4%B8%9D.jpg"/ ></template >
-<template>我猜你喜欢>image src="https://maohaoji.com/zhindocimage/%E7%99%BD%E4%B8%9D.jpeg"/> </template>
-<template>我猜你喜欢>image src="https://maohaoji.com/zhindocimage/%E6%B8%94%E7%BD%91.jpg"/> </template>
-</random>
-
-
-我猜你喜欢
-
-
-
-
-## time 标签
-
-- time 标签相比于 new Date()然后解析来获取时间字符串来说是很方便容易的,它会输出 yyyy-MM-dd hh:mm:ss 格式的时间,我们来看有个例子
-
-
- send 现在是<time/>
-
-
- 现在是 2023-02-0518:52:02
-
-
-
-- 我们可以用来实现一个有趣的输出
-
-
-
- send <image :src="`https://q1.qlogo.cn/g?b=qq&nk=${sender.user_id}&s=100`" />[日志][<time />][用户:{{session.sender.nickname}}({{session.sender.user_id}})]是一个来自{{session.sender.area == ""?"未知":sender.area}}的{{session.sender.age}}岁{{session.sender.sex == "unknown"?"人妖":sender.sex}}
-
-
-
-
- [日志][2023-02-0519:49:22][用户:master(1659488338)]是一个来自四川的 26 岁 male
-
-
-
-## at 标签
-
-- 使用 at 标签可以很容易的 at 群内成员,示例如下
-
-
-
- send <at user_id="1659488338" />
-
-
-
- @master
-
-
-- 当然,该标签也可以使用 v-bind 标签实现数据绑定,类似于以下内容
-
-
-
- send <at :user_id="sender.user_id" />
-
-
-
- @master
-
-
-
-- 结合``标签后,很容易的可以实现随机 at
-
-
-
-
- send <random>
- <template>taidixiong233
- <at user_id="2870926164" />
- </template>
- <template>master
- <at user_id="1689919782" />
- </template>
- <template>小叶子
- <at user_id="2870926164" />
- </template>
- </random>
-
-
-
- @taidixiong233
-
-
- 怎么啦
-
-
- 机器人at我干嘛咩
-
-
-
-## prompt 标签
-
-- prompt 标签可以快速的实现表单收集,非常的好用,实例如下
-
-
-
- send 你是<prompt>请输入姓名</prompt>,你在<prompt>请输入地址</prompt>,是个可爱的<prompt>请输入性别</prompt>孩子
-
-
- 请输入姓名
-
-
- master
-
-
- 请输入地址
-
-
- 四川
-
-
- 请输入性别
-
-
- 男
-
-
- 你是master,你在四川,是个可爱的男孩子
-
-
- 这个机器人好酷
-
-
-
-## confirm 标签
-
-- confirm 标签可以问询用户是否确定、继续,我们来看一段演示
-
-
- send 你的选择是<confirm/>
-
-
- 输入 yes,y,Yes,YES,Y,.,。,确认为确认
-
-
- yes
-
-
- 你的选择是 true
-
-
-
-## execute 标签
-
-- execute 标签可以用于执行机器人命令,下图给出了示例,具体命令列表请查看命令列表
-
-
- send <execute>status</execute>
-
-
- 当前状态:
- 系统架构:Zhin 自研
- CPU 架构:65536 核 Zhin(R)CPU9900KF-MaxPro
- 内存:780.26MB/1048576GB(00.01%)
- 进程内存占比:0.01%(45.97MB/1048576GB)
- 持续运行时间:2149 小时 29 分钟
- 掉线次数:0 次
- 发送消息数:3521 条
- 接收消息数:213230 条
- 消息频率:1 条/分
-
-
- 哇趣,65536核心??认真的别搞
-
-
- 1048576GB??1PB的内存,这都比我硬盘空间大了
-
-
-
-## face 标签
-- face 标签可以快速的发送表情消息,需要使用表情的id,示例如下
-
-
- send <face id="2" />
-
-
-
-
-
-
-- 这是一个组合使用face标签的例子
-
-
- send <random><face id="1" /><face id="2" /></random>
-
-
-
-
-
- 机器人发的表情色迷迷的
-
-
diff --git a/docs/src/guide/config.md b/docs/src/guide/config.md
index 6e8333a0d..15030b035 100644
--- a/docs/src/guide/config.md
+++ b/docs/src/guide/config.md
@@ -1,65 +1,39 @@
-::: tip
-阅读本节前,请确认你已根据[试试水](/guide/start)初始化完成你的项目
-:::
-
-# 了解配置
-
-- 上一节中,我们往配置文件中增加第一个机器人账号,但里面还有很多字段,都是代表什么呢?接下来,我们开始熟悉 Zhin 的配置
-- 其中大致可分为适配器配置(`adapters`) 、插件配置(`plugins`)以及通用配置
-- 打开配置文件 `zhin.yaml` ,内容如下(对应作用已通过注释声明)
+# 配置文件
+- 知音将配置文件放在`config`目录下,配置文件的格式为`yml`
+- 主配置文件为`zhin.config.yml`,描述了知音对插件的启用禁用、插件目录信息、日志等级、数据库驱动、机器人配置等
+- 其他配置文件以`*.yml`命名,以供其他插件使用
+- 本篇章节将介绍`zhin.config.yml`的配置项,其他插件的配置项请查看对应插件的文档
+## 配置文件示例
```yaml
-adapters:
- icqq: # 指定使用icqq适配器
- bots:
- - uin: 147258369 # 机器人账号 //
- platform: 5 # 指定qq登录平台为iPad(可不配置 1:安卓 2:安卓平板 3:手表 4:苹果电脑 5:苹果平板
- password: "你的机器人密码" # 账号密码(不配置则使用扫码登录)
- prefix: "" # 指令调用前缀,可不配置
- master: 1659488338 # 机器人主人账号(拥有完整操作该机器人的权限,可不配置)
- admins: [] # 机器人管理员账号(可不配置)
-plugins:
- config: null # 指定启用配置管理插件
- daemon: null # 指定启用守护进程插件
- help: null # 指定启用帮助插件
- login: null # 指定启用命令行登录插件
- plugin: null # 指定启用插件管理插件
- systemInfo: null # 指定启用系统信息查看插件
- watcher: /path/to/zhin-bot # 指定启用文件监听插件
-log_level: info # 指定日志等级
-plugin_dir: plugins # 指定本地插件存放目录
-data_dir: data # 缓存文件存放目录
-delay:
- prompt: 60000 # prompt方法超时时间为1分钟(60*1000毫秒)
+log_level: info # 日志等级
+has_init: true # 知音是否已经初始化
+db_driver: level # 数据库驱动
+db_init_args: # 数据库初始化参数
+ - zhin.db
+ - valueEncoding: json
+ createIfMissing: true
+disable_adapters: [] # 禁用的适配器
+disable_bots: # 禁用的机器人
+ - "2922360890"
+disable_plugins: [] # 禁用的插件
+plugin_dirs: # 插件目录
+ - ../zhin/lib/plugins
+ - plugins
+bots: # 机器人配置
+ - adapter: process # 机器人适配器名称
+ unique_id: developer # 机器人唯一标识
+ title: 终端 # 标题
+ master: "1659488338" # 机器人主人
+ admins: [] # 管理员
+ command_prefix: "#" # 命令前缀
+ quote_self: false # 回复消息是否引用自己
+plugins: # 启用的插件列表
+ - setup # 提供setup 语法开发插件支持,已内置,可直接使用
+ - processAdapter # 进程适配器插件,已内置,可直接使用
+ - commandParser # 指令解析插件,已内置,可直接使用
+ - echo # 基础输出测试插件,已内置,可直接使用
+ - hmr # 提供插件开发热更功能,已内置,可直接使用
+ - zhinManager # 提供知音管理相关指令,已内置,可直接使用
+ - database # 提供数据存储服务,已内置,可直接使用
```
-
-## 适配器配置(adapters)
-
-即 Zhin 当前启用的适配器配置,其中每一项的 key 为适配器名称,对应 value 中的 bots 中存放的则是使用该适配器添加到 Zhin 的每一个机器人账号配置
-
-而每一个 Bot 的配置中,除了不同平台的配置外,只能额外提供了一些通用配置,用于配置 Bot 在 Zhin 中的权限配置和指令设置
-
-### bot 通用配置
-
-| 配置名 | 类型 | 默认值 | 描述 |
-| :----- | :----------------------- | :----- | :------------- |
-| master | string | number | - | 主人账号 |
-| admins | (string | number)[] | [] | 管理员账号列表 |
-| prefix | string | - | 指令调用前缀 |
-
-## 插件配置(plugins)
-
-即 Zhin 当前启用的插件配置,其中每一项的 key 为插件名称,对应 value 则为传递给相应插件的配置内容
-
-其中 `config`、`daemon`、`help`、`login`、`logs`、`plugin`、`status`、`watcher` 为 Zhin 内置插件帮助用户完成一些通用功能,具体功能请见[内置插件](/config/built-plugin)介绍
-
-## 通用配置(...other)
-
-除了通用`适配器配置`和`插件配置`以外的配置,均属于zhin的通用配置,其中各项含义如下表:
-
-| 配置名 | 类型 | 默认值 | 描述 |
-| :--------- | :------------------------------------------------------------------------------------------ | :-------------------------- | :------------------- |
-| log_level | trace | debug | info | warn | error | fatal | mark | off | info | 日志输出等级 |
-| plugin_dir | string | plugins | 插件存放路径 |
-| data_dir | string | data | 数据存放路径 |
-| delay | Record | { prompt: 60000 } | 系统各种超时时长配置 |
diff --git a/docs/src/guide/decorator.md b/docs/src/guide/decorator.md
deleted file mode 100644
index 2e4a3b505..000000000
--- a/docs/src/guide/decorator.md
+++ /dev/null
@@ -1,141 +0,0 @@
-# 装饰器
-
-:::warning
-`TypeScript Decorator`本身就为实验性功能,暂未稳定。但是目前实际上已有很多框架诸如[`Nest.js`](https://nestjs.com)、[`Midway`](https://www.midwayjs.org/)等框架已经用上很久了,所以暂且先出版本,观望观望。
-:::
-
-> 由于装饰器的实验性性质,因此目前仍然处于分包的形式存在在Zhin中。欢迎大家[点击此处](https://github.com/zhinjs/decorator/issues)提交issue。
-
-## 安装
-
-请先参考[安装Zhin机器人](/guide/start.html)创建一个机器人。创建好之后,执行:
-
-::: code-group
-```sh [pnpm]
-pnpm install @zhinjs/decorator
-```
-
-```sh [yarn]
-yarn add @zhinjs/decorator
-```
-
-```sh [npm]
-npm install @zhinjs/decorator
-```
-:::
-
-然后在项目根目录创建一个`tsconfig.json`文件(如果已经有了那么请照葫芦画瓢一下,把这两项给加上):
-
-```json
-{
- "compilerOptions": {
- "emitDecoratorMetadata": true
- }
-}
-```
-
-将上面的两项设置为`true`。
-
-## 使用
-
-熟悉Spring/Nest/Midway等的的开发者应该会很熟悉。
-
-您可以将@Plugin看成是@Controller。
-
-## 撰写一个插件
-
-``` typescript
-// plugins/repeater/index.ts
-import {
- Command,
- CommandDesc,
- CommandOption,
- CommandSugar,
- Inject,
- InjectContext,
- InjectPlugin,
- MessagePattern,
- Middleware,
- Plugin,
- CommandRunTime,
-} from "@zhinjs/decorator";
-import { Next } from "koa";
-import { Session, Context, Plugin as IPlugin } from "zhin";
-import { RepeaterService } from "./repeater.service";
-
-@Plugin
-export default class Repeater {
- // * 注入Plugin Class
- @InjectPlugin
- private readonly plugin: IPlugin;
- // * 注入ctx上下文
- @InjectContext
- private readonly ctx: Context;
- // * 注入一个“服务类”,下面会有说明
- @Inject(RepeaterService)
- private readonly repeaterService: RepeaterService;
-
- // * 定义一个中间件。
- // !不能与@MessagePattern等交叉混用
- @Middleware
- aMiddleware(seesion: Session, next: Next) {
- seesion.reply(this.repeaterService.getMessage("middleware"));
- next();
- }
-
- // * 监听消息事件。允许同一个方法上有多个@MessagePattern
- @MessagePattern("icqq.message")
- onMessageReceived(seesion: Session) {
- seesion.reply("监听到一条消息");
- }
-
- // 定义一个指令
- // !注意:注解是从下往上运行的;但是始终会按照一个顺序:`初始化命令->定义option->定义sugar->执行action`
- // * @CommandSugar是允许定义多个的
- @CommandSugar(/^来一首(.+)$/, {
- args: ["$1"],
- options: { platform: "qq", origin: true },
- })
- @CommandSugar("qq点歌", { options: { platform: "qq", origin: true } })
- // * @CommandOption是可以定义多个的
- @CommandOption("-o [origin:boolean]")
- @CommandOption("-p [platform:string]")
- @CommandOption("-s [singer:number]")
- // * 这是命令的描述
- @CommandDesc("命令的描述")
- // * @Command只能允许有一个;如果有多个会被上面的覆盖掉
- @Command("music ")
- defineCommand({ options, session }: CommandRunTime<{ platform: string; origin: boolean }>, keyword: string) {
- console.log(options);
- session.reply(options.origin);
- }
-}
-```
-
-可以看到,使用装饰器方式写出来的插件会十分`清晰易懂`,不易写乱。
-
-## 撰写一个服务
-
-此处的`服务`指的是`服务类`,而非非zhin的`服务`,请勿搞混;
-
-`服务类`的作用,是用于封装逻辑;比如访问数据库等某些增删改查的逻辑,就可以封装。
-
-```typescript
-// plugins/repeater/repeater.service.ts
-import { Injectable } from "@zhinjs/decorator";
-
-// 标记这是一个服务
-// 按道理来说,这个@Injectable是可以和nestjs通用的哦~
-// 但是我暂时没有测试过,但是十有八九是通用的,因为nest是可插拔的
-@Injectable
-export class RepeaterService {
- // 写一个方法
- getMessage(type: "pattern" | "middleware") {
- if (type === "pattern") {
- return "hello world! This is a Message Pattern's message!";
- } else if (type === "middleware") {
- return "hello world! This is a middleware message!";
- }
- }
-}
-```
diff --git a/docs/src/guide/deploy.md b/docs/src/guide/deploy.md
deleted file mode 100644
index 93ffadfd4..000000000
--- a/docs/src/guide/deploy.md
+++ /dev/null
@@ -1,51 +0,0 @@
-# 部署到服务器
-
-1. 在[ Node.js 官网](https://nodejs.org/en/download/)选择适合你服务器操作系统的 Node.js 版本安装到服务器
-2. 安装完成 `Node.js` 后,使用其自带的 `npm` 全局安装 `pm2`
-
-```shell
-npm install -g pm2
-```
-
-## 使用 cli 快速部署到 Linux 服务器
-
-在项目根目录执行下面的命令,即可快速将当前项目部署到远程 linux 服务器
-
-:::tip
-1. 尖括号(`<>`)为必填参数,方括号(`[]`)为选题参数
-2. username 不传时默认为 `root`
-3. 未传 password 时,使用 `sshKey` 登录,`sshKey` 默认为 `~/.ssh/${ip}.pem`
-4. 传了 password 时,sshKey 将失效,直接使用密码登录
-5. directory 不传时默认为 `~/{当前项目名}`
-:::
-
-```shell
-zhin deploy <服务器ip> [-u ] [-p | -k ] [-d ]
-```
-
-## 自行部署到其他操作系统的服务器
-
-1. 在服务器上安装 Zhin 脚手架 `@zhinjs/cli`
-
-```shell
-npm install @zhinjs/cli -g
-```
-
-2. 使用 cli 初始化一个空项目
-
-```shell
-zhin init zhin-app
-```
-
-3. 将本地项目的 `package.json`、`plugins`、`data`、`zhin.yaml` 拷贝到服务器刚刚创建的空项目中
-1. 在服务器上安装项目依赖
-
-```shell
-cd zhin-app && npm install
-```
-
-5. 使用 pm2 启动 Zhin
-
-```shell
-pm2 start npm --name zhin -- run start:zhin
-```
diff --git a/docs/src/guide/dingtalk.md b/docs/src/guide/dingtalk.md
new file mode 100644
index 000000000..f9881d29c
--- /dev/null
+++ b/docs/src/guide/dingtalk.md
@@ -0,0 +1,28 @@
+# 接入 钉钉 机器人
+## 安装钉钉机器人适配器
+```shell
+npm install @zhinjs/adapter-dingtalk
+```
+## 准备钉钉机器人必要参数
+### 1. 前往 [钉钉开放平台](https://open-dev.dingtalk.com/) 创建一个机器人,并记住 `appId` 和 `appSecret`
+
+## 配置钉钉机器人适配器
+### 1. 打开项目根目录下的 `config/zhin.config.yml` 文件
+### 2. 在 `bots` 下添加以下配置
+```yaml
+bots:
+ - adapter: dingtalk
+ unique_id: 机器人唯一标识
+ clientId: 你的appId
+ clientSecret: 你的appSecret
+ reconnect_interval: 3000 # 重连间隔(ms)
+ max_reconnect_count: 10 # 最大重连次数
+ heartbeat_interval: 3000 # 心跳间隔(ms)
+ request_timeout: 5000 # 请求超时(ms)
+ sandbox: true # 是否沙箱环境
+```
+## 启动 钉钉 机器人
+- 保存配置文件后,执行以下命令启动机器人
+```shell
+npx zhin
+```
diff --git a/docs/src/guide/discord.md b/docs/src/guide/discord.md
new file mode 100644
index 000000000..a20484a89
--- /dev/null
+++ b/docs/src/guide/discord.md
@@ -0,0 +1,28 @@
+# 接入 Discord
+## 安装 Discord 机器人适配器
+```bash
+npm install @zhinjs/adapter-discord
+```
+## 准备机器人必要参数
+### 1. 前往 [Discord Developer Portal](https://discord.com/developers/applications) 创建一个机器人,并记住 `clientId` 和 `clientSecret`
+
+## 配置 Discord 机器人适配器
+### 1. 打开项目根目录下的 `config/zhin.config.yml` 文件
+### 2. 在 `bots` 下添加以下配置
+```yaml
+bots:
+ - adapter: discord
+ unique_id: 机器人唯一标识
+ clientId: 你的clientId
+ clientSecret: 你的clientSecret
+ reconnect_interval: 3000 # 重连间隔(ms)
+ max_reconnect_count: 10 # 最大重连次数
+ heartbeat_interval: 3000 # 心跳间隔(ms)
+ request_timeout: 5000 # 请求超时(ms)
+ sandbox: true # 是否沙箱环境
+```
+## 启动 Discord 机器人
+- 保存配置文件后,执行以下命令启动机器人
+```shell
+npx zhin
+```
diff --git a/docs/src/guide/email.md b/docs/src/guide/email.md
new file mode 100644
index 000000000..f4f0528d9
--- /dev/null
+++ b/docs/src/guide/email.md
@@ -0,0 +1,39 @@
+# 接入邮箱
+## 安装适配器
+```bash
+npm install @zhinjs/adapter-email
+```
+## 准备工作
+- 访问你的邮箱,获取邮箱的SMTP服务器地址和端口号
+- 获取邮箱的用户名和密码
+- 获取邮箱的IMAP服务器地址和端口号
+:::tip
+- 部分邮箱使用授权码代替密码,如QQ邮箱,需要在邮箱设置中开启SMTP服务,并获取授权码
+- 部分邮箱需要开启IMAP服务,如QQ邮箱,需要在邮箱设置中开启IMAP服务
+:::
+
+## 配置邮箱机器人适配器
+### 1. 打开项目根目录下的 `config/zhin.config.yml` 文件
+### 2. 在 `bots` 下添加以下配置
+```yaml
+bots:
+ - adapter: email
+ unique_id: 机器人唯一标识
+ username: 邮箱用户名
+ password: 邮箱密码 # 或授权码
+ smtp:
+ host: SMTP服务器地址 # 如QQ邮箱 smtp.qq.com
+ port: SMTP服务器端口号 # 默认 465
+ tls: true # 是否启用TLS
+ imap:
+ host: IMAP服务器地址 # 如QQ邮箱 imap.qq.com
+ port: IMAP服务器端口号 # 默认 993
+ tls: true # 是否启用TLS
+```
+
+## 启动邮箱机器人
+- 保存配置文件后,执行以下命令启动机器人
+```bash
+npx zhin
+```
+
diff --git a/docs/src/guide/icqq.md b/docs/src/guide/icqq.md
new file mode 100644
index 000000000..84fd4ef1e
--- /dev/null
+++ b/docs/src/guide/icqq.md
@@ -0,0 +1,32 @@
+# 接入icqq
+## 安装 icqq 机器人适配器
+```shell
+npm install @zhinjs/adapter-icqq
+```
+## 准备一个 `QQ号`、`密码`、以及 `icqq 签名api` 地址
+### 1. 前往 [QQ](https://im.qq.com/) 申请一个QQ号,并记住对应的密码(如果已有qq,可忽略)
+### 2. 自行获取 `icqq 签名api` 地址
+## 配置 icqq 机器人适配器
+### 1. 打开项目根目录下的 `config/zhin.config.yml` 文件
+### 2. 在 `bots` 下添加以下配置
+```yaml
+bots:
+ - adapter: icqq
+ unique_id: 机器人唯一标识
+ qq: 你的qq号
+ password: 你的qq密码 # 不填则扫码登录
+ sign_api_addr: icqq签名api地址
+ ver: 使用的qq版本 # 请与确保签名api支持该版本
+ platform: 登录设备平台
+ # 安卓手机(Android) 填 1
+ # 安卓平板(aPad) 填 2
+ # 安卓手表(Watch) 填 3
+ # MacOS(Mac电脑) 填 4
+ # iPad(苹果平板) 填 5
+```
+## 启动 icqq 机器人
+- 保存配置文件后,执行以下命令启动机器人
+```shell
+npx zhin
+```
+- 根据提示,完成登录验证即可
diff --git a/docs/src/guide/intro.md b/docs/src/guide/intro.md
new file mode 100644
index 000000000..260801988
--- /dev/null
+++ b/docs/src/guide/intro.md
@@ -0,0 +1,66 @@
+# 简介
+
+::: warning
+框架任然处于开发阶段,不建议在生产环境中使用
+:::
+
+
+
+[](https://github.com/zhinjs/zhin/actions/workflows/ci.yml)
+
+[](https://github.com/zhinjs/zhin/actions/workflows/docs.yml)
+
+[](https://www.npmjs.com/package/zhin)
+
+[](https://www.npmjs.com/package/zhin)
+
+[](https://nodejs.org)
+
+[](https://pkg-size.dev/zhin)
+
+[](https://pkg-size.dev/zhin)
+
+
+
+- 知音 (Zhin) 是一个基于 NodeJS 的多平台机器人开发框架,兼容 QQ、ICQQ、WeChat、Discord、OneBot(11/12)、钉钉等机器人平台。
+- 知音的目标是提供一个轻量、优雅、热更、统一的机器人开发框架。
+- 知音的内部实现尽可能符合大众开发思维,无论是阅读源码,还是开发插件,都能事半功倍。
+
+## 特性
+- **轻量**:精简内部功能,仅内置系统级的常用插件和适配器,其他功能均通过插件来实现
+- **优雅**:知音的内部实现尽可能符合大众开发思维,无论是阅读源码,还是开发插件,都能事半功倍
+- **热更**:知音内置热更插件,让开发者在开发时避免频繁重启进程,从而降低账号风险概率
+- **统一**:知音通过适配器统一了机器人消息的收发以及事件规范,使得开发者可以只关注一种规范,即可完成机器人开发
+- **多平台**:支持 QQ、ICQQ、WeChat、Discord、OneBot(11/12)、钉钉等机器人平台
+- **插件化**:支持插件化开发,开发者可以通过插件来扩展知音的功能
+
+## 快速开始
+### 安装
+- 你希望在什么设备上使用知音?
+1. [我想在 Windows 电脑上使用](/guide/windows)
+2. [我想在 Linux / Macos 服务器上使用](/guide/linux)
+3. [我想在 安卓 手机上使用](/guide/android)
+
+### 接入平台
+- 你希望接入什么平台?
+- [QQ](/guide/qq)
+- [ICQQ](/guide/icqq)
+- [WeChat](/guide/wechat)
+- [Discord](/guide/discord)
+- [OneBot(11/12)](/guide/onebot)
+- [钉钉](/guide/dingtalk)
+
+### 插件
+- 插件是知音的核心功能,通过插件,你可以扩展知音的功能。
+- 你可以通过 npm 安装插件,也可以自己开发插件。
+- [插件商店](/store)
+- [插件开发](/advance/plugin)
+
+### 配置文件
+- 项目根目录下的 config 文件夹存放着zhin所用到的配置文件,其中 zhin.config.yml 是主配置文件,包含了机器人配置、插件配置、数据库配置、日志配置等基础配置。
+- 你可以根据自己的需求修改配置文件,主配置文件更改后,zhin会自动重启以应用新的配置。
+- 在该目录下创建其他配置文件,并在插件中通过 `useConfig('文件名')` 来使用。
+- 主配置文件介绍,请前往 [配置文件](/guide/config) 查看。
+
+### more
+- [进阶](/advance/plugin)
diff --git a/docs/src/guide/linux.md b/docs/src/guide/linux.md
new file mode 100644
index 000000000..014673308
--- /dev/null
+++ b/docs/src/guide/linux.md
@@ -0,0 +1,24 @@
+# 在 Linux / Mac 电脑上使用知音
+## 安装 Node.js
+- 下载 [Node.js](https://nodejs.org/zh-cn/download/) 并安装
+- 安装时请勾选 `npm` 选项
+- 安装时请勾选 `Add to PATH` 选项
+- 安装完成后,打开 `cmd` 输入 `node -v` 和 `npm -v` 查看是否安装成功
+## 安装知音
+- 新建一个文件夹,打开 `cmd` 输入 `cd 文件夹路径` 进入文件夹,执行以下命令
+```shell
+npm init -y # 初始化一个新的项目
+npm install zhin # 安装 zhin
+```
+- 如果速度慢,可考虑使用国内镜像
+```shell
+npm install zhin --registy https://registry.npmmirror.com
+```
+## 初始化知音
+```shell
+npx zhin init
+```
+## 启动知音
+```shell
+npx zhin
+```
diff --git a/docs/src/guide/onebot.md b/docs/src/guide/onebot.md
new file mode 100644
index 000000000..fdac57455
--- /dev/null
+++ b/docs/src/guide/onebot.md
@@ -0,0 +1,72 @@
+# 接入 Onebot 11/12 机器人
+::: info
+Onebot 是一个开放的机器人协议,支持多种连接方式,现已更新到 OneBot 12。本文档将介绍如何接入 Onebot 11/12 机器人
+:::
+
+## 安装适配器
+- 根据需要接入的 `OneBot` 版本,安装不同的适配器
+::: code-group
+
+```shell [Onebot 11]
+npm install @zhinjs/adapter-onebot-11
+```
+```shell [Onebot 12]
+npm install @zhinjs/adapter-onebot-12
+```
+:::
+## 准备 Onebot 机器人必要参数
+::: tip
+知音 仅支持 正向ws 和 反向ws 两种连接方式,不支持 http 连接方式。
+:::
+- 请确保你已经有一个 `Onebot 实现端`,并且实现端支持 `正向ws` 或 `反向ws` 连接方式。
+
+## 配置 Onebot 机器人适配器
+### 1. 打开项目根目录下的 `config/zhin.config.yml` 文件
+### 2. 在 `bots` 下添加以下配置
+::: code-group
+```yaml [Onebot 11正向ws]
+bots:
+ - adapter: onebot-11
+ unique_id: 机器人唯一标识
+ type: ws
+ url: 正向ws地址
+ max_reconnect_count: 10 # 重连次数
+ reconnect_interval: 3000 # 重连间隔(ms)
+ access_token: 鉴权token # 没有可不设置
+```
+```yaml [Onebot 12正向ws]
+bots:
+ - adapter: onebot-12
+ unique_id: 机器人唯一标识
+ type: ws
+ url: 正向ws地址
+ max_reconnect_count: 10 # 重连次数
+ reconnect_interval: 3000 # 重连间隔(ms)
+ access_token: 鉴权token # 没有可不设置
+```
+```yaml [Onebot 11反向ws]
+bots:
+ - adapter: onebot-11
+ unique_id: 机器人唯一标识
+ type: ws_reverse
+ prefix: 监听路径 # 默认 /onebot/v11
+ access_token: 鉴权token # 没有可不设置
+```
+```yaml [Onebot 12反向ws]
+bots:
+ - adapter: onebot-12
+ unique_id: 机器人唯一标识
+ type: ws_reverse
+ prefix: 监听路径 # 默认 /onebot/v11
+ access_token: 鉴权token # 没有可不设置
+```
+:::
+
+## 启动 Onebot 机器人
+- 保存配置文件后,执行以下命令启动机器人
+```shell
+npx zhin
+```
+::: tip
+- 若为反向ws连接方式,请确保你的 `OneBot 实现端` 能够访问到知音服务端。并将服务端地址填写到 `OneBot 实现端` 的配置中。
+- 若为正向ws连接方式,请确保zhin 能访问到你的 `OneBot 实现端`
diff --git a/docs/src/guide/plugin-guide.md b/docs/src/guide/plugin-guide.md
deleted file mode 100644
index 615703a21..000000000
--- a/docs/src/guide/plugin-guide.md
+++ /dev/null
@@ -1,140 +0,0 @@
-# 编写第一个插件
-
-到目前为止,我们虽然让 zhin 运行起来了,但除了内置插件外,还没有任何功能,接下来,让我们通过实现一个复读机的小功能,来初步了解下 zhin 插件开发的大体流程。
-
-## 1. 创建插件 (二选一)
-### - 使用内置指令创建
-```shell
-plugin.new repeater # 此处 repeater 为插件名
-
-# 或者
-
-plugin.new repeater -t # 如果你想使用 TS 进行开发,可增加 `-t` 选项,声明需要插件开发语言为ts
-```
-
-### - 手动创建
-
-```shell
-# 进入插件保存目录
-cd plugins
-
-#创建一个存放插件的目录
-mkdir repeater
-
-#进入刚刚创建的插件目录
-cd repeater
-
-#初始化插件仓库 (可选)
-npm init
-
-#创建入口文件
-touch index.ts
-```
-
-完成创建后,插件目录大体如下:
-
-::: code-group
-
-```txt [通过 内置指令 创建]
-plugins/
-└─ repeater/ test 插件
- └─ src/ 资源目录 插件
- ├─ index.ts 程序主入口
- └─ package.json 包管理文件 (可选)
-```
-
-```txt [手动创建]
-plugins/
-└─ repeater/ test 插件
- ├─ index.js 程序主入口
- └─ package.json 包管理文件 (可选)
-```
-
-:::
-
-::: warning
-除非你创建了 package.json ,否则 index 文件名不能随意更改,不然会导致插件无法被检索。
-:::
-
-打开入口文件,并输入如下内容
-
-::: code-group
-
-```ts [src/index.ts]
-import {Plugin} from 'zhin';
-
-const repeater = new Plugin();
-
-// write your code here
-
-export default repeater
-```
-
-```js [index.js]
-import {Plugin} from 'zhin';
-
-const repeater = new Plugin();
-
-// write your code here
-
-export default repeater
-```
-
-:::
-
-这个时候你就已经写好了一个插件,不需要任何额外操作,不过目前这个插件还什么都不能干,我们没有为其编写相应的交互逻辑。
-
-## 2. 实现插件交互逻辑
-
-相信你这个时候一定有很多疑问,因为这其中涉及到相当多的概念,`Plugin` 到底是什么?
-
-::: info
-当前章节仅提供示例,目的在于让你能自己编写出可以进行简单交互的插件。目前你无需关心这段代码是什么意思,后面会逐一介绍,所以不用着急,让我们继续。
-:::
-
-你可以参考下列代码段,在[上下文](/api/context)上添加一个[中间件](/api/middleware),拦截[消息会话](/api/session),并将[消息元素](/interface/element)原封不动回复给用户。
-
-::: code-group
-
-```ts [src/index.ts]
-import {Plugin} from 'zhin';
-
-const repeater = new Plugin();
-
-repeater.middleware((adapter,bot,message,next)=>{
- message.reply(message.raw_message)
- next()
-})
-
-export default repeater
-
-```
-
-```js [index.js]
-import {Plugin} from 'zhin';
-
-const repeater = new Plugin();
-
-repeater.middleware((adapter,bot,message,next)=>{
- message.reply(message.raw_message)
- next()
-})
-
-export default repeater
-```
-
-:::
-## 3.载入插件
-- 你可使用内部指令 `plugin.add` 载入已安装的插件
-```shell
-plugin.add repeater
-```
-## 4.测试一下
-
-
- hello
- hello
-
-
-## 5.更多
-- 更多插件相关内容,请前往[插件](./plugin-introduce.md)了解
diff --git a/docs/src/guide/plugin-introduce.md b/docs/src/guide/plugin-introduce.md
deleted file mode 100644
index 2044220f4..000000000
--- a/docs/src/guide/plugin-introduce.md
+++ /dev/null
@@ -1,107 +0,0 @@
-# 插件介绍
-
-## 插件类型
-
-zhin 的插件共分为 `内置插件` 、`本地插件` 、`npm 插件` 和 `git 插件` 四类。
-- 内置插件
-
-为了给开发者提供更良好的开发体验, zhin 内置了部分插件,以方便用户管理适配器、插件、机器人、配置文件 `zhinManager`,基本输出测试 `echo`,以及一些基本系统运行所需功能(指令解析 `commandParser` 、热重载 `hmr` 、setup语法支持 `setup` )
-
-::: tip
-内置插件默认为启用状态,你可根据自身需求进行禁用
-:::
-- 本地插件
-
-本地插件将全部存放在根目录的 plugins 下。所有由你自己编写或从 `git clone` 而来,并仅供**个人使用**的插件就可以称为本地插件。
-
-- npm 插件
-
-npm 插件可使用 `npm install [moduleName]` 或 `plugin.add [moduelName]` 命令进行安装,存放在 `node_modules` 目录下。是由我或者其他开发者编写,上传至 `npmjs` 平台,为 **所有使用 zhin 框架的人** 提供服务。
-
-
-- git插件
-
-`git插件` 通常为存放于某个git仓库的单独项目,使用git插件时,需使用 `plugin.add [url]` 将对应的git项目 clone 到本地插件目录后,作为本地插件使用(若该项目有依赖其他 npm 模块,需要你手动进行依赖安装)
-## setup 语法
-
-::: tip
-zhin 通过内置的 `setup` 插件支持了 *setup* 语法 ,使用前请确保启用了内置插件 `setup`
-:::
-- zhin的插件开发参考了 `Vue` 的 [script setup](https://cn.vuejs.org/guide/typescript/composition-api.html#using-script-setup) 设计,使得用户可以写更少的代码,实现相同的功能
-
-### setup 示例
-
-::: code-group
-
-```javascript [JavaScript-setup]
-const {command} = require('zhin');
-
-command('foo')
-.action(()=>'bar')
-
-```
-
-```typescript [TypeScript-setup]
-import {command} from 'zhin'
-
-command('foo')
-.action(()=>'bar')
-
-```
-:::
-- 对于 zhin 而言,这都是一个有效的插件
-- 更多 setup 语法,请前往 [setup](./setup.md) 了解
-
-## 插件管理
-- zhin 通过内置插件 `zhinManager` 提供了插件管理的指令,你可在 `zhin` 启动后,在命令行键入相应指令,管理插件
-
-### 新建插件
-```shell
-plugin.new [插件名] # 新建基于 JavaScript 开发的插件
-# or
-plugin.new [插件名] -t # 新建基于 TypeScript 开发的插件
-```
-### 安装插件
-```shell
-plugin.install [packageName] # 安装 npm 模块插件
-# or
-plugin.install [url] # clone git仓库作为插件
-```
-### 挂载插件
-- 只能挂载已安装的插件
-```shell
-plugin.add [插件名]
-```
-### 取消挂载插件
-```shell
-plugin.remove [插件名]
-```
-### 启用插件
-- 只能启用已挂载的插件
-```shell
-plugin.enable [插件名]
-```
-### 禁用插件
-```shell
-plugin.disable [插件名]
-```
-### 发布插件
-- 仅能发布使用 `plugin.new` 创建的插件
-- 需要你有 `npmjs` 账号并且具备对应包名的发布权限
-```shell
-plugin.publish [插件名]
-```
-### 推送插件变更
-- 仅能推送通过 `git` 拉取的插件
-- 需要你具备对应仓库的推送权限
-```shell
-plugin.push [插件名] # 推送已commit的git变更
-# or
-plugin.push [插件名] -m [message:string] # commit 所有 git 变更并提交
-```
-### 拉取插件变更
-- 仅能拉取通过 `git` 拉取的插件
-- 需要你具备对应仓库的拉取权限
-```shell
-plugin.pull [插件名]
-```
diff --git a/docs/src/guide/prepare.md b/docs/src/guide/prepare.md
deleted file mode 100644
index df74e6fbe..000000000
--- a/docs/src/guide/prepare.md
+++ /dev/null
@@ -1,26 +0,0 @@
-# 准备工作
-
-## 技能要求
-
-1. 熟练使用 [百度](https://www.baidu.com)、[必应](https://www.bing.com) 等搜索引擎获取信息,能 [谷歌](https://www.google.com) 更佳
-2. 熟悉 `JavaScript` (`JS`),具备 `JS` 代码的阅读和编写能力
-3. 了解 `Typescript` (`TS`),具备阅读 `TS` 代码的能力(可选)
-
-## 环境要求
-
-- 支持以下系统
- - `Windows`
- - `Linux`
- - `MacOS`
-- 运行环境要求
- - `Node.js` (要求版本 >= 16.0)
-
-Zhin 基于 Node.js,可以使用如下指令查询当前设备已安装的 Node.js 版本。
-
-
-```bash
-node -v
-```
-
-如还未下载,请根据自身系统到 [Node.js](https://nodejs.org/zh-cn) 官网下载安装。
-
diff --git a/docs/src/guide/prompt.md b/docs/src/guide/prompt.md
deleted file mode 100644
index 406b28608..000000000
--- a/docs/src/guide/prompt.md
+++ /dev/null
@@ -1,182 +0,0 @@
-# 可交互输入(Prompt)
-
-## 引言
-
-在实际开发过程中,我们可能会需要用户输入必填参数,或者需要用户确认操作,才能继续执行函数,为此, Zhin 在**会话(Session)**上提供了一个 `prompt` 对象,用于接收用户下一次输入的内容,具体用例如下:
-
-## 案例
-
-```typescript
-import { Context } from "zhin";
-
-export function install(ctx: Context) {
- ctx
- .command("del [id:number]")
- .desc("删除用户")
- .action(async ({ session }, id) => {
- if (!id) id = await session.prompt.number("请输入你要删除的用户ID");
- const confirm = await session.prompt.confirm(`确认删除用户(${id})么?`);
- if (confirm) {
- // 删除用户
- return "删除成功";
- }
- return "已取消";
- });
-}
-```
-
-
- del
- 请输入你要删除的用户ID
- 78
- 确认删除用户(78)么? 输入yes,y,Yes,YES,Y,.,。,确认为确认
- yes
- 删除成功
-
-
-当然,prompt 可以支持的交互不仅与 number 和 confirm,下面将详细介绍有那些交互输入的类型
-
-## 可交互输入的类型
-
-### 1.text
-
-- 输出一条提示信息,提示用户输入一行文本
-
-```typescript
-const name = session.prompt.text("请输入姓名");
-```
-
-### 2.number
-
-- 输出一条提示信息,提示用户输入一个数字
-
-```typescript
-const age = session.prompt.number("请输入年龄");
-```
-
-### 3.date
-
-- 输入一条提示信息,提示用户输入一个日期
-
-```typescript
-const birthDay = session.prompt.date("请输入出生年月日");
-```
-
-### 4.regexp
-
-- 输入一条提示信息,提示用户输入一个正则表达式
-
-```typescript
-const reg = session.prompt.regexp("请输入一个正则表达式");
-```
-
-### 5.confirm
-
-- 输入一条提示信息,提示用户是否确认
-
-```typescript
-const isAdult = session.prompt.confirm("是否成年");
-```
-
-### 6.list
-
-- 输入一条提示信息,提示用户输入一个指定类型的list
-
-```typescript
-const hobbies = session.prompt.list("请输入你的兴趣爱好", {
- child_type: "text",
-});
-```
-
-### 7.select
-
-- 输入一条提示信息,提示用户选择一个或多个给出选项的值
-
-```typescript
-const selctedList = session.prompt.select("请选择你喜欢的水果", {
- child_type: "text",
- multiple: true, // 不传则为单选
- options: [
- { label: "苹果", value: "apple" },
- { label: "香蕉", value: "banana" },
- { label: "橙子", value: "orange" },
- ],
-});
-```
-
-### 8.组合成对象使用 (prompts)
-
-除了上述单条单条的让用户输入,Zhin 还允许,你将配置组合成一个对象,让用户依次输入,最后组装成一个对象返回。
-
-我们将上述7个例子组装在一起后,试试效果
-
-```typescript
-import { Context } from "zhin";
-
-export function install(ctx: Context) {
- ctx
- .command("collect")
- .desc("采集用户信息")
- .action(async ({ session }, id) => {
- const userInfo = await session.prompt.prompts({
- name: { type: "text", message: "请输入姓名" },
- age: { type: "number", message: "请输入年龄" },
- birthDay: { type: "date", message: "请输入出生年月日" },
- reg: { type: "regexp", message: "请输入一个正则表达式" },
- isAdult: { type: "confirm", message: "是否成年" },
- hobbies: {
- type: "list",
- child_type: "text",
- message: "请输入你的兴趣爱好",
- },
- likeFruits: {
- type: "select",
- child_type: "text",
- message: "请选择你喜欢的水果",
- multiple: true,
- options: [
- { label: "苹果", value: "apple" },
- { label: "香蕉", value: "banana" },
- { label: "橙子", value: "orange" },
- ],
- },
- });
- return JSON.stringify(userInfo, null, 2);
- });
-}
-```
-
-
- collect
- 请输入姓名
- 张三
- 请输入年龄
- 18
- 请输入出生年月日
- 2000-01-01
- 请输入一个正则表达式
- /^(.*)$/
- 是否成年 输入yes,y,Yes,YES,Y,.,。,确认为确认
- y
- 请输入你的兴趣爱好 值之间使用','分隔
- 唱,跳,Rap,篮球
- 请选择你喜欢的水果 1.苹果 2.香蕉 3.橙子 值之间使用','分隔
- 1
- {
- "name": "张三",
- "age": 18,
- "birthDay": "2000-01-01T00:00:00.000Z",
- "reg": {},
- "isAdult": true,
- "hobbies": [
- "唱",
- "跳",
- "Rap",
- "篮球"
- ],
- "likeFruits": [
- "apple"
- ]
-}
-
-
diff --git a/docs/src/guide/qq.md b/docs/src/guide/qq.md
new file mode 100644
index 000000000..378a59527
--- /dev/null
+++ b/docs/src/guide/qq.md
@@ -0,0 +1,26 @@
+# 接入 QQ
+## 安装 QQ 机器人适配器
+```shell
+npm install @zhinjs/adapter-qq
+```
+## 准备 QQ 机器人账号
+### 1. 前往 [QQ 机器人平台](https://q.qq.com/) 申请机器人账号
+### 2. 获取 机器人 `appid` 和 `secret`
+## 配置 QQ 机器人适配器
+### 1. 打开项目根目录下的 `config/zhin.config.yml` 文件
+### 2. 在 `bots` 下添加以下配置
+```yaml
+bots:
+ - adapter: qq
+ unique_id: 机器人唯一标识
+ appid: 你的 appid
+ secret: 你的 secret
+ group: false # 若你的机器人支持群聊,才可设置为 true
+ public: false # 若你的机器人为公域机器人,才可设置为 true
+ sandbox: false # 是否使用沙箱环境
+```
+## 启动 QQ 机器人
+- 保存配置文件后,执行以下命令启动机器人
+```shell
+npx zhin
+```
diff --git a/docs/src/guide/setup.md b/docs/src/guide/setup.md
deleted file mode 100644
index e69de29bb..000000000
diff --git a/docs/src/guide/start.md b/docs/src/guide/start.md
deleted file mode 100644
index 795b87be7..000000000
--- a/docs/src/guide/start.md
+++ /dev/null
@@ -1,155 +0,0 @@
-# 安装 zhin 机器人
-
-::: tip
-阅读本节前,请确认你已正确配置 [Node.js](https://nodejs.org/zh-cn) 环境。
-:::
-
-## 创建项目
-
-### 1. 自行选择或新建一个文件夹,用于存储zhin机器人配置和插件信息
-```shell
-# 建立zhin-app文件夹(若选择已有文件夹,则可跳过本步骤)
-mkdir zhin-app
-cd zhin-app
-```
-::: tip
-如果你是在`WSL`中运行,请在CMD下运行以下命令,使得UNC路径可用
-```shell
-reg add "HKEY_CURRENT_USER\Software\Microsoft\Command Processor" /v "DisableUNCCheck" /t "REG_DWORD" /d "1" /f
-```
-:::
-### 2. 初始化包管理器
-```shell
-npm init -y # 初始化package.json
-
-npm install typescript -D # 安装ts开发环境依赖
-
-npx tsc --init # 初始化tsconfig.json文件
-
-```
-### 3. 安装zhin
-```shell
-npm install zhin # 安装zhin
-```
-
-### 4. 初始化项目
-```shell
-npx zhin init # 生成zhin配置文件
-
-```
-
-## 项目结构
-
-构建完成后,我们可在项目文件夹下看到如下结构
-
-```tex
-.
-├─ data/ 资源目录
-├─ plugins/ 插件目录(存放编写好的插件)
-├─ config/ 配置文件目录
- └─ zhin.config.yaml zhin配置文件
-├─tsconfig.json typescript项目描述文件(一般情况下无需关心)
-├─ node_modules/ 项目依赖存放文件(npm自动生成,开发者无需关心)
-├─ node_modules/ 项目依赖存放文件(npm自动生成,开发者无需关心)
-├─ package.json 项目描述文件(一般情况下无需关心)
-└─ package-lock.json 项目依赖描述文件(npm自动生成,开发者无需关心)
-```
-
-::: tip
-`node_modules`、`package.json` 等都是由 npm 生成的,**仅开发者**需要了解,可参考 [插件开发](/plugin/start) 一节。
-:::
-
-## 选择安装你所需添加机器人的适配器
-
-默认情况下,zhin仅基本提供命令行适配,添加对应机器人需先安装对应的适配器
-::: code-group
-```shell [ICQQ]
-npm install @zhinjs/adapter-icqq
-```
-```shell [QQ官方机器人]
-npm install @zhinjs/adapter-qq
-```
-```shell [onebot-11]
-npm install @zhinjs/adapter-onebot-11
-```
-```shell [onebot-12]
-npm install @zhinjs/adapter-onebot-12
-```
-```shell [Discord]
-npm install @zhinjs/adapter-discord
-```
-```shell [钉钉]
-npm install @zhinjs/adapter-dingtalk
-```
-```shell [微信]
-npm install @zhinjs/adapter-wechat
-```
-:::
-
-## 添加对应平台的机器人
-
-::: code-group
-```shell [ICQQ]
-npx zhin # 启动zhin
-# 等待启动完成...
-adapter.add @zhinjs/adapter-icqq # 注册适配器
-# 等待zhin 自动重启...
-bot.add icqq # 添加bot配置
-# 根据提示添加...
-```
-```shell [QQ官方机器人]
-npx zhin # 启动zhin
-# 等待启动完成...
-adapter.add @zhinjs/adapter-qq # 注册适配器
-# 等待zhin 自动重启...
-bot.add qq # 添加bot配置
-# 根据提示添加...
-```
-```shell [onebot-11]
-npx zhin # 启动zhin
-# 等待启动完成...
-adapter.add @zhinjs/adapter-onebot-11 # 注册适配器
-# 等待zhin 自动重启...
-bot.add onebot-11 # 添加bot配置
-# 根据提示添加...
-```
-```shell [onebot-12]
-npx zhin # 启动zhin
-# 等待启动完成...
-adapter.add @zhinjs/adapter-onebot-12 # 注册适配器
-# 等待zhin 自动重启...
-bot.add onebot-12 # 添加bot配置
-# 根据提示添加...
-```
-```shell [Discord]
-npx zhin # 启动zhin
-# 等待启动完成...
-adapter.add @zhinjs/adapter-discord # 注册适配器
-# 等待zhin 自动重启...
-bot.add discord # 添加bot配置
-# 根据提示添加...
-```
-```shell [钉钉]
-npx zhin # 启动zhin
-# 等待启动完成...
-adapter.add @zhinjs/adapter-dingtalk # 注册适配器
-# 等待zhin 自动重启...
-bot.add dingtalk # 添加bot配置
-# 根据提示添加...
-```
-```shell [微信]
-npx zhin # 启动zhin
-# 等待启动完成...
-adapter.add @zhinjs/adapter-wechat # 注册适配器
-# 等待zhin 自动重启...
-bot.add wechat # 添加bot配置
-# 根据提示添加...
-```
-:::
-## 测试一下
-
-- 至此,你已成功为 zhin 添加了你的第一个机器人
-- 你可通过向对应机器人发送 `status` 查看他是否正常工作(若添加机器人是配置了`command_prefix`,则需发送`[你配置的前缀] + status`)
-
-## 更多
-若需熟练运用zhin,你还需了解如何编写插件,以及各种专有名词,你可访问后续章节学习到相关知识
diff --git a/docs/src/guide/web-wechat.md b/docs/src/guide/web-wechat.md
new file mode 100644
index 000000000..513f2b63b
--- /dev/null
+++ b/docs/src/guide/web-wechat.md
@@ -0,0 +1,19 @@
+# 接入 微信(web)
+## 安装适配器
+```shell
+npm install @zhinjs/adapter-web-wechat
+```
+## 配置 微信机器人 适配器
+### 1. 打开项目根目录下的 `config/zhin.config.yml` 文件
+### 2. 在 `bots` 下添加以下配置
+```yaml
+bots:
+ - adapter: web-wechat
+ unique_id: 机器人唯一标识
+```
+## 启动 微信 机器人
+- 保存配置文件后,执行以下命令启动机器人
+```shell
+npx zhin
+```
+- 根据提示,使用手机端微信完成扫码登录即可
diff --git a/docs/src/guide/windows.md b/docs/src/guide/windows.md
new file mode 100644
index 000000000..566833a37
--- /dev/null
+++ b/docs/src/guide/windows.md
@@ -0,0 +1,24 @@
+# 在 Windows 电脑上使用知音
+## 安装 Node.js
+- 下载 [Node.js](https://nodejs.org/zh-cn/download/) 并安装
+- 安装时请勾选 `npm` 选项
+- 安装时请勾选 `Add to PATH` 选项
+- 安装完成后,打开 `cmd` 输入 `node -v` 和 `npm -v` 查看是否安装成功
+## 安装知音
+- 新建一个文件夹,打开 `cmd` 输入 `cd 文件夹路径` 进入文件夹,执行以下命令
+```shell
+npm init -y # 初始化一个新的项目
+npm install zhin # 安装 zhin
+```
+- 如果速度慢,可考虑使用国内镜像
+```shell
+npm install zhin --registy https://registry.npmmirror.com
+```
+## 初始化知音
+```shell
+npx zhin init
+```
+## 启动知音
+```shell
+npx zhin
+```
diff --git a/docs/src/index.md b/docs/src/index.md
index 118d53bc7..5ecaff726 100644
--- a/docs/src/index.md
+++ b/docs/src/index.md
@@ -5,28 +5,41 @@ title: 知音 (Zhin)
titleTemplate: :title - 知音 (Zhin)
hero:
- name: 知音 (or Zhin)
+ name: 知音 (Zhin)
text: 一个基于 NodeJS的多平台机器人开发框架
tagline: 轻量、优雅、热更、统一
actions:
- theme: brand
text: 快速开始
- link: /guide/start
+ link: /guide/intro
- theme: alt
- text: 查看 API
- link: /api/
+ text: 插件商店
+ link: /store
- theme: alt
text: GitHub
link: https://github.com/zhinjs/zhin
features:
- title: 轻量
+ icon: 🪡
details: 精简内部功能,仅内置系统级的常用插件和适配器,其他功能均通过插件来实现
- title: 优雅
+ icon: 🎨
details: 知音的内部实现尽可能符合大众开发思维,无论是阅读源码,还是开发插件,都能事半功倍
- title: 热更
+ icon: 🔥
details: 知音内置热更插件,让开发者在开发时避免频繁重启进程,从而降低账号风险概率
- title: 统一
+ icon: 🌐
details:
知音通过适配器统一了机器人消息的收发以及事件规范,使得开发者可以只关注一种规范,即可完成机器人开发
---
+
+## 快速开始
+```sh
+npm init -y # 初始化一个新的项目
+npm install zhin # 安装 zhin
+npx zhin init # 初始化 zhin
+npx zhin # 启动 zhin
+# ...
+```
diff --git a/packages/adapters/onebot-11/src/message.ts b/packages/adapters/onebot-11/src/message.ts
index d63f1222c..964ed7a9a 100644
--- a/packages/adapters/onebot-11/src/message.ts
+++ b/packages/adapters/onebot-11/src/message.ts
@@ -105,7 +105,7 @@ export namespace MessageV11 {
if (key === 'qq' && type === 'at') key = 'user_id';
return `${key}='${escape(JSON.stringify(value))}'`;
})
- .join(' ')}>`;
+ .join(' ')}/>`;
}
return result;
}
diff --git a/packages/adapters/onebot-11/src/onebot.ts b/packages/adapters/onebot-11/src/onebot.ts
index e3986f049..1d3d3a0ac 100644
--- a/packages/adapters/onebot-11/src/onebot.ts
+++ b/packages/adapters/onebot-11/src/onebot.ts
@@ -275,8 +275,7 @@ export namespace OneBotV11 {
} & ConfigMap[T];
export const defaultConfig = {
ws: {
- host: '0.0.0.0',
- port: 6700,
+ url: 'ws://localhost:8080',
max_reconnect_count: 10,
reconnect_interval: 3000,
},
diff --git a/packages/plugins/groupManage/src/index.ts b/packages/plugins/groupManage/src/index.ts
index 8c20e6bfd..40b5ef3d7 100644
--- a/packages/plugins/groupManage/src/index.ts
+++ b/packages/plugins/groupManage/src/index.ts
@@ -8,7 +8,6 @@ const groupCommand = groupManage.command('群管理').desc('群操作模块');
groupCommand
.command('thumbMe')
.desc('给指定用户点赞')
- .alias('赞我')
.sugar(/^赞我(\d+)次$/, {
options: {
times: '$1',
diff --git a/packages/plugins/qa/src/index.ts b/packages/plugins/qa/src/index.ts
index c4ddf7a16..1379d660f 100644
--- a/packages/plugins/qa/src/index.ts
+++ b/packages/plugins/qa/src/index.ts
@@ -1,4 +1,4 @@
-import { Message, Plugin, escape } from 'zhin';
+import { Message, Plugin, escape, unescape } from 'zhin';
type QAInfo = {
adapter: string; // 可用适配器
@@ -182,11 +182,11 @@ qaPlugin.middleware(async (message, next) => {
if (beforeMessage !== afterMessage) return;
const qa = await getAnswer(message);
if (!qa) return;
- if (!qa.regexp) return message.reply(qa.answer);
+ if (!qa.regexp) return message.reply(unescape(qa.answer));
const matchArr = message.raw_message.match(new RegExp(qa.content))!;
matchArr.forEach((match, idx) => {
qa.answer = qa.answer.replace(new RegExp(`\\$${idx}`, 'g'), match);
});
- return message.reply(qa.answer);
+ return message.reply(unescape(qa.answer));
});
export default qaPlugin;
diff --git a/packages/plugins/schedule/src/index.ts b/packages/plugins/schedule/src/index.ts
index 737ad8e20..559402403 100644
--- a/packages/plugins/schedule/src/index.ts
+++ b/packages/plugins/schedule/src/index.ts
@@ -1,4 +1,4 @@
-import { MessageBase, Message, Plugin } from 'zhin';
+import { MessageBase, Message, Plugin, unescape } from 'zhin';
import { CronDescriptors, ScheduleManager } from '@/scheduleManager';
import { ScheduledTask } from 'node-cron';
export type Schedule = {
@@ -59,7 +59,7 @@ scheduleCommand.command('定时列表').action(async ({ adapter, bot, message })
if (!schedules.length) return '暂无任务';
return `已有任务:\n${schedules
.map((schedule, index) => {
- return `${index + 1}.${schedule.cron} ${schedule.template}`;
+ return `${index + 1}.${schedule.cron} ${unescape(schedule.template)}`;
})
.join('\n')}`;
});
@@ -86,8 +86,9 @@ scheduleCommand.command('添加定时').action(async ({ adapter, prompt, bot, me
scheduleCommand
.command('删除定时 ')
.option('-c 是否确认', false)
- .action(async ({ prompt, options, bot, message }, no) => {
- const schedule = await schedulePlugin.database.get(`schedule.${no - 1}`);
+ .action(async ({ prompt, options, message }, no) => {
+ const schedules = await schedulePlugin.database.get(`schedule`, []);
+ const schedule = schedules?.[no - 1];
if (!schedule) return '无此定时任务';
if (schedule.creator_id !== `${message.sender?.user_id}`) return `非作者本人(${schedule.creator_id})不可删除`;
const isConfirm = options.confirm || (await prompt.confirm('确认删除吗?'));
diff --git a/packages/plugins/schedule/src/scheduleManager.ts b/packages/plugins/schedule/src/scheduleManager.ts
index 91b798377..b1a61dea0 100644
--- a/packages/plugins/schedule/src/scheduleManager.ts
+++ b/packages/plugins/schedule/src/scheduleManager.ts
@@ -16,4 +16,4 @@ export class ScheduleManager {
export type CronDescriptors =
`${CronDescriptor} ${CronDescriptor} ${CronDescriptor} ${CronDescriptor} ${CronDescriptor} ${CronDescriptor}`;
-type CronDescriptor = `${number}` | '*' | `*/${number}` | `${number}-${number}` | NumString<','>;
+type CronDescriptor = '*' | `*/${number}` | `${number}-${number}` | NumString<','>;
diff --git a/packages/plugins/transfer/src/index.ts b/packages/plugins/transfer/src/index.ts
index 863021fa1..87331e93b 100644
--- a/packages/plugins/transfer/src/index.ts
+++ b/packages/plugins/transfer/src/index.ts
@@ -1,8 +1,9 @@
-import { Plugin } from 'zhin';
+import { Plugin, Adapters, Message } from 'zhin';
const transferPlugin = new Plugin('问答管理');
type Transport = {
- adapter: string;
+ adapter: Adapters;
bot_id: string;
+ channel: Message.Channel;
};
type TransportConfig = {
from: Transport;
@@ -14,26 +15,10 @@ transferPlugin.waitServices('database', async app => {
transferPlugin
.command('transfer.add')
.permission('master')
- .action(async ({ prompt }) => {
- const from_adapter = await prompt.pick('请选择来源适配器', {
- type: 'text',
- options: [...(transferPlugin.app?.adapters.keys() || [])].map(key => {
- return {
- label: key,
- value: key,
- };
- }),
- });
- const from_bot = await prompt.pick('请选择来源机器人', {
- type: 'text',
- options:
- transferPlugin.app?.adapters.get(from_adapter)?.bots.map(bot => {
- return {
- label: bot.unique_id,
- value: bot.unique_id,
- };
- }) || [],
- });
+ .action(async ({ prompt, message }) => {
+ const from_adapter = message.adapter.name;
+ const from_bot = message.bot.unique_id;
+ const from_channel = message.channel;
const to_adapter = await prompt.pick('请选择目标适配器', {
type: 'text',
options: [...(transferPlugin.app?.adapters.keys() || [])].map(key => {
@@ -53,29 +38,63 @@ transferPlugin
};
}) || [],
});
- if (from_adapter === to_adapter && from_bot === to_bot) return '不能传送到自己';
+ const to_channel = await prompt.text('请输入目标通道');
+ if (from_adapter === to_adapter && from_bot === to_bot && from_channel === to_channel) return '不能传送到自己';
transferPlugin.database.push('transfer', {
from: {
adapter: from_adapter,
bot_id: from_bot,
+ channel: from_channel,
},
to: {
- adapter: to_adapter,
+ adapter: to_adapter as Adapters,
bot_id: to_bot,
+ channel: to_channel as Message.Channel,
},
});
+ return '添加成功';
+ });
+transferPlugin
+ .command('transfer.list')
+ .permission('master')
+ .action(async () => {
+ const transferList = await transferPlugin.database.get('transfer', []);
+ return transferList
+ .map((transfer, idx) => {
+ return `${idx + 1} ${transfer.from.adapter}:${transfer.from.bot_id}:${transfer.from.channel} => ${
+ transfer.to.adapter
+ }:${transfer.to.bot_id}:${transfer.to.channel}`;
+ })
+ .join('\n');
+ });
+transferPlugin
+ .command('transfer.remove')
+ .permission('master')
+ .action(async ({ prompt }) => {
+ const transferList = await transferPlugin.database.get('transfer', []);
+ const transfer = await prompt.pick('请选择要删除的传送', {
+ type: 'number',
+ options: transferList.map((transfer, idx) => {
+ return {
+ label: `${transfer.from.adapter}:${transfer.from.bot_id}:${transfer.from.channel} => ${transfer.to.adapter}:${transfer.to.bot_id}:${transfer.to.channel}`,
+ value: idx,
+ };
+ }),
+ });
+ transferList.splice(transfer, 1);
+ await transferPlugin.database.set('transfer', transferList);
+ return '删除成功';
});
transferPlugin.middleware(async (message, next) => {
+ await next();
const transferList = await transferPlugin.database.get('transfer', []);
const transfer = transferList.find(
transfer => transfer.from.adapter === message.adapter.name && transfer.from.bot_id === message.bot.unique_id,
);
- if (!transfer) return next();
- const toAdapter = transferPlugin.app?.adapters.get(transfer.to.adapter);
- if (!toAdapter) return next();
- const toBot = toAdapter.bots.find(b => b.unique_id === transfer.to.bot_id);
- if (!toBot) return next();
- transferPlugin.app?.emit('message', message);
- return next();
+ if (!transfer) return;
+ transferPlugin
+ .adapter(transfer.to.adapter)
+ ?.pick(transfer.to.bot_id)
+ ?.sendMsg(transfer.to.channel, message.raw_message, message);
});
export default transferPlugin;
diff --git a/packages/services/ollama/src/index.ts b/packages/services/ollama/src/index.ts
index 8e53a8c4a..451270921 100644
--- a/packages/services/ollama/src/index.ts
+++ b/packages/services/ollama/src/index.ts
@@ -10,11 +10,14 @@ declare module 'zhin' {
defineMetadata({
name: 'Ollama',
});
-
const Config = Schema.object({
host: Schema.string('host'),
+ model: Schema.string('model'),
+ max_history: Schema.number('max_history'),
}).default({
host: 'http://localhost:11434',
+ model: 'deepseek-r1:1.5b',
+ max_history: 10,
});
const config = useConfig('ollama', Config);
const ollama = new Ollama(config);
diff --git a/packages/services/screenshot/package.json b/packages/services/screenshot/package.json
index 1921523c1..fe2017da4 100644
--- a/packages/services/screenshot/package.json
+++ b/packages/services/screenshot/package.json
@@ -27,7 +27,9 @@
"vue-node-loader": "^0.0.6",
"jiti": "1.21.0",
"@vue/server-renderer": "^3.4.34",
- "puppeteer-core": "^22.11.2"
+ "puppeteer-core": "^24.2.1"
+ },
+ "devDependencies": {
},
"peerDependencies": {
"zhin": "workspace:^"
diff --git a/packages/services/screenshot/src/adapters/htmlRenderer.ts b/packages/services/screenshot/src/adapters/htmlRenderer.ts
index f89a37e70..f647c93e4 100644
--- a/packages/services/screenshot/src/adapters/htmlRenderer.ts
+++ b/packages/services/screenshot/src/adapters/htmlRenderer.ts
@@ -1,7 +1,44 @@
import { Renderer } from '@/renderer';
+import { axios } from 'zhin';
+import puppeteer, { Browser } from 'puppeteer-core';
export class HtmlRenderer extends Renderer {
- constructor(endpoint: string = process.env.ENDPOINT || '') {
- super('html', endpoint);
+ private browser: Browser | null = null;
+ private maxPages: number = Number(process.env.MAX_PAGES || 10);
+ constructor(private endpoint: string = process.env.ENDPOINT || '') {
+ super('html');
+ }
+ async connect() {
+ return new Promise(async resolve => {
+ if (this.browser) return resolve(this.browser);
+ if (this.endpoint.startsWith('http')) this.endpoint = await this.getEndpointByURL(this.endpoint);
+ this.browser = await puppeteer.connect({
+ browserWSEndpoint: this.endpoint,
+ });
+ console.log('connected to', this.endpoint);
+ return resolve(this.browser);
+ });
+ }
+ async getPage() {
+ const browser = await this.connect();
+ if (!browser) throw new Error(`Could not connect to ${this.endpoint}`);
+ browser.on('disconnected', () => {
+ console.log('disconnected');
+ this.browser = null;
+ });
+ const ctx = await browser.createBrowserContext();
+ const result = await ctx.newPage();
+ if (!result) throw new Error(`could not get page from ${this.endpoint}`);
+ const currentPages = await browser.pages();
+ if (currentPages.length > this.maxPages) {
+ currentPages[0]?.close();
+ }
+ return result;
+ }
+ private async getEndpointByURL(endpoint: string) {
+ const url = new URL(endpoint);
+ const result = await axios.get(`${url.origin}/json/version`);
+ if (!result?.data) throw new Error('getEndpoint failed');
+ return result.data?.webSocketDebuggerUrl as string;
}
async rendering(
input: string,
diff --git a/packages/services/screenshot/src/adapters/vueRenderer/index.ts b/packages/services/screenshot/src/adapters/vueRenderer/index.ts
index b5c4a04f2..fc4616359 100644
--- a/packages/services/screenshot/src/adapters/vueRenderer/index.ts
+++ b/packages/services/screenshot/src/adapters/vueRenderer/index.ts
@@ -12,8 +12,8 @@ function getCss(component: IComponent): string {
return ``;
}
export class VueRenderer extends Renderer {
- constructor(endpoint: string = process.env.ENDPOINT || '') {
- super(endpoint);
+ constructor() {
+ super('vue');
register();
}
diff --git a/packages/services/screenshot/src/index.ts b/packages/services/screenshot/src/index.ts
index 8c3b00ec2..915dbd05e 100644
--- a/packages/services/screenshot/src/index.ts
+++ b/packages/services/screenshot/src/index.ts
@@ -1,8 +1,8 @@
import { Plugin } from 'zhin';
import { Renderer } from '@/renderer';
import htmlRenderer from '@/adapters/htmlRenderer';
-import vueRenderer, { IComponent, VueRenderer } from '@/adapters/vueRenderer';
-import { Component, DefineComponent } from 'vue';
+import vueRenderer, { VueRenderer } from '@/adapters/vueRenderer';
+import { Component } from 'vue';
declare module 'zhin' {
namespace App {
interface Services {
@@ -22,6 +22,5 @@ plugin.service('renderHtml', htmlRenderer.rendering.bind(htmlRenderer));
plugin.service('renderVue', vueRenderer.rendering.bind(vueRenderer));
plugin.mounted(() => {
htmlRenderer.connect();
- vueRenderer.connect();
});
export default plugin;
diff --git a/packages/services/screenshot/src/renderer.ts b/packages/services/screenshot/src/renderer.ts
index 50bcbe72a..416449d5a 100644
--- a/packages/services/screenshot/src/renderer.ts
+++ b/packages/services/screenshot/src/renderer.ts
@@ -1,43 +1,7 @@
-import * as process from 'process';
-import { axios } from 'zhin';
-import puppeteer, { Browser, ScreenshotOptions, Viewport, WaitForOptions } from 'puppeteer-core';
+import { ScreenshotOptions, Viewport, WaitForOptions } from 'puppeteer-core';
export class Renderer {
- browser: Browser | null = null;
-
- constructor(
- public type: string,
- public endpoint: string = process.env.ENDPOINT || '',
- private maxPages: number = Number(process.env.MAX_PAGES || 10),
- ) {}
-
- async connect() {
- return new Promise(async resolve => {
- if (this.browser) return resolve(this.browser);
- if (this.endpoint.startsWith('http')) this.endpoint = await this.getEndpointByURL(this.endpoint);
- this.browser = await puppeteer.connect({
- browserWSEndpoint: this.endpoint,
- });
- return resolve(this.browser);
- });
- }
- private async getEndpointByURL(endpoint: string) {
- const url = new URL(endpoint);
- const result = await axios.get(`${url.origin}/json/version`);
- if (!result?.data) throw new Error('getEndpoint failed');
- return result.data?.webSocketDebuggerUrl as string;
- }
- async getPage() {
- const browser = await this.connect();
- if (!browser) throw new Error(`Could not connect to ${this.endpoint}`);
- const result = await browser.newPage();
- if (!result) throw new Error(`could not get page from ${this.endpoint}`);
- const currentPages = await browser.pages();
- if (currentPages.length > this.maxPages) {
- currentPages[0]?.close();
- }
- return result;
- }
+ constructor(public type: string) {}
}
export namespace Renderer {
diff --git a/test/plugins/chat.ts b/test/plugins/chat.ts
new file mode 100644
index 000000000..619f95a82
--- /dev/null
+++ b/test/plugins/chat.ts
@@ -0,0 +1,151 @@
+import {
+ Adapters,
+ Message,
+ context,
+ defineMetadata,
+ Command,
+ registerMiddleware,
+ inject,
+ segment,
+ logger,
+ Schema,
+ useConfig,
+ parseFromTemplate,
+} from 'zhin';
+import type {} from '@zhinjs/plugin-ollama';
+defineMetadata({
+ name: 'chat',
+ // adapters: ['process'],
+});
+const Config = Schema.object({
+ host: Schema.string('host'),
+ model: Schema.string('model'),
+ max_history: Schema.number('max_history'),
+}).default({
+ host: 'http://localhost:11434',
+ model: 'deepseek-r1:1.5b',
+ max_history: 10,
+});
+const config = useConfig('ollama', Config);
+function createTools(message: Message) {
+ return context.app?.getSupportCommands(message.adapter.name).map(command => {
+ const options = Object.values(command['optionsConfig'] as Command.OptionsConfig);
+ const args = command['argsConfig'] as Command.ArgsConfig;
+ const requiredOptions = options.filter(option => option.required).map(option => option.name);
+ const requiredArgs = args.filter(arg => arg.required).map(arg => arg.name);
+ return {
+ type: 'function',
+ function: {
+ name: command.name as string,
+ description: command.config.desc as string,
+ parameters: {
+ type: 'object',
+ properties: Object.fromEntries([
+ ...args.map(arg => {
+ return [
+ arg.name as string,
+ {
+ type: arg.type as string,
+ description: arg.name as string,
+ },
+ ];
+ }),
+ ...options.map(option => {
+ return [
+ option.name as string,
+ {
+ type: option.type as string,
+ description: option.desc as string,
+ },
+ ];
+ }),
+ ]),
+ required: [...requiredArgs, ...requiredOptions] as string[],
+ },
+ },
+ };
+ });
+}
+const callFunction = async (message: Message, name: string, params: Record) => {
+ const command = context.app?.getSupportCommands(message.adapter.name).find(command => command.name === name);
+ if (!command) return;
+ const optionsConfig = Object.values(command['optionsConfig'] as Command.OptionsConfig);
+ const args = Object.keys(params)
+ .filter(key => Reflect.has(command['argsConfig'], key))
+ .map(key => params[key]);
+ const options = Object.fromEntries(
+ Object.keys(params)
+ .filter(key => optionsConfig.some(option => option.name === key))
+ .map(key => [key, params[key]]),
+ );
+ return await command.execute(
+ message,
+ `${name} ${args.join(' ')} ${Object.entries(options)
+ .map(([key, value]) => `-${key} ${value}`)
+ .join(' ')}`,
+ );
+};
+type HistoryInfo = {
+ channel: Message.Channel;
+ content: string;
+ role: string;
+ images?: string[];
+};
+export async function getResult(message: Message) {
+ const ollama = inject('ollama');
+ const db = inject('database');
+ const imageElems = parseFromTemplate(message.raw_message).filter(({ type }) => type === 'image');
+ const imgs: string[] = imageElems.map(({ data }) => data.url).filter(Boolean);
+
+ const history = await db.get('chat_history', []);
+ await db.push('chat_history', {
+ channel: message.channel,
+ content: message.raw_message,
+ role: 'user',
+ images: imgs,
+ });
+ // const tools = createTools(message);
+ const result = await ollama.chat({
+ stream: false,
+ // tools: tools,
+ model: config.model,
+ messages: [
+ {
+ role: 'system',
+ content: `你的名字叫知音(zhin),在接下来的聊天中,如果用户(user)输入的消息没有提到你名字,你必须回复空字符串,一切会话均已中文回复`,
+ },
+ ...history
+ .filter(info => info.channel === message.channel)
+ .slice(-(config.max_history || 10))
+ .map(info => ({
+ role: info.role,
+ content: info.content,
+ })),
+ {
+ role: 'user',
+ content: message.raw_message,
+ images: imgs,
+ },
+ ],
+ });
+ const { content, role, images = [] } = result.message;
+ const newContent = content.replace(/([^]+)<\/think>/, '').trim();
+ await db.push('chat_history', {
+ channel: message.channel,
+ content: newContent,
+ role,
+ images,
+ });
+ return `${newContent}${images.map(url => segment.image(url)).join('')}`;
+}
+registerMiddleware(async (message, next) => {
+ await next();
+ if (!message.raw_message.includes('zhin')) return;
+ try {
+ const result = await getResult(message);
+ if (result) message.reply(result);
+ } catch (e) {
+ console.error(e);
+ logger.debug(e);
+ }
+});