Skip to content

开始

简介

Saukko (发音为 /ˈsaukːo/) 是一款可拓展、轻量级的聊天机器人框架。

Saukko 一词取自芬兰语,意为“水獭”。水獭是一种机敏、灵活的动物,具有细长、流线型的身体结构,擅长在水中快速移动和捕食。Saukko 的设计灵感来源于水獭的这些特点,旨在为用户提供一个上手简单、部署快速的聊天机器人框架。

Saukko Core 使用依赖注入管理框架内部服务。这给予了用户与开发者极大的灵活性,让框架易于拓展与维护。

创建项目

首先,你需要创建一个空白的项目。我们推荐使用的包管理器是 pnpm

bash
pnpm init

安装 CLI 依赖。

bash
pnpm add -D saukko

随后,手动创建配置文件 saukko.toml

toml
[project]
name = "project"

[plugin]
config = {}

[service]
config = {}

接着,就可以启动框架了:

bash
pnpm saukko

当然,你也可以手动安装 Core,并自行编写脚本管理框架。

bash
pnpm i @saukkojs/core

不稳定的 API

Saukko Core 的 API 尚处于开发阶段,可能会发生重大变化。

typescript
import { Container, injectionProvider } from '@saukkojs/core';

const container = new Container();

// 配置信息将会被框架所使用
const config = {};

// @saukkojs/core 提供的 injectionProvider 方法,用于一键注入依赖。
injectionProvider(container, config, {
  headless: false, // 是否以无头模式运行,即是否装载 `plugin` 与 `app` 服务。
});

// 这里,你还可以注入你自己所需的服务。
// 注意,服务需要向 `ServiceRegistry` 进行声明扩展,才能获得正确的语法提示。
declare module '@saukkojs/core' {
    interface ServiceRegistry {
        'my-service': /* ... */;
    }
}
container.register('my-service', /* ... */);

const app = container.get('app');

// 请在 app.start() 之前注入所需要的所有服务。
await app.start();

CLI

Saukko CLI 是用于管理框架的命令行工具。

不稳定的 API

Saukko CLI 尚处于开发阶段,可能会发生重大变化。

对于已经初始化完成的项目,可以使用 CLI 快速启动框架:

bash
npx saukko

此时,CLI 会启动一个 Daemon 进程,用于初始化框架容器并加载服务与插件,随后启动框架。

CLI 内部保留了以下命令:

  • start: 等价于 npx saukko,启动框架
  • stop: 停止框架的运行

其他的命令,将被传入 CLI 启动的 Daemon 进程中进行处理。

不稳定的 API

Saukko CLI 的 Daemon 进程 尚处于设计阶段,可能会发生重大变化。

Daemon 进程可以管理框架所加载的服务与插件,还可以与插件交互,实现更复杂的管理逻辑。

在 Daemon 进程意外终止时,框架会自动重启。

Daemon 进程与 CLI 间通过 .saukko.sock 文件(Windows 环境下为 pipe)进行通信。

对于 Daemon 进程的功能,请参阅 saukko README.md

配置文件

Saukko CLI 支持读取 TOML 格式的配置文件。你可以通过配置文件,精准控制框架的行为,也可以管理服务与插件的配置。

不稳定的 API

Saukko CLI 的配置文件 API 尚处于开发阶段,可能会发生重大变化。

typescript
type Config = {
    project: {
        name: string;
    };
    plugin: {
        scopes?: string[];
        files?: string[];
        config: {
            [key in keyof PluginConfigRegistry]?: PluginConfigRegistry[key];
        };
    };
    service: {
        scopes?: string[];
        files?: string[];
        config: {
            [key in keyof ServiceConfigRegistry]?: ServiceConfigRegistry[key];
        };
    }
}

环境变量属于配置之一,只不过其影响的是 CLI 的行为。

typescript
type SaukkoEnv = {
    SAUKKO_LOG_LEVEL?: 'trace' | 'debug' | 'info' | 'notice' | 'warn' | 'error' | 'silent';
    SAUKKO_CONFIG_PATH?: string;
    SAUKKO_SOCKET_PATH?: string;
}

服务

Saukko Core 通过服务实现特定的功能。框架内置了如下的服务:

不稳定的 API

Saukko Core 的内置服务尚处于开发阶段,可能会发生重大变化。

  • app: 应用服务,用于管理框架的生命周期。
  • logger: 日志服务,用于记录框架的日志。
  • config: 配置服务,用于管理框架的配置。
  • database(WIP): 数据库服务,用于管理框架的数据库连接。
  • plugin: 插件服务,用于管理框架的插件。

关于 app 服务

为了让框架更加灵活,我们将 app 也设计为一个服务。这使得用户可以更加方便地管理框架的生命周期。

你也可以轻松地实现自己的服务。

typescript
// 这一行代码用于导入所依赖的 logger 服务的类型定义。
import type { LoggerService } from '@saukkojs/core';

// 通过对 ServiceRegistry 进行扩展,让依赖于此服务的服务与插件能够获得正确的语法提示。
declare module '@saukkojs/core' {
    interface ServiceRegistry {
        myService: MyService;
    }
}

export const inject = ['logger'];
export class MyService {
    constructor(private readonly logger: LoggerService) {}

    method() {
      this.logger.log('my-service', 'info', 'hello, world!');
    }
}

随后,在配置文件中注册此服务。如果你需要操作 Core 的 Container 获取服务实例,那么需要手动引入类型:

typescript
// 这一行代码用于引入 MyService 对 ServiceRegistry 的扩展。
import {} from './path/to/my-service';

const myService = container.get('my-service');

myService.method();

需要注意的是,当前版本的 Saukko Core 不计划自动处理副作用。你需要在服务卸载时,手动处理副作用。

插件

Saukko Core 通过插件自定义处理聊天平台的事件与消息。

不稳定的 API

Saukko Core 的插件 API 尚处于开发阶段,可能会发生重大变化。

Saukko Core 的插件系统由 plugin 服务实现。插件语法类似于:

typescript
export const inject = []; // 声明所依赖的服务
export const name = 'my-plugin';
export default function (context: Context) {}

plugin 服务使用 Context 向插件提供上下文。

上下文拥有 dependencies 属性,包含声明的依赖。上下文仅会向插件提供其所依赖的服务与指定给插件的配置,不会向插件暴露其他服务。

typescript
// 这一行代码用于导入所依赖的 external-service 服务的类型定义。
import {} from 'saukko-service-external';

// 这一行代码用于声明所依赖的服务。
export const inject = ['external-service'];

export const name = 'my-plugin';
export default function (context: Context) {
  console.log(context.dependencies['external-service']); // external-service 服务的实例
}

上下文拥有 config 属性,包含指定给插件的配置。

typescript
export const name = 'my-plugin';
export default function (context: Context) {
  console.log(context.config); // my-plugin 插件在配置文件中的属性
}

上下文拥有一个 bots 数组,包含已挂载的聊天平台适配器实例。

typescript
export default function (context: Context) {
  for (const bot of context.bots) {
    console.log(bot); // 已挂载的聊天平台适配器实例
  }
}

上下文还实现了与 EventEmitter 相似的API,用于处理来自框架内部与聊天平台的事件。

typescript
export default function (context: Context) {
  context.on('message.private', (event) => {
    event.bot.sendPrivateMessage(event.data.author.id, event.data.content);
  });
}

值得注意的是,Saukko 的聊天平台适配器属于插件。为了统一 API 并方便开发,我们提供了 Bot 抽象类,供适配器插件继承与实现。

typescript
import { Bot, Context } from '@saukkojs/core';
import eventbus from /* ... */;

declare module '@saukkojs/core' {
    interface Events {
        'message.private': {
          /* ... */
          // 此处无需声明 bot 属性。
        }
    }
}

class MyBot extends Bot {
  constructor(context: Context) {
    super(context);
    eventbus.on('message', (message) => {
      // 来自 `Bot` 的 `emit` 方法会将 `MyBot` 对象放在事件的 `bot` 属性里传递给上下文的事件。
      this.emit('message.private', message);
    });
  }
  sendPrivateMessage(userId: string, content: string): Promise<void> {
    return /* ... */;
  }
}

export default function (context: Context) {
  const bot = new MyBot(context);
  context.mountBot(bot);
}

需要注意的是,当前版本的 Saukko Core 不计划自动处理副作用。你需要在插件卸载时,手动处理副作用。

感谢

Cocotais Bot 是 Saukko.js 系列项目的缘起,为 Saukko.js 系列项目提供了丰富的灵感与参考。

CordisKoishiSatori 等项目的框架设计,深刻地影响了 Saukko.js 系列项目。

Last updated:

Saukko.js 系列项目均使用 Apache 2.0 协议开源。