Skip to content

Commit

Permalink
feat(federation): Implement notifications for mentions, reply and full
Browse files Browse the repository at this point in the history
Signed-off-by: Joas Schilling <coding@schilljs.com>
  • Loading branch information
nickvergessen committed Mar 6, 2024
1 parent 8a823da commit d1fa2c0
Show file tree
Hide file tree
Showing 17 changed files with 365 additions and 66 deletions.
26 changes: 23 additions & 3 deletions lib/Chat/MessageParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use OCA\Talk\MatterbridgeManager;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Model\Message;
use OCA\Talk\Model\ProxyCacheMessage;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\BotService;
Expand All @@ -51,17 +52,36 @@ class MessageParser {
protected array $botNames = [];

public function __construct(
protected IEventDispatcher $dispatcher,
protected IUserManager $userManager,
protected IEventDispatcher $dispatcher,
protected IUserManager $userManager,
protected ParticipantService $participantService,
protected BotService $botService,
protected BotService $botService,
) {
}

public function createMessage(Room $room, ?Participant $participant, IComment $comment, IL10N $l): Message {
return new Message($room, $participant, $comment, $l);
}

public function createMessageFromProxyCache(Room $room, ?Participant $participant, ProxyCacheMessage $proxy, IL10N $l): Message {
$message = new Message($room, $participant, null, $l, $proxy);

$message->setActor(
$proxy->getActorType(),
$proxy->getActorId(),
$proxy->getActorDisplayName(),
);

$message->setMessageType($proxy->getMessageType());

$message->setMessage(
$proxy->getMessage(),
$proxy->getParsedMessageParameters()
);

return $message;
}

public function parseMessage(Message $message): void {
$message->setMessage($message->getComment()->getMessage(), []);

Expand Down
2 changes: 1 addition & 1 deletion lib/Chat/Parser/UserMention.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ protected function parseMessage(Message $chatMessage): void {
$messageParameters[$mentionParameterId] = [
'type' => $mention['type'],
'id' => $chatMessage->getRoom()->getToken(),
'name' => $chatMessage->getRoom()->getDisplayName($userId),
'name' => $chatMessage->getRoom()->getDisplayName($userId, true),
'call-type' => $this->getRoomType($chatMessage->getRoom()),
'icon-url' => $this->avatarService->getAvatarUrl($chatMessage->getRoom()),
];
Expand Down
2 changes: 1 addition & 1 deletion lib/Controller/ChatController.php
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,7 @@ protected function prepareCommentsAsDataResponse(array $comments, int $lastCommo
$message = $this->messageParser->createMessage($this->room, $this->participant, $comment, $this->l);
$this->messageParser->parseMessage($message);

$expireDate = $message->getComment()->getExpireDate();
$expireDate = $message->getExpirationDateTime();
if ($expireDate instanceof \DateTime && $expireDate < $now) {
$commentIdToIndex[$id] = null;
continue;
Expand Down
1 change: 1 addition & 0 deletions lib/Federation/BackendNotifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ public function sendRoomModifiedUpdate(
* Send information to remote participants that a message was posted
* Sent from Host server to Remote participant server
*
* @param array{remoteMessageId: int, actorType: string, actorId: string, actorDisplayName: string, messageType: string, systemMessage: string, expirationDatetime: string, message: string, messageParameter: string, creationDatetime: string, metaData: string} $messageData
* @param array{unreadMessages: int, unreadMention: bool, unreadMentionDirect: bool} $unreadInfo
*/
public function sendMessageUpdate(
Expand Down
39 changes: 32 additions & 7 deletions lib/Federation/CloudFederationProviderTalk.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,15 @@
use OCA\Talk\Events\AttendeesAddedEvent;
use OCA\Talk\Exceptions\ParticipantNotFoundException;
use OCA\Talk\Exceptions\RoomNotFoundException;
use OCA\Talk\Federation\Proxy\TalkV1\UserConverter;
use OCA\Talk\Manager;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Model\AttendeeMapper;
use OCA\Talk\Model\Invitation;
use OCA\Talk\Model\InvitationMapper;
use OCA\Talk\Model\ProxyCacheMessages;
use OCA\Talk\Model\ProxyCacheMessagesMapper;
use OCA\Talk\Model\ProxyCacheMessage;
use OCA\Talk\Model\ProxyCacheMessageMapper;
use OCA\Talk\Notification\FederationChatNotifier;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\ParticipantService;
Expand Down Expand Up @@ -87,7 +89,9 @@ public function __construct(
private ISession $session,
private IEventDispatcher $dispatcher,
private LoggerInterface $logger,
private ProxyCacheMessagesMapper $proxyCacheMessagesMapper,
private ProxyCacheMessageMapper $proxyCacheMessageMapper,
private FederationChatNotifier $federationChatNotifier,
private UserConverter $userConverter,
ICacheFactory $cacheFactory,
) {
$this->proxyCacheMessages = $cacheFactory->isAvailable() ? $cacheFactory->createDistributed('talk/pcm/') : null;
Expand Down Expand Up @@ -316,7 +320,7 @@ private function roomModified(int $remoteAttendeeId, array $notification): array

/**
* @param int $remoteAttendeeId
* @param array{remoteServerUrl: string, sharedSecret: string, remoteToken: string, messageData: array{remoteMessageId: int, actorType: string, actorId: string, actorDisplayName: string, messageType: string, systemMessage: string, expirationDatetime: string, message: string, messageParameter: string}, unreadInfo: array{unreadMessages: int, unreadMention: bool, unreadMentionDirect: bool}} $notification
* @param array{remoteServerUrl: string, sharedSecret: string, remoteToken: string, messageData: array{remoteMessageId: int, actorType: string, actorId: string, actorDisplayName: string, messageType: string, systemMessage: string, expirationDatetime: string, message: string, messageParameter: string, creationDatetime: string, metaData: string}, unreadInfo: array{unreadMessages: int, unreadMention: bool, unreadMentionDirect: bool}} $notification
* @return array
* @throws ActionNotSupportedException
* @throws AuthenticationFailedException
Expand All @@ -335,7 +339,7 @@ private function messagePosted(int $remoteAttendeeId, array $notification): arra
throw new ShareNotFound();
}

$message = new ProxyCacheMessages();
$message = new ProxyCacheMessage();
$message->setLocalToken($room->getToken());
$message->setRemoteServerUrl($notification['remoteServerUrl']);
$message->setRemoteToken($notification['remoteToken']);
Expand All @@ -346,12 +350,25 @@ private function messagePosted(int $remoteAttendeeId, array $notification): arra
$message->setMessageType($notification['messageData']['messageType']);
$message->setSystemMessage($notification['messageData']['systemMessage']);
if ($notification['messageData']['expirationDatetime']) {
$message->setExpirationDatetime(new \DateTimeImmutable($notification['messageData']['expirationDatetime']));
$message->setExpirationDatetime(new \DateTime($notification['messageData']['expirationDatetime']));
}

// We transform the parameters when storing in the PCM, so we only have
// to do it once for each message.
$convertedParameters = $this->userConverter->convertMessageParameters($room, [
'message' => $notification['messageData']['message'],
'messageParameters' => json_decode($notification['messageData']['messageParameter'], true, flags: JSON_THROW_ON_ERROR),
]);
$notification['messageData']['message'] = $convertedParameters['message'];
$notification['messageData']['messageParameter'] = json_encode($convertedParameters['messageParameters'], JSON_THROW_ON_ERROR);

$message->setMessage($notification['messageData']['message']);
$message->setMessageParameters($notification['messageData']['messageParameter']);
$message->setCreationDatetime(new \DateTime($notification['messageData']['creationDatetime']));
$message->setMetaData($notification['messageData']['metaData']);

try {
$this->proxyCacheMessagesMapper->insert($message);
$this->proxyCacheMessageMapper->insert($message);

$lastMessageId = $room->getLastMessageId();
if ($notification['messageData']['remoteMessageId'] > $lastMessageId) {
Expand All @@ -374,6 +391,12 @@ private function messagePosted(int $remoteAttendeeId, array $notification): arra
$this->logger->error('Error saving proxy cache message failed: ' . $e->getMessage(), ['exception' => $e]);
throw $e;
}

$message = $this->proxyCacheMessageMapper->findByRemote(
$notification['remoteServerUrl'],
$notification['remoteToken'],
$notification['messageData']['remoteMessageId'],
);
}

try {
Expand All @@ -390,6 +413,8 @@ private function messagePosted(int $remoteAttendeeId, array $notification): arra
$notification['unreadInfo']['unreadMentionDirect'],
);

$this->federationChatNotifier->handleChatMessage($room, $participant, $message, $notification);

return [];
}

Expand Down
14 changes: 13 additions & 1 deletion lib/Federation/Proxy/TalkV1/Notifier/MessageSentListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
use OCA\Talk\Events\SystemMessagesMultipleSentEvent;
use OCA\Talk\Federation\BackendNotifier;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Model\ProxyCacheMessage;
use OCA\Talk\Service\ParticipantService;
use OCP\Comments\IComment;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Federation\ICloudIdManager;
Expand Down Expand Up @@ -78,6 +80,14 @@ public function handle(Event $event): void {
}

$expireDate = $event->getComment()->getExpireDate();
$creationDate = $event->getComment()->getCreationDateTime();

$metaData = $event->getComment()->getMetaData() ?? [];
$parent = $event->getParent();
if ($parent instanceof IComment) {
$metaData[ProxyCacheMessage::METADATA_REPLYTO_TYPE] = $parent->getActorType();
$metaData[ProxyCacheMessage::METADATA_REPLYTO_ID] = $parent->getActorId();
}

$messageData = [
'remoteMessageId' => (int) $event->getComment()->getId(),
Expand All @@ -88,7 +98,9 @@ public function handle(Event $event): void {
'systemMessage' => $chatMessage->getMessageType() === ChatManager::VERB_SYSTEM ? $chatMessage->getMessageRaw() : '',
'expirationDatetime' => $expireDate ? $expireDate->format(\DateTime::ATOM) : '',
'message' => $chatMessage->getMessage(),
'messageParameter' => json_encode($chatMessage->getMessageParameters()),
'messageParameter' => json_encode($chatMessage->getMessageParameters(), JSON_THROW_ON_ERROR),
'creationDatetime' => $creationDate->format(\DateTime::ATOM),
'metaData' => json_encode($metaData, JSON_THROW_ON_ERROR),
];

$participants = $this->participantService->getParticipantsByActorType($event->getRoom(), Attendee::ACTOR_FEDERATED_USERS);
Expand Down
10 changes: 5 additions & 5 deletions lib/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -1096,10 +1096,6 @@ public function resolveRoomDisplayName(Room $room, string $userId, bool $forceNa
return $this->l->t('Talk updates ✅');
}

if ($forceName) {
return $room->getName();
}

if ($this->federationAuthenticator->isFederationRequest()) {
try {
$authenticatedRoom = $this->federationAuthenticator->getRoom();
Expand All @@ -1110,7 +1106,7 @@ public function resolveRoomDisplayName(Room $room, string $userId, bool $forceNa
}
}

if ($userId === '' && $room->getType() !== Room::TYPE_PUBLIC) {
if (!$forceName && $userId === '' && $room->getType() !== Room::TYPE_PUBLIC) {
return $this->l->t('Private conversation');
}

Expand Down Expand Up @@ -1151,6 +1147,10 @@ public function resolveRoomDisplayName(Room $room, string $userId, bool $forceNa
return $otherParticipant;
}

if ($forceName) {
return $room->getName();
}

if (!$this->isRoomListableByUser($room, $userId)) {
try {
if ($userId === '') {
Expand Down
13 changes: 11 additions & 2 deletions lib/Model/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@ class Message {
public function __construct(
protected Room $room,
protected ?Participant $participant,
protected IComment $comment,
protected ?IComment $comment,
protected IL10N $l,
protected ?ProxyCacheMessage $proxy = null,
) {
}

Expand All @@ -93,7 +94,7 @@ public function getRoom(): Room {
return $this->room;
}

public function getComment(): IComment {
public function getComment(): ?IComment {
return $this->comment;
}

Expand All @@ -109,6 +110,14 @@ public function getParticipant(): ?Participant {
* Parsed message information
*/

public function getMessageId(): int {
return $this->comment ? (int) $this->comment->getId() : $this->proxy->getRemoteMessageId();
}

public function getExpirationDateTime(): ?\DateTimeInterface {
return $this->comment ? $this->comment->getExpireDate() : $this->proxy->getExpirationDatetime();
}

public function setVisibility(bool $visible): void {
$this->visible = $visible;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
declare(strict_types=1);

/**
* @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com>
* @copyright Copyright (c) 2024 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
Expand Down Expand Up @@ -48,16 +48,23 @@
* @method string getMessageType()
* @method void setSystemMessage(?string $systemMessage)
* @method string|null getSystemMessage()
* @method void setExpirationDatetime(?\DateTimeImmutable $expirationDatetime)
* @method \DateTimeImmutable|null getExpirationDatetime()
* @method void setExpirationDatetime(?\DateTime $expirationDatetime)
* @method \DateTime|null getExpirationDatetime()
* @method void setMessage(?string $message)
* @method string|null getMessage()
* @method void setMessageParameters(?string $messageParameters)
* @method string|null getMessageParameters()
* @method void setCreationDatetime(?\DateTime $creationDatetime)
* @method \DateTime|null getCreationDatetime()
* @method void setMetaData(?string $metaData)
* @method string|null getMetaData()
*
* @psalm-import-type TalkRoomProxyMessage from ResponseDefinitions
*/
class ProxyCacheMessages extends Entity implements \JsonSerializable {
class ProxyCacheMessage extends Entity implements \JsonSerializable {
public const METADATA_REPLYTO_TYPE = 'replyToActorType';
public const METADATA_REPLYTO_ID = 'replyToActorId';


protected string $localToken = '';
protected string $remoteServerUrl = '';
Expand All @@ -68,9 +75,11 @@ class ProxyCacheMessages extends Entity implements \JsonSerializable {
protected ?string $actorDisplayName = null;
protected ?string $messageType = null;
protected ?string $systemMessage = null;
protected ?\DateTimeImmutable $expirationDatetime = null;
protected ?\DateTime $expirationDatetime = null;
protected ?string $message = null;
protected ?string $messageParameters = null;
protected ?\DateTime $creationDatetime = null;
protected ?string $metaData = null;

public function __construct() {
$this->addType('localToken', 'string');
Expand All @@ -85,6 +94,16 @@ public function __construct() {
$this->addType('expirationDatetime', 'datetime');
$this->addType('message', 'string');
$this->addType('messageParameters', 'string');
$this->addType('creationDatetime', 'datetime');
$this->addType('metaData', 'string');
}

public function getParsedMessageParameters(): array {
return json_decode($this->getMessageParameters() ?? '[]', true);
}

public function getParsedMetaData(): array {
return json_decode($this->getMetaData() ?? '[]', true);
}

/**
Expand All @@ -100,11 +119,12 @@ public function jsonSerialize(): array {
'actorType' => $this->getActorType(),
'actorId' => $this->getActorId(),
'actorDisplayName' => $this->getActorDisplayName(),
'timestamp' => $this->getCreationDatetime()->getTimestamp(),
'expirationTimestamp' => $expirationTimestamp,
'messageType' => $this->getMessageType(),
'systemMessage' => $this->getSystemMessage() ?? '',
'message' => $this->getMessage() ?? '',
'messageParameters' => json_decode($this->getMessageParameters() ?? '[]', true),
'messageParameters' => $this->getParsedMessageParameters(),
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,36 @@
use OCP\IDBConnection;

/**
* @method ProxyCacheMessages mapRowToEntity(array $row)
* @method ProxyCacheMessages findEntity(IQueryBuilder $query)
* @method ProxyCacheMessages[] findEntities(IQueryBuilder $query)
* @template-extends QBMapper<ProxyCacheMessages>
* @method ProxyCacheMessage mapRowToEntity(array $row)
* @method ProxyCacheMessage findEntity(IQueryBuilder $query)
* @method ProxyCacheMessage[] findEntities(IQueryBuilder $query)
* @template-extends QBMapper<ProxyCacheMessage>
*/
class ProxyCacheMessagesMapper extends QBMapper {
class ProxyCacheMessageMapper extends QBMapper {
use TTransactional;

public function __construct(
IDBConnection $db,
) {
parent::__construct($db, 'talk_proxy_messages', ProxyCacheMessages::class);
parent::__construct($db, 'talk_proxy_messages', ProxyCacheMessage::class);
}

/**
* @throws DoesNotExistException
*/
public function findByRemote(string $remoteServerUrl, string $remoteToken, int $remoteMessageId): ProxyCacheMessages {
public function findById(int $proxyId): ProxyCacheMessage {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('id', $query->createNamedParameter($proxyId, IQueryBuilder::PARAM_INT)));

return $this->findEntity($query);
}

/**
* @throws DoesNotExistException
*/
public function findByRemote(string $remoteServerUrl, string $remoteToken, int $remoteMessageId): ProxyCacheMessage {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
Expand Down
Loading

0 comments on commit d1fa2c0

Please sign in to comment.