finochat-botkit

2.0.5 • Public • Published

FinoChat Botkit机器人开发框架

Build Status


机器人框架主要基于Matrix聊天协议接入FinoChat应用并通过FSM(有限状态机)管理会话状态,以及FinoChat ConvoUI协议扩展实现的聊天机器人。


1. 安装

1.1 npm 安装

npm install finochat-botkit

nodejs版本要求为v9.4.0

2. 原理:

2.1 大致流程

  • 基于本框架的应用程序在启动时,就启动matrix聊天协议的客户端,并保持连接,并进行聊天服务器事件的监听
  • 用户添加机器人后,机器人受邀自动加入房间,机器人可以发送欢迎消息提示
  • 状态机以某个状态进行初始化,机器人接收到用户消息后进行匹配,成功后发给用户发送会话提问消息
  • 用户在ConvoUI操作或者文字回复,机器人根据回复消息选择是否推进会话到下一个状态,或者停留在本状态继续会话,甚至结束会话

2.2 ConvoUI协议

  • ConvoUI是指聊天对话当中的富文本消息(文字、图片、视频、菜单等等的组合)
  • 机器人框架通过封装ConvoUI工厂方法向客户端发送满足协议的扩展消息体,由客户端实现对扩展消息体的具体渲染

3. 使用

3.1 基本用法

3.1.1 配置文件

config.js说明

  • homeserver:finoChat聊天服务器接口
  • loginUrl:机器人中心登录接口
  • fcid:机器人登录ID
  • password:机器人登录密码
  • ENABLE_MONITOR:是否开启该机器人的监控,默认false
module.exports = {
    // 以下配置接口和机器人账号仅供演示测试用
    homeserver: process.env.HOMESERVER || "https://api.finolabs.club",
    loginUrl: process.env.LOGIN_URL || "https://api.finolabs.club/api/v1/registry/botlogin",
    fcid: process.env.FCID || "@test-bot:finolabs.club",
    password: process.env.PASSWORD || "123456",

    ENABLE_MONITOR: process.env.ENABLE_MONITOR || false
    logLevel: 'debug',
    timeout: 30000,
    routelist: [],
    whitelist: [],
    blacklist: [],
};

3.1.2 引入依赖

const botkit = require('finochat-botkit');

3.1.3 定义状态

const States = {
    INIT: 'INIT',
    STEP1: 'STEP1',
    STEP2: 'STEP2'
};

3.1.4 机器人开发

继承botkit.Bot类

class demoBot extends botkit.Bot
  • 机器人开发者只需继承botkit.Bot类,然后在状态机定义函数 describe()里面进行状态描述和消息匹配,即可集中于书写机器人业务处理逻辑。
  • describe(fsm, bot)接收两个参数,fsm状态机实例,bot实例

引入状态机和匹配 API

// 引入状态机API
const { startWith, when, goto, stay, stop } = botkit.DSL(fsm);

// 引入匹配API
const { match, BodyCase, ActionCase, CommandCase, DefaultCase } = botkit.Matcher;

状态机API基本用法

 // 初始化状态,接收一个状态参数
startWith(States.INIT);

// 描述某个状态,接收一个状态参数,返回一个asnyc回调函数
// sender发送消息的用户信息,content用户发送的消息体(注意:content.body才是消息体文本内容)
when(States.STEP1)(asnyc (sender, content) => {

	// 机器人发送消息接口,第一个参数为房间ID,第二个参数为JSON消息体
	bot.sendMessage(sender.roomId, { body: 'hello from bot' });
	
	// 机器人发送文本消息接口,第一个参数为房间ID,第二个参数为具体文本
	bot.sendMessage(sender.roomId, 'hello from bot');
	
	// 描述状态转移,全部都可以链式调用withConvoMsg方法给用户附带发送消息,其中goto接收下一个状态作为参数
	return stop()/stay()/goto(States.STEP2).withConvoMsg('Hi there!');
	
});

匹配API基本用法

when(States.STEP1)(asnyc (sender, content) => {
	
	 // 匹配函数,接收第一个参数(content消息体)和其他具体匹配方法作为后续参数传入
    return match(content,
    
        // 普通聊天文本消息匹配,支持字符串和正则表达式,匹配成功后做对应的逻辑处理
        BodyCase('Hey bot!')(() => {
            return goto(States.STEP1).withConvoMsg('Hi there!');
        })

    );
	
});

3.1.5 机器人运行

new demoBot(require('config')).run();

3.2 代码demo

// 引入机器人框架依赖
const botkit = require('finochat-botkit');

// 定义状态机的各种状态
const States = {
    INIT: 'INIT',
    STEP1: 'STEP1',
    STEP2: 'STEP2'
};

// 创建自己的机器人
class demoBot extends botkit.Bot {
	
	// 机器人被邀请加入房间
    onJoinRoom(bot, roomId, userId, displayName) {
        return `亲,我是DemoBot【roomId: ${roomId}, userId: ${userId}, displayName: ${displayName}】`;
    }

    // 用户被邀请加入房间
    onUserJoinRoom(bot, roomId, userId, displayName) {
        return this.onJoinRoom(bot, roomId, userId, displayName);
    }

    // 用户当前视图切换入房间
    onUserEnterRoom(bot, roomId, userId, displayName) {
        return this.onJoinRoom(bot, roomId, userId, displayName);
    }

    // 会话超时结束
    onTimeout(bot, roomId, userId, displayName) {
        return `会话结束【roomId: ${roomId}, userId: ${userId}, displayName: ${displayName}】`;
    }
    
    // 状态机定义函数, 主要的逻辑写在这里
    describe(fsm, bot) {
 		/**
         * 状态机 DSL
         * startWith 描述状态机的初始状态和初始 data,只需调用一次
         * when 描述某个状态下,发生Event时,Bot业务执行与状态迁移的细节
         * goto 用于生成when()函数的返回值,返回 nextState
         * stay goto(CurrentState)的另一种形式,停留在本状态
         * stop goto(Done)的另一种形式,结束会话
         */
        const { startWith, when, goto, stay, stop } = botkit.DSL(fsm);
		/**
         * matcher API
         * BodyCase 匹配普通聊天中的string, 支持变长 pattern(String or RegExp type)
         * ActionCase 匹配convoUI消息的action,支持变长 pattern(String or RegExp type)
         * CommandCase 匹配convoUI消息的command类型,支持变长pattern(String or RegExp type)
         * DefaultCase 模式匹配的Default分支
         */
        const { match, BodyCase, ActionCase, CommandCase, DefaultCase } = botkit.Matcher;
		
		// 初始化状态
        startWith(States.INIT);
		
		// INIT状态描述
        when(States.INIT)(async (sender, content) => {
        
        	// 匹配函数,接收第一个参数(content消息体)和其余参数(消息匹配方法)
            return match(content,
            
				// 匹配消息成功后回调,返回客户端消息(withConvoMsg方法)并转移到STEP1状态
                BodyCase('Hey bot!')(() => {
                    return goto(States.STEP1).withConvoMsg('Hi there!');
                })

            );
        });

        when(States.STEP1)(async (sender, content) => {
            return match(content,

                ActionCase('action1')(() => {
                    return goto(States.STEP2).withConvoMsg('U r in Step2 now');
                }),

                DefaultCase(() => {
                
                	// ConvoUI工厂方法创建Assist消息
                    const ui = botkit.ConvoFactory.ui()
                    	
                    	// body文本在ConvoUI无法渲染时显示,类似HTML中img标签的alt提示属性
                        .setBody('assist demo')
                        
                        // Layout工厂方法创建带两个按钮的Assist消息
                        .setPayload(
                            botkit.LayoutFactory.assist().setTitle('assist').addItems(
                                botkit.ActionFactory.button('button1', 'action1')
                                botkit.ActionFactory.button('button2', 'action2')
                            )
                        );
                    return stay().withConvoMsg(ui);
                })

            );
        });

        when(States.STEP2)(async (sender, content) => {
            return match(content,

                BodyCase('apple')(() => {
                    return stop().withConvoMsg('You can buy an Apple product in https://www.apple.com/');
                })

            );
        });
    }
}

// 机器人运行
new demoBot(require('config')).run();

4. 接口

4.1 会话事件

  • onJoinRoom(bot, roomId, userId, displayName) :机器人被邀请加入房间
  • onUserJoinRoom(bot, roomId, userId, displayName):用户被邀请加入房间
  • onUserEnterRoom(bot, roomId, userId, displayName):用户当前视图切换入房间
  • onTimeout(bot, roomId, userId, displayName):会话超时结束

4.2 状态机DSL

  • startWith:描述状态机的初始状态和初始 data,只需调用一次
  • when:描述某个状态下,发生Event时,Bot业务执行与状态迁移的细节
  • goto:用于生成when()函数的返回值,返回 nextState
  • stay:goto(CurrentState)的另一种形式,停留在本状态
  • stop:goto(Done)的另一种形式,结束会话

4.3 消息匹配API

  • BodyCase:匹配普通聊天中的string, 支持变长 pattern(String or RegExp type)
  • ActionCase:匹配convoUI消息的action,支持变长 pattern(String or RegExp type)
  • CommandCase:匹配convoUI消息的command类型,支持变长pattern(String or RegExp type)
  • DefaultCase:模式匹配的Default分支

4.4 状态机data的共享

状态机describe()的方法体内可以使用如下两种方式共享变量(session scope)

  • 常规方式是在状态迁移时,将修改后的data对象传递给 using(). 这里建议通过 spread-rest 语法构造 immutable object 对象。后续会方便利用到状态跟踪,重演,TimeTravel Debugging 等很多玩法。
  • 还有一种可行的方式是,直接将变量挂在 fsm 上面

4.5 底层API

在回调函数内,可以不借助 matcher API,通过判断 content 或者 data 的具体细节来控制分支走向, 获得最大的灵活度:

when(MyStates.IDLE)(async (sender, content, data) => {
    if(content.body === "step1") {
        return goto(MyStates.STEP1).withConvoMsg({body: "I goto step1!"});
    } else if (content.body === "开始业务2") {
        return goto(MyStates.STEP2)
    }

    return stay().withConvoMsg({body: "Stay!"});
});

Readme

Keywords

none

Package Sidebar

Install

npm i finochat-botkit

Weekly Downloads

3

Version

2.0.5

License

ISC

Unpacked Size

115 kB

Total Files

27

Last publish

Collaborators

  • gordanyang