Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,15 @@
'token' => '^[a-z0-9]{4,30}$',
],
],
[
'name' => 'Chat#clearHistory',
'url' => '/api/{apiVersion}/chat/{token}',
'verb' => 'DELETE',
'requirements' => [
'apiVersion' => 'v1',
'token' => '^[a-z0-9]{4,30}$',
],
],
[
'name' => 'Chat#deleteMessage',
'url' => '/api/{apiVersion}/chat/{token}/{messageId}',
Expand Down
27 changes: 26 additions & 1 deletion docs/chat.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,30 @@ See [OCP\RichObjectStrings\Definitions](https://github.com/nextcloud/server/blob

* Response: [See official OCS Share API docs](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-share-api.html?highlight=sharing#create-a-new-share)

## Clear chat history

* Required capability: `clear-history`
* Method: `DELETE`
* Endpoint: `/chat/{token}`

* Response:
- Status code:
+ `200 OK` - When deleting was successful
+ `202 Accepted` - When deleting was successful but Matterbridge is enabled so the message was leaked to other services
+ `403 Forbidden` When the user is not a moderator
+ `404 Not Found` When the conversation could not be found for the participant

- Header:

field | type | Description
---|---|---
`X-Chat-Last-Common-Read` | int | ID of the last message read by every user that has read privacy set to public. When the user themself has it set to private the value the header is not set (only available with `chat-read-status` capability)

- Data:
The full message array of the new system message "You cleared the history of the conversation", as defined in [Receive chat messages of a conversation](#receive-chat-messages-of-a-conversation)
When rendering this message the client should also remove all messages from any cache/storage of the device.


## Deleting a chat message

* Required capability: `delete-messages`
Expand All @@ -179,7 +203,7 @@ See [OCP\RichObjectStrings\Definitions](https://github.com/nextcloud/server/blob
- Data:
The full message array of the new system message "You deleted a message", as defined in [Receive chat messages of a conversation](#receive-chat-messages-of-a-conversation)
The parent message is the object of the deleted message with the replaced text "Message deleted by you".
This message should not be displayed to the user but instead be used to remove the original message from any cache/storage of the device.
This message should **NOT** be displayed to the user but instead be used to remove the original message from any cache/storage of the device.


## Mark chat as read
Expand Down Expand Up @@ -268,6 +292,7 @@ See [OCP\RichObjectStrings\Definitions](https://github.com/nextcloud/server/blob
* `guest_moderator_promoted` - {actor} promoted {user} to moderator
* `guest_moderator_demoted` - {actor} demoted {user} from moderator
* `message_deleted` - Message deleted by {actor} (Should not be shown to the user)
* `history_cleared` - {actor} cleared the history of the conversation
* `file_shared` - {file}
* `object_shared` - {object}
* `matterbridge_config_added` - {actor} set up Matterbridge to synchronize this conversation with other chats
Expand Down
26 changes: 26 additions & 0 deletions lib/Chat/ChatManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\ParticipantService;
use OCA\Talk\Share\RoomShareProvider;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Comments\IComment;
use OCP\Comments\ICommentsManager;
Expand Down Expand Up @@ -69,6 +70,8 @@ class ChatManager {
private $connection;
/** @var INotificationManager */
private $notificationManager;
/** @var RoomShareProvider */
private $shareProvider;
/** @var ParticipantService */
private $participantService;
/** @var Notifier */
Expand All @@ -84,6 +87,7 @@ public function __construct(CommentsManager $commentsManager,
IEventDispatcher $dispatcher,
IDBConnection $connection,
INotificationManager $notificationManager,
RoomShareProvider $shareProvider,
ParticipantService $participantService,
Notifier $notifier,
ICacheFactory $cacheFactory,
Expand All @@ -92,6 +96,7 @@ public function __construct(CommentsManager $commentsManager,
$this->dispatcher = $dispatcher;
$this->connection = $connection;
$this->notificationManager = $notificationManager;
$this->shareProvider = $shareProvider;
$this->participantService = $participantService;
$this->notifier = $notifier;
$this->cache = $cacheFactory->createDistributed('talk/lastmsgid');
Expand Down Expand Up @@ -279,6 +284,25 @@ public function deleteMessage(Room $chat, int $messageId, string $actorType, str
);
}

public function clearHistory(Room $chat, string $actorType, string $actorId): IComment {
$this->commentsManager->deleteCommentsAtObject('chat', (string) $chat->getId());

$this->shareProvider->deleteInRoom($chat->getToken());

$this->notifier->removePendingNotificationsForRoom($chat, true);

$this->participantService->resetChatDetails($chat);

return $this->addSystemMessage(
$chat,
$actorType,
$actorId,
json_encode(['message' => 'history_cleared', 'parameters' => []]),
$this->timeFactory->getDateTime(),
false
);
}

/**
* @param Room $chat
* @param string $parentId
Expand Down Expand Up @@ -483,6 +507,8 @@ protected function waitForNewMessagesWithDatabase(Room $chat, int $offset, int $
public function deleteMessages(Room $chat): void {
$this->commentsManager->deleteCommentsAtObject('chat', (string) $chat->getId());

$this->shareProvider->deleteInRoom($chat->getToken());

$this->notifier->removePendingNotificationsForRoom($chat);
}

Expand Down
12 changes: 7 additions & 5 deletions lib/Chat/Notifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ public function notifyOtherParticipant(Room $chat, IComment $comment, array $alr
*
* @param Room $chat
*/
public function removePendingNotificationsForRoom(Room $chat): void {
public function removePendingNotificationsForRoom(Room $chat, bool $chatOnly = false): void {
$notification = $this->notificationManager->createNotification();
$shouldFlush = $this->notificationManager->defer();

Expand All @@ -207,11 +207,13 @@ public function removePendingNotificationsForRoom(Room $chat): void {
$notification->setObject('chat', $chat->getToken());
$this->notificationManager->markProcessed($notification);

$notification->setObject('room', $chat->getToken());
$this->notificationManager->markProcessed($notification);
if (!$chatOnly) {
$notification->setObject('room', $chat->getToken());
$this->notificationManager->markProcessed($notification);

$notification->setObject('call', $chat->getToken());
$this->notificationManager->markProcessed($notification);
$notification->setObject('call', $chat->getToken());
$this->notificationManager->markProcessed($notification);
}

if ($shouldFlush) {
$this->notificationManager->flush();
Expand Down
5 changes: 5 additions & 0 deletions lib/Chat/Parser/SystemMessage.php
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,11 @@ public function parseMessage(Message $chatMessage): void {
if ($currentUserIsActor) {
$parsedMessage = $this->l->t('You deleted a message');
}
} elseif ($message === 'history_cleared') {
$parsedMessage = $this->l->t('{actor} cleared the history of the conversation');
if ($currentUserIsActor) {
$parsedMessage = $this->l->t('You cleared the history of the conversation');
}
} else {
throw new \OutOfBoundsException('Unknown subject');
}
Expand Down
38 changes: 37 additions & 1 deletion lib/Controller/ChatController.php
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,43 @@ public function deleteMessage(int $messageId): DataResponse {

$bridge = $this->matterbridgeManager->getBridgeOfRoom($this->room);

$response = new DataResponse($data, $bridge['enabled'] ? Http::STATUS_ACCEPTED: Http::STATUS_OK);
$response = new DataResponse($data, $bridge['enabled'] ? Http::STATUS_ACCEPTED : Http::STATUS_OK);
if ($this->participant->getAttendee()->getReadPrivacy() === Participant::PRIVACY_PUBLIC) {
$response->addHeader('X-Chat-Last-Common-Read', $this->chatManager->getLastCommonReadMessage($this->room));
}
return $response;
}

/**
* @NoAdminRequired
* @RequireModeratorParticipant
* @RequireReadWriteConversation
*
* @return DataResponse
*/
public function clearHistory(): DataResponse {
$attendee = $this->participant->getAttendee();
if (!$this->participant->hasModeratorPermissions(false)
|| $this->room->getType() === Room::ONE_TO_ONE_CALL) {
// Actor is not a moderator or not the owner of the message
return new DataResponse([], Http::STATUS_FORBIDDEN);
}

$systemMessageComment = $this->chatManager->clearHistory(
$this->room,
$attendee->getActorType(),
$attendee->getActorId()
);

$systemMessage = $this->messageParser->createMessage($this->room, $this->participant, $systemMessageComment, $this->l);
$this->messageParser->parseMessage($systemMessage);


$data = $systemMessage->toArray();

$bridge = $this->matterbridgeManager->getBridgeOfRoom($this->room);

$response = new DataResponse($data, $bridge['enabled'] ? Http::STATUS_ACCEPTED : Http::STATUS_OK);
if ($this->participant->getAttendee()->getReadPrivacy() === Participant::PRIVACY_PUBLIC) {
$response->addHeader('X-Chat-Last-Common-Read', $this->chatManager->getLastCommonReadMessage($this->room));
}
Expand Down
9 changes: 9 additions & 0 deletions lib/Service/ParticipantService.php
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,15 @@ public function markUsersAsMentioned(Room $room, array $userIds, int $messageId)
$query->execute();
}

public function resetChatDetails(Room $room): void {
$query = $this->connection->getQueryBuilder();
$query->update('talk_attendees')
->set('last_read_message', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))
->set('last_mention_message', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))
->where($query->expr()->eq('room_id', $query->createNamedParameter($room->getId(), IQueryBuilder::PARAM_INT)));
$query->executeStatement();
}

public function updateReadPrivacyForActor(string $actorType, string $actorId, int $readPrivacy): void {
$query = $this->connection->getQueryBuilder();
$query->update('talk_attendees')
Expand Down
16 changes: 16 additions & 0 deletions tests/integration/features/bootstrap/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -1277,6 +1277,22 @@ public function userDeletesMessageFromRoom($user, $message, $identifier, $status
$this->assertStatusCode($this->response, $statusCode);
}

/**
* @Then /^user "([^"]*)" deletes chat history for room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
* @param string $user
* @param string $identifier
* @param int $statusCode
* @param string $apiVersion
*/
public function userDeletesHistoryFromRoom(string $user, string $identifier, int $statusCode, string $apiVersion = 'v1'): void {
$this->setCurrentUser($user);
$this->sendRequest(
'DELETE', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier]
);
$this->assertStatusCode($this->response, $statusCode);
}

/**
* @Then /^user "([^"]*)" reads message "([^"]*)" in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
*
Expand Down
32 changes: 31 additions & 1 deletion tests/integration/features/chat/delete.feature
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ Feature: chat/reply
Then user "participant2" received a system messages in room "group room" to delete "Message 1"

Scenario: Can only delete own messages in one-to-one
Given user "participant1" creates room "room1"
Given user "participant1" creates room "room1" (v4)
| roomType | 1 |
| invite | participant2 |
And user "participant1" sends message "Message 1" to room "room1" with 201
Expand Down Expand Up @@ -177,3 +177,33 @@ Feature: chat/reply
| room | actorType | actorId | actorDisplayName | message | messageParameters |
| room1 | users | participant2 | participant2-displayname | Message deleted by you | {"actor":{"type":"user","id":"participant2","name":"participant2-displayname"}} |
| room1 | users | participant1 | participant1-displayname | Message deleted by author | {"actor":{"type":"user","id":"participant1","name":"participant1-displayname"}} |

Scenario: Clear chat history as a moderator
Given user "participant1" creates room "room1" (v4)
| roomType | 2 |
| roomName | room |
And user "participant1" adds user "participant2" to room "room1" with 200 (v4)
And user "participant1" sends message "Message 1" to room "room1" with 201
And user "participant2" sends message "Message 2" to room "room1" with 201
Then user "participant1" sees the following messages in room "room1" with 200
| room | actorType | actorId | actorDisplayName | message | messageParameters |
| room1 | users | participant2 | participant2-displayname | Message 2 | [] |
| room1 | users | participant1 | participant1-displayname | Message 1 | [] |
Then user "participant2" sees the following messages in room "room1" with 200
| room | actorType | actorId | actorDisplayName | message | messageParameters |
| room1 | users | participant2 | participant2-displayname | Message 2 | [] |
| room1 | users | participant1 | participant1-displayname | Message 1 | [] |
And user "participant2" deletes chat history for room "room1" with 403
Then user "participant1" sees the following messages in room "room1" with 200
| room | actorType | actorId | actorDisplayName | message | messageParameters |
| room1 | users | participant2 | participant2-displayname | Message 2 | [] |
| room1 | users | participant1 | participant1-displayname | Message 1 | [] |
And user "participant1" deletes chat history for room "room1" with 200
Then user "participant1" sees the following messages in room "room1" with 200
| room | actorType | actorId | actorDisplayName | message | messageParameters |
Then user "participant1" sees the following system messages in room "room1" with 200 (v1)
| room | actorType | actorId | actorDisplayName | systemMessage |
| room1 | users | participant1 | participant1-displayname | history_cleared |
Then user "participant2" sees the following system messages in room "room1" with 200 (v1)
| room | actorType | actorId | actorDisplayName | systemMessage |
| room1 | users | participant1 | participant1-displayname | history_cleared |
Loading