diff --git a/CHANGELOG.md b/CHANGELOG.md
index d10b0cd4a49..6c4c6cbec5a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,36 @@
# Changelog
All notable changes to this project will be documented in this file.
+## 17.1.0-rc.1 – 2023-08-11
+### Added
+- Add support for bots via webhooks
+ [#10139](https://github.com/nextcloud/spreed/issues/10139)
+ [#10151](https://github.com/nextcloud/spreed/issues/10151)
+- Add Markdown support for chat messages
+ [#10089](https://github.com/nextcloud/spreed/issues/10089)
+ [#10090](https://github.com/nextcloud/spreed/issues/10090)
+- Allow to filter the conversation list for unread mentions and messages
+ [#10093](https://github.com/nextcloud/spreed/issues/10093)
+- Provide an overview list of open conversations
+ [#10095](https://github.com/nextcloud/spreed/issues/10095)
+- Set a reminder to get notified about a chat messages at a later time
+ [#10104](https://github.com/nextcloud/spreed/issues/10104)
+ [#10152](https://github.com/nextcloud/spreed/issues/10152)
+ [#10155](https://github.com/nextcloud/spreed/issues/10155)
+- Show a hint when the call is running since one hour
+ [#10101](https://github.com/nextcloud/spreed/issues/10101)
+- Show the talking time of participants in the right sidebar
+ [#10145](https://github.com/nextcloud/spreed/issues/10145)
+
+### Changed
+- System messages of the same actions are now grouped
+ [#10143](https://github.com/nextcloud/spreed/issues/10143)
+- Update dependencies
+
+### Fixed
+- Fix resetting the bruteforce protection when joining a conversation
+ [#10165](https://github.com/nextcloud/spreed/issues/10165)
+
## 17.0.3 – 2023-07-28
### Changed
- Update dependencies
diff --git a/appinfo/info.xml b/appinfo/info.xml
index 29bc9b786a3..4c95e242734 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -16,7 +16,7 @@ And in the works for the [coming versions](https://github.com/nextcloud/spreed/m
]]>
- 17.1.0-dev.2
+ 17.1.0-rc.1agplDaniel Calviño Sánchez
diff --git a/appinfo/routes/routesBotController.php b/appinfo/routes/routesBotController.php
index 90910d2a3d4..3578cc7f514 100644
--- a/appinfo/routes/routesBotController.php
+++ b/appinfo/routes/routesBotController.php
@@ -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() */
diff --git a/docs/bot-list.md b/docs/bot-list.md
new file mode 100644
index 00000000000..9961dde5a38
--- /dev/null
+++ b/docs/bot-list.md
@@ -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.
+
+
+
+* 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.
+
+
+
+* 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)
diff --git a/docs/bot-management.md b/docs/bot-management.md
new file mode 100644
index 00000000000..301f8ba617f
--- /dev/null
+++ b/docs/bot-management.md
@@ -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
diff --git a/docs/bots.md b/docs/bots.md
new file mode 100644
index 00000000000..573d99968da
--- /dev/null
+++ b/docs/bots.md
@@ -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
diff --git a/docs/botscreenshots/call-summary.png b/docs/botscreenshots/call-summary.png
new file mode 100644
index 00000000000..b0b3cf87810
Binary files /dev/null and b/docs/botscreenshots/call-summary.png differ
diff --git a/docs/capabilities.md b/docs/capabilities.md
index 3922b54d520..2fb160374f2 100644
--- a/docs/capabilities.md
+++ b/docs/capabilities.md
@@ -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
diff --git a/docs/commands.md b/docs/commands.md
index 3aca4e06390..629abb682b1 100644
--- a/docs/commands.md
+++ b/docs/commands.md
@@ -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
diff --git a/docs/constants.md b/docs/constants.md
index 729fa33fbe8..b469770368c 100644
--- a/docs/constants.md
+++ b/docs/constants.md
@@ -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
diff --git a/lib/Controller/BotController.php b/lib/Controller/BotController.php
index 3e763dc13db..24dbe149e32 100644
--- a/lib/Controller/BotController.php
+++ b/lib/Controller/BotController.php
@@ -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 {
diff --git a/mkdocs.yml b/mkdocs.yml
index 31f29074e79..0dd650be541 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -23,9 +23,11 @@ nav:
- 'Server system requirements': 'system-requirements.md'
- 'Configuring coTURN': 'TURN.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'
@@ -39,6 +41,7 @@ nav:
- 'Reaction management': 'reaction.md'
- 'Poll management': 'poll.md'
- 'Breakout rooms management': 'breakout-rooms.md'
+ - 'Bots management': 'bot-management.md'
- 'Webinar management': 'webinar.md'
- 'Settings': 'settings.md'
- 'Integration by other apps': 'integration.md'
diff --git a/package-lock.json b/package-lock.json
index 6d06bc2f15a..deffe4c9d1c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "talk",
- "version": "17.0.3",
+ "version": "17.1.0-rc.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "talk",
- "version": "17.0.3",
+ "version": "17.1.0-rc.1",
"license": "agpl",
"dependencies": {
"@linusborg/vue-simple-portal": "^0.1.5",
diff --git a/package.json b/package.json
index d8777f6b6e2..44208c2b72a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "talk",
- "version": "17.0.3",
+ "version": "17.1.0-rc.1",
"private": true,
"description": "",
"author": "Joas Schilling ",
diff --git a/src/components/AdminSettings/BotsSettings.vue b/src/components/AdminSettings/BotsSettings.vue
new file mode 100644
index 00000000000..3cd7926eaa7
--- /dev/null
+++ b/src/components/AdminSettings/BotsSettings.vue
@@ -0,0 +1,209 @@
+
+
+
+
+