Skip to content

Commit

Permalink
Merge pull request #10151 from nextcloud/docs/10094/document-bots
Browse files Browse the repository at this point in the history
feat: Document bots
  • Loading branch information
nickvergessen authored Aug 10, 2023
2 parents 922691d + aa96ada commit c49fdf1
Show file tree
Hide file tree
Showing 16 changed files with 550 additions and 29 deletions.
2 changes: 2 additions & 0 deletions appinfo/routes/routesBotController.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
['name' => 'Bot#react', 'url' => '/api/{apiVersion}/bot/{token}/reaction/{messageId}', 'verb' => 'POST', 'requirements' => $requirementsWithMessageId],
/** @see \OCA\Talk\Controller\BotController::deleteReaction() */
['name' => 'Bot#deleteReaction', 'url' => '/api/{apiVersion}/bot/{token}/reaction/{messageId}', 'verb' => 'DELETE', 'requirements' => $requirementsWithMessageId],
/** @see \OCA\Talk\Controller\BotController::adminListBots() */
['name' => 'Bot#adminListBots', 'url' => '/api/{apiVersion}/bot/admin', 'verb' => 'GET', 'requirements' => $requirements],
/** @see \OCA\Talk\Controller\BotController::listBots() */
['name' => 'Bot#listBots', 'url' => '/api/{apiVersion}/bot/{token}', 'verb' => 'GET', 'requirements' => $requirements],
/** @see \OCA\Talk\Controller\BotController::enableBot() */
Expand Down
31 changes: 31 additions & 0 deletions docs/bot-list.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# List of available bots

If you want to write your own bot or webhook, please see the
[Bots and Webhooks developer documentation](bots.md). Afterwards send a pull
request against the [docs/bot-list.md](https://github.com/nextcloud/spreed/blob/master/docs/bot-list.md)
to add your bot to the list, using the following template:

```markdown
## Name of the bot

Useful, but short description, best to keep it to 1-3 lines of text.

![Screenshot can be put into docs/botscreenshots/bot-name.png, max. width 700px, max. height 480px](botscreenshots/bot-name.png)

* License: Identifier of the license (See https://spdx.org/licenses/)
* [Link to source code](https://github.com/nextcloud/call_summary_bot)
* [Link to installation documentation](https://github.com/nextcloud/call_summary_bot#readme)
```

Here you can find a brief overview of known bots that allows administration to
easily discover your bot and install it on their Nextcloud server.

## Call summary

The call summary bot posts an overview message after the call listing all participants and outlining tasks.

![Screenshot showing a call summary chat message](botscreenshots/call-summary.png)

* License: AGPL-3.0-or-later
* [Link to source code](https://github.com/nextcloud/call_summary_bot)
* [Link to installation documentation](https://github.com/nextcloud/call_summary_bot#readme)
84 changes: 84 additions & 0 deletions docs/bot-management.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Setup and management bots

Base endpoint is: `/ocs/v2.php/apps/spreed/api/v1`: (requires the `bots-v1` capability - available since Nextcloud 27.1)

## Get list of bots installed on the server

Lists the bots that are enabled and can be enabled for the conversation

* Required capability: `bots-v1`
* Method: `GET`
* Endpoint: `bot/admin`

* Response:
- Status code:
+ `200 OK`
+ `403 Forbidden` When the current user is not a moderator/owner
+ `404 Not Found` When the conversation could not be found for the participant
- Data:
List of bots, each bot has at least:

| field | type | Description |
|----------------------|--------|----------------------------------------------------------------------------------------------|
| `id` | int | Unique numeric identifier of the bot on this server |
| `name` | string | Display name of the bot shown as author when it posts a message or reaction |
| `description` | string | A longer description of the bot helping moderators to decide if they want to enable this bot |
| `url` | string | URL endpoint that is triggered by this bot |
| `url_hash` | string | Hash of the URL prefixed with `bot-` serves as `actor_id` |
| `state` | int | One of the [Bot states](constants.md#bot-states) |
| `error_count` | int | Number of consecutive errors |
| `last_error_date` | int | UNIX timestamp of the last error |
| `last_error_message` | string | The last exception message or error response information when trying to reach the bot. |

## Get list of bots for a conversation

Lists the bots that are enabled and can be enabled for the conversation

* Required capability: `bots-v1`
* Method: `GET`
* Endpoint: `bot/{token}`

* Response:
- Status code:
+ `200 OK`
+ `403 Forbidden` When the current user is not a moderator/owner
+ `404 Not Found` When the conversation could not be found for the participant
- Data:
List of bots, each bot has at least:

| field | type | Description |
|-----------------------|--------|----------------------------------------------------------------------------------------------|
| `id` | int | Unique numeric identifier of the bot on this server |
| `name` | string | Display name of the bot shown as author when it posts a message or reaction |
| `description` | string | A longer description of the bot helping moderators to decide if they want to enable this bot |
| `state` | int | One of the [Bot states](constants.md#bot-states) |

## Enable a bot for a conversation as a moderator

* Required capability: `bots-v1`
* Method: `POST`
* Endpoint: `bot/{token}/{botId}`

* Response:
- Status code:
+ `200 OK` When the bot is already enabled in the conversation
+ `201 Created` When the bot is now enabled in the conversation
+ `400 Bad Request` When the bot ID is unknown on the server
+ `400 Bad Request` When the bot is disabled or set to "no-setup" on the server
+ `403 Forbidden` When the current user is not a moderator/owner
+ `404 Not Found` When the conversation could not be found for the participant

## Disable a bot for a conversation as a moderator

* Required capability: `bots-v1`
* Method: `DELETE`
* Endpoint: `bot/{token}/{botId}`

* Response:
- Status code:
+ `200 OK` When the bot is already enabled in the conversation
+ `201 Created` When the bot is now enabled in the conversation
+ `400 Bad Request` When the bot ID is unknown on the server
+ `400 Bad Request` When the bot is disabled or set to "no-setup" on the server
+ `403 Forbidden` When the current user is not a moderator/owner
+ `404 Not Found` When the conversation could not be found for the participant
171 changes: 171 additions & 0 deletions docs/bots.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# Bots and Webhooks

Base endpoint is: `/ocs/v2.php/apps/spreed/api/v1`: (requires the `bots-v1` capability - available since Nextcloud 27.1)

Webhook based bots are available with the Nextcloud 27.1 compatible Nextcloud Talk 17.1 release as a first version

!!! note

For security reasons bots can only be added via the
command line. `./occ talk:bot:install --help` gives you
a short overview of the required arguments, but they are
also explained in the [OCC documentation](occ.md#talkbotinstall).

---

## Receiving chat messages

Messages are signed using the shared secret that is specified when installing a bot on the server.
Create a HMAC with SHA256 over the request body and the `RANDOM` header using the shared secret.
Only when the `SIGNATURE` matches the request should be accepted and handled.

**Sample PHP code:**

```php
$digest = hash_hmac('sha256', $_SERVER['HTTP_X_NEXTCLOUD_TALK_RANDOM'] . file_get_contents('php://input'), $secret);

if (!hash_equals($digest, strtolower($_SERVER['HTTP_X_NEXTCLOUD_TALK_SIGNATURE']))) {
exit;
}
```

### Headers

| Header | Content type | Description |
|-----------------------------------|---------------------|------------------------------------------------------|
| `HTTP_X_NEXTCLOUD_TALK_SIGNATURE` | `[a-f0-9]{64}` | SHA265 signature of the body |
| `HTTP_X_NEXTCLOUD_TALK_RANDOM` | `[A-Za-z0-9+\]{64}` | Random string used when signing the body |
| `HTTP_X_NEXTCLOUD_TALK_BACKEND` | URI | Base URL of the Nextcloud server sending the message |

### Content

The content format follows the [ActivityPub](https://www.w3.org/TR/activitypub/) dictionary.

#### Sample chat message

```json
{
"type": "Create",
"actor": {
"type": "Person",
"id": "users/ada-lovelace",
"name": "Ada Lovelace"
},
"object": {
"type": "Note",
"id": "1567",
"name": "message",
"content": "{\"message\":\"hi {mention-call1} !\",\"parameters\":{\"mention-call1\":{\"type\":\"call\",\"id\":\"n3xtc10ud\",\"name\":\"world\",\"call-type\":\"group\",\"icon-url\":\"https:\\/\\/nextcloud.local\\/ocs\\/v2.php\\/apps\\/spreed\\/api\\/v1\\/room\\/n3xtc10ud\\/avatar\"}}}",
"mediaType": "text/markdown"
},
"target": {
"type": "Collection",
"id": "n3xtc10ud",
"name": "world"
}
}
```

#### Explanation

| Path | Description |
|------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| actor.id | One of the known [attendee types](constants.md#attendee-types) followed by the `/` slash character and a unique identifier within the given type. For users it is the Nextcloud user ID, for guests a sha1 value. |
| actor.name | The display name of the attendee sending the message. |
| object.id | The message ID of the given message on the origin server. It can be used to react or reply to the given message. |
| object.name | For normal written messages `message`, otherwise one of the known [system message identifiers](chat.md#system-messages). |
| object.content | A JSON encoded dictionary with a `message` and `parameters` key. The message can include placeholders and the [Rich Object parameters](https://github.com/nextcloud/server/blob/master/lib/public/RichObjectStrings/Definitions.php) are rendered into it in the chat view. |
| object.mediaType | `text/markdown` when the message should be interpreted as Markdown. Otherwise `text/plain`. |
| target.id | The token of the conversation in which the message was posted. It can be used to react or reply to the given message. |
| target.name | The name of the conversation in which the message was posted. |

## Sending a chat message

Bots can also send message. On the sending process the same signature/verification method is applied.

* Required capability: `bots-v1`
* Method: `POST`
* Endpoint: `/bot/{token}/message`
* Header:

| Name | Description |
|----------------------------------|-----------------------------------------------------------------|
| `X-Nextcloud-Talk-Bot-Random` | The random value used when signing the request |
| `X-Nextcloud-Talk-Bot-Signature` | The signature to validate the request comes from an enabled bot |
| `OCS-APIRequest` | Needs to be set to `true` to access the ocs/vX.php endpoint |

* Data:

| field | type | Description |
|--------------------|--------|---------------------------------------------------------------------------------------------------------------------------------------------------------|
| `message` | string | The message the user wants to say |
| `replyTo` | int | The message ID this message is a reply to (only allowed for messages from the same conversation and when the message type is not `system` or `command`) |
| `referenceId` | string | A reference string to be able to identify the message again in a "get messages" request, should be a random sha256 |
| `silent` | bool | If sent silent the message will not create chat notifications even for users |

* Response:
- Status code:
+ `201 Created` When the message was posted successfully
+ `400 Bad Request` When the provided replyTo parameter is invalid
+ `401 Unauthenticated` When the bot could not be verified for the conversation
+ `404 Not Found` When the conversation could not be found
+ `413 Payload Too Large` When the message was longer than the allowed limit of 32000 characters (or 1000 until Nextcloud 16.0.1, check the `spreed => config => chat => max-length` capability for the limit)
+ `429 Too Many Requests` When `401 Unauthenticated` was triggered too often

## Reacting to a chat message

Bots can also react to a message. The same signature/verification method is applied.

* Required capability: `bots-v1`
* Method: `POST`
* Endpoint: `/bot/{token}/reaction/{messageId}`
* Header:

| Name | Description |
|----------------------------------|-----------------------------------------------------------------|
| `X-Nextcloud-Talk-Bot-Random` | The random value used when signing the request |
| `X-Nextcloud-Talk-Bot-Signature` | The signature to validate the request comes from an enabled bot |
| `OCS-APIRequest` | Needs to be set to `true` to access the ocs/vX.php endpoint |

* Data:

| field | type | Description |
|------------|--------|----------------|
| `reaction` | string | A single emoji |

* Response:
- Status code:
+ `201 Created` When the reaction was created successfully
+ `400 Bad Request` When the provided emoji was invalid
+ `401 Unauthenticated` When the bot could not be verified for the conversation
+ `404 Not Found` When the conversation or message could not be found
+ `429 Too Many Requests` When `401 Unauthenticated` was triggered too often

## Delete a reaction

Bots can also remove their previous reaction from amessage. The same signature/verification method is applied.

* Required capability: `bots-v1`
* Method: `DELETE`
* Endpoint: `/bot/{token}/reaction/{messageId}`
* Header:

| Name | Description |
|----------------------------------|-----------------------------------------------------------------|
| `X-Nextcloud-Talk-Bot-Random` | The random value used when signing the request |
| `X-Nextcloud-Talk-Bot-Signature` | The signature to validate the request comes from an enabled bot |
| `OCS-APIRequest` | Needs to be set to `true` to access the ocs/vX.php endpoint |

* Data:

| field | type | Description |
|------------|--------|----------------|
| `reaction` | string | A single emoji |

* Response:
- Status code:
+ `200 OK` When the reaction was deleted successfully
+ `400 Bad Request` When the provided emoji was invalid
+ `401 Unauthenticated` When the bot could not be verified for the conversation
+ `404 Not Found` When the conversation or message could not be found
+ `429 Too Many Requests` When `401 Unauthenticated` was triggered too often
Binary file added docs/botscreenshots/call-summary.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,4 @@

## 17.1
* `remind-me-later` - Support for "Remind me later" for chat messages exists
* `bots-v1` - Support of the first version for Bots and Webhooks is available
6 changes: 6 additions & 0 deletions docs/commands.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Chat commands

!!! warning

**Deprecation:** Commands are deprecated in favor of [Bots](bots.md).

---

!!! note

For security reasons commands can only be added via the
Expand Down
7 changes: 7 additions & 0 deletions docs/constants.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,13 @@
* `0` - Public: Participants can see the result immediately and also who voted for which option
* `1` - Hidden: The result is hidden until the poll is closed and then only the number of votes for each option are displayed

## Bots

### Bot states
* `0` Disabled
* `1` Enabled
* `2` No setup - The bot can neither be enabled nor disabled by a moderator

## Signaling modes
* `internal` - No external signaling server is used
* `external` - A single external signaling server is used
Expand Down
15 changes: 15 additions & 0 deletions lib/Controller/BotController.php
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,21 @@ public function deleteReaction(string $token, int $messageId, string $reaction):
return new DataResponse([], Http::STATUS_OK);
}

/**
* Admin required
*/
public function adminListBots(): DataResponse {
$data = [];
$bots = $this->botServerMapper->getAllBots();
foreach ($bots as $bot) {
$botData = $bot->jsonSerialize();
unset($botData['secret']);
$data[] = $botData;
}

return new DataResponse($data);
}

#[NoAdminRequired]
#[RequireLoggedInModeratorParticipant]
public function listBots(): DataResponse {
Expand Down
5 changes: 4 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ nav:
- 'Scalability': 'scalability.md'
- 'Call experience': 'call-experience.md'
- 'Occ commands': 'occ.md'
- 'Commands': 'commands.md'
- 'Bots': 'bot-list.md'
- 'Commands (deprecated)': 'commands.md'
- 'Matterbridge integration': 'matterbridge.md'
- 'Developer documentation':
- 'Bots and webhooks': 'bots.md'
- 'Constants': 'constants.md'
- 'Capabilities': 'capabilities.md'
- 'PHP events': 'events.md'
Expand All @@ -41,6 +43,7 @@ nav:
- 'Reaction management': 'reaction.md'
- 'Poll management': 'poll.md'
- 'Breakout rooms management': 'breakout-rooms.md'
- 'Bots management': 'bot-management.md'
- 'Integration by other apps': 'integration.md'
- 'Webinar management': 'webinar.md'
- 'Settings': 'settings.md'
Expand Down
Loading

0 comments on commit c49fdf1

Please sign in to comment.