Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MSC3006: Bot Interactions #3006

Open
wants to merge 6 commits into
base: old_master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
322 changes: 322 additions & 0 deletions proposals/3006-bot-interactions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,322 @@
# MSC3006: Bot Interactions
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the link. My main point was that buttons should be bound to a message. I agree that HTML is not ideal. I meant this to be more of a minimal example. The approach that every message can get a number of buttons in a special format sounds good to me.

Moving from #3006 (comment) (cc @JoshuaDietz ) to a thread. (Next time please use thread to make the PR more readable please)

The goal of this MSC is actually to not do inline buttons. That would be a separate MSC and I do agree it should exist. This one is meant to be non message bound commands :) Thats why buttons are not bound to messages.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I got that the goal of this MSC are not inline buttons. But as I described I don't really see how these keyboard buttons can work with matrix since every user can send them to every room as he wants (as long as there are no protections against that, i.e. a new permission). So if they are implemented in my opinion there needs to be a concept on how to avoid abuse of the feature (see the problems that I described in my previous comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well a) as "m.bot.interactions" is an own event you could restrict it by powerlevels b) thats more an overall "issue" in matrix if you want to call it like that. Because bots are not separate from users. They are the same thing. So solving this is imho out of scope of this MSC. This is only possible to solve by separating users and bots as otherwise it is impossible to make restrictions that work for bots but restrict users :/


Bots are currently only possible in IRC style. They are entirely text based and not interactive like, for example, Telegram bots.
This is trying to solve that issue by using a similar backwards compatible way like the one in Telegram.

This is highly inspired by https://core.telegram.org/bots#keyboards and is trying to allow similar types
of keyboards for bots to allow more "fancy" bots while keeping full backwards compatibility.

## Proposal

The proposal is to have multiple interaction events. Which tell the client what kind of interactions the
bot has and the client replies with a specific format on that.

_Note: a deeper explanation of the events will be below the interaction flow_

### Interaction Flow

1. The bot joins a room and sends a state event containing information about possible interactions:

```json
{
"type": "m.bot.interactions",
"state_key": "@faq_bot:example.matrix",
"content": {
"command_prefix": "!",
"interactions": [
{
"type": "m.interaction.command",
"description": "Shows the help of the FAQ Bot",
"name": "help"
},
{
"type": "m.interaction.button",
"description": "Search question",
"name": "Search"
}
]
}
}
```

2. The clients shows a button to open the commands list and/or a button to open the custom keyboard

3. The user clicks on for example the help command one.

4. The client sends an event of type `m.room.message` and msgtype `m.text`. This allows us to have fallback for free while
having typed commands when client and bot support it.

```json
{
"content": {
"body": "!help",
"msgtype": "m.text",
"m.interaction.command": {
"command": "help",
"target": "@faq_bot:example.matrix"
}
},
"type": "m.room.message"
}
```

5. The Bot responds with a request for the client to display a separate custom keyboard for the next stage of
this command

```json
{
"content": {
"event_id": "$random_id123",
"interactions": [
{
"type": "m.interaction.button",
"description": "The Search command",
"name": "Search"
}
]
},
"type": "m.interaction.stage.start"
}
```

6. The bot now directly shows the new custom keyboard, indicating that the user currently is in a stage of the previous command.
Additionally a way of exiting that stage should be available.

7a. The user selects the exit way and the client now sends a stage end event and returns to normal chat operation.

```json
{
"content": {
"event_id": "$random_id123"
},
"type": "m.interaction.stage.end"
}
```

7b. If the user clicks the new button the client sends the command as in step 4 with additionally mentioning the `stage_id` under the
`m.interaction.command` object. The bot after that can react either with a new stage or with the same `m.interaction.stage.end`
event to indicate the end of a interaction flow.

#### Flow as a graphic

```
+----------------------------+
| |
| User sends a command |
| |
+---------------|------------+
|
|
|
|
|
|
|
+----------------v--------------+
| |
| Bot instructs client |
| to show a submenu of | +-------------------------------------------+
+------------------------>+ buttons. | User exits the command flow | |
| | +----------------------------------------->+ Client sends a stage.end event. |
| | Starting a new "stage" | | |
| | | | This indicates a flow end to the bot. |
| +----------------|----|---------+ | |
| | | +-------------------------------------------+
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | | +---------------------------------------------+
| | | Bot doesn't have another submenu | |
| | +----------------------------------------->+ Bot sends a stage.end event. |
| | | |
| | | This indicated a flow end to the client. |
| | | |
| | +---------------------------------------------+
| User selects an option and bot |
| has a new submenu. |
| Another stage.start is sent by the bot |
+-------------------------------------<----+
```


### Event Specification

#### m.bot.interactions

A state event defining the possible entry actions a bot has. It does not list any stage specific actions.
It is used to allow clients to display a custom keyboard and a list of command based on the defined data.

The `state_key` should be the mxid of the bot adding that event.

The `command_prefix` is used to support fallback for clients not supporting the MSC and is used to generate a irc style
bot message as fallback. (See "Fallback" section for Details).

The interactions array defines all supported entry actions of a bot, Those can be either of type: `m.interaction.command`
or `m.interaction.button`,

Example:

```json
{
"type": "m.bot.interactions",
"state_key": "@faq_bot:example.matrix",
"content": {
"command_prefix": "!",
"interactions": [
{
"type": "m.interaction.command",
"description": "Shows the help of the FAQ Bot",
"name": "help"
},
{
"type": "m.interaction.button",
"description": "Search question",
"name": "Search"
}
]
}
}
```

#### Interaction event types

Both `m.interaction.command` and `m.interaction.button` are required to be an object consisting of:

* Its `type`
* A useful `description` which might be used to be displayed to the user
* A `name` which is used for displaying in the UI. Be aware this is also used as lowercase for the fallback!

#### `m.room.message` extension with `m.interaction.command`

Each message that was sent using selecting either a button or command must have an additional object with the key `m.interaction.command`
besides the body of that event.

The `target` field is required to distinguish between bots in a room. It is required at all time to contain the mxid of the bot that is meant.
This is required because there might be multiple bots with the same command.

Example:

```json
"m.interaction.command": {
"command": "help",
"target": "@faq_bot:example.matrix"
}
```

#### `m.interaction.stage.start`

This event indicates a new stage being entered.

The `event_id` is supposed to reference the event that was the start of the last event. So this might initially be the initial command message a user sent.

The `interactions` array is the same format as in the state event. It indicates new action specific to the current stage.
The client should indicate that this is currently a stage of the initially started command. It should show also show the interactions the bot now added
as well as a way to abort the stage immediately. (See flow example for details)

The bot which is currently starting a stage should be identified by the sender field of the event which contains the bot mxid.

This event should be ignored when the user not started a command or the user has not previously received a stage start event.
This can happen either because of abusive bots or because someone else started a command. Possible ways to check this are:

* Track the commands locally in the client
* Check for a `m.room.message` event with `m.interaction.command` and the `sender` matching the logged in user.

Benefit of the second option is that this works across multiple used clients.

Example:

```json
{
"content": {
"event_id": "$random_id123",
"interactions": [
{
"type": "m.interaction.button",
"description": "The Search command",
"name": "Search"
}
]
},
"type": "m.interaction.stage.start"
}
```

#### `m.interaction.stage.end`

This event indicates a flow end.

The `event_id` is supposed to reference the event that was the start of the last event. It is not supposed to be the initial command.

This event can be sent by both the client as well as the bot.
If a client sends the event the bot should think of this as if the user aborted the current action entirely.
If a bot sends the event the client should think of this as having completed a flow fully.

This event should be ignored when the user has not started a command or the user has not previously received a stage start event.
This can happen either because of abusive bots or because someone else started a command. Possible ways to check this are:

* Track the commands locally in the client
* Check for a `m.room.message` event with `m.interaction.command` and the `sender` matching the logged in user.

Benefit of the second option is that this works across multiple used clients.

Example:

```json
{
"content": {
"event_id": "$random_id123"
},
"type": "m.interaction.stage.end"
}
```

## What is `m.interaction.button`?

This type is meant to be like Telegram's custom keyboard feature. It is used to display a button in the UI which behaves like a custom keyboard with fixed functions.
It is however in this MSC not meant to support inline buttons on events but those in theory can be done by reusing this type.

## How are stages meant to work

Imagine stages as submenus. They are indicating the next step in a greater action.

If a stage start is sent the user moves into the next decision tree of a menu. If a stage end happens the user gets back to the top of this decision tree.
Stage end should not be treated as a back button.


## Fallback

The fallback is done by making this a transparent change to the message event. It only adds metadata to the event instead of proposing a new message event.
This brings the benefit that the fallback works in any existing client without needing to change the clients code. This means that user without the
MSC implemented in their client still can fully follow the conversation without missing out events.

## Other neccesarry changes to make this even work

To make this even work this needs an addition to the default room powerlevels. The `m.bot.interactions` state event type MUST be able to be set by
the rooms default Powerlevel as bots usually are just regular users and not moderators of the room.

## Potential issues

* Priority of stages is not defined if 2 commands happen at the same time

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

define this as the timestamp order of the interaction targets' read receipts

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Timestamps are difficult across federated systems. However this most likely is already solved by the relations. (Would love to have some more input on this assumption from some that understand relations better than I do)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Timestamps are difficult across federated systems.

Read receipts only advance forwards, hence that order is always well defined and the problem boils down to retrieving the read receipts from the bot's server. And relations would not be useful across multiple unrelated commands from different users.

* Multiple users using multiple bots in a group is currently not handled directly. The MSC only demands to ignore stages if the user not started the command itself.

## Alternatives

* MSC2929 - This seems to solve parts of this as well but doesn't seem to specify custom keyboards
MTRNord marked this conversation as resolved.
Show resolved Hide resolved
* A full form based approach where a bot can define layouts - In a discussion with multiple client developers at #matrix-client-developers:matrix.org
this was being decided difficult or even dangerous as bots or abusive users might do harm with that and in general it seems too hard to implement.

## Security considerations

* This wasn't thought with e2ee in mind yet. It would need to be tried if it still works with being fully encrypted.
* It might leak metadata if it is not encrypted

## Unstable prefix

`dev.nordgedanken.msc3006.` shall be used as a prefix.

e.g.: `m.bot.interactions` becomes `dev.nordgedanken.msc3006.bot.interactions`