Skip to content
Draft
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
2 changes: 1 addition & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* 🌉 **Sync with other chat solutions** With [Matterbridge](https://github.com/42wim/matterbridge/) being integrated in Talk, you can easily sync a lot of other chat solutions to Nextcloud Talk and vice-versa.
]]></description>

<version>24.0.0-dev.2</version>
<version>24.0.0-dev.3</version>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the note: conversation-tags were merged

Suggested change
<version>24.0.0-dev.3</version>
<version>24.0.0-dev.4</version>

<licence>agpl</licence>

<author>Anna Larch</author>
Expand Down
1 change: 1 addition & 0 deletions docs/capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,4 @@
* `config => conversations => group-mode` (local) - User selected grouping mode for conversations (`none`, `group-first` or `private-first`)
* `private-reply` - Whether clients can link the original message to a private reply in one-to-one conversations
* `config => attachments => conversation-subfolders` (local) - Whether per-conversation subfolders are used for Talk attachments; when `true` files must be uploaded to `Talk/<ConversationName>-<token>/<DisplayName>-<uid>/` before calling the attachment endpoint
* `mute-conversations` - Whether conversations can be muted for a given time
1 change: 1 addition & 0 deletions lib/Capabilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ class Capabilities implements IPublicCapability {
'scheduled-messages',
'conversation-presets',
'private-reply',
'mute-conversations',
];

public const CONDITIONAL_FEATURES = [
Expand Down
8 changes: 8 additions & 0 deletions lib/Chat/Notifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,10 @@ protected function shouldMentionedUserBeNotified(string $userId, IComment $comme
return self::PRIORITY_NONE;
}

if ($attendee->getMuteUntil() >= $this->timeFactory->getTime()) {
return self::PRIORITY_NONE;
}

$notificationLevel = $attendee->getNotificationLevel();
$threadId = (int)$comment->getTopmostParentId();
if ($threadId !== 0) {
Expand Down Expand Up @@ -768,6 +772,10 @@ protected function shouldParticipantBeNotified(Participant $participant, ICommen
return self::PRIORITY_NONE;
}

if ($participant->getAttendee()->getMuteUntil() >= $this->timeFactory->getTime()) {
return self::PRIORITY_NONE;
}

if ($participant->getAttendee()->isImportant()) {
return self::PRIORITY_IMPORTANT;
}
Expand Down
50 changes: 50 additions & 0 deletions lib/Controller/RoomController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3238,4 +3238,54 @@ public function scheduleMeeting(string $calendarUri, int $start, ?array $attende

return new DataResponse(null, Http::STATUS_OK);
}

/**
* Mute all notifications in a conversation until a specific time.
* Does not alter notification settings for the attendee.
*
* Required capability: `mute-conversations`
*
* @param int $muteUntil Unix timestamp until notifications are muted
* @return DataResponse<Http::STATUS_OK, TalkRoom, array{}>|DataResponse<Http::STATUS_BAD_REQUEST, array{error: 'mute-until'}, array{}>
*
* 200: Conversation muted
* 400: Timestamp is in the past
*/
#[NoAdminRequired]
#[FederationSupported]
#[RequireLoggedInParticipant]
#[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/room/{token}/mute', requirements: [
'apiVersion' => '(v4)',
'token' => '[a-z0-9]{4,30}',
])]
public function muteConversation(int $muteUntil): DataResponse {
if ($muteUntil <= $this->timeFactory->getTime()) {
return new DataResponse(['error' => 'mute-until'], Http::STATUS_BAD_REQUEST);
}

$this->participantService->setMuteUntil($this->participant, $muteUntil);
return new DataResponse($this->formatRoom($this->room, $this->participant));
}

/**
* Unmute all notifications in a conversation, when they were muted before.
* Does not alter notification settings for the attendee.
*
* Required capability: `mute-conversations`
*
* @return DataResponse<Http::STATUS_OK, TalkRoom, array{}>
*
* 200: Conversation unmuted
*/
#[NoAdminRequired]
#[FederationSupported]
#[RequireLoggedInParticipant]
#[ApiRoute(verb: 'DELETE', url: '/api/{apiVersion}/room/{token}/mute', requirements: [
'apiVersion' => '(v4)',
'token' => '[a-z0-9]{4,30}',
])]
public function unmuteConversation(): DataResponse {
$this->participantService->setMuteUntil($this->participant, 0);
return new DataResponse($this->formatRoom($this->room, $this->participant));
}
}
42 changes: 42 additions & 0 deletions lib/Migration/Version24000Date20260419190830.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Talk\Migration;

use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\DB\Types;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
use Override;

class Version24000Date20260419190830 extends SimpleMigrationStep {

/**
* @param IOutput $output
* @param Closure(): ISchemaWrapper $schemaClosure
* @param array $options
* @return null|ISchemaWrapper
*/
#[Override]
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();

$table = $schema->getTable('talk_attendees');
if (!$table->hasColumn('mute_until')) {
$table->addColumn('mute_until', Types::BIGINT, [
'notnull' => true,
Comment thread
SystemKeeper marked this conversation as resolved.
'default' => 0,
]);
}

return $schema;
}
}
5 changes: 4 additions & 1 deletion lib/Model/Attendee.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@
* @method int getHiddenPinnedId()
* @method void setHasScheduledMessages(int $scheduledMessages)
* @method int getHasScheduledMessages()
* @method void setMuteUntil(int $muteUntil)
* @method int getMuteUntil()
*/
class Attendee extends Entity {
public const ACTOR_USERS = 'users';
Expand Down Expand Up @@ -150,6 +152,7 @@ class Attendee extends Entity {
protected bool $hasUnreadThreadDirects = false;
protected int $hiddenPinnedId = 0;
protected int $hasScheduledMessages = 0;
protected int $muteUntil = 0;

public function __construct() {
$this->addType('roomId', Types::BIGINT);
Expand Down Expand Up @@ -183,7 +186,7 @@ public function __construct() {
$this->addType('hasUnreadThreadDirects', Types::BOOLEAN);
$this->addType('hiddenPinnedId', Types::BIGINT);
$this->addType('hasScheduledMessages', Types::INTEGER);

$this->addType('muteUntil', Types::BIGINT);
}

public function getDisplayName(): string {
Expand Down
1 change: 1 addition & 0 deletions lib/Model/AttendeeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ public function createAttendeeFromRow(array $row): Attendee {
'has_unread_thread_directs' => (bool)$row['has_unread_thread_directs'],
'hidden_pinned_id' => (int)$row['hidden_pinned_id'],
'has_scheduled_messages' => (int)$row['has_scheduled_messages'],
'mute_until' => (int)$row['mute_until'],
]);
}
}
1 change: 1 addition & 0 deletions lib/Model/SelectHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ public function selectAttendeesTable(IQueryBuilder $query, string $alias = 'a'):
$alias . 'has_unread_thread_directs',
$alias . 'hidden_pinned_id',
$alias . 'has_scheduled_messages',
$alias . 'mute_until',
])->selectAlias($alias . 'id', 'a_id');
}

Expand Down
2 changes: 2 additions & 0 deletions lib/ResponseDefinitions.php
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,8 @@
* hasScheduledMessages: int,
* // Bit-flag of enabled attributes of this conversation (only available with capability: `conversation-attributes`). See [attributes list](https://nextcloud-talk.readthedocs.io/en/latest/constants/#conversation-attributes) for details
* attributes: int,
* // Required capability: `mute-conversations`. Timestamp until the conversation is muted, i.e. not receiving notifications
* muteUntil: int,
* }
*
* @psalm-type TalkDashboardEventAttachment = array{
Expand Down
8 changes: 8 additions & 0 deletions lib/Service/ParticipantService.php
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,13 @@ public function hidePinnedMessage(Participant $participant, int $messagesId): vo
$this->attendeeMapper->update($attendee);
}

public function setMuteUntil(Participant $participant, int $muteUntil): void {
$attendee = $participant->getAttendee();
$attendee->setMuteUntil($muteUntil);
$attendee->setLastAttendeeActivity($this->timeFactory->getTime());
$this->attendeeMapper->update($attendee);
}

/**
* @param RoomService $roomService
* @param Room $room
Expand Down Expand Up @@ -2058,6 +2065,7 @@ public function getParticipantUsersForCallNotifications(Room $room): array {
->where($query->expr()->eq('a.room_id', $query->createNamedParameter($room->getId(), IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->eq('a.actor_type', $query->createNamedParameter(Attendee::ACTOR_USERS)))
->andWhere($query->expr()->eq('a.notification_calls', $query->createNamedParameter(Participant::NOTIFY_CALLS_ON)))
->andWhere($query->expr()->lte('a.mute_until', $query->createNamedParameter($this->timeFactory->getTime(), IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->isNull('s.in_call'));

if ($room->getLobbyState() !== Webinary::LOBBY_NONE) {
Expand Down
2 changes: 2 additions & 0 deletions lib/Service/RoomFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ public function formatRoomV4(
'isSensitive' => false,
'hasScheduledMessages' => 0,
'attributes' => 0,
'muteUntil' => 0,
];

if ($room->isFederatedConversation()) {
Expand Down Expand Up @@ -251,6 +252,7 @@ public function formatRoomV4(
'lastPinnedId' => $room->getLastPinnedId(),
'hiddenPinnedId' => $attendee->getHiddenPinnedId(),
'attributes' => $room->getAttributes(),
'muteUntil' => $attendee->getMuteUntil(),
]);

if ($room->isFederatedConversation()) {
Expand Down
8 changes: 7 additions & 1 deletion openapi-backend-sipbridge.json
Original file line number Diff line number Diff line change
Expand Up @@ -941,7 +941,8 @@
"lastPinnedId",
"hiddenPinnedId",
"hasScheduledMessages",
"attributes"
"attributes",
"muteUntil"
],
"properties": {
"actorId": {
Expand Down Expand Up @@ -1255,6 +1256,11 @@
"type": "integer",
"format": "int64",
"description": "Bit-flag of enabled attributes of this conversation (only available with capability: `conversation-attributes`). See [attributes list](https://nextcloud-talk.readthedocs.io/en/latest/constants/#conversation-attributes) for details"
},
"muteUntil": {
"type": "integer",
"format": "int64",
"description": "Required capability: `mute-conversations`. Timestamp until the conversation is muted, i.e. not receiving notifications"
}
}
},
Expand Down
8 changes: 7 additions & 1 deletion openapi-federation.json
Original file line number Diff line number Diff line change
Expand Up @@ -1006,7 +1006,8 @@
"lastPinnedId",
"hiddenPinnedId",
"hasScheduledMessages",
"attributes"
"attributes",
"muteUntil"
],
"properties": {
"actorId": {
Expand Down Expand Up @@ -1320,6 +1321,11 @@
"type": "integer",
"format": "int64",
"description": "Bit-flag of enabled attributes of this conversation (only available with capability: `conversation-attributes`). See [attributes list](https://nextcloud-talk.readthedocs.io/en/latest/constants/#conversation-attributes) for details"
},
"muteUntil": {
"type": "integer",
"format": "int64",
"description": "Required capability: `mute-conversations`. Timestamp until the conversation is muted, i.e. not receiving notifications"
}
}
},
Expand Down
Loading
Loading