Skip to content

Commit

Permalink
feat(federation): Support mentioning federated users
Browse files Browse the repository at this point in the history
Signed-off-by: Joas Schilling <coding@schilljs.com>
  • Loading branch information
nickvergessen committed Feb 28, 2024
1 parent 39f2ae5 commit a916baf
Show file tree
Hide file tree
Showing 14 changed files with 303 additions and 183 deletions.
17 changes: 9 additions & 8 deletions docs/chat.md
Original file line number Diff line number Diff line change
Expand Up @@ -472,14 +472,15 @@ See [OCP\RichObjectStrings\Definitions](https://github.com/nextcloud/server/blob
- Data:
Array of suggestions, each suggestion has at least:

| field | type | Description |
|-----------------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | string | The user id which should be sent as `@<id>` in the message (user ids that contain spaces as well as guest ids need to be wrapped in double-quotes when sending in a message: `@"space user"` and `@"guest/random-string"`) |
| `label` | string | The displayname of the user |
| `source` | string | The type of the user, currently only `users`, `guests` or `calls` (for mentioning the whole conversation |
| `status` | string | Optional: Only available with `includeStatus=true` and for users with a set status |
| `statusIcon` | string | Optional: Only available with `includeStatus=true` and for users with a set status |
| `statusMessage` | string | Optional: Only available with `includeStatus=true` and for users with a set status |
| field | type | Description |
|-----------------|--------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `insert` | string | Optional: Only available with `federation-v1` capability - the identifier which should be sent as `@<id>` in the message (ids that contain spaces or slashes need to be wrapped in double-quotes when sending in a message: `@"space user"` and `@"guest/random-string"`) |
| `id` | string | The id of the participant to mention |
| `label` | string | The display name of the mention option |
| `source` | string | The type of the mention option, currently only `users`, `federated_users`, `group`, `guests` or `calls` (for mentioning the whole conversation) |
| `status` | string | Optional: Only available with `includeStatus=true` and for users with a set status |
| `statusIcon` | string | Optional: Only available with `includeStatus=true` and for users with a set status |
| `statusMessage` | string | Optional: Only available with `includeStatus=true` and for users with a set status |

## System messages

Expand Down
1 change: 1 addition & 0 deletions lib/Chat/ChatManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,7 @@ public function addConversationNotify(array $results, string $search, Room $room
'id' => 'all',
'label' => $roomDisplayName,
'source' => 'calls',
'insert' => 'all',
]);
}
return $results;
Expand Down
39 changes: 35 additions & 4 deletions lib/Chat/Parser/UserMention.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
use OCP\Comments\ICommentsManager;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Federation\ICloudIdManager;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\IL10N;
Expand All @@ -56,6 +57,7 @@ public function __construct(
protected IGroupManager $groupManager,
protected GuestManager $guestManager,
protected AvatarService $avatarService,
protected ICloudIdManager $cloudIdManager,
protected ParticipantService $participantService,
protected IL10N $l,
) {
Expand Down Expand Up @@ -125,20 +127,29 @@ protected function parseMessage(Message $chatMessage): void {
$mentionTypeCount[$mention['type']]++;

$search = $mention['id'];
if ($mention['type'] === 'group') {
$search = 'group/' . $mention['id'];
if (
$mention['type'] === 'group' ||
// $mention['type'] === 'federated_group' ||
// $mention['type'] === 'team' ||
// $mention['type'] === 'federated_team' ||
$mention['type'] === 'federated_user') {
$search = $mention['type'] . '/' . $mention['id'];
}

// To keep a limited character set in parameter IDs ([a-zA-Z0-9-])
// the mention parameter ID does not include the mention ID (which
// could contain characters like '@' for user IDs) but a one-based
// index of the mentions of that type.
$mentionParameterId = 'mention-' . $mention['type'] . $mentionTypeCount[$mention['type']];
$mentionParameterId = 'mention-' . str_replace('_', '-', $mention['type']) . $mentionTypeCount[$mention['type']];

$message = str_replace('@"' . $search . '"', '{' . $mentionParameterId . '}', $message);
if (!str_contains($search, ' ')
&& !str_starts_with($search, 'guest/')
&& !str_starts_with($search, 'group/')) {
&& !str_starts_with($search, 'group/')
// && !str_starts_with($search, 'federated_group/')
// && !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 @@ -168,6 +179,26 @@ protected function parseMessage(Message $chatMessage): void {
'id' => $mention['id'],
'name' => $displayName,
];
} elseif ($mention['type'] === 'federated_user') {
try {
$cloudId = $this->cloudIdManager->resolveCloudId($mention['id']);
} catch (\Throwable) {
continue;
}

try {
$participant = $this->participantService->getParticipantByActor($chatMessage->getRoom(), Attendee::ACTOR_FEDERATED_USERS, $mention['id']);
$displayName = $participant->getAttendee()->getDisplayName() ?: $cloudId->getDisplayId();
} catch (ParticipantNotFoundException) {
$displayName = $mention['id'];
}

$messageParameters[$mentionParameterId] = [
'type' => 'user',
'id' => $cloudId->getUser(),
'name' => $displayName,
'server' => $cloudId->getRemote()
];
} elseif ($mention['type'] === 'group') {
$group = $this->groupManager->get($mention['id']);
if ($group instanceof IGroup) {
Expand Down
10 changes: 10 additions & 0 deletions lib/Controller/ChatController.php
Original file line number Diff line number Diff line change
Expand Up @@ -1279,6 +1279,7 @@ protected function prepareResultArray(array $results, array $statuses): array {
'id' => $result['value']['shareWith'],
'label' => $result['label'],
'source' => $type,
'insert' => $this->createMentionString($type, $result['value']['shareWith']),
];

if ($type === Attendee::ACTOR_USERS && isset($statuses[$data['id']])) {
Expand All @@ -1293,4 +1294,13 @@ protected function prepareResultArray(array $results, array $statuses): array {
}
return $output;
}

protected function createMentionString(string $type, string $id): string {
if ($type === Attendee::ACTOR_USERS || $type === Attendee::ACTOR_GUESTS) {
return $id;
}

// It's "group/admin" so we have to strip off the trailing "s" from the type
return substr($type, 0, -1) . '/' . $id;
}
}
1 change: 1 addition & 0 deletions lib/ResponseDefinitions.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
* id: string,
* label: string,
* source: string,
* insert: string,
* status: ?string,
* statusClearAt: ?int,
* statusIcon: ?string,
Expand Down
4 changes: 4 additions & 0 deletions openapi-full.json
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@
"id",
"label",
"source",
"insert",
"status",
"statusClearAt",
"statusIcon",
Expand All @@ -322,6 +323,9 @@
"source": {
"type": "string"
},
"insert": {
"type": "string"
},
"status": {
"type": "string",
"nullable": true
Expand Down
4 changes: 4 additions & 0 deletions openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@
"id",
"label",
"source",
"insert",
"status",
"statusClearAt",
"statusIcon",
Expand All @@ -263,6 +264,9 @@
"source": {
"type": "string"
},
"insert": {
"type": "string"
},
"status": {
"type": "string",
"nullable": true
Expand Down
1 change: 1 addition & 0 deletions src/components/NewMessage/NewMessage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,7 @@ export default {
}

// Caching the user id data for each possible mention
possibleMention.id = possibleMention.insert
this.userData[possibleMention.id] = possibleMention
})

Expand Down
12 changes: 11 additions & 1 deletion tests/integration/features/bootstrap/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -2874,17 +2874,27 @@ public function userGetsTheFollowingCandidateMentionsInRoomFor($user, $identifie
Assert::assertRegExp('/^guest\/[0-9a-f]{40}$/', $mentions[$key]['id']);
$mentions[$key]['id'] = 'GUEST_ID';
}
if ($row['insert'] === 'GUEST_ID') {
Assert::assertRegExp('/^guest\/[0-9a-f]{40}$/', $mentions[$key]['insert']);
$mentions[$key]['insert'] = 'GUEST_ID';
}
if (str_ends_with($row['id'], '@{$BASE_URL}')) {
$row['id'] = str_replace('{$BASE_URL}', rtrim($this->baseUrl, '/'), $row['id']);
}
if (str_ends_with($row['id'], '@{$REMOTE_URL}')) {
$row['id'] = str_replace('{$REMOTE_URL}', rtrim($this->baseRemoteUrl, '/'), $row['id']);
}
if (str_ends_with($row['insert'], '@{$BASE_URL}')) {
$row['insert'] = str_replace('{$BASE_URL}', rtrim($this->baseUrl, '/'), $row['insert']);
}
if (str_ends_with($row['insert'], '@{$REMOTE_URL}')) {
$row['insert'] = str_replace('{$REMOTE_URL}', rtrim($this->baseRemoteUrl, '/'), $row['insert']);
}
if (array_key_exists('avatar', $row)) {
Assert::assertRegExp('/' . self::$identifierToToken[$row['avatar']] . '\/avatar/', $mentions[$key]['avatar']);
unset($row['avatar']);
}
unset($mentions[$key]['avatar'], );
unset($mentions[$key]['avatar']);
Assert::assertEquals($row, $mentions[$key]);
}
}
Expand Down
Loading

0 comments on commit a916baf

Please sign in to comment.