Skip to content

Commit

Permalink
feat(mentions): allow teams to be mentioned
Browse files Browse the repository at this point in the history
Signed-off-by: Anna Larch <anna@nextcloud.com>
  • Loading branch information
miaulalala authored and nickvergessen committed Jan 30, 2025
1 parent 9815309 commit dc560f9
Show file tree
Hide file tree
Showing 16 changed files with 404 additions and 21 deletions.
67 changes: 67 additions & 0 deletions lib/Chat/AutoComplete/SearchPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ public function search($search, $limit, $offset, ISearchResult $searchResult): b
$emailAttendees = [];
/** @var list<Attendee> $guestAttendees */
$guestAttendees = [];
/** @var array<string, string> $teamIds */
$teamIds = [];

if ($this->room->getType() === Room::TYPE_ONE_TO_ONE) {
// Add potential leavers of one-to-one rooms again.
Expand All @@ -92,6 +94,8 @@ public function search($search, $limit, $offset, ISearchResult $searchResult): b
$cloudIds[$attendee->getActorId()] = $attendee->getDisplayName();
} elseif ($attendee->getActorType() === Attendee::ACTOR_GROUPS) {
$groupIds[$attendee->getActorId()] = $attendee->getDisplayName();
} elseif ($attendee->getActorType() === Attendee::ACTOR_CIRCLES) {
$teamIds[$attendee->getActorId()] = $attendee->getDisplayName();
}
}
}
Expand All @@ -101,6 +105,7 @@ public function search($search, $limit, $offset, ISearchResult $searchResult): b
$this->searchGuests($search, $guestAttendees, $searchResult);
$this->searchEmails($search, $emailAttendees, $searchResult);
$this->searchFederatedUsers($search, $cloudIds, $searchResult);
$this->searchTeams($search, $teamIds, $searchResult);

return false;
}
Expand Down Expand Up @@ -352,6 +357,58 @@ protected function searchEmails(string $search, array $attendees, ISearchResult
$searchResult->addResultSet($type, $matches, $exactMatches);
}

/**
* @param string $search
* @param array<string, Attendee> $attendees
* @param ISearchResult $searchResult
*/
/**
* @param array<string|int, string> $teams
*/
protected function searchTeams(string $search, array $teams, ISearchResult $searchResult): void {
$search = strtolower($search);

$type = new SearchResultType('teams');

$matches = $exactMatches = [];
foreach ($teams as $teamId => $displayName) {
if ($displayName === '') {
continue;
}

$teamId = (string)$teamId;
if ($searchResult->hasResult($type, $teamId)) {
continue;
}

if ($search === '') {
$matches[] = $this->createTeamResult($teamId, $displayName);
continue;
}

if (strtolower($teamId) === $search) {
$exactMatches[] = $this->createTeamResult($teamId, $displayName);
continue;
}

if (stripos($teamId, $search) !== false) {
$matches[] = $this->createTeamResult($teamId, $displayName);
continue;
}

if (strtolower($displayName) === $search) {
$exactMatches[] = $this->createTeamResult($teamId, $displayName);
continue;
}

if (stripos($displayName, $search) !== false) {
$matches[] = $this->createTeamResult($teamId, $displayName);
}
}

$searchResult->addResultSet($type, $matches, $exactMatches);
}

protected function createResult(string $type, string $uid, string $name): array {
if ($type === 'user' && $name === '') {
$name = $this->userManager->getDisplayName($uid) ?? $uid;
Expand Down Expand Up @@ -401,4 +458,14 @@ protected function createEmailResult(string $actorId, string $name, ?string $ema

return $data;
}

protected function createTeamResult(string $actorId, string $name): array {
return [
'label' => $name,
'value' => [
'shareType' => 'team',
'shareWith' => 'team/' . $actorId,
],
];
}
}
46 changes: 46 additions & 0 deletions lib/Chat/Notifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ public function notifyMentionedUsers(Room $chat, IComment $comment, array $alrea
public function getUsersToNotify(Room $chat, IComment $comment, array $alreadyNotifiedUsers, ?Participant $participant = null): array {
$usersToNotify = $this->getMentionedUsers($comment);
$usersToNotify = $this->getMentionedGroupMembers($chat, $comment, $usersToNotify);
$usersToNotify = $this->getMentionedTeamMembers($chat, $comment, $usersToNotify);
$usersToNotify = $this->addMentionAllToList($chat, $usersToNotify, $participant);
$usersToNotify = $this->removeAlreadyNotifiedUsers($usersToNotify, $alreadyNotifiedUsers);

Expand Down Expand Up @@ -532,6 +533,51 @@ private function getMentionedGroupMembers(Room $chat, IComment $comment, array $
return $list;
}

/**
* @param Room $chat
* @param IComment $comment
* @param array $list
* @psalm-param array<int, array{type: string, id: string, reason: string, sourceId?: string}> $list
* @return array[]
* @psalm-return array<int, array{type: string, id: string, reason: string, sourceId?: string}>
*/
private function getMentionedTeamMembers(Room $chat, IComment $comment, array $list): array {
$mentions = $comment->getMentions();

if (empty($mentions)) {
return [];
}

$alreadyMentionedUserIds = array_filter(
array_map(static fn (array $entry) => $entry['type'] === Attendee::ACTOR_USERS ? $entry['id'] : null, $list),
static fn ($userId) => $userId !== null
);
$alreadyMentionedUserIds = array_flip($alreadyMentionedUserIds);

foreach ($mentions as $mention) {
if ($mention['type'] !== 'team') {
continue;
}

$members = $this->participantService->getCircleMembers($mention['id']);
if (empty($members)) {
continue;
}

foreach ($members as $member) {
$list[] = [
'id' => $member->getUserId(),
'type' => Attendee::ACTOR_USERS,
'reason' => 'team',
'sourceId' => $mention['id'],
];
$alreadyMentionedUserIds[$member->getUserId()] = true;
}
}

return $list;
}

/**
* Creates a notification for the given chat message comment and mentioned
* user ID.
Expand Down
57 changes: 55 additions & 2 deletions lib/Chat/Parser/UserMention.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

namespace OCA\Talk\Chat\Parser;

use OCA\Circles\CirclesManager;
use OCA\Talk\Chat\ChatManager;
use OCA\Talk\Events\MessageParseEvent;
use OCA\Talk\Exceptions\ParticipantNotFoundException;
Expand All @@ -17,6 +18,7 @@
use OCA\Talk\Room;
use OCA\Talk\Service\AvatarService;
use OCA\Talk\Service\ParticipantService;
use OCP\App\IAppManager;
use OCP\Comments\ICommentsManager;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
Expand All @@ -25,14 +27,20 @@
use OCP\IGroupManager;
use OCP\IL10N;
use OCP\IUserManager;
use OCP\Server;

/**
* Helper class to get a rich message from a plain text message.
* @template-implements IEventListener<Event>
*/
class UserMention implements IEventListener {
/** @var array<string, string> */
protected array $circleNames = [];
/** @var array<string, string> */
protected array $circleLinks = [];

public function __construct(
protected IAppManager $appManager,
protected ICommentsManager $commentsManager,
protected IUserManager $userManager,
protected IGroupManager $groupManager,
Expand Down Expand Up @@ -117,7 +125,7 @@ protected function parseMessage(Message $chatMessage): void {
$mention['type'] === 'email' ||
$mention['type'] === 'group' ||
// $mention['type'] === 'federated_group' ||
// $mention['type'] === 'team' ||
$mention['type'] === 'team' ||
// $mention['type'] === 'federated_team' ||
$mention['type'] === 'federated_user') {
$search = $mention['type'] . '/' . $mention['id'];
Expand All @@ -135,7 +143,7 @@ protected function parseMessage(Message $chatMessage): void {
&& !str_starts_with($search, 'email/')
&& !str_starts_with($search, 'group/')
// && !str_starts_with($search, 'federated_group/')
// && !str_starts_with($search, 'team/')
&& !str_starts_with($search, 'team/')
// && !str_starts_with($search, 'federated_team/')
&& !str_starts_with($search, 'federated_user/')) {
$message = str_replace('@' . $search, '{' . $mentionParameterId . '}', $message);
Expand Down Expand Up @@ -213,6 +221,8 @@ protected function parseMessage(Message $chatMessage): void {
'id' => $mention['id'],
'name' => $displayName,
];
} elseif ($mention['type'] === 'team') {
$messageParameters[$mentionParameterId] = $this->getCircle($mention['id']);
} else {
try {
$displayName = $this->commentsManager->resolveDisplayName($mention['type'], $mention['id']);
Expand Down Expand Up @@ -256,4 +266,47 @@ protected function getRoomType(Room $room): string {
throw new \InvalidArgumentException('Unknown room type');
}
}

protected function getCircle(string $circleId): array {
if (!$this->appManager->isEnabledForUser('circles')) {
return [
'type' => 'highlight',
'id' => $circleId,
'name' => $circleId,
];
}

if (!isset($this->circleNames[$circleId])) {
$this->loadCircleDetails($circleId);
}

if (!isset($this->circleNames[$circleId])) {
return [
'type' => 'highlight',
'id' => $circleId,
'name' => $circleId,
];
}

return [
'type' => 'circle',
'id' => $circleId,
'name' => $this->circleNames[$circleId],
'link' => $this->circleLinks[$circleId],
];
}

protected function loadCircleDetails(string $circleId): void {
try {
$circlesManager = Server::get(CirclesManager::class);
$circlesManager->startSuperSession();
$circle = $circlesManager->getCircle($circleId);

$this->circleNames[$circleId] = $circle->getDisplayName();
$this->circleLinks[$circleId] = $circle->getUrl();
} catch (\Exception) {
} finally {
$circlesManager?->stopSession();
}
}
}
4 changes: 2 additions & 2 deletions lib/Controller/RoomController.php
Original file line number Diff line number Diff line change
Expand Up @@ -1147,7 +1147,7 @@ protected function formatParticipantList(array $participants, bool $includeStatu
* Add a participant to a room
*
* @param string $newParticipant New participant
* @param 'users'|'groups'|'circles'|'emails'|'federated_users'|'phones' $source Source of the participant
* @param 'users'|'groups'|'circles'|'emails'|'federated_users'|'phones'|'teams' $source Source of the participant
* @return DataResponse<Http::STATUS_OK, array{type?: int}, array{}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_NOT_FOUND|Http::STATUS_NOT_IMPLEMENTED, array{error: 'ban'|'cloud-id'|'federation'|'moderator'|'new-participant'|'outgoing'|'reach-remote'|'room-type'|'sip'|'source'|'trusted-servers'}, array{}>
*
* 200: Participant successfully added
Expand Down Expand Up @@ -1215,7 +1215,7 @@ public function addParticipantToRoom(string $newParticipant, string $source = 'u
}

$this->participantService->addGroup($this->room, $group, $participants);
} elseif ($source === 'circles') {
} elseif ($source === 'circles' || $source === 'teams') {
if (!$this->appManager->isEnabledForUser('circles')) {
return new DataResponse(['error' => 'new-participant'], Http::STATUS_BAD_REQUEST);
}
Expand Down
Loading

0 comments on commit dc560f9

Please sign in to comment.