Skip to content

Commit

Permalink
Add command module (#9)
Browse files Browse the repository at this point in the history
* update base command module
* refactor utils
* add command event
* update readme
  • Loading branch information
Ovizro authored Feb 3, 2024
1 parent cab1f12 commit 412a0d6
Show file tree
Hide file tree
Showing 37 changed files with 2,187 additions and 334 deletions.
6 changes: 3 additions & 3 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
[run]
branch = True
omit =
kola/lib/debugger/*
*/__main__.py
__main__.py
karuha/plugin_server.py

[report]
# Regexes for lines to exclude from consideration
Expand Down Expand Up @@ -33,4 +33,4 @@ exclude_lines =
@(typing(_extensions)?\.)?deprecated(.*)


ignore_errors = True
ignore_errors = True
132 changes: 108 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,16 @@ From source code:

## Quick Start

> Before starting, you need to make sure you have the Tinode service running locally with its gRPC port set to the default value of 16060. If your service is not local or the default port has been changed, you need to add additional server configuration items in the following code.
Before starting, you need to make sure you have the Tinode service running locally with its gRPC port set to the default value of 16060.

Create a new file `config.json` and write the following content:
> If your service is not local or the gRPC port has been changed, you may need to modify or add additional server configuration items in the following code.
Create a new file config.json and write the configuration in it:

```json
{
"server": {
"host": "localhost:16060",
"listen": "0.0.0.0:40051"
},
"bots": [
{
Expand All @@ -52,40 +53,123 @@ Create a new file `config.json` and write the following content:

> Replace `{chatbot_login_name}` and `{chatebot_login_passwd}` with the chatbot’s login account name and password in the Tinode server.
Start the chatbot using the following command:
Use the following command to start the chatbot:

python -m Karuha ./config.json

You can now see the messages others have sent to the chatbot from the command line.Yeah, that's it, no more.
Now you can view the messages others have sent to the chatbot from the command line.

## Go Further?
## User Interaction

Well, you can actually go a step further and send messages to users.
Of course, only receiving messages is not enough, we need some interaction with users. Karuha provides a powerful command system. With the command system, we can conveniently receive user-issued information and make appropriate responses.

Currently, if you want to reply to a message, you currently need to add a handler for the event yourself. This is not a simple process. Fortunately, we are about to introduce a command module to improve this.
### Simple Command Example
Let's start with a very simple command. We want to implement a hi command, which replies Hello! when the chatbot receives this command.

Because this is a relatively low-level API, I will only give an example to show how karuha is currently used in python:
Create a new file hi.py and write the following:

```python
import karuha
from karuha import Bot, MessageEvent, PublishEvent
from karuha import on_command, MessageSession

@on_command
async def hi(session: MessageSession) -> None:
await session.send("Hello!")
```

The above code involves some Python knowledge, I will briefly introduce it one by one. If you already know this knowledge, you can skip this part.

bot = Bot(
"chatbot",
"basic",
"chatbot:123456"
)
In the first line of code, we import the `on_command` decorator and `MessageSession` class from the `karuha` module. A decorator is an object that can be used to decorate a function or class. Here, its usage is as shown in the fourth line, where the function is modified with `@on_command` before the function definition. The modified function will be registered as a command and will be called when the corresponding message is received.

Next is the definition of the `hi` function. Here we use `async def` to define the command function. Unlike ordinary functions defined using `def`, functions defined with `async def` are asynchronous functions. Asynchronous is a relatively complex topic. It doesn't matter if you don't understand it. Here we will only use some simple syntax similar to normal functions.

@karuha.on(MessageEvent)
async def reply(event: MessageEvent) -> None:
if event.text == "Hello!":
await event.bot.publish(event.topic, "Hello world!")
You may be unfamiliar with the line `(session: MessageSession) -> None`. This is a type annotation that describes the parameter types and return value types of a function. Here we declare that the type of `session` is `MessageSession`, and the return value type is `None`, that is, there is no return value. In Python, type annotations are optional, but for commands in Karuha, they are used for parsing message data. Although not required, it is recommended to add type annotations when writing commands so that Karuha can better understand your code.

Then there is the content of the function, which is very short and only one line. `session` is a session object, which encapsulates many APIs for receiving and sending messages. Here, we use the `send` method to send the message. `send` is an asynchronous method, so you need to use `await` in front when calling it.

if __name__ == "__main__":
karuha.init_config()
karuha.add_bot(bot)
karuha.run()
After finishing writing the command, we can run the chatbot to test it. Run the chatbot using the following command:

```sh
python -m Karuha ./config.json -m hi
```

Then in the conversation with the bot, enter the following:

/hi


> By default, karuha will only process text messages starting with `/` as commands. This behavior can be set through the `set_prefix` function before defining all commands.
If everything goes well, you should see the 'Hello!' reply from the bot.

### Getting User Input

In the above example, we did not directly use the user's input. What if I want to get the user's input content?

One way is to use the message record in the `session`. `session.last_message` contains the complete user input. But this is not very elegant.

A more convenient method is to directly modify the function signature, such as:

```python
async def hi(session: MessageSession, text: str) -> None:
...
```

We add a text parameter of type str to represent the user's input content. Let's modify the contents of the hi function a bit to allow it to:


```python
@on_command
async def hi(session: MessageSession, text: str) -> None:
total = text.split(' ', 1)
if len(total) == 1:
await session.send("Hello!")
name = total[1]
await session.send(f"Hello {name}!")
```

The code above builds on the previous logic by adding the name to greet in the command's response. This involves some string processing operations that are not explained here for now.

Run the chatbot and send it:

/hi world

You should receive the chatbot's response of `Hello world!`.

## About More
Of course, Karuha provides more APIs than just these. If you are interested in learning more, please refer to the source code of the library.

### Development Goals
Features that may be added in the future include:

- [ ] APIs related to user information getting and setting
- [ ] Automatic argument parsing in argparse format for commands

### Architecture Overview
The overall architecture of Karuha is as follows:

| Layer | Provided Module | Function |
| --- | --- | --- |
| Upper layer | karuha.command | 命Command registration and processing |
| Middle layer | karuha.event | Async event-driven system |
| Lower layer | karuha.bot | Tinode API basic encapsulation |

In addition, there are some relatively independent modules:

| Module | Function |
| --- | --- |
| karuha.text | Text processing module |
| karuha.config | Configuration file parsing module |
| karuha.plugin_server | Plugin module for Tinode server, not enabled by default |

### Message Handling
Karuha internally provides two complementary message handling systems: the asynchronous event message system (event) and the message dispatcher system (dispatcher).

The asynchronous event message is used to receive and parallelly process messages. By registering message event handlers, the related information of the message can be quickly collected. Message handlers are parallel and non-interfering with each other.

The message dispatcher is downstream of the asynchronous event message system. It is used to determine the final message handler. This system is used to handle the connection between the middle layer event system and the upper command system. If you want to provide feedback to users according to message content and avoid interference from other message processing modules, you should use this system.

## About Documentation
This project does not have documentation plans. Documentation will not be provided for the foreseeable future. If you want to provide documentation for this project, I will greatly appreciate it.

## About Contribution
Welcome to post your questions and suggestions in issues. If you are interested in developing Tinode chatbots, welcome to contribute.
142 changes: 116 additions & 26 deletions README_cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@

一个简单的Tinode聊天机器人框架

> 目前项目仍处于开发期,部分次要接口可能会频繁变化,造成的不便敬请谅解
库的名称`Karuha`来自游戏星空列车与白的旅行中的角色 狩叶·朗姆柯妮(カルハ・ラムコネ)

<center>
Expand All @@ -18,7 +16,7 @@

> カルハ・ラムコネと申します。カルハちゃんって呼んでいいわよ
# 安装
## 安装

从Pypi安装:

Expand All @@ -30,17 +28,18 @@
cd Karuha
make install

# 快速开始
## 快速开始

在开始前,你需要确保在本地有运行的Tinode服务,其gRPC端口为默认值16060。

在开始前,你需要确保在本地有运行的Tinode服务,其gRPC端口为默认值16060。如果你的服务不在本地,在接下来的代码中,你需要额外添加服务器的配置项
> 如果你的服务不在本地或更改了gRPC端口,在接下来的代码中,你可能需要修改或额外添加服务器的配置项
创建一个新的文件`config.json`并写入以下内容作为配置文件:

```json
{
"server": {
"host": "localhost:16060",
"listen": "0.0.0.0:40051"
},
"bots": [
{
Expand All @@ -58,36 +57,127 @@

python -m Karuha ./config.json

现在您可以在命令行上查看其他人发送到聊天机器人的消息。是的,这就是我们目前能做到的大部分内容。
现在就可以在命令行上查看其他人发给聊天机器人的消息了。

## 用户交互

## 更进一步?
只能接收消息当然是不够的,我们需要与用户进行一些交互。Karuha提供了一套强大的命令系统。利用命令系统,我们可以方便的接收用户发出的信息并进行相应的回复。

好吧,其实我们的确可以再进一步,让我们试着回复一些消息吧。
### 简单命令示例

至于为什么我并不在上面一段提及这个,因为就目前来说,回复消息的确不是几件简单的事情。这需要我们手动对事件进行绑定。但不要担心,我们很快就会有一套命令模块来简化这一流程
让我们从一个最简单的命令开始。我们希望实现一个`hi`命令,当机器人收到此命令时,回复`Hello!`

由于这涉及到低层级API,故此处仅提供一个例子,以展示其目前在Python中的使用方法
新建一个`hi.py`,并在其中写入以下内容

```python
import karuha
from karuha import Bot, MessageEvent, PublishEvent
from karuha import on_command, MessageSession


@on_command
async def hi(session: MessageSession) -> None:
await session.send("Hello!")
```

以上的代码涉及到一些Python知识,我将会逐个的简单介绍一下。如果你已经了解这些知识,可以跳过这一部分。

bot = Bot(
"chatbot",
"basic",
"chatbot:123456"
)
代码的第一行,我们从`karuha`模块中导入了`on_command`装饰器和`MessageSession`类。装饰器是一种可以用来修饰函数或类的的对象。在这里,它的用法如第四行所示,在函数定义前通过`@on_command`来修饰函数。被修饰的函数会被注册为命令,将会在收到对应的消息时被调用。

接下来是`hi`函数的定义。这里我们使用`async def`来定义命令函数。与使用`def`定义的普通函数不同,`async def`定义的函数是异步函数。异步是一个比较复杂的话题,如果你不了解它也没有关系,这里我们只会使用一些简单的与正常函数类似的语法。

你可能对`(session: MessageSession) -> None`这一行有些陌生。这是一段类型注释,用于说明函数的参数类型和返回值类型。这里我们声明了`session`的类型为`MessageSession`,而返回值类型为`None`,也就是没有返回值。在Python中,类型注释是可选的,但对于Karuha中的命令来说,它们会被用于消息数据的解析。虽然不是必须的,但建议在编写命令时添加类型注释,以便于Karuha更好的理解你的代码。

然后是函数的内容,这里非常短只有一行。`session`是一个会话对象,其中封装了很多接收与发送消息的API。在这里,我们使用`send`方法来发送消息。`send`是一个异步方法,因此在调用时需要在前面使用`await`

在完成编写命令后,我们可以来运行一下机器人来测试一下。使用如下的命令运行机器人:

```sh
python -m Karuha ./config.json -m hi
```

然后在与机器人的对话中,输入以下内容:

@karuha.on(MessageEvent)
async def reply(event: MessageEvent) -> None:
if event.text == "Hello!":
await event.bot.publish(event.topic, "Hello world!")
/hi


if __name__ == "__main__":
karuha.init_config()
karuha.add_bot(bot)
karuha.run()
> 在默认情况下,karuha只会将以`/`为开头的文本消息作为命令处理,此行为可以在定义所以命令前通过`set_prefix`函数来设置。
如果一切顺利,你就应该能看到机器人回复的`Hello!`了。

### 示例扩展

在上面的示例中,我们并没有直接用到用户的输入内容。如果我希望获取用户的输入内容应该怎么办呢?

一个方法是使用`session`中的消息记录。`session.last_message`中就包含了完整的用户输入内容。但这样并不够雅观。

更加方便的方法是直接修改函数签名,比如:

```python
@on_command
async def hi(session: MessageSession, text: str) -> None:
...
```

我们增加了一个`text`参数,类型为`str`,表示用户输入的内容。让我们稍微修改一下`hi`函数的内容,使它可以:

```python
@on_command
async def hi(session: MessageSession, text: str) -> None:
total = text.split(' ', 1)
if len(total) == 1:
await session.send("Hello!")
name = total[1]
await session.send(f"Hello {name}!")
```

以上代码中,我们在之前的逻辑的基础上,在命令的回复内容中增加要打招呼的名称。其中涉及到了一些字符串处理相关的操作,这里暂时不做过多的解释。

让我们运行机器人,然后向它发送以下内容试试:

/hi world

你就会收到机器人返回的`Hello world!`了。

## 关于更多

当然Karuha提供的API并不止这些,如果你想对更多内容感兴趣,可以参考库的源码。

### 开发目标

在接下来可能会添加的功能包括:

- [ ] 用户信息获取与设置相关的API
- [ ] argparse格式的命令参数自动解析

### 架构说明

Karuha的总体架构如下:

| 接口层次 | 提供模块 | 功能 |
| --- | --- | --- |
| 上层 | karuha.command | 命令注册与处理 |
| 中层 | karuha.event | 异步事件驱动系统 |
| 底层 | karuha.bot | Tinode API基础封装 |

除此之外,也有一些较为独立的模块:

| 模块 | 功能 |
| --- | --- |
| karuha.text | 文本处理模块 |
| karuha.config | 配置文件解析模块 |
| karuha.plugin_server | Tinode服务器使用的插件模块,默认不启用 |

### 消息处理

karuha内部提供了两相互配合的消息处理系统,即异步消息事件系统(event)和消息调度器系统(dispatcher)。

异步消息事件用于接收并并行的处理消息。通过注册消息事件处理器,可以快速的收集到消息的相关信息。不同的消息处理器间是并行且互不干扰的。

消息调度器则位于异步消息事件系统的下游,用于决定最终的消息的处理者。此系统用来承接中层事件系统与上层的命令系统。如果你希望根据消息内容对用户进行反馈并避免被其他消息处理模块干扰,则应该使用此系统。

## 关于文档

此项目并没有完善文档的计划。在可预见的时间内,此项目都不会有文档。如果你希望为此项目提供文档,我将感激不尽。

## 关于贡献

欢迎在issues中提出你的问题和建议。如果你对Tinode聊天机器人的开发感兴趣,欢迎参与贡献。
Loading

0 comments on commit 412a0d6

Please sign in to comment.