-
Notifications
You must be signed in to change notification settings - Fork 446
MSC4391: Simplified in-room bot commands #4391
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
base: main
Are you sure you want to change the base?
Changes from all commits
40d9eec
e4fb942
a8e7f2a
3c57869
6229934
d560364
24f3ab7
01defb8
fa2ae36
793c0ee
3593591
b7bc11c
07e45e6
e0e4f21
5c4b68e
ee43dd8
40486d0
d529b16
bb6c471
8cefeb8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,374 @@ | ||
| # MSC4391: Simplified in-room bot commands | ||
|
Gnuxie marked this conversation as resolved.
|
||
|
|
||
| > [!NOTE] | ||
| > | ||
| > This proposal is a simplification of | ||
| > [MSC4332: In-room bot commands](https://github.com/matrix-org/matrix-spec-proposals/pull/4332) | ||
| > that specifies only an object format for bot commands, to be embedded within | ||
| > message event content. The proposal does not make any impositions on the | ||
| > textual command syntax used by bots or clients. To make use of the MSC, | ||
| > clients and bots will have to implement a simple parser for the proposal's | ||
| > command invocation protocol, which is similar in nature to | ||
| > [JSON-RPC](https://www.jsonrpc.org/specification). | ||
|
|
||
| > [!NOTE] | ||
| > | ||
| > While this MSC expects and anticipates clients will start with text parsing of | ||
| > commands, for clients to provide their own syntax, and that TUI clients will | ||
| > likely always use text parsing. The MSC is designed specifically so that it is | ||
| > possible to support clients that will only provide a full command UI. | ||
|
|
||
| ## Background | ||
|
|
||
| Interaction with matrix bots today is done by sending shell-like command within | ||
| the body of a Matrix `m.room.message` e.g. | ||
| `!draupnir unban @example:example.com --invite --no-confirm`. | ||
|
|
||
| This interface has the following problems: | ||
|
|
||
| - Users unfamiliar with command line interfaces have to learn heavily by example | ||
| in order to start using bots, which may be providing essential functions | ||
| within a matrix room or community. | ||
|
|
||
| - No feedback is provided within the clients if a user enters incorrect syntax, | ||
| an unexpected argument, or a value of an incorrect type. Additionally, bots | ||
| have to explain these failure modes to a range of users. | ||
|
|
||
| - The `body` emitted in `m.room.message` content is treated as _plain text | ||
| fallback_ or markdown by most matrix clients, preferring to render | ||
| `org.matrix.custom.html`. This means there are a number of inconsistencies in | ||
| clients, particularly around the fallback for room and user pills, which can | ||
| complicate and limit argument parsing | ||
| https://github.com/the-draupnir-project/Draupnir/issues/131. | ||
|
|
||
| - Long help menus that can make it difficult to find the right command. Many | ||
| clients already have a concept of "slash commands" which are | ||
| [desirable to reuse](https://github.com/matrix-org/matrix-spec/issues/93) and | ||
| [come up occasionally](https://github.com/matrix-org/matrix-spec/issues/2170) - | ||
| finding a way to populate this feature with bot-specific details is | ||
| beneficial. | ||
|
|
||
| - [_prompt_](https://github.com/the-draupnir-project/Draupnir?tab=readme-ov-file#prompt-ux) | ||
| and [_button_](https://core.telegram.org/api/bots/buttons) interfaces have to | ||
| be implemented with `m.reaction` events. | ||
|
|
||
| - _partial commands_ have to be implemented with `m.reaction` | ||
| [prompts](https://github.com/the-draupnir-project/Draupnir?tab=readme-ov-file#prompt-ux) | ||
| and replies containing an arbitrary payload of suggestions or a default. This | ||
| also leads to noisy timeline views when there are multiple partial steps to | ||
| entering a complete command (e.g. `!draupnir ban @spam:example.com`: | ||
| [demo](https://github.com/the-draupnir-project/Draupnir/blob/main/docs/ban-command-prompt.gif)). | ||
|
|
||
| ## Proposal | ||
|
|
||
| This proposal introduces the following in order to solve the above problems: | ||
|
|
||
| - _command description_ state events that can be used by a bot to communicate to | ||
| clients the parameters of the command and the types of arguments that those | ||
| parameters can accept. | ||
|
|
||
| - A JSON representation of a command and the user supplied arguments, which a | ||
| client can send to the bot within a matrix event. This is a similar idea to a | ||
| JSON-RPC request object. | ||
|
|
||
| - While a response format is not specified in this MSC, the MSC is designed to | ||
| be forward compatible with a follow up MSC for _partial commands_ and | ||
| _prompts_. | ||
|
|
||
| The proposal makes no attempt to specify the syntax of commands within an | ||
| `m.room.message` body or describe the syntax of those commands. This is | ||
| specifically to avoid the fallback version of the command becoming the source of | ||
| truth over the JSON representation, which was previously a source of | ||
| [compatibility issues](https://github.com/matrix-org/matrix-spec-proposals/pull/4332/files#r2313755345) | ||
| in [MSC4332](https://github.com/matrix-org/matrix-spec-proposals/pull/4332). | ||
| Advanced bots would be required some to make changes to their parsers. | ||
| Additionally the argument and overloading semantics in MSC4332 make _partial | ||
| commands_ and _prompts_ difficult to design. However, this proposal may be | ||
| extended by another proposal to specify a fallback derived from the JSON | ||
| representation that may work for many simple bots. | ||
|
|
||
| ### Command description | ||
|
|
||
| A new state event type is introduced: `m.bot.command_description`. When | ||
| presenting command options to users, clients SHOULD use this event to suggest | ||
| commands, scoped by the `sender` of the description. | ||
|
|
||
| The `content` for such an event fits the following implied schema: | ||
|
|
||
| ```jsonc | ||
| { | ||
| "type": "m.bot.command_description", | ||
| "sender": "@draupnir:draupnir.space", | ||
| // derived from `sha256(command + mxid)` | ||
| "state_key": "JBDLR6YMe+72yqsEMi/MVdTmjN3ynPThMz+M7QLATZQ=", | ||
|
Gnuxie marked this conversation as resolved.
|
||
| "content": { | ||
| // space separated string for commands such as "rooms add" | ||
| "command": "ban", | ||
| "parameters": [ | ||
| { | ||
| "key": "target_room", | ||
| "schema": { "schema_type": "primitive", "type": "room_id" }, | ||
| "description": { | ||
| // Descriptions use m.text from MSC1767 Extensible Events to later support MSC3554-style translations. | ||
| // See https://spec.matrix.org/v1.15/client-server-api/#mroomtopic_topiccontentblock | ||
| // See https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/1767-extensible-events.md | ||
| // See https://github.com/matrix-org/matrix-spec-proposals/pull/3554 | ||
| "m.text": [{ "body": "The room ID" }], | ||
| }, | ||
| }, | ||
|
|
||
| { | ||
| "key": "timeout_seconds", | ||
| "schema": { "schema_type": "primitive", "type": "integer" }, | ||
| "description": { "m.text": [{ "body": "The timeout in seconds" }] }, | ||
| }, | ||
|
|
||
| { | ||
| "key": "apply_to_policy", | ||
| "schema": { "schema_type": "primitive", "type": "boolean" }, | ||
| "description": { | ||
| "m.text": [{ "body": "Whether to apply this to the policy" }], | ||
| }, | ||
| // This argument is not required | ||
| "optional": true, | ||
| }, | ||
|
|
||
| { | ||
| "key": "target_users", | ||
| "schema": { | ||
| "schema_type": "array", | ||
| "items": { "schema_type": "primitive", "type": "user_id" }, | ||
| }, | ||
| "description": { "m.text": [{ "body": "The user ID(s)" }] }, | ||
| }, | ||
| ], | ||
| "description": { | ||
| // We also use m.text here for the same reason as the argument descriptions above. | ||
| "m.text": [{ "body": "An example command with arguments" }], | ||
| }, | ||
| }, | ||
| } | ||
| ``` | ||
|
|
||
| A client may show the arguments and commands similar to Discord: | ||
|
|
||
|  | ||
|
|
||
| #### Invariants of command descriptions | ||
|
|
||
| - A command description with parameters that have duplicate keys is invalid and | ||
| the command SHOULD be hidden by clients. | ||
|
|
||
| - The position of parameter descriptions in the _parameters_ property that are | ||
| _required_ is significant and clients SHOULD use the same order when prompting | ||
| for arguments. | ||
|
|
||
| - The position of parameter descriptions that are not _required_ is not | ||
| significant. | ||
|
|
||
| - Command descriptions only specify commands for the sender of the | ||
| `m.bot.command_description`. If the `sender` is not currently joined to the | ||
| room, the command should be hidden. | ||
|
|
||
| - Parameters with an _array_ schema may appear in any place, not just at the end | ||
| of the parameter list. This is to provide a ubiquitous way of providing | ||
| multiple arguments. Clients that parse command text may need to provide | ||
| special syntax for lists or multiple arguments in order to support _required_ | ||
| parameters that have an array schema. | ||
|
|
||
| - The state_key MUST be a padded base64 encoded SHA256 digest of the `command` | ||
| property of the command description AND the mxid of the sender. This is in | ||
| order to ensure that command descriptions are deduplicated and conflict free | ||
| between different bots. | ||
|
|
||
| ### Command invocation | ||
|
|
||
| When the user sends the command, the client creates either an `m.room.message` | ||
| or an `m.room.bot.command` event with the following `content` shape: | ||
|
|
||
| ```jsonc | ||
| { | ||
| // body is client supplied and may not match at all with the bot's fallback syntax. | ||
| // body may be omitted entirely in an `m.room.bot.command`. | ||
| "body": "@bot:example.org ban !room:example.org 42 true @alice:example.org @bob:example.org", | ||
| "msgtype": "m.text", | ||
|
|
||
| // Mentions should always be added, to lower the chances of command conflicts. | ||
| // Bots SHOULD look for mentions where possible to avoid accidental activations. | ||
| "m.mentions": { | ||
| "user_ids": ["@bot:example.org"], // should be a single element array, containing the bot's user ID | ||
| // from the `m.bot.commands` state event's `state_key` (or `sender`). | ||
| // Note: doesn't include other users which may be referenced by the | ||
| // command being sent, such as via `user_id` arguments. | ||
| }, | ||
|
|
||
| // This is a new content block so bots don't *need* to do string unpacking when | ||
| // commands are sent this way. Bots may still need to unpack `body` when users | ||
| // send commands manually or without client support. | ||
| "m.bot.command": { | ||
| "command": "ban", | ||
| "arguments": { | ||
| // These are just the arguments and their user-supplied values. | ||
| "target_room": { | ||
| // Room IDs are special because they can carry routing information too. | ||
| // Object types have a type specifier. | ||
| "id": "!room:example.org", | ||
| "via": ["second.example.org"], // Optional, but recommended. | ||
| "type": "room_id", | ||
| }, | ||
| "timeout_seconds": 42, // integers and booleans use appropriate value types (converted from (probably) strings) | ||
| "apply_to_policy": true, // tip: clients can convert user input like "yes" to booleans | ||
| "target_users": ["@alice:example.org", "@bob:example.org"], | ||
| }, | ||
| }, | ||
| } | ||
| ``` | ||
|
|
||
| Bots can then respond however they normally would to the command input. | ||
|
|
||
| Clients SHOULD be aware that some bots may attempt to create conflicts with the | ||
| client's built-in commands (such as `/myroomnick`) or the commands of other | ||
| bots. Where conflicts with built-in events exist, clients SHOULD NOT show the | ||
| bot's option to the user. Where conflicts with other bots exist, clients SHOULD | ||
| show the bot's name/user ID in the autocomplete text. For example, "@Giphy /gif | ||
| {search}". Clients MAY wish to always disambiguate commands like this to avoid | ||
| future conflicts with built-in commands. From an implementation perspective, | ||
| clients might cause their built-in commands to always take precedence over any | ||
| bot's commands to avoid users becoming confused. | ||
|
|
||
| ### Type schema | ||
|
|
||
| > [!NOTE] | ||
| > | ||
| > The reason for providing an _integer_ type instead of a _number_ type to match | ||
| > the [JSON value](https://datatracker.ietf.org/doc/html/rfc8259#section-6) is | ||
| > because matrix.org's canonical JSON | ||
| > [does not support encoding floats](https://spec.matrix.org/v1.15/appendices/#canonical-json). | ||
|
|
||
| > [!NOTE] | ||
| > | ||
| > Bots SHOULD NOT rely on clients to validate or build input with respect to the | ||
| > input and should perform their own parsing of command arguments. | ||
|
|
||
| The following essential types are the predefined `types` for an argument: | ||
|
|
||
| - `string` - An arbitrary string. | ||
| - `integer` - An arbitrary whole number. May be negative or zero. | ||
| - `boolean` - `true` or `false` literal. | ||
|
|
||
| Constrained string types: | ||
|
|
||
| - `user_id` - Must be a valid | ||
| [user ID](https://spec.matrix.org/v1.15/appendices/#user-identifiers) for the | ||
| room version encoded as a string. | ||
| - `server_name` - Must be a valid | ||
| [server name](https://spec.matrix.org/v1.15/appendices/#server-name). | ||
| - `room_alias` - Must be a valid | ||
| [room alias](https://spec.matrix.org/v1.15/appendices/#room-aliases). | ||
|
|
||
| Room reference types: | ||
|
|
||
| - `room_id` - An object with the property `room_id` which must be a valid | ||
| [room ID](https://spec.matrix.org/v1.15/appendices/#room-ids). The object | ||
| SHOULD also contain a `via` property that contains an array of server names | ||
| that the room can be joined via. | ||
| - `event_id` - An object with the same properties as `room_id` with the addition | ||
| of an `event_id` property that must be a valid | ||
| [event ID](https://spec.matrix.org/v1.15/appendices/#event-ids). | ||
|
|
||
| When an object is provided as the type schema, the type is a derived from a | ||
| schema described by the property `schema_type`: | ||
|
Gnuxie marked this conversation as resolved.
|
||
|
|
||
| - The `array` schema type specifies the type of the items with the `items` | ||
| property. The following additional restrictions apply to `array`: | ||
| - Arrays can only appear at the top level, `items` can only be a schema type | ||
| of `union`, `primitive`, and `literal`. | ||
| - The `union` schema type specifies the types of the variants with the | ||
| `variants` property. Which is an array of type schema. The following | ||
| restrictions apply to unions: | ||
| - Outside of the top-level, only an array of unions is permitted. | ||
| - Nested unions are not permitted as they are unnecessary and should be | ||
| flatted. | ||
| - A union of arrays is not permitted. | ||
| - Therefore `variants` can only be a schema type of `primitive` or `literal`. | ||
| - The `literal` schema type specifies a literal value with the `value` property | ||
| and the type of the literal value with the `literal_type` property. | ||
|
Gnuxie marked this conversation as resolved.
|
||
| - For simplicity only `boolean`, `integer`, and `string` may be provided for | ||
| the `value` property. This may be extended in future if need is found. | ||
|
|
||
| > [!TIP] | ||
| > | ||
| > Clients can accept a wider variety of inputs for some types, provided they | ||
| > reduce them down to the expected value types when sending the command. For | ||
| > example, accepting a room permalink for a `room_id` type, or "yes" in place of | ||
| > `true` for a `boolean`. | ||
|
|
||
| ## Extensions | ||
|
|
||
| The following extensions/features are best considered by future MSCs: | ||
|
|
||
| - A standardised command response similar to JSON-RPC response object. This | ||
| would also enable dynamic and arbitrary prompt flow. | ||
|
|
||
| - Specifying a minimum power level required to send a command, to hint to users | ||
| that a command may be unavailable to them. This wouldn't be enforced by auth | ||
| rules, but clients can stop a lot of the accidental usage if they know the | ||
| power level the caller must have. | ||
|
|
||
| - Specifying a non-`m.room.message` event template to send instead. This could | ||
| be useful if the bot wants to minimize "visible" traffic in the room or has | ||
| custom event types it wants to use. In future, being able to specify | ||
| extensible event content blocks which should be added to the resulting event | ||
| may be a better option. In either case, bots should not be able to cause users | ||
| to send state events to prevent bots from tricking users into changing power | ||
| levels, join rules, etc. | ||
|
|
||
| - Support for non-text-like arguments like images, files, etc. | ||
|
|
||
| - Some predefined validation on arguments, like a range for integers or | ||
| maximum/minimum length of strings. | ||
|
|
||
| ## Potential issues | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When commands are available across an entire space it is a pita to have to resend the state events everywhere. So maybe it would be nice to be able to only specify the commands in a space or take inspiration from sticker pack MSCs if they haven't encountered the same problem. |
||
|
|
||
| - Using state events limits a bot's ability to advertise commands if it isn't | ||
| given power to do so. | ||
|
|
||
| ## Alternatives | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm in the process of adding a "response" mixin in a follow up MSC. It would be really useful to also be able to proxy the commands into a mutual room (like draupnir's command room). See the-draupnir-project/Draupnir#1041 (comment) |
||
|
|
||
| - Not using state events would work, but can be tricky to manage. This proposal | ||
| fills a gap until proposals which solve the problem space more completely are | ||
| written and proven by implementation. Sticky events maybe? | ||
|
|
||
| - [MSC4332](https://github.com/matrix-org/matrix-spec-proposals/pull/4332). As | ||
| stated throughout this proposal, MSC4332 makes a number of complicated | ||
| considerations for command syntax that are not backwards compatible for all | ||
| bots. In addition the semantics for variadic and positional arguments as they | ||
| appear in the MSC make it difficult to implement _options_, _prompts_ and | ||
| _partial commands_ | ||
| https://github.com/matrix-org/matrix-spec-proposals/pull/4332#discussion_r2313755345. | ||
| This MSC uses a JSON object to supply arguments to a command and provides | ||
| union and array types that can be used to implement variadic arguments and | ||
| various styles of options. | ||
|
|
||
| ## Security considerations | ||
|
|
||
| Mentioned in the proposal, clients should be explicitly aware that bots may try | ||
| to create confusion for users and override built-in commands or another bot's | ||
| commands. For example, a bot may advertise a `myroomnick` command which leads to | ||
| the client's functionality not working as expected. Clients should be taking | ||
| measures to minimize this confusion from happening, and always showing the | ||
| profile of the sender that registered the command. | ||
|
|
||
| ## Unstable prefix | ||
|
|
||
| While this proposal is not considered stable, implementations should use | ||
| `org.matrix.msc4391.command_description` in place of `m.bot.command_description` | ||
| and `org.matrix.msc4391.command` in place of `m.bot.command`. | ||
|
|
||
| ## Dependencies | ||
|
|
||
| This proposal has no direct dependencies, but benefits more strongly from the | ||
| following Extensible Events MSCs: | ||
|
|
||
| - [MSC1767 (accepted)](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/1767-extensible-events.md) | ||
| - [MSC3554: Translatable Text](https://github.com/matrix-org/matrix-spec-proposals/pull/3554) | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implementation requirements:
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gomuks/gomuks#675 is mostly complete implementation of client and technically also bot, converted from MSC4332. It doesn't have full support for room/event IDs or unions yet, but mostly it should be there
It assumes most of my suggestions in other comments are applied. Specifically:
commandstring instead ofdesignatorarray"optional": trueinstead of"required": false"schema_type": "primitive"aliases,default_valuefor default valuesThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(all of these suggestions were up-streamed into the MSC)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Proper bot implementation: maunium/meowlnir#50
Both meowlnir and parts of gomuks use logic from mautrix/go#446 (gomuks web has its own typescript implementation, gomuks terminal uses mautrix-go)