diff --git a/.drone.jsonnet b/.drone.jsonnet
index 8e7b6e5681e..96e75d61d9a 100644
--- a/.drone.jsonnet
+++ b/.drone.jsonnet
@@ -1,8 +1,16 @@
## 1. Download/install drone binary:
-## curl -L https://github.com/harness/drone-cli/releases/latest/download/drone_linux_amd64.tar.gz | tar zx
+## curl -L https://github.com/harness/drone-cli/releases/latest/download/drone_linux_amd64.tar.gz | tar zx
## 2. Adjust the matrix as wished
-## 3. Run: ./drone jsonnet --stream --format yml
-## 4. Commit the result
+## 3. Transform jsonnet to yml:
+## ./drone jsonnet --stream --format yml
+## 4. Export your drone token and the server:
+## export DRONE_TOKEN=… export DRONE_SERVER=https://drone.nextcloud.com
+## 5. Sign off the changes:
+## ./drone sign nextcloud/spreed --save
+## 6. Copy the new signature from .drone.yml to `hmac` field in this file
+## 7. Transform jsonnet to yml again (to transfer the signature correctly):
+## ./drone jsonnet --stream --format yml
+## 8. Commit the result
local Pipeline(test_set, database, services) = {
kind: "pipeline",
@@ -16,6 +24,7 @@ local Pipeline(test_set, database, services) = {
APP_NAME: "spreed",
CORE_BRANCH: "stable27",
GUESTS_BRANCH: "master",
+ CSB_BRANCH: "main",
NOTIFICATIONS_BRANCH: "stable27",
DATABASEHOST: database
},
@@ -31,12 +40,11 @@ local Pipeline(test_set, database, services) = {
"cd ../..",
"./occ app:enable $APP_NAME",
"git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications apps/notifications",
- "./occ app:enable notifications"
- ] + (
- if test_set == "conversation" || test_set == "conversation-2" then [
- "git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests"
- ] else []
- ) + [
+ "./occ app:enable --force notifications",
+ "git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests",
+ "./occ app:enable --force guests",
+ "git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot apps/call_summary_bot",
+ "./occ app:enable --force call_summary_bot",
"cd apps/$APP_NAME/tests/integration/",
"bash run.sh features/"+test_set
]
@@ -155,6 +163,6 @@ local PipelinePostgreSQL(test_set) = Pipeline(
{
kind: "signature",
- hmac: "42a9326446817e073491d2b887a20254394c1cc988640bc9df9b57f2ad445ff0"
+ hmac: "8a6c9dd22806c07b68b5d263748cf226693b847a9d4773e094ba549d98834dd5"
},
]
diff --git a/.drone.yml b/.drone.yml
index c10969232f8..6767270e459 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -18,12 +18,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
+ - git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/callapi
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: sqlite
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -55,12 +61,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
+ - git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/chat
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: sqlite
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -92,12 +104,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
+ - git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/chat-2
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: sqlite
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -129,12 +147,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
+ - git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/command
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: sqlite
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -166,13 +190,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
- git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/conversation
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: sqlite
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -204,13 +233,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
- git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/conversation-2
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: sqlite
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -242,12 +276,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
+ - git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/federation
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: sqlite
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -279,12 +319,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
+ - git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/integration
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: sqlite
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -316,12 +362,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
+ - git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/sharing
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: sqlite
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -353,12 +405,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
+ - git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/sharing-2
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: sqlite
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -404,12 +462,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
+ - git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/callapi
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: mysql
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -455,12 +519,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
+ - git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/chat
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: mysql
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -506,12 +576,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
+ - git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/chat-2
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: mysql
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -557,12 +633,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
+ - git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/command
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: mysql
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -608,13 +690,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
- git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/conversation
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: mysql
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -660,13 +747,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
- git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/conversation-2
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: mysql
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -712,12 +804,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
+ - git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/federation
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: mysql
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -763,12 +861,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
+ - git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/integration
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: mysql
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -814,12 +918,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
+ - git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/sharing
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: mysql
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -865,12 +975,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
+ - git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/sharing-2
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: mysql
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -911,12 +1027,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
+ - git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/callapi
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: pgsql
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -958,12 +1080,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
+ - git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/chat
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: pgsql
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -1005,12 +1133,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
+ - git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/chat-2
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: pgsql
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -1052,12 +1186,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
+ - git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/command
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: pgsql
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -1099,13 +1239,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
- git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/conversation
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: pgsql
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -1147,13 +1292,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
- git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/conversation-2
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: pgsql
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -1195,12 +1345,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
+ - git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/federation
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: pgsql
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -1242,12 +1398,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
+ - git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/integration
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: pgsql
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -1289,12 +1451,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
+ - git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/sharing
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: pgsql
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -1336,12 +1504,18 @@ steps:
- ./occ app:enable $APP_NAME
- git clone --depth 1 -b $NOTIFICATIONS_BRANCH https://github.com/nextcloud/notifications
apps/notifications
- - ./occ app:enable notifications
+ - ./occ app:enable --force notifications
+ - git clone --depth 1 -b $GUESTS_BRANCH https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable --force guests
+ - git clone --depth 1 -b $CSB_BRANCH https://github.com/nextcloud/call_summary_bot
+ apps/call_summary_bot
+ - ./occ app:enable --force call_summary_bot
- cd apps/$APP_NAME/tests/integration/
- bash run.sh features/sharing-2
environment:
APP_NAME: spreed
CORE_BRANCH: stable27
+ CSB_BRANCH: main
DATABASEHOST: pgsql
GUESTS_BRANCH: master
NOTIFICATIONS_BRANCH: stable27
@@ -1355,5 +1529,5 @@ trigger:
- pull_request
- push
---
-hmac: 42a9326446817e073491d2b887a20254394c1cc988640bc9df9b57f2ad445ff0
+hmac: 8a6c9dd22806c07b68b5d263748cf226693b847a9d4773e094ba549d98834dd5
kind: signature
diff --git a/appinfo/info.xml b/appinfo/info.xml
index 8df7257dc97..a93c4205ca7 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.0.3
+ 17.1.0-dev.1agplDaniel Calviño Sánchez
@@ -80,6 +80,12 @@ And in the works for the [coming versions](https://github.com/nextcloud/spreed/m
+ OCA\Talk\Command\Bot\Install
+ OCA\Talk\Command\Bot\ListBots
+ OCA\Talk\Command\Bot\Remove
+ OCA\Talk\Command\Bot\State
+ OCA\Talk\Command\Bot\Setup
+ OCA\Talk\Command\Bot\UninstallOCA\Talk\Command\Command\AddOCA\Talk\Command\Command\AddSamplesOCA\Talk\Command\Command\Delete
diff --git a/appinfo/routes.php b/appinfo/routes.php
index b6a2a9966e2..3cefda0d9a4 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -26,6 +26,7 @@
return array_merge_recursive(
include(__DIR__ . '/routes/routesAvatarController.php'),
+ include(__DIR__ . '/routes/routesBotController.php'),
include(__DIR__ . '/routes/routesBreakoutRoomController.php'),
include(__DIR__ . '/routes/routesCallController.php'),
include(__DIR__ . '/routes/routesChatController.php'),
diff --git a/appinfo/routes/routesBotController.php b/appinfo/routes/routesBotController.php
new file mode 100644
index 00000000000..90910d2a3d4
--- /dev/null
+++ b/appinfo/routes/routesBotController.php
@@ -0,0 +1,58 @@
+
+ *
+ * @author Joas Schilling
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+$requirements = [
+ 'apiVersion' => 'v1',
+ 'token' => '[a-z0-9]{4,30}',
+];
+
+$requirementsWithMessageId = [
+ 'apiVersion' => 'v1',
+ 'token' => '[a-z0-9]{4,30}',
+ 'messageId' => '[0-9]+',
+];
+
+$requirementsWithBotId = [
+ 'apiVersion' => 'v1',
+ 'token' => '[a-z0-9]{4,30}',
+ 'botId' => '[0-9]+',
+];
+
+return [
+ 'ocs' => [
+ /** @see \OCA\Talk\Controller\BotController::sendMessage() */
+ ['name' => 'Bot#sendMessage', 'url' => '/api/{apiVersion}/bot/{token}/message', 'verb' => 'POST', 'requirements' => $requirements],
+ /** @see \OCA\Talk\Controller\BotController::react() */
+ ['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::listBots() */
+ ['name' => 'Bot#listBots', 'url' => '/api/{apiVersion}/bot/{token}', 'verb' => 'GET', 'requirements' => $requirements],
+ /** @see \OCA\Talk\Controller\BotController::enableBot() */
+ ['name' => 'Bot#enableBot', 'url' => '/api/{apiVersion}/bot/{token}/{botId}', 'verb' => 'POST', 'requirements' => $requirementsWithBotId],
+ /** @see \OCA\Talk\Controller\BotController::disableBot() */
+ ['name' => 'Bot#disableBot', 'url' => '/api/{apiVersion}/bot/{token}/{botId}', 'verb' => 'DELETE', 'requirements' => $requirementsWithBotId],
+ ],
+];
diff --git a/composer.lock b/composer.lock
index 9c69d469eda..c7f633267b2 100644
--- a/composer.lock
+++ b/composer.lock
@@ -134,12 +134,12 @@
"source": {
"type": "git",
"url": "https://github.com/nextcloud-deps/ocp.git",
- "reference": "6af8fc960e0526d3641b8c0da9cbf0d5a85be920"
+ "reference": "352a0ae30111cf4f52f7ec828ede7d1f33423843"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/6af8fc960e0526d3641b8c0da9cbf0d5a85be920",
- "reference": "6af8fc960e0526d3641b8c0da9cbf0d5a85be920",
+ "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/352a0ae30111cf4f52f7ec828ede7d1f33423843",
+ "reference": "352a0ae30111cf4f52f7ec828ede7d1f33423843",
"shasum": ""
},
"require": {
@@ -170,7 +170,7 @@
"issues": "https://github.com/nextcloud-deps/ocp/issues",
"source": "https://github.com/nextcloud-deps/ocp/tree/stable27"
},
- "time": "2023-08-02T00:37:00+00:00"
+ "time": "2023-08-08T14:44:26+00:00"
},
{
"name": "psr/clock",
diff --git a/docs/occ.md b/docs/occ.md
index 99a330bd242..9b7a442bc72 100644
--- a/docs/occ.md
+++ b/docs/occ.md
@@ -1,5 +1,108 @@
# Talk occ commands
+## talk:bot:install
+
+Install a new bot on the server
+
+### Usage
+
+* `talk:bot:install [--output [OUTPUT]] [--no-setup] [--] []`
+
+| Arguments | Description | Is required | Is array | Default |
+|---|---|---|---|---|
+| `name` | The name under which the messages will be posted | yes | no | `NULL` |
+| `secret` | Secret used to validate API calls | yes | no | `NULL` |
+| `url` | Webhook endpoint to post messages to | yes | no | `NULL` |
+| `description` | Optional description shown in the admin settings | no | no | `NULL` |
+
+| Options | Accept value | Is value required | Is multiple | Default |
+|---|---|---|---|---|
+| `--output` | Output format (plain, json or json_pretty, default is plain) | yes | no | no | 'plain'` |
+| `--no-setup` | Prevent moderators from setting up the bot in a conversation | no | no | no | false` |
+
+## talk:bot:list
+
+List all installed bots of the server or a conversation
+
+### Usage
+
+* `talk:bot:list [--output [OUTPUT]] [--] []`
+
+| Arguments | Description | Is required | Is array | Default |
+|---|---|---|---|---|
+| `token` | Conversation token to limit the bot list for | no | no | `NULL` |
+
+| Options | Accept value | Is value required | Is multiple | Default |
+|---|---|---|---|---|
+| `--output` | Output format (plain, json or json_pretty, default is plain) | yes | no | no | 'plain'` |
+
+## talk:bot:remove
+
+Remove a bot from a conversation
+
+### Usage
+
+* `talk:bot:remove [--output [OUTPUT]] [--] [...]`
+
+| Arguments | Description | Is required | Is array | Default |
+|---|---|---|---|---|
+| `bot-id` | The ID of the bot to remove in a conversation | yes | no | `NULL` |
+| `token` | Conversation tokens to remove bot up for | no | yes | `array ()` |
+
+| Options | Accept value | Is value required | Is multiple | Default |
+|---|---|---|---|---|
+| `--output` | Output format (plain, json or json_pretty, default is plain) | yes | no | no | 'plain'` |
+
+## talk:bot:state
+
+List all installed bots of the server or a conversation
+
+### Usage
+
+* `talk:bot:state [--output [OUTPUT]] [--] `
+
+| Arguments | Description | Is required | Is array | Default |
+|---|---|---|---|---|
+| `bot-id` | Bot ID to change the state for | yes | no | `NULL` |
+| `state` | New state for the bot (0 = disabled, 1 = enabled, 2 = no setup via GUI) | yes | no | `NULL` |
+
+| Options | Accept value | Is value required | Is multiple | Default |
+|---|---|---|---|---|
+| `--output` | Output format (plain, json or json_pretty, default is plain) | yes | no | no | 'plain'` |
+
+## talk:bot:setup
+
+Add a bot to a conversation
+
+### Usage
+
+* `talk:bot:setup [--output [OUTPUT]] [--] [...]`
+
+| Arguments | Description | Is required | Is array | Default |
+|---|---|---|---|---|
+| `bot-id` | The ID of the bot to set up in a conversation | yes | no | `NULL` |
+| `token` | Conversation tokens to set the bot up for | no | yes | `array ()` |
+
+| Options | Accept value | Is value required | Is multiple | Default |
+|---|---|---|---|---|
+| `--output` | Output format (plain, json or json_pretty, default is plain) | yes | no | no | 'plain'` |
+
+## talk:bot:uninstall
+
+Uninstall a bot from the server
+
+### Usage
+
+* `talk:bot:uninstall [--output [OUTPUT]] [--] `
+
+| Arguments | Description | Is required | Is array | Default |
+|---|---|---|---|---|
+| `id` | The ID of the bot | yes | no | `NULL` |
+
+| Options | Accept value | Is value required | Is multiple | Default |
+|---|---|---|---|---|
+| `--output` | Output format (plain, json or json_pretty, default is plain) | yes | no | no | 'plain'` |
+
## talk:command:add
Add a new command
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
index 66ef2929cb3..fef18588f2c 100644
--- a/lib/AppInfo/Application.php
+++ b/lib/AppInfo/Application.php
@@ -45,6 +45,7 @@
use OCA\Talk\Deck\DeckPluginLoader;
use OCA\Talk\Events\AttendeesAddedEvent;
use OCA\Talk\Events\AttendeesRemovedEvent;
+use OCA\Talk\Events\BotInstallEvent;
use OCA\Talk\Events\RoomEvent;
use OCA\Talk\Events\SendCallNotificationEvent;
use OCA\Talk\Federation\CloudFederationProviderTalk;
@@ -52,6 +53,7 @@
use OCA\Talk\Files\TemplateLoader as FilesTemplateLoader;
use OCA\Talk\Flow\RegisterOperationsListener;
use OCA\Talk\Listener\BeforeUserLoggedOutListener;
+use OCA\Talk\Listener\BotListener;
use OCA\Talk\Listener\CircleDeletedListener;
use OCA\Talk\Listener\CircleMembershipListener;
use OCA\Talk\Listener\CSPListener;
@@ -124,6 +126,7 @@ public function register(IRegistrationContext $context): void {
$context->registerEventListener(AddContentSecurityPolicyEvent::class, CSPListener::class);
$context->registerEventListener(AddFeaturePolicyEvent::class, FeaturePolicyListener::class);
+ $context->registerEventListener(BotInstallEvent::class, BotListener::class);
$context->registerEventListener(GroupDeletedEvent::class, GroupDeletedListener::class);
$context->registerEventListener(GroupChangedEvent::class, DisplayNameListener::class);
$context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class);
@@ -186,6 +189,7 @@ public function boot(IBootContext $context): void {
CollaboratorsListener::register($dispatcher);
ResourceListener::register($dispatcher);
ReferenceInvalidationListener::register($dispatcher);
+ BotListener::register($dispatcher);
// Register only when Talk Updates are not disabled
if ($server->getConfig()->getAppValue('spreed', 'changelog', 'yes') === 'yes') {
ChangelogListener::register($dispatcher);
diff --git a/lib/Chat/ChatManager.php b/lib/Chat/ChatManager.php
index 2ef4425be56..104d5fcd7ae 100644
--- a/lib/Chat/ChatManager.php
+++ b/lib/Chat/ChatManager.php
@@ -262,7 +262,7 @@ public function addChangelogMessage(Room $chat, string $message): IComment {
* Sends a new message to the given chat.
*
* @param Room $chat
- * @param Participant $participant
+ * @param ?Participant $participant
* @param string $actorType
* @param string $actorId
* @param string $message
@@ -271,7 +271,7 @@ public function addChangelogMessage(Room $chat, string $message): IComment {
* @param string $referenceId
* @return IComment
*/
- public function sendMessage(Room $chat, Participant $participant, string $actorType, string $actorId, string $message, \DateTime $creationDateTime, ?IComment $replyTo, string $referenceId, bool $silent): IComment {
+ public function sendMessage(Room $chat, ?Participant $participant, string $actorType, string $actorId, string $message, \DateTime $creationDateTime, ?IComment $replyTo, string $referenceId, bool $silent): IComment {
$comment = $this->commentsManager->create($actorType, $actorId, 'chat', (string) $chat->getId());
$comment->setMessage($message, self::MAX_CHAT_LENGTH);
$comment->setCreationDateTime($creationDateTime);
@@ -289,17 +289,23 @@ public function sendMessage(Room $chat, Participant $participant, string $actorT
}
$this->setMessageExpiration($chat, $comment);
- $event = new ChatParticipantEvent($chat, $comment, $participant, $silent);
+ if ($participant instanceof Participant) {
+ $event = new ChatParticipantEvent($chat, $comment, $participant, $silent);
+ } else {
+ $event = new ChatEvent($chat, $comment, false, $silent);
+ }
$this->dispatcher->dispatch(self::EVENT_BEFORE_MESSAGE_SEND, $event);
$shouldFlush = $this->notificationManager->defer();
try {
$this->commentsManager->save($comment);
- $this->participantService->updateLastReadMessage($participant, (int) $comment->getId());
+ if ($participant instanceof Participant) {
+ $this->participantService->updateLastReadMessage($participant, (int) $comment->getId());
+ }
// Update last_message
- if ($comment->getActorType() !== 'bots' || $comment->getActorId() === 'changelog') {
+ if ($comment->getActorType() !== Attendee::ACTOR_BOTS || $comment->getActorId() === 'changelog' || str_starts_with($comment->getActorId(), Attendee::ACTOR_BOT_PREFIX)) {
$this->roomService->setLastMessage($chat, $comment);
$this->unreadCountCache->clear($chat->getId() . '-');
} else {
diff --git a/lib/Chat/Command/Listener.php b/lib/Chat/Command/Listener.php
index 4602eb0e72d..19d5eb90adf 100644
--- a/lib/Chat/Command/Listener.php
+++ b/lib/Chat/Command/Listener.php
@@ -24,6 +24,7 @@
namespace OCA\Talk\Chat\Command;
use OCA\Talk\Chat\ChatManager;
+use OCA\Talk\Events\ChatEvent;
use OCA\Talk\Events\ChatParticipantEvent;
use OCA\Talk\Model\Command;
use OCA\Talk\Service\CommandService;
@@ -47,7 +48,12 @@ public static function register(IEventDispatcher $dispatcher): void {
$dispatcher->addListener(ChatManager::EVENT_BEFORE_MESSAGE_SEND, [self::class, 'executeCommand']);
}
- public static function executeCommand(ChatParticipantEvent $event): void {
+ public static function executeCommand(ChatEvent $event): void {
+ if (!$event instanceof ChatParticipantEvent) {
+ // No commands for bots 🚓
+ return;
+ }
+
$message = $event->getComment();
$participant = $event->getParticipant();
diff --git a/lib/Chat/MessageParser.php b/lib/Chat/MessageParser.php
index 9968a439253..74441ccedbd 100644
--- a/lib/Chat/MessageParser.php
+++ b/lib/Chat/MessageParser.php
@@ -31,6 +31,7 @@
use OCA\Talk\Model\Message;
use OCA\Talk\Participant;
use OCA\Talk\Room;
+use OCA\Talk\Service\BotService;
use OCA\Talk\Service\ParticipantService;
use OCP\Comments\IComment;
use OCP\EventDispatcher\IEventDispatcher;
@@ -48,18 +49,21 @@ class MessageParser {
protected ParticipantService $participantService;
protected array $guestNames = [];
+ protected array $bots = [];
+ protected array $botNames = [];
public function __construct(
IEventDispatcher $dispatcher,
IUserManager $userManager,
ParticipantService $participantService,
+ protected BotService $botService,
) {
$this->dispatcher = $dispatcher;
$this->participantService = $participantService;
$this->userManager = $userManager;
}
- public function createMessage(Room $room, Participant $participant, IComment $comment, IL10N $l): Message {
+ public function createMessage(Room $room, ?Participant $participant, IComment $comment, IL10N $l): Message {
return new Message($room, $participant, $comment, $l);
}
@@ -98,8 +102,17 @@ protected function setActor(Message $message): void {
}
$this->guestNames[$comment->getActorId()] = $displayName;
}
- } elseif ($comment->getActorType() === 'bots') {
+ } elseif ($comment->getActorType() === Attendee::ACTOR_BOTS) {
+ $actorId = $comment->getActorId();
$displayName = $comment->getActorId() . '-bot';
+ $token = $message->getRoom()->getToken();
+ if (str_starts_with($actorId, Attendee::ACTOR_BOT_PREFIX)) {
+ $urlHash = substr($actorId, strlen(Attendee::ACTOR_BOT_PREFIX));
+ $botName = $this->getBotNameByUrlHashForConversation($token, $urlHash);
+ if ($botName) {
+ $displayName = $botName . ' (Bot)';
+ }
+ }
}
$message->setActor(
@@ -108,4 +121,17 @@ protected function setActor(Message $message): void {
$displayName
);
}
+
+ protected function getBotNameByUrlHashForConversation(string $token, string $urlHash): ?string {
+ if (!isset($this->botNames[$token])) {
+ $this->botNames[$token] = [];
+ $bots = $this->botService->getBotsForToken($token);
+ foreach ($bots as $bot) {
+ $botServer = $bot->getBotServer();
+ $this->botNames[$token][$botServer->getUrlHash()] = $botServer->getName();
+ }
+ }
+
+ return $this->botNames[$token][$urlHash] ?? null;
+ }
}
diff --git a/lib/Chat/Parser/Command.php b/lib/Chat/Parser/Command.php
index 6a69f2c8223..e0fa1b2f753 100644
--- a/lib/Chat/Parser/Command.php
+++ b/lib/Chat/Parser/Command.php
@@ -45,6 +45,7 @@ public function parseMessage(Message $message): void {
$participant = $message->getParticipant();
if ($data['visibility'] !== \OCA\Talk\Model\Command::RESPONSE_ALL &&
+ $participant !== null &&
($participant->getAttendee()->getActorType() !== Attendee::ACTOR_USERS
|| $data['user'] !== $participant->getAttendee()->getActorId())) {
$message->setVisibility(false);
diff --git a/lib/Chat/Parser/SystemMessage.php b/lib/Chat/Parser/SystemMessage.php
index 335ecf4c844..41e062c955a 100644
--- a/lib/Chat/Parser/SystemMessage.php
+++ b/lib/Chat/Parser/SystemMessage.php
@@ -118,7 +118,10 @@ public function parseMessage(Message $chatMessage): void {
$parsedParameters = ['actor' => $this->getActorFromComment($room, $comment)];
$participant = $chatMessage->getParticipant();
- if (!$participant->isGuest()) {
+ if ($participant === null) {
+ $currentActorId = null;
+ $currentUserIsActor = false;
+ } elseif (!$participant->isGuest()) {
$currentActorId = $participant->getAttendee()->getActorId();
$currentUserIsActor = $parsedParameters['actor']['type'] === 'user' &&
$participant->getAttendee()->getActorType() === Attendee::ACTOR_USERS &&
@@ -583,19 +586,20 @@ public function parseDeletedMessage(Message $chatMessage): void {
$parsedParameters = ['actor' => $this->getActor($room, $data['deleted_by_type'], $data['deleted_by_id'])];
$participant = $chatMessage->getParticipant();
- $currentActorId = $participant->getAttendee()->getActorId();
$authorIsActor = $data['deleted_by_type'] === $chatMessage->getComment()->getActorType()
&& $data['deleted_by_id'] === $chatMessage->getComment()->getActorId();
- if (!$participant->isGuest()) {
+ if ($participant === null) {
+ $currentUserIsActor = false;
+ } elseif (!$participant->isGuest()) {
$currentUserIsActor = $parsedParameters['actor']['type'] === 'user' &&
$participant->getAttendee()->getActorType() === Attendee::ACTOR_USERS &&
- $currentActorId === $parsedParameters['actor']['id'];
+ $participant->getAttendee()->getActorId() === $parsedParameters['actor']['id'];
} else {
$currentUserIsActor = $parsedParameters['actor']['type'] === 'guest' &&
$participant->getAttendee()->getActorType() === 'guest' &&
- $currentActorId === $parsedParameters['actor']['id'];
+ $participant->getAttendee()->getActorId() === $parsedParameters['actor']['id'];
}
if ($chatMessage->getMessageType() === ChatManager::VERB_MESSAGE_DELETED) {
diff --git a/lib/Chat/Parser/UserMention.php b/lib/Chat/Parser/UserMention.php
index 47495e46e7b..9bd54fb66fb 100644
--- a/lib/Chat/Parser/UserMention.php
+++ b/lib/Chat/Parser/UserMention.php
@@ -135,7 +135,7 @@ public function parseMessage(Message $chatMessage): void {
if ($mention['type'] === 'call') {
$userId = '';
- if ($chatMessage->getParticipant()->getAttendee()->getActorType() === Attendee::ACTOR_USERS) {
+ if ($chatMessage->getParticipant()?->getAttendee()->getActorType() === Attendee::ACTOR_USERS) {
$userId = $chatMessage->getParticipant()->getAttendee()->getActorId();
}
diff --git a/lib/Chat/ReactionManager.php b/lib/Chat/ReactionManager.php
index 52de4d53ab8..d00fb39f8f1 100644
--- a/lib/Chat/ReactionManager.php
+++ b/lib/Chat/ReactionManager.php
@@ -63,7 +63,8 @@ public function __construct(
* Add reaction
*
* @param Room $chat
- * @param Participant $participant
+ * @param string $actorType
+ * @param string $actorId
* @param integer $messageId
* @param string $reaction
* @return IComment
@@ -72,14 +73,14 @@ public function __construct(
* @throws ReactionNotSupportedException
* @throws ReactionOutOfContextException
*/
- public function addReactionMessage(Room $chat, Participant $participant, int $messageId, string $reaction): IComment {
+ public function addReactionMessage(Room $chat, string $actorType, string $actorId, int $messageId, string $reaction): IComment {
$parentMessage = $this->getCommentToReact($chat, (string) $messageId);
try {
// Check if the user already reacted with the same reaction
$this->commentsManager->getReactionComment(
(int) $parentMessage->getId(),
- $participant->getAttendee()->getActorType(),
- $participant->getAttendee()->getActorId(),
+ $actorType,
+ $actorId,
$reaction
);
throw new ReactionAlreadyExistsException();
@@ -87,8 +88,8 @@ public function addReactionMessage(Room $chat, Participant $participant, int $me
}
$comment = $this->commentsManager->create(
- $participant->getAttendee()->getActorType(),
- $participant->getAttendee()->getActorId(),
+ $actorType,
+ $actorId,
'chat',
(string) $chat->getId()
);
@@ -105,7 +106,8 @@ public function addReactionMessage(Room $chat, Participant $participant, int $me
* Delete reaction
*
* @param Room $chat
- * @param Participant $participant
+ * @param string $actorType
+ * @param string $actorId
* @param integer $messageId
* @param string $reaction
* @return IComment
@@ -113,20 +115,20 @@ public function addReactionMessage(Room $chat, Participant $participant, int $me
* @throws ReactionNotSupportedException
* @throws ReactionOutOfContextException
*/
- public function deleteReactionMessage(Room $chat, Participant $participant, int $messageId, string $reaction): IComment {
+ public function deleteReactionMessage(Room $chat, string $actorType, string $actorId, int $messageId, string $reaction): IComment {
// Just to verify that messageId is part of the room and throw error if not.
$this->getCommentToReact($chat, (string) $messageId);
$comment = $this->commentsManager->getReactionComment(
$messageId,
- $participant->getAttendee()->getActorType(),
- $participant->getAttendee()->getActorId(),
+ $actorType,
+ $actorId,
$reaction
);
$comment->setMessage(
json_encode([
- 'deleted_by_type' => $participant->getAttendee()->getActorType(),
- 'deleted_by_id' => $participant->getAttendee()->getActorId(),
+ 'deleted_by_type' => $actorType,
+ 'deleted_by_id' => $actorId,
'deleted_on' => $this->timeFactory->getDateTime()->getTimestamp(),
])
);
@@ -135,8 +137,8 @@ public function deleteReactionMessage(Room $chat, Participant $participant, int
$this->chatManager->addSystemMessage(
$chat,
- $participant->getAttendee()->getActorType(),
- $participant->getAttendee()->getActorId(),
+ $actorType,
+ $actorId,
json_encode(['message' => 'reaction_revoked', 'parameters' => ['message' => (int) $comment->getId()]]),
$this->timeFactory->getDateTime(),
false,
diff --git a/lib/Chat/SystemMessage/Listener.php b/lib/Chat/SystemMessage/Listener.php
index 85e2a17a998..4fb0eba8e62 100644
--- a/lib/Chat/SystemMessage/Listener.php
+++ b/lib/Chat/SystemMessage/Listener.php
@@ -448,12 +448,12 @@ protected function sendSystemMessage(Room $room, string $message, array $paramet
} elseif (\OC::$CLI || $this->session->exists('talk-overwrite-actor-cli')) {
$actorType = Attendee::ACTOR_GUESTS;
$actorId = 'cli';
- } elseif ($this->session->exists('talk-overwrite-actor')) {
- $actorType = Attendee::ACTOR_USERS;
- $actorId = $this->session->get('talk-overwrite-actor');
} elseif ($this->session->exists('talk-overwrite-actor-type')) {
$actorType = $this->session->get('talk-overwrite-actor-type');
$actorId = $this->session->get('talk-overwrite-actor-id');
+ } elseif ($this->session->exists('talk-overwrite-actor-id')) {
+ $actorType = Attendee::ACTOR_USERS;
+ $actorId = $this->session->get('talk-overwrite-actor-id');
} else {
$actorType = Attendee::ACTOR_GUESTS;
$sessionId = $this->talkSession->getSessionForRoom($room->getToken());
diff --git a/lib/Command/Bot/Install.php b/lib/Command/Bot/Install.php
new file mode 100644
index 00000000000..c481a7d0168
--- /dev/null
+++ b/lib/Command/Bot/Install.php
@@ -0,0 +1,112 @@
+
+ *
+ * @author Joas Schilling
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\Talk\Command\Bot;
+
+use OC\Core\Command\Base;
+use OCA\Talk\Model\Bot;
+use OCA\Talk\Model\BotServer;
+use OCA\Talk\Model\BotServerMapper;
+use OCP\Http\Client\IClientService;
+use OCP\Util;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class Install extends Base {
+ public function __construct(
+ private BotServerMapper $botServerMapper,
+ private IClientService $clientService,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ parent::configure();
+ $this
+ ->setName('talk:bot:install')
+ ->setDescription('Install a new bot on the server')
+ ->addArgument(
+ 'name',
+ InputArgument::REQUIRED,
+ 'The name under which the messages will be posted'
+ )
+ ->addArgument(
+ 'secret',
+ InputArgument::REQUIRED,
+ 'Secret used to validate API calls'
+ )
+ ->addArgument(
+ 'url',
+ InputArgument::REQUIRED,
+ 'Webhook endpoint to post messages to'
+ )
+ ->addArgument(
+ 'description',
+ InputArgument::OPTIONAL,
+ 'Optional description shown in the admin settings'
+ )
+ ->addOption(
+ 'no-setup',
+ null,
+ InputOption::VALUE_NONE,
+ 'Prevent moderators from setting up the bot in a conversation'
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $client = $this->clientService->newClient();
+ if (!method_exists($client, 'postAsync')) {
+ $output->writeln('You need Nextcloud Server version 27.1 or higher for Bot support (detected: ' . implode('.', Util::getVersion()) . ').');
+ return 1;
+ }
+
+ $name = $input->getArgument('name');
+ $secret = $input->getArgument('secret');
+ $url = $input->getArgument('url');
+ $description = $input->getArgument('description');
+ $noSetup = $input->getOption('no-setup');
+
+ $bot = new BotServer();
+ $bot->setName($name);
+ $bot->setSecret($secret);
+ $bot->setUrl($url);
+ $bot->setUrlHash(sha1($url));
+ $bot->setDescription($description);
+ $bot->setState($noSetup ? Bot::STATE_NO_SETUP : Bot::STATE_ENABLED);
+ try {
+ $this->botServerMapper->insert($bot);
+ } catch (\Exception $e) {
+ $output->writeln('' . get_class($e) . ': ' . $e->getMessage() . '');
+ return 1;
+ }
+
+
+ $output->writeln('Bot installed');
+ return 0;
+ }
+}
diff --git a/lib/Command/Bot/ListBots.php b/lib/Command/Bot/ListBots.php
new file mode 100644
index 00000000000..9f65a0a0cf5
--- /dev/null
+++ b/lib/Command/Bot/ListBots.php
@@ -0,0 +1,98 @@
+
+ *
+ * @author Joas Schilling
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\Talk\Command\Bot;
+
+use OC\Core\Command\Base;
+use OCA\Talk\Model\BotConversation;
+use OCA\Talk\Model\BotConversationMapper;
+use OCA\Talk\Model\BotServerMapper;
+use OCP\Http\Client\IClientService;
+use OCP\Util;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class ListBots extends Base {
+ public function __construct(
+ private BotConversationMapper $botConversationMapper,
+ private BotServerMapper $botServerMapper,
+ private IClientService $clientService,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ parent::configure();
+ $this
+ ->setName('talk:bot:list')
+ ->setDescription('List all installed bots of the server or a conversation')
+ ->addArgument(
+ 'token',
+ InputArgument::OPTIONAL,
+ 'Conversation token to limit the bot list for'
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $client = $this->clientService->newClient();
+ if (!method_exists($client, 'postAsync')) {
+ $output->writeln('You need Nextcloud Server version 27.1 or higher for Bot support (detected: ' . implode('.', Util::getVersion()) . ').');
+ return 1;
+ }
+
+ $bots = $this->botServerMapper->getAllBots();
+ $token = $input->getArgument('token');
+
+ if ($token) {
+ $botIds = array_map(static function (BotConversation $bot): int {
+ return $bot->getBotId();
+ }, $this->botConversationMapper->findForToken($token));
+ }
+
+ $data = [];
+ foreach ($bots as $bot) {
+ if ($token && !in_array($bot->getId(), $botIds, true)) {
+ continue;
+ }
+
+ $botData = $bot->jsonSerialize();
+
+ if (!$output->isVerbose()) {
+ unset($botData['url']);
+ unset($botData['url_hash']);
+ unset($botData['secret']);
+ unset($botData['last_error_date']);
+ unset($botData['last_error_message']);
+ }
+
+ $data[] = $botData;
+ }
+
+ $this->writeTableInOutputFormat($input, $output, $data);
+ return 0;
+ }
+}
diff --git a/lib/Command/Bot/Remove.php b/lib/Command/Bot/Remove.php
new file mode 100644
index 00000000000..fa6378ea9d9
--- /dev/null
+++ b/lib/Command/Bot/Remove.php
@@ -0,0 +1,68 @@
+
+ *
+ * @author Joas Schilling
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\Talk\Command\Bot;
+
+use OC\Core\Command\Base;
+use OCA\Talk\Model\BotConversationMapper;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class Remove extends Base {
+ public function __construct(
+ private BotConversationMapper $botConversationMapper,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ parent::configure();
+ $this
+ ->setName('talk:bot:remove')
+ ->setDescription('Remove a bot from a conversation')
+ ->addArgument(
+ 'bot-id',
+ InputArgument::REQUIRED,
+ 'The ID of the bot to remove in a conversation'
+ )
+ ->addArgument(
+ 'token',
+ InputArgument::IS_ARRAY,
+ 'Conversation tokens to remove bot up for'
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $botId = (int) $input->getArgument('bot-id');
+ $tokens = $input->getArgument('token');
+
+ $this->botConversationMapper->deleteByBotIdAndTokens($botId, $tokens);
+
+ $output->writeln('Remove bot from given conversations');
+ return 0;
+ }
+}
diff --git a/lib/Command/Bot/Setup.php b/lib/Command/Bot/Setup.php
new file mode 100644
index 00000000000..00562e96553
--- /dev/null
+++ b/lib/Command/Bot/Setup.php
@@ -0,0 +1,103 @@
+
+ *
+ * @author Joas Schilling
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\Talk\Command\Bot;
+
+use OC\Core\Command\Base;
+use OCA\Talk\Exceptions\RoomNotFoundException;
+use OCA\Talk\Manager;
+use OCA\Talk\Model\Bot;
+use OCA\Talk\Model\BotConversation;
+use OCA\Talk\Model\BotConversationMapper;
+use OCA\Talk\Model\BotServerMapper;
+use OCP\AppFramework\Db\DoesNotExistException;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class Setup extends Base {
+ public function __construct(
+ private Manager $roomManager,
+ private BotServerMapper $botServerMapper,
+ private BotConversationMapper $botConversationMapper,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ parent::configure();
+ $this
+ ->setName('talk:bot:setup')
+ ->setDescription('Add a bot to a conversation')
+ ->addArgument(
+ 'bot-id',
+ InputArgument::REQUIRED,
+ 'The ID of the bot to set up in a conversation'
+ )
+ ->addArgument(
+ 'token',
+ InputArgument::IS_ARRAY,
+ 'Conversation tokens to set the bot up for'
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $botId = (int) $input->getArgument('bot-id');
+ $tokens = $input->getArgument('token');
+
+ try {
+ $this->botServerMapper->findById($botId);
+ } catch (DoesNotExistException) {
+ $output->writeln('Bot could not be found by id: ' . $botId . '');
+ return 1;
+ }
+
+ $returnCode = 0;
+ foreach ($tokens as $token) {
+ try {
+ $this->roomManager->getRoomByToken($token);
+ } catch (RoomNotFoundException) {
+ $output->writeln('Conversation could not be found by token: ' . $token . '');
+ return 1;
+ }
+
+ $bot = new BotConversation();
+ $bot->setBotId($botId);
+ $bot->setToken($token);
+ $bot->setState(Bot::STATE_ENABLED);
+
+ try {
+ $this->botConversationMapper->insert($bot);
+ $output->writeln('Successfully set up for conversation ' . $token . '');
+ } catch (\Exception $e) {
+ $output->writeln('' . get_class($e) . ': ' . $e->getMessage() . '');
+ $returnCode = 3;
+ }
+ }
+
+ return $returnCode;
+ }
+}
diff --git a/lib/Command/Bot/State.php b/lib/Command/Bot/State.php
new file mode 100644
index 00000000000..9c059a3ca86
--- /dev/null
+++ b/lib/Command/Bot/State.php
@@ -0,0 +1,83 @@
+
+ *
+ * @author Joas Schilling
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\Talk\Command\Bot;
+
+use OC\Core\Command\Base;
+use OCA\Talk\Model\Bot;
+use OCA\Talk\Model\BotServerMapper;
+use OCP\AppFramework\Db\DoesNotExistException;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class State extends Base {
+ public function __construct(
+ private BotServerMapper $botServerMapper,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ parent::configure();
+ $this
+ ->setName('talk:bot:state')
+ ->setDescription('List all installed bots of the server or a conversation')
+ ->addArgument(
+ 'bot-id',
+ InputArgument::REQUIRED,
+ 'Bot ID to change the state for'
+ )
+ ->addArgument(
+ 'state',
+ InputArgument::REQUIRED,
+ 'New state for the bot (0 = disabled, 1 = enabled, 2 = no setup via GUI)'
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $botId = (int) $input->getArgument('bot-id');
+ $state = (int) $input->getArgument('state');
+
+ if (!in_array($state, [Bot::STATE_DISABLED, Bot::STATE_ENABLED, Bot::STATE_NO_SETUP], true)) {
+ $output->writeln('Provided state is invalid');
+ return 1;
+ }
+
+ try {
+ $bot = $this->botServerMapper->findById($botId);
+ } catch (DoesNotExistException) {
+ $output->writeln('Bot could not be found by id: ' . $botId . '');
+ return 1;
+ }
+
+ $bot->setState($state);
+ $this->botServerMapper->update($bot);
+
+ $output->writeln('Bot state set to ' . $state . '');
+ return 0;
+ }
+}
diff --git a/lib/Command/Bot/Uninstall.php b/lib/Command/Bot/Uninstall.php
new file mode 100644
index 00000000000..157559e7b57
--- /dev/null
+++ b/lib/Command/Bot/Uninstall.php
@@ -0,0 +1,65 @@
+
+ *
+ * @author Joas Schilling
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\Talk\Command\Bot;
+
+use OC\Core\Command\Base;
+use OCA\Talk\Model\BotConversationMapper;
+use OCA\Talk\Model\BotServerMapper;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class Uninstall extends Base {
+ public function __construct(
+ private BotConversationMapper $botConversationMapper,
+ private BotServerMapper $botServerMapper,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ parent::configure();
+ $this
+ ->setName('talk:bot:uninstall')
+ ->setDescription('Uninstall a bot from the server')
+ ->addArgument(
+ 'id',
+ InputArgument::REQUIRED,
+ 'The ID of the bot'
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $botId = (int) $input->getArgument('id');
+
+ $this->botConversationMapper->deleteByBotId($botId);
+ $this->botServerMapper->deleteById($botId);
+
+ $output->writeln('Bot uninstalled');
+ return 0;
+ }
+}
diff --git a/lib/Controller/BotController.php b/lib/Controller/BotController.php
new file mode 100644
index 00000000000..3e763dc13db
--- /dev/null
+++ b/lib/Controller/BotController.php
@@ -0,0 +1,338 @@
+
+ *
+ * @author Joas Schilling
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\Talk\Controller;
+
+use OCA\Talk\Chat\ChatManager;
+use OCA\Talk\Chat\ReactionManager;
+use OCA\Talk\Exceptions\ReactionAlreadyExistsException;
+use OCA\Talk\Exceptions\ReactionNotSupportedException;
+use OCA\Talk\Exceptions\ReactionOutOfContextException;
+use OCA\Talk\Exceptions\UnauthorizedException;
+use OCA\Talk\Manager;
+use OCA\Talk\Middleware\Attribute\RequireLoggedInModeratorParticipant;
+use OCA\Talk\Model\Attendee;
+use OCA\Talk\Model\Bot;
+use OCA\Talk\Model\BotConversation;
+use OCA\Talk\Model\BotConversationMapper;
+use OCA\Talk\Model\BotServer;
+use OCA\Talk\Model\BotServerMapper;
+use OCA\Talk\Service\BotService;
+use OCA\Talk\Service\ChecksumVerificationService;
+use OCA\Talk\Service\ParticipantService;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\Attribute\BruteForceProtection;
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\PublicPage;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Comments\MessageTooLongException;
+use OCP\Comments\NotFoundException;
+use OCP\IRequest;
+use Psr\Log\LoggerInterface;
+
+class BotController extends AEnvironmentAwareController {
+ public function __construct(
+ string $appName,
+ IRequest $request,
+ protected ChatManager $chatManager,
+ protected ParticipantService $participantService,
+ protected ITimeFactory $timeFactory,
+ protected ChecksumVerificationService $checksumVerificationService,
+ protected BotConversationMapper $botConversationMapper,
+ protected BotServerMapper $botServerMapper,
+ protected BotService $botService,
+ protected Manager $manager,
+ protected ReactionManager $reactionManager,
+ protected LoggerInterface $logger,
+ ) {
+ parent::__construct($appName, $request);
+ }
+
+ /**
+ * @param string $token
+ * @param string $message
+ * @return Bot
+ * @throws \InvalidArgumentException When the request could not be linked with a bot
+ */
+ protected function getBotFromHeaders(string $token, string $message): Bot {
+ $random = $this->request->getHeader('X-Nextcloud-Talk-Bot-Random');
+ if (empty($random) || strlen($random) < 32) {
+ $this->logger->error('Invalid Random received from bot response');
+ throw new \InvalidArgumentException('Invalid Random received from bot response', Http::STATUS_BAD_REQUEST);
+ }
+ $checksum = $this->request->getHeader('X-Nextcloud-Talk-Bot-Signature');
+ if (empty($checksum)) {
+ $this->logger->error('Invalid Signature received from bot response');
+ throw new \InvalidArgumentException('Invalid Signature received from bot response', Http::STATUS_BAD_REQUEST);
+ }
+
+ $bots = $this->botService->getBotsForToken($token);
+ foreach ($bots as $botAttempt) {
+ try {
+ $this->checksumVerificationService->validateRequest(
+ $random,
+ $checksum,
+ $botAttempt->getBotServer()->getSecret(),
+ $message
+ );
+ return $botAttempt;
+ } catch (UnauthorizedException) {
+ }
+ }
+
+ $this->logger->debug('No valid Bot entry found');
+ throw new \InvalidArgumentException('No valid Bot entry found', Http::STATUS_UNAUTHORIZED);
+ }
+
+ /**
+ * Sends a new chat message to the given room.
+ *
+ * The author and timestamp are automatically set to the current user/guest
+ * and time.
+ *
+ * @param string $token conversation token
+ * @param string $message the message to send
+ * @param string $referenceId for the message to be able to later identify it again
+ * @param int $replyTo Parent id which this message is a reply to
+ * @param bool $silent If sent silent the chat message will not create any notifications
+ * @return DataResponse the status code is "201 Created" if successful, and
+ * "404 Not found" if the room or session for a guest user was not
+ * found".
+ */
+ #[BruteForceProtection(action: 'bot')]
+ #[PublicPage]
+ public function sendMessage(string $token, string $message, string $referenceId = '', int $replyTo = 0, bool $silent = false): DataResponse {
+ try {
+ $bot = $this->getBotFromHeaders($token, $message);
+ } catch (\InvalidArgumentException $e) {
+ $response = new DataResponse([], $e->getCode());
+ if ($e->getCode() === Http::STATUS_UNAUTHORIZED) {
+ $response->throttle(['action' => 'bot']);
+ }
+ return $response;
+ }
+
+ $room = $this->manager->getRoomByToken($token);
+
+ $actorType = Attendee::ACTOR_BOTS;
+ $actorId = Attendee::ACTOR_BOT_PREFIX . $bot->getBotServer()->getUrlHash();
+
+ $parent = null;
+ if ($replyTo !== 0) {
+ try {
+ $parent = $this->chatManager->getParentComment($room, (string) $replyTo);
+ } catch (NotFoundException $e) {
+ // Someone is trying to reply cross-rooms or to a non-existing message
+ return new DataResponse([], Http::STATUS_BAD_REQUEST);
+ }
+ }
+
+ $this->participantService->ensureOneToOneRoomIsFilled($room);
+ $creationDateTime = $this->timeFactory->getDateTime('now', new \DateTimeZone('UTC'));
+
+ try {
+ $this->chatManager->sendMessage($room, $this->participant, $actorType, $actorId, $message, $creationDateTime, $parent, $referenceId, $silent);
+ } catch (MessageTooLongException) {
+ return new DataResponse([], Http::STATUS_REQUEST_ENTITY_TOO_LARGE);
+ } catch (\Exception) {
+ return new DataResponse([], Http::STATUS_BAD_REQUEST);
+ }
+
+ return new DataResponse([], Http::STATUS_CREATED);
+ }
+
+ #[BruteForceProtection(action: 'bot')]
+ #[PublicPage]
+ public function react(string $token, int $messageId, string $reaction): DataResponse {
+ try {
+ $bot = $this->getBotFromHeaders($token, $reaction);
+ } catch (\InvalidArgumentException $e) {
+ $response = new DataResponse([], $e->getCode());
+ if ($e->getCode() === Http::STATUS_UNAUTHORIZED) {
+ $response->throttle(['action' => 'bot']);
+ }
+ return $response;
+ }
+
+ $room = $this->manager->getRoomByToken($token);
+
+ $actorType = Attendee::ACTOR_BOTS;
+ $actorId = Attendee::ACTOR_BOT_PREFIX . $bot->getBotServer()->getUrlHash();
+
+ try {
+ $this->reactionManager->addReactionMessage(
+ $room,
+ $actorType,
+ $actorId,
+ $messageId,
+ $reaction
+ );
+ } catch (NotFoundException) {
+ return new DataResponse([], Http::STATUS_NOT_FOUND);
+ } catch (ReactionAlreadyExistsException) {
+ return new DataResponse([], Http::STATUS_OK);
+ } catch (ReactionNotSupportedException | ReactionOutOfContextException | \Exception) {
+ return new DataResponse([], Http::STATUS_BAD_REQUEST);
+ }
+
+ return new DataResponse([], Http::STATUS_CREATED);
+ }
+
+ #[BruteForceProtection(action: 'bot')]
+ #[PublicPage]
+ public function deleteReaction(string $token, int $messageId, string $reaction): DataResponse {
+ try {
+ $bot = $this->getBotFromHeaders($token, $reaction);
+ } catch (\InvalidArgumentException $e) {
+ $response = new DataResponse([], $e->getCode());
+ if ($e->getCode() === Http::STATUS_UNAUTHORIZED) {
+ $response->throttle(['action' => 'bot']);
+ }
+ return $response;
+ }
+
+ $room = $this->manager->getRoomByToken($token);
+
+ $actorType = Attendee::ACTOR_BOTS;
+ $actorId = Attendee::ACTOR_BOT_PREFIX . $bot->getBotServer()->getUrlHash();
+
+ try {
+ $this->reactionManager->deleteReactionMessage(
+ $room,
+ $actorType,
+ $actorId,
+ $messageId,
+ $reaction
+ );
+ } catch (ReactionNotSupportedException | ReactionOutOfContextException | NotFoundException) {
+ return new DataResponse([], Http::STATUS_NOT_FOUND);
+ } catch (\Exception) {
+ return new DataResponse([], Http::STATUS_BAD_REQUEST);
+ }
+
+ return new DataResponse([], Http::STATUS_OK);
+ }
+
+ #[NoAdminRequired]
+ #[RequireLoggedInModeratorParticipant]
+ public function listBots(): DataResponse {
+ $alreadyInstalled = array_map(static function (BotConversation $bot): int {
+ return $bot->getBotId();
+ }, $this->botConversationMapper->findForToken($this->room->getToken()));
+
+ $data = [];
+ $bots = $this->botServerMapper->getAllBots();
+ foreach ($bots as $bot) {
+ $botData = $this->formatBot($bot, in_array($bot->getId(), $alreadyInstalled, true));
+ if ($botData !== null) {
+ $data[] = $this->formatBot($bot, in_array($bot->getId(), $alreadyInstalled, true));
+ }
+ }
+
+ return new DataResponse($data);
+ }
+
+ #[NoAdminRequired]
+ #[RequireLoggedInModeratorParticipant]
+ public function enableBot(int $botId): DataResponse {
+ try {
+ $bot = $this->botServerMapper->findById($botId);
+ } catch (DoesNotExistException) {
+ return new DataResponse([
+ 'error' => 'bot',
+ ], Http::STATUS_BAD_REQUEST);
+ }
+
+ if ($bot->getState() !== Bot::STATE_ENABLED) {
+ return new DataResponse([
+ 'error' => 'bot',
+ ], Http::STATUS_BAD_REQUEST);
+ }
+
+ $alreadyInstalled = array_map(static function (BotConversation $bot): int {
+ return $bot->getBotId();
+ }, $this->botConversationMapper->findForToken($this->room->getToken()));
+
+ if (in_array($botId, $alreadyInstalled)) {
+ return new DataResponse($this->formatBot($bot, true), Http::STATUS_OK);
+ }
+
+ $conversationBot = new BotConversation();
+ $conversationBot->setBotId($botId);
+ $conversationBot->setToken($this->room->getToken());
+ $conversationBot->setState(Bot::STATE_ENABLED);
+
+ $this->botConversationMapper->insert($conversationBot);
+ return new DataResponse($this->formatBot($bot, true), Http::STATUS_CREATED);
+ }
+
+ #[NoAdminRequired]
+ #[RequireLoggedInModeratorParticipant]
+ public function disableBot(int $botId): DataResponse {
+ try {
+ $bot = $this->botServerMapper->findById($botId);
+ } catch (DoesNotExistException) {
+ return new DataResponse([
+ 'error' => 'bot',
+ ], Http::STATUS_BAD_REQUEST);
+ }
+
+ if ($bot->getState() !== Bot::STATE_ENABLED) {
+ return new DataResponse([
+ 'error' => 'bot',
+ ], Http::STATUS_BAD_REQUEST);
+ }
+
+ $this->botConversationMapper->deleteByBotIdAndTokens($botId, [$this->room->getToken()]);
+ return new DataResponse($this->formatBot($bot, false), Http::STATUS_OK);
+ }
+
+ /**
+ * @param BotServer $bot
+ * @param bool $conversationEnabled
+ * @return array|null
+ * @psalm-return array{id: int, name: string, description: null|string, state: int}
+ */
+ protected function formatBot(BotServer $bot, bool $conversationEnabled): ?array {
+ $state = $conversationEnabled ? Bot::STATE_ENABLED : Bot::STATE_DISABLED;
+
+ if ($bot->getState() === Bot::STATE_NO_SETUP) {
+ if ($state === Bot::STATE_DISABLED) {
+ return null;
+ }
+ $state = Bot::STATE_NO_SETUP;
+ }
+
+ return [
+ 'id' => $bot->getId(),
+ 'name' => $bot->getName(),
+ 'description' => $bot->getDescription(),
+ 'state' => $state,
+ ];
+ }
+}
diff --git a/lib/Controller/ReactionController.php b/lib/Controller/ReactionController.php
index add91bec709..5d9bbe5317d 100644
--- a/lib/Controller/ReactionController.php
+++ b/lib/Controller/ReactionController.php
@@ -60,7 +60,8 @@ public function react(int $messageId, string $reaction): DataResponse {
try {
$this->reactionManager->addReactionMessage(
$this->getRoom(),
- $this->getParticipant(),
+ $this->getParticipant()->getAttendee()->getActorType(),
+ $this->getParticipant()->getAttendee()->getActorId(),
$messageId,
$reaction
);
@@ -85,7 +86,8 @@ public function delete(int $messageId, string $reaction): DataResponse {
try {
$this->reactionManager->deleteReactionMessage(
$this->getRoom(),
- $this->getParticipant(),
+ $this->getParticipant()->getAttendee()->getActorType(),
+ $this->getParticipant()->getAttendee()->getActorId(),
$messageId,
$reaction
);
diff --git a/lib/Events/BotInstallEvent.php b/lib/Events/BotInstallEvent.php
new file mode 100644
index 00000000000..fa05c44194b
--- /dev/null
+++ b/lib/Events/BotInstallEvent.php
@@ -0,0 +1,55 @@
+
+ *
+ * @author Joas Schilling
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\Talk\Events;
+
+use OCP\EventDispatcher\Event;
+
+class BotInstallEvent extends Event {
+ public function __construct(
+ protected string $name,
+ protected string $secret,
+ protected string $url,
+ protected string $description = '',
+ ) {
+ parent::__construct();
+ }
+
+ public function getName(): string {
+ return $this->name;
+ }
+
+ public function getSecret(): string {
+ return $this->secret;
+ }
+
+ public function getUrl(): string {
+ return $this->url;
+ }
+
+ public function getDescription(): string {
+ return $this->description;
+ }
+}
diff --git a/lib/Events/ChatEvent.php b/lib/Events/ChatEvent.php
index 1b680d4810e..f44a8740d26 100644
--- a/lib/Events/ChatEvent.php
+++ b/lib/Events/ChatEvent.php
@@ -35,6 +35,7 @@ public function __construct(
Room $room,
IComment $comment,
bool $skipLastActivityUpdate = false,
+ protected bool $silent = false,
) {
parent::__construct($room);
$this->comment = $comment;
@@ -59,4 +60,8 @@ public function getComment(): IComment {
public function shouldSkipLastActivityUpdate(): bool {
return $this->skipLastActivityUpdate;
}
+
+ public function isSilentMessage(): bool {
+ return $this->silent;
+ }
}
diff --git a/lib/Events/ChatParticipantEvent.php b/lib/Events/ChatParticipantEvent.php
index e08e0e0dba9..84a5ab3e377 100644
--- a/lib/Events/ChatParticipantEvent.php
+++ b/lib/Events/ChatParticipantEvent.php
@@ -29,7 +29,6 @@
class ChatParticipantEvent extends ChatEvent {
protected Participant $participant;
- protected bool $silent;
public function __construct(
Room $room,
@@ -37,16 +36,11 @@ public function __construct(
Participant $participant,
bool $silent,
) {
- parent::__construct($room, $message);
+ parent::__construct($room, $message, false, $silent);
$this->participant = $participant;
- $this->silent = $silent;
}
public function getParticipant(): Participant {
return $this->participant;
}
-
- public function isSilentMessage(): bool {
- return $this->silent;
- }
}
diff --git a/lib/Federation/CloudFederationProviderTalk.php b/lib/Federation/CloudFederationProviderTalk.php
index 42b93cb36ad..f348f7aeebd 100644
--- a/lib/Federation/CloudFederationProviderTalk.php
+++ b/lib/Federation/CloudFederationProviderTalk.php
@@ -204,6 +204,7 @@ private function shareAccepted(int $id, array $notification): array {
$this->session->set('talk-overwrite-actor-type', $attendee->getActorType());
$this->session->set('talk-overwrite-actor-id', $attendee->getActorId());
+ $this->session->set('talk-overwrite-actor-displayname', $attendee->getDisplayName());
$room = $this->manager->getRoomById($attendee->getRoomId());
$event = new AttendeesAddedEvent($room, [$attendee]);
@@ -211,6 +212,7 @@ private function shareAccepted(int $id, array $notification): array {
$this->session->remove('talk-overwrite-actor-type');
$this->session->remove('talk-overwrite-actor-id');
+ $this->session->remove('talk-overwrite-actor-displayname');
return [];
}
@@ -225,6 +227,7 @@ private function shareDeclined(int $id, array $notification): array {
$this->session->set('talk-overwrite-actor-type', $attendee->getActorType());
$this->session->set('talk-overwrite-actor-id', $attendee->getActorId());
+ $this->session->set('talk-overwrite-actor-displayname', $attendee->getDisplayName());
$room = $this->manager->getRoomById($attendee->getRoomId());
$participant = new Participant($room, $attendee, null);
@@ -232,6 +235,7 @@ private function shareDeclined(int $id, array $notification): array {
$this->session->remove('talk-overwrite-actor-type');
$this->session->remove('talk-overwrite-actor-id');
+ $this->session->remove('talk-overwrite-actor-displayname');
return [];
}
diff --git a/lib/Listener/BotListener.php b/lib/Listener/BotListener.php
new file mode 100644
index 00000000000..c6e73f50930
--- /dev/null
+++ b/lib/Listener/BotListener.php
@@ -0,0 +1,101 @@
+
+ *
+ * @author Joas Schilling
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\Talk\Listener;
+
+use OCA\Talk\Chat\ChatManager;
+use OCA\Talk\Chat\MessageParser;
+use OCA\Talk\Events\BotInstallEvent;
+use OCA\Talk\Events\ChatEvent;
+use OCA\Talk\Events\ChatParticipantEvent;
+use OCA\Talk\Model\Bot;
+use OCA\Talk\Model\BotServer;
+use OCA\Talk\Model\BotServerMapper;
+use OCA\Talk\Service\BotService;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\EventDispatcher\IEventListener;
+use OCP\Server;
+
+/**
+ * @template-implements IEventListener
+ */
+class BotListener implements IEventListener {
+ public function __construct(
+ protected BotServerMapper $botServerMapper,
+ ) {
+ }
+
+ public static function register(IEventDispatcher $dispatcher): void {
+ $dispatcher->addListener(ChatManager::EVENT_AFTER_MESSAGE_SEND, [self::class, 'afterMessageSendStatic']);
+ $dispatcher->addListener(ChatManager::EVENT_AFTER_SYSTEM_MESSAGE_SEND, [self::class, 'afterSystemMessageSendStatic']);
+ }
+
+ public static function afterMessageSendStatic(ChatEvent $event): void {
+ if (!$event instanceof ChatParticipantEvent) {
+ // No bots for bots
+ return;
+ }
+
+ /** @var BotService $service */
+ $service = Server::get(BotService::class);
+ $messageParser = Server::get(MessageParser::class);
+ $service->afterChatMessageSent($event, $messageParser);
+ }
+
+ public static function afterSystemMessageSendStatic(ChatEvent $event): void {
+ /** @var BotService $service */
+ $service = Server::get(BotService::class);
+ $messageParser = Server::get(MessageParser::class);
+ $service->afterSystemMessageSent($event, $messageParser);
+ }
+
+ public function handle(Event $event): void {
+ if ($event instanceof BotInstallEvent) {
+ $this->handleBotInstallEvent($event);
+ }
+ }
+
+ protected function handleBotInstallEvent(BotInstallEvent $event): void {
+ try {
+ $bot = $this->botServerMapper->findByUrlAndSecret($event->getUrl(), $event->getSecret());
+
+ $bot->setName($event->getName());
+ $bot->setDescription($event->getDescription());
+ $this->botServerMapper->update($bot);
+ } catch (DoesNotExistException) {
+ $bot = new BotServer();
+ $bot->setName($event->getName());
+ $bot->setDescription($event->getDescription());
+ $bot->setSecret($event->getSecret());
+ $bot->setUrl($event->getUrl());
+ $bot->setUrlHash(sha1($event->getUrl()));
+ $bot->setState(Bot::STATE_ENABLED);
+ $this->botServerMapper->insert($bot);
+ }
+ }
+}
diff --git a/lib/Listener/CircleMembershipListener.php b/lib/Listener/CircleMembershipListener.php
index 48cd32e94cc..59a83fb599a 100644
--- a/lib/Listener/CircleMembershipListener.php
+++ b/lib/Listener/CircleMembershipListener.php
@@ -109,7 +109,8 @@ protected function addingCircleMemberEvent(AddingCircleMemberEvent $event): void
$invitedBy = $newMember->getInvitedBy();
if ($invitedBy->getUserType() === Member::TYPE_USER && $invitedBy->getUserId() !== '') {
- $this->session->set('talk-overwrite-actor', $invitedBy->getUserId());
+ $this->session->set('talk-overwrite-actor-id', $invitedBy->getUserId());
+ $this->session->set('talk-overwrite-actor-displayname', $invitedBy->getDisplayName());
} elseif ($invitedBy->getUserType() === Member::TYPE_APP && $invitedBy->getBasedOn()->getSource() === Member::APP_OCC) {
$this->session->set('talk-overwrite-actor-cli', 'cli');
}
@@ -117,7 +118,8 @@ protected function addingCircleMemberEvent(AddingCircleMemberEvent $event): void
foreach ($userMembers as $userMember) {
$this->addNewMemberToRooms(array_values($roomsToAdd), $userMember);
}
- $this->session->remove('talk-overwrite-actor');
+ $this->session->remove('talk-overwrite-actor-displayname');
+ $this->session->remove('talk-overwrite-actor-id');
$this->session->remove('talk-overwrite-actor-cli');
}
@@ -165,7 +167,8 @@ protected function removeFormerMemberFromRooms(RemovingCircleMemberEvent $event)
$removedBy = $removedMember->getInvitedBy();
if ($removedBy->getUserType() === Member::TYPE_USER && $removedBy->getUserId() !== '') {
- $this->session->set('talk-overwrite-actor', $removedBy->getUserId());
+ $this->session->set('talk-overwrite-actor-id', $removedBy->getUserId());
+ $this->session->set('talk-overwrite-actor-displayname', $removedBy->getDisplayName());
} elseif ($removedBy->getUserType() === Member::TYPE_APP && $removedBy->getUserId() === 'occ') {
$this->session->set('talk-overwrite-actor-cli', 'cli');
}
@@ -177,7 +180,8 @@ protected function removeFormerMemberFromRooms(RemovingCircleMemberEvent $event)
$this->removeFromRoomsUnlessStillLinked($rooms, $user);
- $this->session->remove('talk-overwrite-actor');
+ $this->session->remove('talk-overwrite-actor-displayname');
+ $this->session->remove('talk-overwrite-actor-id');
$this->session->remove('talk-overwrite-actor-cli');
}
}
diff --git a/lib/Migration/Version18000Date20230504205823.php b/lib/Migration/Version18000Date20230504205823.php
new file mode 100644
index 00000000000..32cbc0c26f2
--- /dev/null
+++ b/lib/Migration/Version18000Date20230504205823.php
@@ -0,0 +1,115 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\Talk\Migration;
+
+use Closure;
+use OCP\DB\ISchemaWrapper;
+use OCP\DB\Types;
+use OCP\Migration\IOutput;
+use OCP\Migration\SimpleMigrationStep;
+
+class Version18000Date20230504205823 extends SimpleMigrationStep {
+
+ /**
+ * @param IOutput $output
+ * @param Closure(): ISchemaWrapper $schemaClosure
+ * @param array $options
+ * @return ?ISchemaWrapper
+ */
+ public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
+ /** @var ISchemaWrapper $schema */
+ $schema = $schemaClosure();
+
+ if (!$schema->hasTable('talk_bots_server')) {
+ $table = $schema->createTable('talk_bots_server');
+ $table->addColumn('id', Types::BIGINT, [
+ 'autoincrement' => true,
+ 'notnull' => true,
+ 'length' => 20,
+ ]);
+ $table->addColumn('name', Types::STRING, [
+ 'length' => 64,
+ ]);
+ $table->addColumn('url', Types::STRING, [
+ 'length' => 4000,
+ ]);
+ $table->addColumn('url_hash', Types::STRING, [
+ 'length' => 64,
+ ]);
+ $table->addColumn('description', Types::STRING, [
+ 'length' => 4000,
+ 'notnull' => false,
+ ]);
+ $table->addColumn('secret', Types::STRING, [
+ 'length' => 128,
+ ]);
+ $table->addColumn('error_count', Types::BIGINT, [
+ 'default' => 0,
+ 'unsigned' => true,
+ ]);
+ $table->addColumn('last_error_date', Types::DATETIME, [
+ 'notnull' => false,
+ ]);
+ $table->addColumn('last_error_message', Types::STRING, [
+ 'length' => 4000,
+ 'notnull' => false,
+ ]);
+ $table->addColumn('state', Types::SMALLINT, [
+ 'default' => 0,
+ 'notnull' => false,
+ 'unsigned' => true,
+ ]);
+
+ $table->setPrimaryKey(['id']);
+ $table->addIndex(['state'], 'talk_bots_server_state');
+
+ $table = $schema->createTable('talk_bots_conversation');
+ $table->addColumn('id', Types::BIGINT, [
+ 'autoincrement' => true,
+ 'notnull' => true,
+ 'length' => 20,
+ ]);
+ $table->addColumn('bot_id', Types::BIGINT, [
+ 'default' => 0,
+ 'unsigned' => true,
+ ]);
+ $table->addColumn('token', Types::STRING, [
+ 'length' => 64,
+ 'notnull' => false,
+ ]);
+ $table->addColumn('state', Types::SMALLINT, [
+ 'default' => 0,
+ 'notnull' => false,
+ 'unsigned' => true,
+ ]);
+
+ $table->setPrimaryKey(['id']);
+ $table->addIndex(['token', 'state'], 'talk_bots_convo_token');
+ $table->addIndex(['bot_id'], 'talk_bots_convo_id');
+ return $schema;
+ }
+
+ return null;
+ }
+}
diff --git a/lib/Model/Attendee.php b/lib/Model/Attendee.php
index cabda580faa..ffb84d9b57e 100644
--- a/lib/Model/Attendee.php
+++ b/lib/Model/Attendee.php
@@ -68,7 +68,9 @@ class Attendee extends Entity {
public const ACTOR_EMAILS = 'emails';
public const ACTOR_CIRCLES = 'circles';
public const ACTOR_BRIDGED = 'bridged';
+ public const ACTOR_BOTS = 'bots';
public const ACTOR_FEDERATED_USERS = 'federated_users';
+ public const ACTOR_BOT_PREFIX = 'bot-';
public const PERMISSIONS_DEFAULT = 0;
public const PERMISSIONS_CUSTOM = 1;
diff --git a/lib/Model/Bot.php b/lib/Model/Bot.php
new file mode 100644
index 00000000000..474fe1fb4e2
--- /dev/null
+++ b/lib/Model/Bot.php
@@ -0,0 +1,52 @@
+
+ *
+ * @author Joas Schilling
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\Talk\Model;
+
+class Bot {
+ public const STATE_DISABLED = 0;
+ public const STATE_ENABLED = 1;
+ public const STATE_NO_SETUP = 2;
+
+ public function __construct(
+ protected BotServer $botServer,
+ protected BotConversation $botConversation,
+ ) {
+ }
+
+ public function getBotServer(): BotServer {
+ return $this->botServer;
+ }
+
+ public function getBotConversation(): BotConversation {
+ return $this->botConversation;
+ }
+
+ public function isEnabled(): bool {
+ return $this->botServer->getState() !== self::STATE_DISABLED
+ && $this->botConversation->getState() !== self::STATE_DISABLED;
+ }
+}
diff --git a/lib/Model/BotConversation.php b/lib/Model/BotConversation.php
new file mode 100644
index 00000000000..7d90d17a26e
--- /dev/null
+++ b/lib/Model/BotConversation.php
@@ -0,0 +1,55 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\Talk\Model;
+
+use OCP\AppFramework\Db\Entity;
+
+/**
+ * @method void setBotId(int $botId)
+ * @method int getBotId()
+ * @method void setToken(string $token)
+ * @method string getToken()
+ * @method void setState(int $state)
+ * @method int getState()
+ */
+class BotConversation extends Entity implements \JsonSerializable {
+ protected int $botId = 0;
+ protected string $token = '';
+ protected int $state = Bot::STATE_DISABLED;
+
+ public function __construct() {
+ $this->addType('bot_id', 'int');
+ $this->addType('token', 'string');
+ $this->addType('state', 'int');
+ }
+
+ public function jsonSerialize(): array {
+ return [
+ 'id' => $this->getId(),
+ 'bot_id' => $this->getBotId(),
+ 'token' => $this->getToken(),
+ 'state' => $this->getState(),
+ ];
+ }
+}
diff --git a/lib/Model/BotConversationMapper.php b/lib/Model/BotConversationMapper.php
new file mode 100644
index 00000000000..fdaa638de7b
--- /dev/null
+++ b/lib/Model/BotConversationMapper.php
@@ -0,0 +1,74 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\Talk\Model;
+
+use OCP\AppFramework\Db\QBMapper;
+use OCP\AppFramework\Db\TTransactional;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+
+/**
+ * @method BotConversation mapRowToEntity(array $row)
+ * @method BotConversation findEntity(IQueryBuilder $query)
+ * @method BotConversation[] findEntities(IQueryBuilder $query)
+ * @template-extends QBMapper
+ */
+class BotConversationMapper extends QBMapper {
+ use TTransactional;
+
+ public function __construct(
+ IDBConnection $db,
+ ) {
+ parent::__construct($db, 'talk_bots_conversation', BotConversation::class);
+ }
+
+ /**
+ * @return BotConversation[]
+ */
+ public function findForToken(string $token): array {
+ $query = $this->db->getQueryBuilder();
+ $query->select('*')
+ ->from($this->getTableName())
+ ->where($query->expr()->eq('token', $query->createNamedParameter($token)));
+
+ return $this->findEntities($query);
+ }
+
+ public function deleteByBotId(int $botId): int {
+ $query = $this->db->getQueryBuilder();
+ $query->delete($this->getTableName())
+ ->where($query->expr()->eq('bot_id', $query->createNamedParameter($botId, IQueryBuilder::PARAM_INT)));
+
+ return $query->executeStatement();
+ }
+
+ public function deleteByBotIdAndTokens(int $botId, array $tokens): int {
+ $query = $this->db->getQueryBuilder();
+ $query->delete($this->getTableName())
+ ->where($query->expr()->eq('bot_id', $query->createNamedParameter($botId, IQueryBuilder::PARAM_INT)))
+ ->andWhere($query->expr()->in('token', $query->createNamedParameter($tokens, IQueryBuilder::PARAM_STR_ARRAY)));
+
+ return $query->executeStatement();
+ }
+}
diff --git a/lib/Model/BotServer.php b/lib/Model/BotServer.php
new file mode 100644
index 00000000000..265845daf8a
--- /dev/null
+++ b/lib/Model/BotServer.php
@@ -0,0 +1,85 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\Talk\Model;
+
+use OCP\AppFramework\Db\Entity;
+
+/**
+ * @method void setName(string $name)
+ * @method string getName()
+ * @method void setUrl(string $url)
+ * @method string getUrl()
+ * @method void setUrlHash(string $urlHash)
+ * @method string getUrlHash()
+ * @method void setDescription(?string $description)
+ * @method null|string getDescription()
+ * @method void setSecret(string $secret)
+ * @method string getSecret()
+ * @method void setErrorCount(int $errorCount)
+ * @method int getErrorCount()
+ * @method void setLastErrorDate(?\DateTimeImmutable $lastErrorDate)
+ * @method ?\DateTimeImmutable getLastErrorDate()
+ * @method void setLastErrorMessage(string $lastErrorMessage)
+ * @method string getLastErrorMessage()
+ * @method void setState(int $state)
+ * @method int getState()
+ */
+class BotServer extends Entity implements \JsonSerializable {
+ protected string $name = '';
+ protected string $url = '';
+ protected string $urlHash = '';
+ protected ?string $description = null;
+ protected string $secret = '';
+ protected int $errorCount = 0;
+ protected ?\DateTimeImmutable $lastErrorDate = null;
+ protected ?string $lastErrorMessage = null;
+ protected int $state = Bot::STATE_DISABLED;
+
+ public function __construct() {
+ $this->addType('name', 'string');
+ $this->addType('url', 'string');
+ $this->addType('url_hash', 'string');
+ $this->addType('description', 'string');
+ $this->addType('secret', 'string');
+ $this->addType('error_count', 'int');
+ $this->addType('last_error_date', 'datetime');
+ $this->addType('last_error_message', 'string');
+ $this->addType('state', 'int');
+ }
+
+ public function jsonSerialize(): array {
+ return [
+ 'id' => $this->getId(),
+ 'name' => $this->getName(),
+ 'url' => $this->getUrl(),
+ 'url_hash' => $this->getUrlHash(),
+ 'description' => $this->getDescription(),
+ 'secret' => $this->getSecret(),
+ 'error_count' => $this->getErrorCount(),
+ 'last_error_date' => $this->getLastErrorDate() ? $this->getLastErrorDate()->getTimestamp() : 0,
+ 'last_error_message' => $this->getLastErrorMessage(),
+ 'state' => $this->getState(),
+ ];
+ }
+}
diff --git a/lib/Model/BotServerMapper.php b/lib/Model/BotServerMapper.php
new file mode 100644
index 00000000000..caee0e77928
--- /dev/null
+++ b/lib/Model/BotServerMapper.php
@@ -0,0 +1,104 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\Talk\Model;
+
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Db\QBMapper;
+use OCP\AppFramework\Db\TTransactional;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+
+/**
+ * @method BotServer mapRowToEntity(array $row)
+ * @method BotServer findEntity(IQueryBuilder $query)
+ * @method BotServer[] findEntities(IQueryBuilder $query)
+ * @template-extends QBMapper
+ */
+class BotServerMapper extends QBMapper {
+ use TTransactional;
+
+ public function __construct(
+ IDBConnection $db,
+ ) {
+ parent::__construct($db, 'talk_bots_server', BotServer::class);
+ }
+
+ /**
+ * @throws DoesNotExistException
+ */
+ public function findById(int $botId): BotServer {
+ $query = $this->db->getQueryBuilder();
+ $query->select('*')
+ ->from($this->getTableName())
+ ->where($query->expr()->eq('id', $query->createNamedParameter($botId, IQueryBuilder::PARAM_INT)));
+
+ return $this->findEntity($query);
+ }
+
+ /**
+ * @throws DoesNotExistException
+ */
+ public function findByUrlAndSecret(string $url, string $secret): BotServer {
+ $urlHash = sha1($url);
+
+ $query = $this->db->getQueryBuilder();
+ $query->select('*')
+ ->from($this->getTableName())
+ ->where($query->expr()->eq('url_hash', $query->createNamedParameter($urlHash)))
+ ->andWhere($query->expr()->eq('secret', $query->createNamedParameter($secret)));
+
+ return $this->findEntity($query);
+ }
+
+ public function deleteById(int $botId): int {
+ $query = $this->db->getQueryBuilder();
+ $query->delete($this->getTableName())
+ ->where($query->expr()->eq('id', $query->createNamedParameter($botId, IQueryBuilder::PARAM_INT)));
+
+ return $query->executeStatement();
+ }
+
+ /**
+ * @return BotServer[]
+ */
+ public function findByIds(array $botIds): array {
+ $query = $this->db->getQueryBuilder();
+ $query->select('*')
+ ->from($this->getTableName())
+ ->where($query->expr()->in('id', $query->createNamedParameter($botIds, IQueryBuilder::PARAM_INT_ARRAY)));
+
+ return $this->findEntities($query);
+ }
+
+ /**
+ * @return BotServer[]
+ */
+ public function getAllBots(): array {
+ $query = $this->db->getQueryBuilder();
+ $query->select('*')
+ ->from($this->getTableName());
+
+ return $this->findEntities($query);
+ }
+}
diff --git a/lib/Model/Message.php b/lib/Model/Message.php
index 78d614a4ec2..669b73cbcc7 100644
--- a/lib/Model/Message.php
+++ b/lib/Model/Message.php
@@ -39,7 +39,7 @@ class Message {
/** @var IL10N */
protected $l;
- /** @var Participant */
+ /** @var null|Participant */
protected $participant;
/** @var bool */
@@ -67,7 +67,7 @@ class Message {
protected $actorDisplayName = '';
public function __construct(Room $room,
- Participant $participant,
+ ?Participant $participant,
IComment $comment,
IL10N $l) {
$this->room = $room;
@@ -92,7 +92,7 @@ public function getL10n(): IL10N {
return $this->l;
}
- public function getParticipant(): Participant {
+ public function getParticipant(): ?Participant {
return $this->participant;
}
diff --git a/lib/Service/BotService.php b/lib/Service/BotService.php
new file mode 100644
index 00000000000..59cfa5e37ff
--- /dev/null
+++ b/lib/Service/BotService.php
@@ -0,0 +1,292 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\Talk\Service;
+
+use OCA\Talk\Chat\MessageParser;
+use OCA\Talk\Events\ChatEvent;
+use OCA\Talk\Events\ChatParticipantEvent;
+use OCA\Talk\Model\Attendee;
+use OCA\Talk\Model\Bot;
+use OCA\Talk\Model\BotConversation;
+use OCA\Talk\Model\BotConversationMapper;
+use OCA\Talk\Model\BotServerMapper;
+use OCA\Talk\Room;
+use OCA\Talk\TalkSession;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Http\Client\IClientService;
+use OCP\Http\Client\IResponse;
+use OCP\IConfig;
+use OCP\ISession;
+use OCP\IURLGenerator;
+use OCP\IUser;
+use OCP\IUserSession;
+use OCP\L10N\IFactory;
+use OCP\Security\ISecureRandom;
+use OCP\Util;
+use Psr\Log\LoggerInterface;
+
+class BotService {
+ public function __construct(
+ protected BotServerMapper $botServerMapper,
+ protected BotConversationMapper $botConversationMapper,
+ protected IClientService $clientService,
+ protected IConfig $serverConfig,
+ protected IUserSession $userSession,
+ protected TalkSession $talkSession,
+ protected ISession $session,
+ protected ISecureRandom $secureRandom,
+ protected IURLGenerator $urlGenerator,
+ protected IFactory $l10nFactory,
+ protected ITimeFactory $timeFactory,
+ protected LoggerInterface $logger,
+ ) {
+ }
+
+ public function afterChatMessageSent(ChatParticipantEvent $event, MessageParser $messageParser): void {
+ $client = $this->clientService->newClient();
+ if (!method_exists($client, 'postAsync')) {
+ $this->logger->error('You need Nextcloud Server version 27.1 or higher for Bot support (detected: ' . implode('.', Util::getVersion()) . ')');
+ return;
+ }
+
+ $bots = $this->getBotsForToken($event->getRoom()->getToken());
+ if (empty($bots)) {
+ return;
+ }
+
+ $message = $messageParser->createMessage(
+ $event->getRoom(),
+ $event->getParticipant(),
+ $event->getComment(),
+ $this->l10nFactory->get('spreed', 'en', 'en')
+ );
+ $messageParser->parseMessage($message);
+ $messageData = [
+ 'message' => $message->getMessage(),
+ 'parameters' => $message->getMessageParameters(),
+ ];
+
+ $attendee = $event->getParticipant()->getAttendee();
+
+ $this->sendAsyncRequests($bots, [
+ 'type' => 'Create',
+ 'actor' => [
+ 'type' => 'Person',
+ 'id' => $attendee->getActorType() . '/' . $attendee->getActorId(),
+ 'name' => $attendee->getDisplayName(),
+ ],
+ 'object' => [
+ 'type' => 'Note',
+ 'id' => $event->getComment()->getId(),
+ 'name' => 'message',
+ 'content' => json_encode($messageData, JSON_THROW_ON_ERROR),
+ 'mediaType' => 'text/markdown', // FIXME or text/plain when markdown is disabled
+ ],
+ 'target' => [
+ 'type' => 'Collection',
+ 'id' => $event->getRoom()->getToken(),
+ 'name' => $event->getRoom()->getName(),
+ ]
+ ]);
+ }
+
+ public function afterSystemMessageSent(ChatEvent $event, MessageParser $messageParser): void {
+ $bots = $this->getBotsForToken($event->getRoom()->getToken());
+ if (empty($bots)) {
+ return;
+ }
+
+ $message = $messageParser->createMessage(
+ $event->getRoom(),
+ null,
+ $event->getComment(),
+ $this->l10nFactory->get('spreed', 'en', 'en')
+ );
+ $messageParser->parseMessage($message);
+ $messageData = [
+ 'message' => $message->getMessage(),
+ 'parameters' => $message->getMessageParameters(),
+ ];
+
+ $this->sendAsyncRequests($bots, [
+ 'type' => 'Activity',
+ 'actor' => [
+ 'type' => 'Person',
+ 'id' => $message->getActorType() . '/' . $message->getActorId(),
+ 'name' => $message->getActorDisplayName(),
+ ],
+ 'object' => [
+ 'type' => 'Note',
+ 'id' => $event->getComment()->getId(),
+ 'name' => $message->getMessageRaw(),
+ 'content' => json_encode($messageData),
+ 'mediaType' => 'text/markdown',
+ ],
+ 'target' => [
+ 'type' => 'Collection',
+ 'id' => $event->getRoom()->getToken(),
+ 'name' => $event->getRoom()->getName(),
+ ]
+ ]);
+ }
+
+ /**
+ * @param Bot[] $bots
+ * @param array $body
+ */
+ protected function sendAsyncRequests(array $bots, array $body): void {
+ $jsonBody = json_encode($body, JSON_THROW_ON_ERROR);
+
+ foreach ($bots as $bot) {
+ $botServer = $bot->getBotServer();
+ $random = $this->secureRandom->generate(64);
+ $hash = hash_hmac('sha256', $random . $jsonBody, $botServer->getSecret());
+ $headers = [
+ 'Content-Type' => 'application/json',
+ 'X-Nextcloud-Talk-Random' => $random,
+ 'X-Nextcloud-Talk-Signature' => $hash,
+ 'X-Nextcloud-Talk-Backend' => rtrim($this->serverConfig->getSystemValueString('overwrite.cli.url'), '/') . '/',
+ 'OCS-APIRequest' => 'true', // FIXME optional?
+ ];
+
+ $data = [
+ 'verify' => false,
+ 'nextcloud' => [
+ 'allow_local_address' => true, // FIXME don't enforce
+ ],
+ 'headers' => $headers,
+ 'timeout' => 5,
+ 'body' => json_encode($body),
+ ];
+
+ $client = $this->clientService->newClient();
+ $promise = $client->postAsync($botServer->getUrl(), $data);
+
+ $promise->then(function (IResponse $response) use ($botServer) {
+ if ($response->getStatusCode() !== Http::STATUS_OK && $response->getStatusCode() !== Http::STATUS_ACCEPTED) {
+ $this->logger->error('Bot responded with unexpected status code (Received: ' . $response->getStatusCode() . '), increasing error count');
+ $botServer->setErrorCount($botServer->getErrorCount() + 1);
+ $botServer->setLastErrorDate($this->timeFactory->now());
+ $botServer->setLastErrorMessage('UnexpectedStatusCode: ' . $response->getStatusCode());
+ $this->botServerMapper->update($botServer);
+ }
+ }, function (\Exception $exception) use ($botServer) {
+ $this->logger->error('Bot error occurred, increasing error count', ['exception' => $exception]);
+ $botServer->setErrorCount($botServer->getErrorCount() + 1);
+ $botServer->setLastErrorDate($this->timeFactory->now());
+ $botServer->setLastErrorMessage(get_class($exception) . ': ' . $exception->getMessage());
+ $this->botServerMapper->update($botServer);
+ });
+ }
+ }
+
+ /**
+ * @param Room $room
+ * @return array
+ * @psalm-return array{type: string, id: string, name: string}
+ */
+ protected function getActor(Room $room): array {
+ if (\OC::$CLI || $this->session->exists('talk-overwrite-actor-cli')) {
+ return [
+ 'type' => Attendee::ACTOR_GUESTS,
+ 'id' => 'cli',
+ 'name' => 'Administration',
+ ];
+ }
+
+ if ($this->session->exists('talk-overwrite-actor-type')) {
+ return [
+ 'type' => $this->session->get('talk-overwrite-actor-type'),
+ 'id' => $this->session->get('talk-overwrite-actor-id'),
+ 'name' => $this->session->get('talk-overwrite-actor-displayname'),
+ ];
+ }
+
+ if ($this->session->exists('talk-overwrite-actor-id')) {
+ return [
+ 'type' => Attendee::ACTOR_USERS,
+ 'id' => $this->session->get('talk-overwrite-actor-id'),
+ 'name' => $this->session->get('talk-overwrite-actor-displayname'),
+ ];
+ }
+
+ $user = $this->userSession->getUser();
+ if ($user instanceof IUser) {
+ return [
+ 'type' => Attendee::ACTOR_USERS,
+ 'id' => $user->getUID(),
+ 'name' => $user->getDisplayName(),
+ ];
+ }
+
+ $sessionId = $this->talkSession->getSessionForRoom($room->getToken());
+ $actorId = $sessionId ? sha1($sessionId) : 'failed-to-get-session';
+ return [
+ 'type' => Attendee::ACTOR_GUESTS,
+ 'id' => $actorId,
+ 'name' => $user->getDisplayName(),
+ ];
+ }
+
+ /**
+ * @param string $token
+ * @return Bot[]
+ */
+ public function getBotsForToken(string $token): array {
+ $botConversations = $this->botConversationMapper->findForToken($token);
+
+ if (empty($botConversations)) {
+ return [];
+ }
+
+ $botIds = array_map(static fn (BotConversation $bot): int => $bot->getBotId(), $botConversations);
+
+ $serversMap = [];
+ $botServers = $this->botServerMapper->findByIds($botIds);
+ foreach ($botServers as $botServer) {
+ $serversMap[$botServer->getId()] = $botServer;
+ }
+
+ $bots = [];
+ foreach ($botConversations as $botConversation) {
+ if (!isset($serversMap[$botConversation->getBotId()])) {
+ $this->logger->warning('Can not find bot by ID ' . $botConversation->getBotId() . ' for token ' . $botConversation->getToken());
+ continue;
+ }
+
+ $bot = new Bot(
+ $serversMap[$botConversation->getBotId()],
+ $botConversation,
+ );
+
+ if ($bot->isEnabled()) {
+ $bots[] = $bot;
+ }
+ }
+
+ return $bots;
+ }
+}
diff --git a/src/components/ConversationSettings/BotsSettings.vue b/src/components/ConversationSettings/BotsSettings.vue
new file mode 100644
index 00000000000..4902171d513
--- /dev/null
+++ b/src/components/ConversationSettings/BotsSettings.vue
@@ -0,0 +1,183 @@
+
+
+
+
+
+ {{ botsSettingsDescription }}
+
+
+
+
+
+
+ {{ bot.name }}
+
+
+ {{ bot.description ?? t('spreed', 'Description is not provided') }}
+
+
+
+
+ {{ toggleButtonTitle(bot) }}
+
+
+
+
+
+
+
+
+
diff --git a/src/components/ConversationSettings/ConversationSettingsDialog.vue b/src/components/ConversationSettings/ConversationSettingsDialog.vue
index 2e80672b838..4c60f7bd15b 100644
--- a/src/components/ConversationSettings/ConversationSettingsDialog.vue
+++ b/src/components/ConversationSettings/ConversationSettingsDialog.vue
@@ -3,7 +3,7 @@
-
- @author Vincent Petry
-
- - @license GNU AGPL version 3 or any later version
+ - @license AGPL-3.0-or-later
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
@@ -84,6 +84,13 @@
+
+
+
+
+
+ *
+ * @author Maksim Sukharev
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+import axios from '@nextcloud/axios'
+import { generateOcsUrl } from '@nextcloud/router'
+
+/**
+ * Get information about available bots for this conversation
+ *
+ * @param {string} token The conversation token
+ * @return {object} The axios response
+ */
+const getConversationBots = async function(token) {
+ return axios.get(generateOcsUrl('/apps/spreed/api/v1/bot/{token}', { token }))
+}
+
+/**
+ * Enable bot for conversation
+ *
+ * @param {string} token The conversation token
+ * @param {number} id The bot id
+ * @return {object} The axios response
+ */
+const enableBotForConversation = async function(token, id) {
+ return axios.post(generateOcsUrl('/apps/spreed/api/v1/bot/{token}/{id}', { token, id }))
+}
+
+/**
+ * Disable bot for conversation
+ *
+ * @param {string} token The conversation token
+ * @param {number} id The bot id
+ * @return {object} The axios response
+ */
+const disableBotForConversation = async function(token, id) {
+ return axios.delete(generateOcsUrl('/apps/spreed/api/v1/bot/{token}/{id}', { token, id }))
+}
+
+/**
+ * Send a message to bot in conversation
+ *
+ * @param {string} token The conversation token
+ * @param {object} object Object with arguments
+ * @param {string} object.message The message to send
+ * @param {string} object.referenceId for the message to be able to later identify it again
+ * @param {number} object.replyTo Parent id which this message is a reply to
+ * @param {boolean} object.silent If sent silent the chat message will not create any notifications
+ * @return {object} The axios response
+ */
+const sendMessageToBot = async function(token, { message, referenceId, replyTo, silent }) {
+ return axios.post(generateOcsUrl('/apps/spreed/api/v1/bot/{token}/message', { token }), {
+ message,
+ referenceId,
+ replyTo,
+ silent,
+ })
+}
+
+export {
+ getConversationBots,
+ enableBotForConversation,
+ disableBotForConversation,
+ sendMessageToBot,
+}
diff --git a/src/stores/bots.js b/src/stores/bots.js
new file mode 100644
index 00000000000..2d82a380c1a
--- /dev/null
+++ b/src/stores/bots.js
@@ -0,0 +1,73 @@
+/**
+ * @copyright Copyright (c) 2023 Maksim Sukharev
+ *
+ * @author Maksim Sukharev
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+import { defineStore } from 'pinia'
+import Vue from 'vue'
+
+import { BOT } from '../constants.js'
+import { disableBotForConversation, enableBotForConversation, getConversationBots } from '../services/botsService.js'
+
+export const useBotsStore = defineStore('bots', {
+ state: () => ({
+ bots: {},
+ }),
+
+ actions: {
+ getConversationBots(token) {
+ return this.bots[token] ? Object.values(this.bots[token]) : []
+ },
+
+ /**
+ * Fetch a list of available bots for conversation and save them to store
+ *
+ * @param {string} token The conversation token
+ * @return {Array} An array of bots ids
+ */
+ async loadConversationBots(token) {
+ if (!this.bots[token]) {
+ Vue.set(this.bots, token, {})
+ }
+
+ const response = await getConversationBots(token)
+
+ return response.data.ocs.data.map((bot) => {
+ Vue.set(this.bots[token], bot.id, bot)
+ return bot.id
+ })
+ },
+
+ /**
+ * Enable or disable a bot for conversation
+ *
+ * @param {string} token The conversation token
+ * @param {object} bot The bot to toggle state
+ */
+ async toggleBotState(token, bot) {
+ const response = bot.state === BOT.STATE.ENABLED
+ ? await disableBotForConversation(token, bot.id)
+ : await enableBotForConversation(token, bot.id)
+
+ Vue.set(this.bots[token], bot.id, response.data.ocs.data)
+ },
+
+ },
+})
diff --git a/tests/integration/features/bootstrap/CommandLineTrait.php b/tests/integration/features/bootstrap/CommandLineTrait.php
index e13a00d8327..968486a8bd7 100644
--- a/tests/integration/features/bootstrap/CommandLineTrait.php
+++ b/tests/integration/features/bootstrap/CommandLineTrait.php
@@ -86,6 +86,10 @@ public function invokingTheCommand($cmd) {
$this->runOcc($args);
}
+ public function getLastStdOut(): string {
+ return $this->lastStdOut;
+ }
+
/**
* Find exception texts in stderr
*/
@@ -160,6 +164,13 @@ public function theCommandOutputContainsTheText($text) {
Assert::assertStringContainsString($text, $this->lastStdOut, 'The command did not output the expected text on stdout');
}
+ /**
+ * @Then /^the command output is empty$/
+ */
+ public function theCommandOutputIsEmpty() {
+ Assert::assertEmpty($this->lastStdOut, 'The command did output unexpected text on stdout');
+ }
+
/**
* @Then /^the command output contains the list entry '([^']*)' with value '([^']*)'$/
*/
diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php
index 2e7e3bc54a2..a5d159ba4dd 100644
--- a/tests/integration/features/bootstrap/FeatureContext.php
+++ b/tests/integration/features/bootstrap/FeatureContext.php
@@ -64,6 +64,12 @@ class FeatureContext implements Context, SnippetAcceptingContext {
protected static $questionToPollId;
/** @var array[] */
protected static $lastNotifications;
+ /** @var array */
+ protected static $botIdToName;
+ /** @var array */
+ protected static $botNameToId;
+ /** @var array */
+ protected static $botNameToHash;
protected static $permissionsMap = [
@@ -1664,6 +1670,7 @@ public function userSeesPeersInCall(string $user, int $numPeers, string $identif
*/
public function userSendsMessageToRoom(string $user, string $sendingMode, string $message, string $identifier, string $statusCode, string $apiVersion = 'v1') {
$message = substr($message, 1, -1);
+ $message = str_replace('\n', "\n", $message);
if ($sendingMode === 'silent sends') {
$body = new TableNode([['message', $message], ['silent', true]]);
} else {
@@ -2296,6 +2303,23 @@ protected function compareDataResponse(TableNode $formData = null) {
$expected[$i]['messageParameters'] = str_replace($matches[0], json_encode($messages[$i]['messageParameters']['object']['icon-url']), $expected[$i]['messageParameters']);
}
}
+ $expected[$i]['message'] = str_replace('\n', "\n", $expected[$i]['message']);
+
+ if ($expected[$i]['actorType'] === 'bots') {
+ $result = preg_match('/BOT\(([^)]+)\)/', $expected[$i]['actorId'], $matches);
+ if ($result && isset(self::$botNameToHash[$matches[1]])) {
+ $expected[$i]['actorId'] = 'bot-' . self::$botNameToHash[$matches[1]];
+ }
+ }
+
+ // Replace the date/time line of the call summary because we can not know if we jumped a minute, hour or day on the execution.
+ if (str_contains($expected[$i]['message'], '{DATE}')) {
+ $messages[$i]['message'] = preg_replace(
+ '/[A-Za-z]+day, [A-Za-z]+ \d+, \d+ · \d+:\d+[ ][AP]M – \d+:\d+[ ][AP]M \(UTC\)/u',
+ '{DATE}',
+ $messages[$i]['message']
+ );
+ }
}
Assert::assertEquals($expected, array_map(function ($message) use ($includeParents, $includeReferenceId, $includeReactions, $includeReactionsSelf) {
@@ -3167,6 +3191,7 @@ public function userReactWithOnMessageToRoomWith(string $user, string $action, s
* @Given /^user "([^"]*)" retrieve reactions "([^"]*)" of message "([^"]*)" in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*/
public function userRetrieveReactionsOfMessageInRoomWith(string $user, string $reaction, string $message, string $identifier, int $statusCode, string $apiVersion = 'v1', ?TableNode $formData = null): void {
+ $message = str_replace('\n', "\n", $message);
$token = self::$identifierToToken[$identifier];
$messageId = self::$textToMessageId[$message];
$this->setCurrentUser($user);
@@ -3184,6 +3209,14 @@ private function assertReactionList(?TableNode $formData): void {
foreach ($formData->getHash() as $row) {
$reaction = $row['reaction'];
unset($row['reaction']);
+
+ if ($row['actorType'] === 'bots') {
+ $result = preg_match('/BOT\(([^)]+)\)/', $row['actorId'], $matches);
+ if ($result && isset(self::$botNameToHash[$matches[1]])) {
+ $row['actorId'] = 'bot-' . self::$botNameToHash[$matches[1]];
+ }
+ }
+
$expected[$reaction][] = $row;
}
@@ -3217,7 +3250,7 @@ public function userSetTheMessageExpirationToXWithStatusCode(string $user, int $
}
/**
- * @When wait for :seconds (second|seconds)
+ * @When /^wait for ([0-9]+) (second|seconds)$/
*/
public function waitForXSecond($seconds): void {
sleep($seconds);
@@ -3447,6 +3480,59 @@ public function userStoreRecordingFileInRoom(string $user, string $file, string
$this->assertStatusCode($this->response, $statusCode);
}
+ /**
+ * @Then /^read bot ids from OCC$/
+ */
+ public function readBotIds(): void {
+ $this->invokingTheCommand('talk:bot:list -v --output json');
+ $this->theCommandWasSuccessful();
+ $json = $this->getLastStdOut();
+
+ $botData = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
+ foreach ($botData as $bot) {
+ self::$botNameToId[$bot['name']] = $bot['id'];
+ self::$botNameToHash[$bot['name']] = $bot['url_hash'];
+ self::$botIdToName[$bot['id']] = $bot['name'];
+ }
+ }
+
+ /**
+ * @Then /^(setup|remove) bot "([^"]*)" for room "([^"]*)" via OCC$/
+ */
+ public function setupOrRemoveBotInRoom(string $action, string $botName, string $identifier): void {
+ $this->invokingTheCommand('talk:bot:' . $action . ' ' . self::$botNameToId[$botName] . ' ' . self::$identifierToToken[$identifier]);
+ $this->theCommandWasSuccessful();
+ }
+
+ /**
+ * @Then /^set state (enabled|disabled|no-setup) for bot "([^"]*)" via OCC$/
+ */
+ public function stateUpdateForBot(string $state, string $botName): void {
+ if ($state === 'enabled') {
+ $state = 1;
+ } elseif ($state === 'disabled') {
+ $state = 0;
+ } elseif ($state === 'no-setup') {
+ $state = 2;
+ }
+
+ $this->invokingTheCommand('talk:bot:state ' . self::$botNameToId[$botName] . ' ' . $state);
+ $this->theCommandWasSuccessful();
+ }
+
+ /**
+ * @Then /^user "([^"]*)" (sets up|removes) bot "([^"]*)" for room "([^"]*)" with (\d+)(?: \((v1)\))?$/
+ */
+ public function setupOrRemoveBotViaOCSAPI(string $user, string $action, string $botName, string $identifier, int $status, string $apiVersion): void {
+ $this->setCurrentUser($user);
+
+ $this->sendRequest(
+ $action === 'sets up' ? 'POST' : 'DELETE',
+ '/apps/spreed/api/' . $apiVersion . '/bot/' . self::$identifierToToken[$identifier] . '/' .self::$botNameToId[$botName]
+ );
+ $this->assertStatusCode($this->response, $status);
+ }
+
/**
* @Then /^user "([^"]*)" shares file from the (first|last) notification to room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
diff --git a/tests/integration/features/chat/bots.feature b/tests/integration/features/chat/bots.feature
new file mode 100644
index 00000000000..89ff60b5665
--- /dev/null
+++ b/tests/integration/features/chat/bots.feature
@@ -0,0 +1,106 @@
+Feature: chat/bots
+ Background:
+ Given user "participant1" exists
+
+ Scenario: Installing the call summary bot
+ Given invoking occ with "talk:bot:list"
+ Then the command was successful
+ And the command output is empty
+ Given invoking occ with "app:disable call_summary_bot"
+ And the command was successful
+ And invoking occ with "app:enable call_summary_bot"
+ And the command was successful
+ When invoking occ with "talk:bot:list"
+ Then the command was successful
+ And the command output contains the text "Call summary"
+
+ Scenario: Simple call summary bot run
+ # Populate default options again
+ And invoking occ with "app:disable call_summary_bot"
+ And the command was successful
+ And invoking occ with "app:enable call_summary_bot"
+ And the command was successful
+ And invoking occ with "talk:bot:list"
+ And the command was successful
+ And the command output contains the text "Call summary"
+
+ # Set up in room
+ Given invoking occ with "talk:bot:list room-name:room"
+ And the command was successful
+ And the command output is empty
+ Given user "participant1" creates room "room" (v4)
+ | roomType | 2 |
+ | roomName | room |
+ And read bot ids from OCC
+ And setup bot "Call summary" for room "room" via OCC
+ Given invoking occ with "talk:bot:list room-name:room"
+ And the command was successful
+ And the command output contains the text "Call summary"
+
+ # Call summary
+ Given the following call_summary_bot app config is set
+ | min-length | -1 |
+ And user "participant1" sends message "- Before call" to room "room" with 201
+ And wait for 2 seconds
+ Then user "participant1" joins room "room" with 200 (v4)
+ Then user "participant1" joins call "room" with 200 (v4)
+ | flags | 1 |
+ And user "participant1" sends message "* Task 1" to room "room" with 201
+ And user "participant1" sends message "- Task 2\n-Task 3" to room "room" with 201
+ Then user "participant1" sees the following messages in room "room" with 200
+ | room | actorType | actorId | actorDisplayName | message | messageParameters |
+ | room | users | participant1 | participant1-displayname | - Task 2\n-Task 3 | [] |
+ | room | users | participant1 | participant1-displayname | * Task 1 | [] |
+ | room | users | participant1 | participant1-displayname | - Before call | [] |
+ Then user "participant1" leaves call "room" with 200 (v4)
+ Then user "participant1" leaves room "room" with 200 (v4)
+ Then user "participant1" sees the following messages in room "room" with 200
+ | room | actorType | actorId | actorDisplayName | message | messageParameters |
+ | room | bots | BOT(Call summary) | Call summary (Bot) | # Call summary - room\n\n{DATE}\n\n## Attendees\n- participant1-displayname\n\n## Tasks\n- Task 1\n- Task 2\n- Task 3 | [] |
+ | room | users | participant1 | participant1-displayname | - Task 2\n-Task 3 | [] |
+ | room | users | participant1 | participant1-displayname | * Task 1 | [] |
+ | room | users | participant1 | participant1-displayname | - Before call | [] |
+ Then user "participant1" retrieve reactions "👍" of message "- Before call" in room "room" with 200
+ | actorType | actorId | actorDisplayName | reaction |
+ Then user "participant1" retrieve reactions "👍" of message "* Task 1" in room "room" with 200
+ | actorType | actorId | actorDisplayName | reaction |
+ | bots | BOT(Call summary) | Call summary (Bot) | 👍 |
+ Then user "participant1" retrieve reactions "👍" of message "- Task 2\n-Task 3" in room "room" with 200
+ | actorType | actorId | actorDisplayName | reaction |
+ | bots | BOT(Call summary) | Call summary (Bot) | 👍 |
+
+ # Different states bot
+ # Already enabled
+ And user "participant1" sets up bot "Call summary" for room "room" with 200 (v1)
+ Given invoking occ with "talk:bot:list room-name:room"
+ And the command was successful
+ And the command output contains the text "Call summary"
+ # Disabling
+ And user "participant1" removes bot "Call summary" for room "room" with 200 (v1)
+ Given invoking occ with "talk:bot:list room-name:room"
+ And the command was successful
+ And the command output is empty
+ # Enabling
+ And user "participant1" sets up bot "Call summary" for room "room" with 201 (v1)
+ Given invoking occ with "talk:bot:list room-name:room"
+ And the command was successful
+ And the command output contains the text "Call summary"
+
+ # No-setup
+ And set state no-setup for bot "Call summary" via OCC
+
+ ## Failed removing
+ And user "participant1" removes bot "Call summary" for room "room" with 400 (v1)
+ Given invoking occ with "talk:bot:list room-name:room"
+ And the command was successful
+ And the command output contains the text "Call summary"
+
+ ## Failed adding
+ And remove bot "Call summary" for room "room" via OCC
+ Given invoking occ with "talk:bot:list room-name:room"
+ And the command was successful
+ And the command output is empty
+ And user "participant1" sets up bot "Call summary" for room "room" with 400 (v1)
+ Given invoking occ with "talk:bot:list room-name:room"
+ And the command was successful
+ And the command output is empty
diff --git a/tests/integration/run.sh b/tests/integration/run.sh
index b22beefe39a..b6dd66087ce 100755
--- a/tests/integration/run.sh
+++ b/tests/integration/run.sh
@@ -3,6 +3,7 @@
APP_NAME=spreed
NOTIFICATIONS_BRANCH="master"
GUESTS_BRANCH="master"
+CSB_BRANCH="main"
APP_INTEGRATION_DIR=$PWD
ROOT_DIR=${APP_INTEGRATION_DIR}/../../../..
@@ -16,7 +17,7 @@ echo ''
echo '#'
echo '# Starting PHP webserver'
echo '#'
-php -S localhost:8080 -t ${ROOT_DIR} &
+PHP_CLI_SERVER_WORKERS=3 php -S localhost:8080 -t ${ROOT_DIR} &
PHPPID1=$!
echo 'Running on process ID:'
echo $PHPPID1
@@ -41,6 +42,9 @@ export NEXTCLOUD_ROOT_DIR
export TEST_SERVER_URL="http://localhost:8080/"
export TEST_REMOTE_URL="http://localhost:8180/"
+OVERWRITE_CLI_URL=$(${ROOT_DIR}/occ config:system:get overwrite.cli.url)
+${ROOT_DIR}/occ config:system:set overwrite.cli.url --value "http://localhost:8080/"
+
echo ''
echo '#'
echo '# Setting up apps'
@@ -52,15 +56,18 @@ ${ROOT_DIR}/occ app:getpath spreedcheats
# already there or in "apps").
${ROOT_DIR}/occ app:getpath notifications || (cd ../../../ && git clone --depth 1 --branch ${NOTIFICATIONS_BRANCH} https://github.com/nextcloud/notifications)
${ROOT_DIR}/occ app:getpath guests || (cd ../../../ && git clone --depth 1 --branch ${GUESTS_BRANCH} https://github.com/nextcloud/guests)
+${ROOT_DIR}/occ app:getpath call_summary_bot || (cd ../../../ && git clone --depth 1 --branch ${CSB_BRANCH} https://github.com/nextcloud/call_summary_bot)
${ROOT_DIR}/occ app:enable spreed || exit 1
${ROOT_DIR}/occ app:enable --force spreedcheats || exit 1
${ROOT_DIR}/occ app:enable --force notifications || exit 1
${ROOT_DIR}/occ app:enable --force guests || exit 1
+${ROOT_DIR}/occ app:enable --force call_summary_bot || exit 1
${ROOT_DIR}/occ app:list | grep spreed
${ROOT_DIR}/occ app:list | grep notifications
${ROOT_DIR}/occ app:list | grep guests
+${ROOT_DIR}/occ app:list | grep call_summary_bot
echo ''
echo '#'
@@ -88,6 +95,7 @@ kill $PHPPID1
kill $PHPPID2
${ROOT_DIR}/occ app:disable spreedcheats
+${ROOT_DIR}/occ config:system:set overwrite.cli.url --value $OVERWRITE_CLI_URL
rm -rf ../../../spreedcheats
wait $PHPPID1
diff --git a/tests/integration/spreedcheats/lib/Controller/ApiController.php b/tests/integration/spreedcheats/lib/Controller/ApiController.php
index c59896a3bae..a150342d3ee 100644
--- a/tests/integration/spreedcheats/lib/Controller/ApiController.php
+++ b/tests/integration/spreedcheats/lib/Controller/ApiController.php
@@ -55,6 +55,12 @@ public function resetSpreed(): DataResponse {
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_attendees')->executeStatement();
+ $delete = $this->db->getQueryBuilder();
+ $delete->delete('talk_bots_conversation')->executeStatement();
+
+ $delete = $this->db->getQueryBuilder();
+ $delete->delete('talk_bots_server')->executeStatement();
+
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_bridges')->executeStatement();
@@ -70,15 +76,15 @@ public function resetSpreed(): DataResponse {
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_invitations')->executeStatement();
- $delete = $this->db->getQueryBuilder();
- $delete->delete('talk_rooms')->executeStatement();
-
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_polls')->executeStatement();
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_poll_votes')->executeStatement();
+ $delete = $this->db->getQueryBuilder();
+ $delete->delete('talk_rooms')->executeStatement();
+
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_sessions')->executeStatement();
diff --git a/tests/psalm-baseline.xml b/tests/psalm-baseline.xml
index a0de5f1c1a3..4fc69c30674 100644
--- a/tests/psalm-baseline.xml
+++ b/tests/psalm-baseline.xml
@@ -29,6 +29,36 @@
\OC_Util
+
+
+ Base
+
+
+
+
+ Base
+
+
+
+
+ Base
+
+
+
+
+ Base
+
+
+
+
+ Base
+
+
+
+
+ Base
+
+ Base