summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoas Schilling <213943+nickvergessen@users.noreply.github.com>2022-05-13 13:09:23 +0200
committerGitHub <noreply@github.com>2022-05-13 13:09:23 +0200
commita4f3e569e2777c20b4ab497109bbf9e44a7eeed6 (patch)
treee1f7861a36e3d5338353365b11c66b9b32c39538
parent2742779a7621e13bb92a059eda34b4a433f739f9 (diff)
parent866201678f95301ef37d2a93ea05707edb0f6927 (diff)
Merge pull request #7331 from nextcloud/feature/6257/silent-send
Add an option for "Silent send" to the API
-rw-r--r--docs/capabilities.md1
-rw-r--r--docs/chat.md1
-rw-r--r--lib/Capabilities.php1
-rw-r--r--lib/Chat/ChatManager.php12
-rw-r--r--lib/Chat/Notifier.php35
-rw-r--r--lib/Controller/ChatController.php5
-rw-r--r--lib/Events/ChatParticipantEvent.php9
-rw-r--r--lib/Flow/Operation.php3
-rw-r--r--tests/integration/features/bootstrap/FeatureContext.php13
-rw-r--r--tests/integration/features/chat/notifications.feature35
-rw-r--r--tests/php/CapabilitiesTest.php1
-rw-r--r--tests/php/Chat/ChatManagerTest.php2
-rw-r--r--tests/php/Chat/NotifierTest.php4
13 files changed, 94 insertions, 28 deletions
diff --git a/docs/capabilities.md b/docs/capabilities.md
index ef882095d..6208c6412 100644
--- a/docs/capabilities.md
+++ b/docs/capabilities.md
@@ -95,5 +95,6 @@ title: Capabilities
## 15
* `chat-permission` - When permission 128 is required to post chat messages, reaction or share items to the conversation
+* `silent-send` - Whether the chat API allows to send chat messages without triggering notifications
* `sip-support-nopin` - Whether SIP can be configured to not require a custom attendee PIN
* `config => call => enabled` - Whether calling is enabled on the instance or not
diff --git a/docs/chat.md b/docs/chat.md
index adbc9af52..13be99302 100644
--- a/docs/chat.md
+++ b/docs/chat.md
@@ -79,6 +79,7 @@ Base endpoint is: `/ocs/v2.php/apps/spreed/api/v1`
`actorDisplayName` | string | Guest display name (ignored for logged in users)
`replyTo` | int | The message ID this message is a reply to (only allowed for messages from the same conversation and when the message type is not `system` or `command`)
`referenceId` | string | A reference string to be able to identify the message again in a "get messages" request, should be a random sha256 (only available with `chat-reference-id` capability)
+ `slient` | bool | If sent silent the message will not create chat notifications even for mentions (only available with `silent-send` capability)
* Response:
- Status code:
diff --git a/lib/Capabilities.php b/lib/Capabilities.php
index 3b1433f22..18b4b2362 100644
--- a/lib/Capabilities.php
+++ b/lib/Capabilities.php
@@ -103,6 +103,7 @@ class Capabilities implements IPublicCapability {
'rich-object-list-media',
'rich-object-delete',
'chat-permission',
+ 'silent-send',
],
'config' => [
'attachments' => [
diff --git a/lib/Chat/ChatManager.php b/lib/Chat/ChatManager.php
index 398a5f6cf..b20021f75 100644
--- a/lib/Chat/ChatManager.php
+++ b/lib/Chat/ChatManager.php
@@ -179,7 +179,7 @@ class ChatManager {
}
if ($sendNotifications) {
- $this->notifier->notifyOtherParticipant($chat, $comment, []);
+ $this->notifier->notifyOtherParticipant($chat, $comment, [], false);
}
$this->dispatcher->dispatch(self::EVENT_AFTER_SYSTEM_MESSAGE_SEND, $event);
@@ -238,7 +238,7 @@ class ChatManager {
* @param string $referenceId
* @return IComment
*/
- public function sendMessage(Room $chat, Participant $participant, string $actorType, string $actorId, string $message, \DateTime $creationDateTime, ?IComment $replyTo, string $referenceId): 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);
@@ -255,7 +255,7 @@ class ChatManager {
$comment->setReferenceId($referenceId);
}
- $event = new ChatParticipantEvent($chat, $comment, $participant);
+ $event = new ChatParticipantEvent($chat, $comment, $participant, $silent);
$this->dispatcher->dispatch(self::EVENT_BEFORE_MESSAGE_SEND, $event);
$shouldFlush = $this->notificationManager->defer();
@@ -273,20 +273,20 @@ class ChatManager {
$alreadyNotifiedUsers = [];
$usersDirectlyMentioned = $this->notifier->getMentionedUserIds($comment);
if ($replyTo instanceof IComment) {
- $alreadyNotifiedUsers = $this->notifier->notifyReplyToAuthor($chat, $comment, $replyTo);
+ $alreadyNotifiedUsers = $this->notifier->notifyReplyToAuthor($chat, $comment, $replyTo, $silent);
if ($replyTo->getActorType() === Attendee::ACTOR_USERS) {
$usersDirectlyMentioned[] = $replyTo->getActorId();
}
}
- $alreadyNotifiedUsers = $this->notifier->notifyMentionedUsers($chat, $comment, $alreadyNotifiedUsers);
+ $alreadyNotifiedUsers = $this->notifier->notifyMentionedUsers($chat, $comment, $alreadyNotifiedUsers, $silent);
if (!empty($alreadyNotifiedUsers)) {
$userIds = array_column($alreadyNotifiedUsers, 'id');
$this->participantService->markUsersAsMentioned($chat, $userIds, (int) $comment->getId(), $usersDirectlyMentioned);
}
// User was not mentioned, send a normal notification
- $this->notifier->notifyOtherParticipant($chat, $comment, $alreadyNotifiedUsers);
+ $this->notifier->notifyOtherParticipant($chat, $comment, $alreadyNotifiedUsers, $silent);
$this->dispatcher->dispatch(self::EVENT_AFTER_MESSAGE_SEND, $event);
} catch (NotFoundException $e) {
diff --git a/lib/Chat/Notifier.php b/lib/Chat/Notifier.php
index 7891db273..5874d05bf 100644
--- a/lib/Chat/Notifier.php
+++ b/lib/Chat/Notifier.php
@@ -87,19 +87,25 @@ class Notifier {
* @return string[] Users that were mentioned
* @psalm-return array<int, array{id: string, type: string, ?attendee: Attendee}>
*/
- public function notifyMentionedUsers(Room $chat, IComment $comment, array $alreadyNotifiedUsers): array {
+ public function notifyMentionedUsers(Room $chat, IComment $comment, array $alreadyNotifiedUsers, bool $silent): array {
$usersToNotify = $this->getUsersToNotify($chat, $comment, $alreadyNotifiedUsers);
if (!$usersToNotify) {
return $alreadyNotifiedUsers;
}
- $notification = $this->createNotification($chat, $comment, 'mention');
- $shouldFlush = $this->notificationManager->defer();
+
+ $shouldFlush = false;
+ if (!$silent) {
+ $notification = $this->createNotification($chat, $comment, 'mention');
+ $shouldFlush = $this->notificationManager->defer();
+ }
foreach ($usersToNotify as $mentionedUser) {
if ($this->shouldMentionedUserBeNotified($mentionedUser['id'], $comment, $chat, $mentionedUser['attendee'] ?? null)) {
- $notification->setUser($mentionedUser['id']);
- $this->notificationManager->notify($notification);
+ if (!$silent) {
+ $notification->setUser($mentionedUser['id']);
+ $this->notificationManager->notify($notification);
+ }
$alreadyNotifiedUsers[] = $mentionedUser;
}
}
@@ -193,11 +199,11 @@ class Notifier {
* @param Room $chat
* @param IComment $comment
* @param IComment $replyTo
- * @param string $subject
+ * @param bool $silent
* @return array[] Actor that was replied to
* @psalm-return array<int, array{id: string, type: string}>
*/
- public function notifyReplyToAuthor(Room $chat, IComment $comment, IComment $replyTo, string $subject = 'reply'): array {
+ public function notifyReplyToAuthor(Room $chat, IComment $comment, IComment $replyTo, bool $silent): array {
if ($replyTo->getActorType() !== Attendee::ACTOR_USERS) {
// No reply notification when the replyTo-author was not a user
return [];
@@ -207,9 +213,11 @@ class Notifier {
return [];
}
- $notification = $this->createNotification($chat, $comment, $subject);
- $notification->setUser($replyTo->getActorId());
- $this->notificationManager->notify($notification);
+ if (!$silent) {
+ $notification = $this->createNotification($chat, $comment, 'reply');
+ $notification->setUser($replyTo->getActorId());
+ $this->notificationManager->notify($notification);
+ }
return [
[
@@ -231,9 +239,14 @@ class Notifier {
* @param Room $chat
* @param IComment $comment
* @param array[] $alreadyNotifiedUsers
+ * @param bool $silent
* @psalm-param array<int, array{id: string, type: string, ?attendee: Attendee}> $alreadyNotifiedUsers
*/
- public function notifyOtherParticipant(Room $chat, IComment $comment, array $alreadyNotifiedUsers): void {
+ public function notifyOtherParticipant(Room $chat, IComment $comment, array $alreadyNotifiedUsers, bool $silent): void {
+ if ($silent) {
+ return;
+ }
+
$participants = $this->participantService->getParticipantsByNotificationLevel($chat, Participant::NOTIFY_ALWAYS);
$notification = $this->createNotification($chat, $comment, 'chat');
diff --git a/lib/Controller/ChatController.php b/lib/Controller/ChatController.php
index 8e508d0b0..f0033649c 100644
--- a/lib/Controller/ChatController.php
+++ b/lib/Controller/ChatController.php
@@ -192,11 +192,12 @@ class ChatController extends AEnvironmentAwareController {
* @param string $actorDisplayName for guests
* @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".
*/
- public function sendMessage(string $message, string $actorDisplayName = '', string $referenceId = '', int $replyTo = 0): DataResponse {
+ public function sendMessage(string $message, string $actorDisplayName = '', string $referenceId = '', int $replyTo = 0, bool $silent = false): DataResponse {
[$actorType, $actorId] = $this->getActorInfo($actorDisplayName);
if (!$actorId) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
@@ -222,7 +223,7 @@ class ChatController extends AEnvironmentAwareController {
$creationDateTime = $this->timeFactory->getDateTime('now', new \DateTimeZone('UTC'));
try {
- $comment = $this->chatManager->sendMessage($this->room, $this->participant, $actorType, $actorId, $message, $creationDateTime, $parent, $referenceId);
+ $comment = $this->chatManager->sendMessage($this->room, $this->participant, $actorType, $actorId, $message, $creationDateTime, $parent, $referenceId, $silent);
} catch (MessageTooLongException $e) {
return new DataResponse([], Http::STATUS_REQUEST_ENTITY_TOO_LARGE);
} catch (\Exception $e) {
diff --git a/lib/Events/ChatParticipantEvent.php b/lib/Events/ChatParticipantEvent.php
index b33dce216..0a7c967ed 100644
--- a/lib/Events/ChatParticipantEvent.php
+++ b/lib/Events/ChatParticipantEvent.php
@@ -29,14 +29,19 @@ use OCP\Comments\IComment;
class ChatParticipantEvent extends ChatEvent {
protected Participant $participant;
+ protected bool $silent;
-
- public function __construct(Room $room, IComment $message, Participant $participant) {
+ public function __construct(Room $room, IComment $message, Participant $participant, bool $silent) {
parent::__construct($room, $message);
$this->participant = $participant;
+ $this->silent = $silent;
}
public function getParticipant(): Participant {
return $this->participant;
}
+
+ public function isSilentMessage(): bool {
+ return $this->silent;
+ }
}
diff --git a/lib/Flow/Operation.php b/lib/Flow/Operation.php
index bae71fd12..c130722c1 100644
--- a/lib/Flow/Operation.php
+++ b/lib/Flow/Operation.php
@@ -126,7 +126,8 @@ class Operation implements IOperation {
$this->prepareMention($mode, $participant) . $message,
new \DateTime(),
null,
- ''
+ '',
+ false
);
} catch (UnexpectedValueException $e) {
continue;
diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php
index 65e30539f..264a57629 100644
--- a/tests/integration/features/bootstrap/FeatureContext.php
+++ b/tests/integration/features/bootstrap/FeatureContext.php
@@ -1399,19 +1399,26 @@ class FeatureContext implements Context, SnippetAcceptingContext {
}
/**
- * @Then /^user "([^"]*)" sends message "([^"]*)" to room "([^"]*)" with (\d+)(?: \((v1)\))?$/
+ * @Then /^user "([^"]*)" (silent sends|sends) message "([^"]*)" to room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
+ * @param string $sendingMode
* @param string $message
* @param string $identifier
* @param string $statusCode
* @param string $apiVersion
*/
- public function userSendsMessageToRoom($user, $message, $identifier, $statusCode, $apiVersion = 'v1') {
+ public function userSendsMessageToRoom(string $user, string $sendingMode, string $message, string $identifier, string $statusCode, string $apiVersion = 'v1') {
+ if ($sendingMode === 'silent sends') {
+ $body = new TableNode([['message', $message], ['silent', true]]);
+ } else {
+ $body = new TableNode([['message', $message]]);
+ }
+
$this->setCurrentUser($user);
$this->sendRequest(
'POST', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier],
- new TableNode([['message', $message]])
+ $body
);
$this->assertStatusCode($this->response, $statusCode);
sleep(1); // make sure Postgres manages the order of the messages
diff --git a/tests/integration/features/chat/notifications.feature b/tests/integration/features/chat/notifications.feature
index 4d62db44c..8301f8345 100644
--- a/tests/integration/features/chat/notifications.feature
+++ b/tests/integration/features/chat/notifications.feature
@@ -25,6 +25,17 @@ Feature: chat/notifications
| app | object_type | object_id | subject |
| spreed | chat | one-to-one room/Message 1 | participant1-displayname sent you a private message |
+ Scenario: Silent sent message when recipient is offline in the one-to-one
+ When user "participant1" creates room "one-to-one room" (v4)
+ | roomType | 1 |
+ | invite | participant2 |
+ # Join and leave to clear the invite notification
+ Given user "participant2" joins room "one-to-one room" with 200 (v4)
+ Given user "participant2" leaves room "one-to-one room" with 200 (v4)
+ When user "participant1" silent sends message "Message 1" to room "one-to-one room" with 201
+ Then user "participant2" has the following notifications
+ | app | object_type | object_id | subject |
+
Scenario: Normal message when recipient disabled notifications in the one-to-one
When user "participant1" creates room "one-to-one room" (v4)
| roomType | 1 |
@@ -152,6 +163,18 @@ Feature: chat/notifications
| app | object_type | object_id | subject |
| spreed | chat | room/Hi @participant2 bye | participant1-displayname mentioned you in conversation room |
+ Scenario: Silent mention when recipient is online in the group room
+ When user "participant1" creates room "room" (v4)
+ | roomType | 2 |
+ | roomName | room |
+ And user "participant1" adds user "participant2" to room "room" with 200 (v4)
+ # Join and leave to clear the invite notification
+ Given user "participant2" joins room "room" with 200 (v4)
+ Given user "participant2" leaves room "room" with 200 (v4)
+ When user "participant1" silent sends message "Hi @participant2 bye" to room "room" with 201
+ Then user "participant2" has the following notifications
+ | app | object_type | object_id | subject |
+
Scenario: Mention when recipient is offline in the group room
When user "participant1" creates room "room" (v4)
| roomType | 2 |
@@ -202,6 +225,18 @@ Feature: chat/notifications
| app | object_type | object_id | subject |
| spreed | chat | room/Hi @all bye | participant1-displayname mentioned you in conversation room |
+ Scenario: Silent at-all when recipient is offline in the group room
+ When user "participant1" creates room "room" (v4)
+ | roomType | 2 |
+ | roomName | room |
+ And user "participant1" adds user "participant2" to room "room" with 200 (v4)
+ # Join and leave to clear the invite notification
+ Given user "participant2" joins room "room" with 200 (v4)
+ Given user "participant2" leaves room "room" with 200 (v4)
+ When user "participant1" silent sends message "Hi @all bye" to room "room" with 201
+ Then user "participant2" has the following notifications
+ | app | object_type | object_id | subject |
+
Scenario: At-all when recipient with disabled notifications in the group room
When user "participant1" creates room "room" (v4)
| roomType | 2 |
diff --git a/tests/php/CapabilitiesTest.php b/tests/php/CapabilitiesTest.php
index e64a66c9d..60cb07ab2 100644
--- a/tests/php/CapabilitiesTest.php
+++ b/tests/php/CapabilitiesTest.php
@@ -107,6 +107,7 @@ class CapabilitiesTest extends TestCase {
'rich-object-list-media',
'rich-object-delete',
'chat-permission',
+ 'silent-send',
'reactions',
];
}
diff --git a/tests/php/Chat/ChatManagerTest.php b/tests/php/Chat/ChatManagerTest.php
index 86750e8c4..e0e79869f 100644
--- a/tests/php/Chat/ChatManagerTest.php
+++ b/tests/php/Chat/ChatManagerTest.php
@@ -275,7 +275,7 @@ class ChatManagerTest extends TestCase {
$participant = $this->createMock(Participant::class);
- $return = $this->chatManager->sendMessage($chat, $participant, 'users', $userId, $message, $creationDateTime, $replyTo, $referenceId);
+ $return = $this->chatManager->sendMessage($chat, $participant, 'users', $userId, $message, $creationDateTime, $replyTo, $referenceId, false);
$this->assertCommentEquals($commentExpected, $return);
}
diff --git a/tests/php/Chat/NotifierTest.php b/tests/php/Chat/NotifierTest.php
index 0acec8008..25133491b 100644
--- a/tests/php/Chat/NotifierTest.php
+++ b/tests/php/Chat/NotifierTest.php
@@ -177,7 +177,7 @@ class NotifierTest extends TestCase {
'type' => Attendee::ACTOR_USERS,
];
}, $expectedReturn);
- $actual = $notifier->notifyMentionedUsers($room, $comment, $alreadyNotifiedUsers);
+ $actual = $notifier->notifyMentionedUsers($room, $comment, $alreadyNotifiedUsers, false);
$this->assertEqualsCanonicalizing($expectedReturn, $actual);
}
@@ -341,7 +341,7 @@ class NotifierTest extends TestCase {
$this->assertCount(count($return), $actual);
foreach ($actual as $key => $value) {
$this->assertIsArray($value);
- if (key_exists('attendee', $valu