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
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>22.0.0-dev.2</version>
<version>22.0.0-dev.3</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 @@ -181,3 +181,4 @@

## 21.1
* `conversation-creation-all` - Whether the conversation creation endpoint allows to specify all attributes of a conversation
* `important-conversations` (local) - Whether important conversations are supported
2 changes: 2 additions & 0 deletions lib/Capabilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ class Capabilities implements IPublicCapability {
'schedule-meeting',
'edit-draft-poll',
'conversation-creation-all',
'important-conversations',
];

public const CONDITIONAL_FEATURES = [
Expand All @@ -138,6 +139,7 @@ class Capabilities implements IPublicCapability {
'chat-summary-api',
'call-notification-state-api',
'schedule-meeting',
'important-conversations',
];

public const LOCAL_CONFIGS = [
Expand Down
34 changes: 34 additions & 0 deletions lib/Controller/RoomController.php
Original file line number Diff line number Diff line change
Expand Up @@ -1681,6 +1681,40 @@ public function unarchiveConversation(): DataResponse {
return new DataResponse($this->formatRoom($this->room, $this->participant));
}

/**
* Mark a conversation as important (still sending notifications while on DND)
*
* Required capability: `important-conversations`
*
* @return DataResponse<Http::STATUS_OK, TalkRoom, array{}>
*
* 200: Conversation was marked as important
*/
#[NoAdminRequired]
#[FederationSupported]
#[RequireLoggedInParticipant]
public function markConversationAsImportant(): DataResponse {
$this->participantService->markConversationAsImportant($this->participant);
return new DataResponse($this->formatRoom($this->room, $this->participant));
}

/**
* Mark a conversation as unimportant (no longer sending notifications while on DND)
*
* Required capability: `important-conversations`
*
* @return DataResponse<Http::STATUS_OK, TalkRoom, array{}>
*
* 200: Conversation was marked as unimportant
*/
#[NoAdminRequired]
#[FederationSupported]
#[RequireLoggedInParticipant]
public function markConversationAsUnimportant(): DataResponse {
$this->participantService->markConversationAsUnimportant($this->participant);
return new DataResponse($this->formatRoom($this->room, $this->participant));
}

/**
* Join a room
*
Expand Down
41 changes: 41 additions & 0 deletions lib/Migration/Version21001Date20250328123156.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2025 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;

class Version21001Date20250328123156 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('important')) {
$table->addColumn('important', Types::BOOLEAN, [
'default' => 0,
'notnull' => false,
]);
}

return $schema;
}
}
4 changes: 4 additions & 0 deletions lib/Model/Attendee.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
* @method void setPermissions(int $permissions)
* @method void setArchived(bool $archived)
* @method bool isArchived()
* @method void setImportant(bool $important)
* @method bool isImportant()
* @internal
* @method int getPermissions()
* @method void setAccessToken(string $accessToken)
Expand Down Expand Up @@ -113,6 +115,7 @@ class Attendee extends Entity {
protected int $notificationLevel = 0;
protected int $notificationCalls = 0;
protected bool $archived = false;
protected bool $important = false;
protected int $lastJoinedCall = 0;
protected int $lastReadMessage = 0;
protected int $lastMentionMessage = 0;
Expand All @@ -137,6 +140,7 @@ public function __construct() {
$this->addType('participantType', Types::SMALLINT);
$this->addType('favorite', Types::BOOLEAN);
$this->addType('archived', Types::BOOLEAN);
$this->addType('important', Types::BOOLEAN);
$this->addType('notificationLevel', Types::INTEGER);
$this->addType('notificationCalls', Types::INTEGER);
$this->addType('lastJoinedCall', Types::INTEGER);
Expand Down
1 change: 1 addition & 0 deletions lib/Model/AttendeeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ public function createAttendeeFromRow(array $row): Attendee {
'unread_messages' => (int)$row['unread_messages'],
'last_attendee_activity' => (int)$row['last_attendee_activity'],
'archived' => (bool)$row['archived'],
'important' => (bool)$row['important'],
]);
}
}
1 change: 1 addition & 0 deletions lib/Model/SelectHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public function selectAttendeesTable(IQueryBuilder $query, string $alias = 'a'):
->addSelect($alias . 'unread_messages')
->addSelect($alias . 'last_attendee_activity')
->addSelect($alias . 'archived')
->addSelect($alias . 'important')
->selectAlias($alias . 'id', 'a_id');
}

Expand Down
13 changes: 8 additions & 5 deletions lib/Notification/Listener.php
Original file line number Diff line number Diff line change
Expand Up @@ -285,11 +285,11 @@ protected function sendCallNotifications(Room $room): void {
}

$this->preparedCallNotifications = [];
$userIds = $this->participantsService->getParticipantUserIdsForCallNotifications($room);
$users = $this->participantsService->getParticipantUsersForCallNotifications($room);
// Room name depends on the notification user for one-to-one,
// so we avoid pre-parsing it there. Also, it comes with some base load,
// so we only do it for "big enough" calls.
$preparseNotificationForPush = count($userIds) > 10;
$preparseNotificationForPush = count($users) > 10;
if ($preparseNotificationForPush) {
$fallbackLang = $this->serverConfig->getSystemValue('force_language', null);
if (is_string($fallbackLang)) {
Expand All @@ -298,13 +298,14 @@ protected function sendCallNotifications(Room $room): void {
} else {
$fallbackLang = $this->serverConfig->getSystemValueString('default_language', 'en');
/** @psalm-var array<string, string> $userLanguages */
$userLanguages = $this->serverConfig->getUserValueForUsers('core', 'lang', $userIds);
$userLanguages = $this->serverConfig->getUserValueForUsers('core', 'lang', array_map('strval', array_keys($users)));
}
}

$this->connection->beginTransaction();
try {
foreach ($userIds as $userId) {
foreach ($users as $userId => $isImportant) {
$userId = (string)$userId;
if ($actorId === $userId) {
continue;
}
Expand All @@ -327,6 +328,7 @@ protected function sendCallNotifications(Room $room): void {

try {
$userNotification->setUser($userId);
$userNotification->setPriorityNotification($isImportant);
$this->notificationManager->notify($userNotification);
} catch (\InvalidArgumentException $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
Expand Down Expand Up @@ -359,7 +361,8 @@ protected function sendCallNotification(Room $room, ?Attendee $actor, Attendee $
$notification->setSubject('call', [
'callee' => $actor?->getActorId(),
])
->setDateTime($dateTime);
->setDateTime($dateTime)
->setPriorityNotification($target->isImportant());
$this->notificationManager->notify($notification);
} catch (\InvalidArgumentException $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
Expand Down
4 changes: 4 additions & 0 deletions lib/Notification/Notifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@ public function prepare(INotification $notification, string $languageCode): INot
->setIcon($this->url->getAbsoluteURL($this->url->imagePath(Application::APP_ID, 'app-dark.svg')))
->setLink($this->url->linkToRouteAbsolute('spreed.Page.showCall', ['token' => $room->getToken()]));

if ($participant instanceof Participant && $this->notificationManager->isPreparingPushNotification()) {
$notification->setPriorityNotification($participant->getAttendee()->isImportant());
}

$subject = $notification->getSubject();
if ($subject === 'record_file_stored' || $subject === 'transcript_file_stored' || $subject === 'transcript_failed' || $subject === 'summary_file_stored' || $subject === 'summary_failed') {
return $this->parseStoredRecording($notification, $room, $participant, $l);
Expand Down
2 changes: 2 additions & 0 deletions lib/ResponseDefinitions.php
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,8 @@
* unreadMentionDirect: bool,
* unreadMessages: int,
* isArchived: bool,
* // Required capability: `important-conversations`
* isImportant: bool,
* }
*
* @psalm-type TalkRoomWithInvalidInvitations = TalkRoom&array{
Expand Down
32 changes: 26 additions & 6 deletions lib/Service/ParticipantService.php
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,26 @@ public function unarchiveConversation(Participant $participant): void {
$this->attendeeMapper->update($attendee);
}

/**
* @param Participant $participant
*/
public function markConversationAsImportant(Participant $participant): void {
$attendee = $participant->getAttendee();
$attendee->setImportant(true);
$attendee->setLastAttendeeActivity($this->timeFactory->getTime());
$this->attendeeMapper->update($attendee);
}

/**
* @param Participant $participant
*/
public function markConversationAsUnimportant(Participant $participant): void {
$attendee = $participant->getAttendee();
$attendee->setImportant(false);
$attendee->setLastAttendeeActivity($this->timeFactory->getTime());
$this->attendeeMapper->update($attendee);
}

/**
* @param RoomService $roomService
* @param Room $room
Expand Down Expand Up @@ -1918,12 +1938,12 @@ public function getActorsCountByType(Room $room, string $actorType, int $maxLast

/**
* @param Room $room
* @return string[]
* @return array<string, bool> (userId => isImportant)
*/
public function getParticipantUserIdsForCallNotifications(Room $room): array {
public function getParticipantUsersForCallNotifications(Room $room): array {
$query = $this->connection->getQueryBuilder();

$query->select('a.actor_id')
$query->select('a.actor_id', 'a.important')
->from('talk_attendees', 'a')
->leftJoin(
'a', 'talk_sessions', 's',
Expand Down Expand Up @@ -1960,14 +1980,14 @@ public function getParticipantUserIdsForCallNotifications(Room $room): array {
);
}

$userIds = [];
$users = [];
$result = $query->executeQuery();
while ($row = $result->fetch()) {
$userIds[] = $row['actor_id'];
$users[$row['actor_id']] = (bool)$row['important'];
}
$result->closeCursor();

return $userIds;
return $users;
}

/**
Expand Down
2 changes: 2 additions & 0 deletions lib/Service/RoomFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ public function formatRoomV4(
'recordingConsent' => $this->talkConfig->recordingConsentRequired() === RecordingService::CONSENT_REQUIRED_OPTIONAL ? $room->getRecordingConsent() : $this->talkConfig->recordingConsentRequired(),
'mentionPermissions' => Room::MENTION_PERMISSIONS_EVERYONE,
'isArchived' => false,
'isImportant' => false,
];

if ($room->isFederatedConversation()) {
Expand Down Expand Up @@ -229,6 +230,7 @@ public function formatRoomV4(
'breakoutRoomStatus' => $room->getBreakoutRoomStatus(),
'mentionPermissions' => $room->getMentionPermissions(),
'isArchived' => $attendee->isArchived(),
'isImportant' => $attendee->isImportant(),
]);

if ($room->isFederatedConversation()) {
Expand Down
7 changes: 6 additions & 1 deletion openapi-backend-sipbridge.json
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,8 @@
"unreadMention",
"unreadMentionDirect",
"unreadMessages",
"isArchived"
"isArchived",
"isImportant"
],
"properties": {
"actorId": {
Expand Down Expand Up @@ -813,6 +814,10 @@
},
"isArchived": {
"type": "boolean"
},
"isImportant": {
"type": "boolean",
"description": "Required capability: `important-conversations`"
}
}
},
Expand Down
7 changes: 6 additions & 1 deletion openapi-federation.json
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,8 @@
"unreadMention",
"unreadMentionDirect",
"unreadMessages",
"isArchived"
"isArchived",
"isImportant"
],
"properties": {
"actorId": {
Expand Down Expand Up @@ -867,6 +868,10 @@
},
"isArchived": {
"type": "boolean"
},
"isImportant": {
"type": "boolean",
"description": "Required capability: `important-conversations`"
}
}
},
Expand Down
7 changes: 6 additions & 1 deletion openapi-full.json
Original file line number Diff line number Diff line change
Expand Up @@ -1242,7 +1242,8 @@
"unreadMention",
"unreadMentionDirect",
"unreadMessages",
"isArchived"
"isArchived",
"isImportant"
],
"properties": {
"actorId": {
Expand Down Expand Up @@ -1467,6 +1468,10 @@
},
"isArchived": {
"type": "boolean"
},
"isImportant": {
"type": "boolean",
"description": "Required capability: `important-conversations`"
}
}
},
Expand Down
7 changes: 6 additions & 1 deletion openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1147,7 +1147,8 @@
"unreadMention",
"unreadMentionDirect",
"unreadMessages",
"isArchived"
"isArchived",
"isImportant"
],
"properties": {
"actorId": {
Expand Down Expand Up @@ -1372,6 +1373,10 @@
},
"isArchived": {
"type": "boolean"
},
"isImportant": {
"type": "boolean",
"description": "Required capability: `important-conversations`"
}
}
},
Expand Down
2 changes: 2 additions & 0 deletions src/types/openapi/openapi-backend-sipbridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,8 @@ export type components = {
/** Format: int64 */
unreadMessages: number;
isArchived: boolean;
/** @description Required capability: `important-conversations` */
isImportant: boolean;
};
RoomLastMessage: components["schemas"]["ChatMessage"] | components["schemas"]["ChatProxyMessage"];
};
Expand Down
Loading
Loading