Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/spreed.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorJoas Schilling <213943+nickvergessen@users.noreply.github.com>2022-09-29 19:27:22 +0300
committerGitHub <noreply@github.com>2022-09-29 19:27:22 +0300
commit76d376e941a1b51c5627d538a747bdc899a93865 (patch)
tree7ddef9a9e1f1865ff11cbd972b897fc893da7f29 /lib
parentcdc3c5598ffb6eb3f476420c36dcc6646d5d75a1 (diff)
parent0c7599f9d2e076eae51c527ea76b2d005a7186e1 (diff)
Merge pull request #8001 from nextcloud/feature/noid/call-url-reference-provider
🏷️ /call/ reference provider
Diffstat (limited to 'lib')
-rw-r--r--lib/AppInfo/Application.php5
-rw-r--r--lib/Chat/ChatManager.php11
-rw-r--r--lib/Collaboration/Reference/ReferenceInvalidationListener.php52
-rw-r--r--lib/Collaboration/Reference/TalkReferenceProvider.php282
-rw-r--r--lib/Room.php15
5 files changed, 361 insertions, 4 deletions
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
index 3e44c14c7..d50322263 100644
--- a/lib/AppInfo/Application.php
+++ b/lib/AppInfo/Application.php
@@ -24,6 +24,8 @@ declare(strict_types=1);
namespace OCA\Talk\AppInfo;
+use OCA\Talk\Collaboration\Reference\ReferenceInvalidationListener;
+use OCA\Talk\Collaboration\Reference\TalkReferenceProvider;
use OCP\Util;
use OCA\Circles\Events\AddingCircleMemberEvent;
use OCA\Circles\Events\CircleDestroyedEvent;
@@ -145,6 +147,8 @@ class Application extends App implements IBootstrap {
$context->registerProfileLinkAction(TalkAction::class);
+ $context->registerReferenceProvider(TalkReferenceProvider::class);
+
$context->registerTalkBackend(TalkBackend::class);
}
@@ -172,6 +176,7 @@ class Application extends App implements IBootstrap {
CommandListener::register($dispatcher);
CollaboratorsListener::register($dispatcher);
ResourceListener::register($dispatcher);
+ ReferenceInvalidationListener::register($dispatcher);
// Register only when Talk Updates are not disabled
if ($server->getConfig()->getAppValue('spreed', 'changelog', 'yes') === 'yes') {
ChangelogListener::register($dispatcher);
diff --git a/lib/Chat/ChatManager.php b/lib/Chat/ChatManager.php
index d26cbeb40..e62e44b62 100644
--- a/lib/Chat/ChatManager.php
+++ b/lib/Chat/ChatManager.php
@@ -38,6 +38,7 @@ use OCA\Talk\Service\ParticipantService;
use OCA\Talk\Service\PollService;
use OCA\Talk\Share\RoomShareProvider;
use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Collaboration\Reference\IReferenceManager;
use OCP\Comments\IComment;
use OCP\Comments\ICommentsManager;
use OCP\Comments\NotFoundException;
@@ -86,7 +87,6 @@ class ChatManager {
private IDBConnection $connection;
private INotificationManager $notificationManager;
private IManager $shareManager;
- private Manager $manager;
private RoomShareProvider $shareProvider;
private ParticipantService $participantService;
private PollService $pollService;
@@ -95,26 +95,26 @@ class ChatManager {
protected ICache $cache;
protected ICache $unreadCountCache;
protected AttachmentService $attachmentService;
+ protected IReferenceManager $referenceManager;
public function __construct(CommentsManager $commentsManager,
IEventDispatcher $dispatcher,
IDBConnection $connection,
INotificationManager $notificationManager,
IManager $shareManager,
- Manager $manager,
RoomShareProvider $shareProvider,
ParticipantService $participantService,
PollService $pollService,
Notifier $notifier,
ICacheFactory $cacheFactory,
ITimeFactory $timeFactory,
- AttachmentService $attachmentService) {
+ AttachmentService $attachmentService,
+ IReferenceManager $referenceManager) {
$this->commentsManager = $commentsManager;
$this->dispatcher = $dispatcher;
$this->connection = $connection;
$this->notificationManager = $notificationManager;
$this->shareManager = $shareManager;
- $this->manager = $manager;
$this->shareProvider = $shareProvider;
$this->participantService = $participantService;
$this->pollService = $pollService;
@@ -123,6 +123,7 @@ class ChatManager {
$this->unreadCountCache = $cacheFactory->createDistributed('talk/unreadcount');
$this->timeFactory = $timeFactory;
$this->attachmentService = $attachmentService;
+ $this->referenceManager = $referenceManager;
}
/**
@@ -378,6 +379,8 @@ class ChatManager {
$this->attachmentService->deleteAttachmentByMessageId((int) $comment->getId());
+ $this->referenceManager->invalidateCache($chat->getToken());
+
return $this->addSystemMessage(
$chat,
$participant->getAttendee()->getActorType(),
diff --git a/lib/Collaboration/Reference/ReferenceInvalidationListener.php b/lib/Collaboration/Reference/ReferenceInvalidationListener.php
new file mode 100644
index 000000000..2db37c824
--- /dev/null
+++ b/lib/Collaboration/Reference/ReferenceInvalidationListener.php
@@ -0,0 +1,52 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Talk\Collaboration\Reference;
+
+use OCA\Talk\Events\RoomEvent;
+use OCA\Talk\Room;
+use OCP\Collaboration\Reference\IReferenceManager;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Server;
+
+class ReferenceInvalidationListener {
+ public static function register(IEventDispatcher $dispatcher): void {
+ $listener = static function (RoomEvent $event): void {
+ $room = $event->getRoom();
+ $referenceManager = Server::get(IReferenceManager::class);
+
+ $referenceManager->invalidateCache($room->getToken());
+ };
+
+ $dispatcher->addListener(Room::EVENT_AFTER_ROOM_DELETE, $listener);
+ $dispatcher->addListener(Room::EVENT_AFTER_USERS_ADD, $listener);
+ $dispatcher->addListener(Room::EVENT_AFTER_USER_REMOVE, $listener);
+ $dispatcher->addListener(Room::EVENT_AFTER_DESCRIPTION_SET, $listener);
+ $dispatcher->addListener(Room::EVENT_AFTER_LISTABLE_SET, $listener);
+ $dispatcher->addListener(Room::EVENT_AFTER_LOBBY_STATE_SET, $listener);
+ $dispatcher->addListener(Room::EVENT_AFTER_NAME_SET, $listener);
+ $dispatcher->addListener(Room::EVENT_AFTER_PARTICIPANT_REMOVE, $listener);
+ $dispatcher->addListener(Room::EVENT_AFTER_PASSWORD_SET, $listener);
+ $dispatcher->addListener(Room::EVENT_AFTER_SET_MESSAGE_EXPIRATION, $listener);
+ }
+}
diff --git a/lib/Collaboration/Reference/TalkReferenceProvider.php b/lib/Collaboration/Reference/TalkReferenceProvider.php
new file mode 100644
index 000000000..f3b0b1ba2
--- /dev/null
+++ b/lib/Collaboration/Reference/TalkReferenceProvider.php
@@ -0,0 +1,282 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\Talk\Collaboration\Reference;
+
+use OCA\Talk\Chat\ChatManager;
+use OCA\Talk\Chat\MessageParser;
+use OCA\Talk\Exceptions\ParticipantNotFoundException;
+use OCA\Talk\Exceptions\RoomNotFoundException;
+use OCA\Talk\Manager;
+use OCA\Talk\Model\Attendee;
+use OCA\Talk\Participant;
+use OCA\Talk\Room;
+use OCP\Collaboration\Reference\IReference;
+use OCP\Collaboration\Reference\IReferenceProvider;
+use OCP\Collaboration\Reference\Reference;
+use OCP\IL10N;
+use OCP\IURLGenerator;
+
+/**
+ * @psalm-type ReferenceMatch = array{token: string, message: int|null}
+ */
+class TalkReferenceProvider implements IReferenceProvider {
+ protected IURLGenerator $urlGenerator;
+ protected Manager $roomManager;
+ protected ChatManager $chatManager;
+ protected MessageParser $messageParser;
+ protected IL10N $l;
+ protected ?string $userId;
+
+ public function __construct(IURLGenerator $urlGenerator,
+ Manager $manager,
+ ChatManager $chatManager,
+ MessageParser $messageParser,
+ IL10N $l,
+ ?string $userId) {
+ $this->urlGenerator = $urlGenerator;
+ $this->roomManager = $manager;
+ $this->chatManager = $chatManager;
+ $this->messageParser = $messageParser;
+ $this->l = $l;
+ $this->userId = $userId;
+ }
+
+
+ public function matchReference(string $referenceText): bool {
+ return $this->getTalkAppLinkToken($referenceText) !== null;
+ }
+
+ /**
+ * @param string $referenceText
+ * @return array|null
+ * @psalm-return ReferenceMatch|null
+ */
+ protected function getTalkAppLinkToken(string $referenceText): ?array {
+ $indexPhpUrl = $this->urlGenerator->getAbsoluteURL('/index.php/call/');
+ $rewriteUrl = $this->urlGenerator->getAbsoluteURL('/call/');
+
+ if (str_starts_with($referenceText, $indexPhpUrl)) {
+ $urlOfInterest = substr($referenceText, strlen($indexPhpUrl));
+ } elseif (str_starts_with($referenceText, $rewriteUrl)) {
+ $urlOfInterest = substr($referenceText, strlen($rewriteUrl));
+ } else {
+ return null;
+ }
+
+ $hashPosition = strpos($urlOfInterest, '#');
+ $queryPosition = strpos($urlOfInterest, '?');
+
+ if ($hashPosition === false && $queryPosition === false) {
+ return [
+ 'token' => $urlOfInterest,
+ 'message' => null,
+ ];
+ }
+
+ if ($hashPosition !== false && $queryPosition !== false) {
+ $cutPosition = min($hashPosition, $queryPosition);
+ } elseif ($hashPosition !== false) {
+ $cutPosition = $hashPosition;
+ } else {
+ $cutPosition = $queryPosition;
+ }
+
+ $token = substr($urlOfInterest, 0, $cutPosition);
+ $messageId = null;
+ if ($hashPosition !== false) {
+ $afterHash = substr($urlOfInterest, $hashPosition + 1);
+ if (preg_match('/^message_(\d+)$/', $afterHash, $matches)) {
+ $messageId = (int) $matches[1];
+ }
+ }
+
+ return [
+ 'token' => $token,
+ 'message' => $messageId,
+ ];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function resolveReference(string $referenceText): ?IReference {
+ if ($this->matchReference($referenceText)) {
+ $reference = new Reference($referenceText);
+ try {
+ $this->fetchReference($reference);
+ } catch (RoomNotFoundException|ParticipantNotFoundException $e) {
+ $reference->setRichObject('call', null);
+ $reference->setAccessible(false);
+ }
+ return $reference;
+ }
+
+ return null;
+ }
+
+ /**
+ * @throws RoomNotFoundException
+ */
+ protected function fetchReference(Reference $reference): void {
+ if ($this->userId === null) {
+ throw new RoomNotFoundException();
+ }
+
+ $referenceMatch = $this->getTalkAppLinkToken($reference->getId());
+ if ($referenceMatch === null) {
+ throw new RoomNotFoundException();
+ }
+
+ $room = $this->roomManager->getRoomForUserByToken($referenceMatch['token'], $this->userId);
+ try {
+ $participant = $room->getParticipant($this->userId);
+ } catch (ParticipantNotFoundException $e) {
+ $participant = null;
+ }
+
+ /**
+ * Default handling:
+ * Title is the conversation name
+ * Description the conversation description
+ */
+ $roomName = $room->getDisplayName($this->userId);
+ $title = $roomName;
+ $description = '';
+
+ if ($participant instanceof Participant
+ || $this->roomManager->isRoomListableByUser($room, $this->userId)) {
+ $description = $room->getDescription();
+ }
+
+
+ /**
+ * If linking to a comment and the user is already a participant
+ * Title is "Message of {user} in {conversation}"
+ * Description is the plain text chat message
+ */
+ if ($participant && !empty($referenceMatch['message'])) {
+ $comment = $this->chatManager->getComment($room, (string) $referenceMatch['message']);
+ $message = $this->messageParser->createMessage($room, $participant, $comment, $this->l);
+ $this->messageParser->parseMessage($message);
+
+ $placeholders = $replacements = [];
+ foreach ($message->getMessageParameters() as $placeholder => $parameter) {
+ $placeholders[] = '{' . $placeholder . '}';
+ if ($parameter['type'] === 'user' || $parameter['type'] === 'guest') {
+ $replacements[] = '@' . $parameter['name'];
+ } else {
+ $replacements[] = $parameter['name'];
+ }
+ }
+ $description = str_replace($placeholders, $replacements, $message->getMessage());
+
+ $titleLine = $this->l->t('Message of {user} in {conversation}');
+ if ($room->getType() === Room::TYPE_ONE_TO_ONE) {
+ $titleLine = $this->l->t('Message of {user}');
+ }
+
+ $displayName = $message->getActorDisplayName();
+ if ($message->getActorType() === Attendee::ACTOR_GUESTS) {
+ if ($displayName === '') {
+ $displayName = $this->l->t('Guest');
+ } else {
+ $displayName = $this->l->t('%s (guest)', $displayName);
+ }
+ } elseif ($displayName === '') {
+ $titleLine = $this->l->t('Message of a deleted user in {conversation}');
+ }
+
+ $title = str_replace(
+ ['{user}', '{conversation}'],
+ [$displayName, $title],
+ $titleLine
+ );
+ }
+
+ $reference->setTitle($title);
+ $reference->setDescription($description);
+ $reference->setUrl($this->urlGenerator->linkToRouteAbsolute('spreed.Page.showCall', ['token' => $room->getToken()]));
+ $reference->setImageUrl($this->getRoomIconUrl($room, $this->userId));
+
+ $reference->setRichObject('call', [
+ 'id' => $room->getToken(),
+ 'name' => $roomName,
+ 'link' => $reference->getUrl(),
+ 'call-type' => $this->getRoomType($room),
+ ]);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getCachePrefix(string $referenceId): string {
+ $referenceMatch = $this->getTalkAppLinkToken($referenceId);
+ if ($referenceMatch === null) {
+ return '';
+ }
+
+ return $referenceMatch['token'];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getCacheKey(string $referenceId): ?string {
+ $referenceMatch = $this->getTalkAppLinkToken($referenceId);
+ if ($referenceMatch === null) {
+ return '';
+ }
+
+ return ($this->userId ?? '') . '#' . ($referenceMatch['message'] ?? 0);
+ }
+
+ protected function getRoomIconUrl(Room $room, string $userId): string {
+ if ($room->getType() === Room::TYPE_ONE_TO_ONE) {
+ return $this->urlGenerator->linkToRouteAbsolute(
+ 'core.avatar.getAvatar',
+ [
+ 'userId' => $room->getSecondParticipant($userId),
+ 'size' => 64,
+ ]
+ );
+ }
+
+ return $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('spreed', 'changelog.svg'));
+ }
+
+ protected function getRoomType(Room $room): string {
+ switch ($room->getType()) {
+ case Room::TYPE_ONE_TO_ONE:
+ return 'one2one';
+ case Room::TYPE_GROUP:
+ return 'group';
+ case Room::TYPE_PUBLIC:
+ return 'public';
+ default:
+ return 'unknown';
+ }
+ }
+}
diff --git a/lib/Room.php b/lib/Room.php
index aee8ff8ce..032325008 100644
--- a/lib/Room.php
+++ b/lib/Room.php
@@ -369,6 +369,21 @@ class Room {
return $this->name;
}
+ public function getSecondParticipant(string $userId): string {
+ if ($this->getType() !== self::TYPE_ONE_TO_ONE) {
+ throw new \InvalidArgumentException('Not a one-to-one room');
+ }
+ $participants = json_decode($this->getName(), true);
+
+ foreach ($participants as $uid) {
+ if ($uid !== $userId) {
+ return $uid;
+ }
+ }
+
+ return $this->getName();
+ }
+
public function getDisplayName(string $userId): string {
return $this->manager->resolveRoomDisplayName($this, $userId);
}