<commit_before><?php

declare(strict_types=1);
/**
 * @copyright Copyright (c) 2016 <NAME> <<EMAIL>>
 * @copyright Copyright (c) 2016 <NAME> <<EMAIL>>
 *
 * @author <NAME> <<EMAIL>>
 * @author <NAME> <<EMAIL>>
 *
 * @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\Controller;

use InvalidArgumentException;
use OCA\Circles\Api\v1\Circles;
use OCA\Circles\Model\Member;
use OCA\Talk\Chat\ChatManager;
use OCA\Talk\Chat\MessageParser;
use OCA\Talk\Config;
use OCA\Talk\Events\UserEvent;
use OCA\Talk\Exceptions\InvalidPasswordException;
use OCA\Talk\Exceptions\ParticipantNotFoundException;
use OCA\Talk\Exceptions\RoomNotFoundException;
use OCA\Talk\Exceptions\UnauthorizedException;
use OCA\Talk\GuestManager;
use OCA\Talk\Manager;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\RoomService;
use OCA\Talk\TalkSession;
use OCA\Talk\Webinary;
use OCP\App\IAppManager;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Comments\IComment;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IL10N;
use OCP\IRequest;
use OCP\IUser;
use OCP\IUserManager;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\IConfig;
use OCP\UserStatus\IManager as IUserStatusManager;

class RoomController extends AEnvironmentAwareController {
	public const EVENT_BEFORE_ROOMS_GET = self::class . '::preGetRooms';

	/** @var string|null */
	protected $userId;
	/** @var IAppManager */
	protected $appManager;
	/** @var TalkSession */
	protected $session;
	/** @var IUserManager */
	protected $userManager;
	/** @var IGroupManager */
	protected $groupManager;
	/** @var Manager */
	protected $manager;
	/** @var RoomService */
	protected $roomService;
	/** @var GuestManager */
	protected $guestManager;
	/** @var IUserStatusManager */
	protected $statusManager;
	/** @var ChatManager */
	protected $chatManager;
	/** @var IEventDispatcher */
	protected $dispatcher;
	/** @var MessageParser */
	protected $messageParser;
	/** @var ITimeFactory */
	protected $timeFactory;
	/** @var IL10N */
	protected $l10n;
	/** @var IConfig */
	protected $config;
	/** @var Config */
	protected $talkConfig;

	public function __construct(string $appName,
								?string $UserId,
								IRequest $request,
								IAppManager $appManager,
								TalkSession $session,
								IUserManager $userManager,
								IGroupManager $groupManager,
								Manager $manager,
								RoomService $roomService,
								GuestManager $guestManager,
								IUserStatusManager $statusManager,
								ChatManager $chatManager,
								IEventDispatcher $dispatcher,
								MessageParser $messageParser,
								ITimeFactory $timeFactory,
								IL10N $l10n,
								IConfig $config,
								Config $talkConfig) {
		parent::__construct($appName, $request);
		$this->session = $session;
		$this->appManager = $appManager;
		$this->userId = $UserId;
		$this->userManager = $userManager;
		$this->groupManager = $groupManager;
		$this->manager = $manager;
		$this->roomService = $roomService;
		$this->guestManager = $guestManager;
		$this->statusManager = $statusManager;
		$this->chatManager = $chatManager;
		$this->dispatcher = $dispatcher;
		$this->messageParser = $messageParser;
		$this->timeFactory = $timeFactory;
		$this->l10n = $l10n;
		$this->config = $config;
		$this->talkConfig = $talkConfig;
	}

	protected function getTalkHashHeader(): array {
		return [
			'X-Nextcloud-Talk-Hash' => sha1(
				$this->config->getSystemValueString('version') . '#' .
				$this->config->getAppValue('spreed', 'installed_version', '') . '#' .
				$this->config->getAppValue('spreed', 'stun_servers', '') . '#' .
				$this->config->getAppValue('spreed', 'turn_servers', '') . '#' .
				$this->config->getAppValue('spreed', 'signaling_servers', '') . '#' .
				$this->config->getAppValue('spreed', 'signaling_mode', '') . '#' .
				$this->config->getAppValue('spreed', 'allowed_groups', '') . '#' .
				$this->config->getAppValue('spreed', 'start_conversations', '') . '#' .
				$this->config->getAppValue('spreed', 'has_reference_id', '') . '#' .
				$this->config->getAppValue('theming', 'cachebuster', '1')
		)];
	}

	/**
	 * Get all currently existent rooms which the user has joined
	 *
	 * @NoAdminRequired
	 *
	 * @return DataResponse
	 */
	public function getRooms(): DataResponse {
		$event = new UserEvent($this->userId);
		$this->dispatcher->dispatch(self::EVENT_BEFORE_ROOMS_GET, $event);

		$rooms = $this->manager->getRoomsForParticipant($this->userId, true);

		$return = [];
		foreach ($rooms as $room) {
			try {
				$return[] = $this->formatRoom($room, $room->getParticipant($this->userId));
			} catch (RoomNotFoundException $e) {
			} catch (\RuntimeException $e) {
			}
		}

		return new DataResponse($return, Http::STATUS_OK, $this->getTalkHashHeader());
	}

	/**
	 * @PublicPage
	 *
	 * @param string $token
	 * @return DataResponse
	 */
	public function getSingleRoom(string $token): DataResponse {
		try {
			$room = $this->manager->getRoomForParticipantByToken($token, $this->userId, true);

			$participant = null;
			try {
				$participant = $room->getParticipant($this->userId);
			} catch (ParticipantNotFoundException $e) {
				try {
					$participant = $room->getParticipantBySession($this->session->getSessionForRoom($token));
				} catch (ParticipantNotFoundException $e) {
				}
			}

			return new DataResponse($this->formatRoom($room, $participant), Http::STATUS_OK, $this->getTalkHashHeader());
		} catch (RoomNotFoundException $e) {
			return new DataResponse([], Http::STATUS_NOT_FOUND);
		}
	}

	/**
	 * @param string $apiVersion
	 * @param Room $room
	 * @param Participant $currentParticipant
	 * @return array
	 * @throws RoomNotFoundException
	 */
	protected function formatRoom(Room $room, ?Participant $currentParticipant): array {
		if ($this->getAPIVersion() === 2) {
			return $this->formatRoomV2($room, $currentParticipant);
		}

		return $this->formatRoomV1($room, $currentParticipant);
	}

	/**
	 * @param Room $room
	 * @param Participant $currentParticipant
	 * @return array
	 * @throws RoomNotFoundException
	 */
	protected function formatRoomV1(Room $room, ?Participant $currentParticipant): array {
		$roomData = [
			'id' => $room->getId(),
			'token' => $room->getToken(),
			'type' => $room->getType(),
			'name' => '',
			'displayName' => '',
			'objectType' => '',
			'objectId' => '',
			'participantType' => Participant::GUEST,
			// Deprecated, use participantFlags instead.
			'participantInCall' => false,
			'participantFlags' => Participant::FLAG_DISCONNECTED,
			'readOnly' => Room::READ_WRITE,
			'count' => 0,
			'hasPassword' => $room->hasPassword(),
			'hasCall' => false,
			'canStartCall' => false,
			'lastActivity' => 0,
			'lastReadMessage' => 0,
			'unreadMessages' => 0,
			'unreadMention' => false,
			'isFavorite' => false,
			'notificationLevel' => Participant::NOTIFY_NEVER,
			'lastPing' => 0,
			'sessionId' => '0',
			'participants' => [],
			'numGuests' => 0,
			'guestList' => '',
			'lastMessage' => [],
		];

		if (!$currentParticipant instanceof Participant) {
			return $roomData;
		}

		$lastActivity = $room->getLastActivity();
		if ($lastActivity instanceof \DateTimeInterface) {
			$lastActivity = $lastActivity->getTimestamp();
		} else {
			$lastActivity = 0;
		}

		$lobbyTimer = $room->getLobbyTimer();
		if ($lobbyTimer instanceof \DateTimeInterface) {
			$lobbyTimer = $lobbyTimer->getTimestamp();
		} else {
			$lobbyTimer = 0;
		}

		$roomData = array_merge($roomData, [
			'name' => $room->getName(),
			'displayName' => $room->getDisplayName($currentParticipant->getUser()),
			'objectType' => $room->getObjectType(),
			'objectId' => $room->getObjectId(),
			'participantType' => $currentParticipant->getParticipantType(),
			// Deprecated, use participantFlags instead.
			'participantInCall' => ($currentParticipant->getInCallFlags() & Participant::FLAG_IN_CALL) !== 0,
			'participantFlags' => $currentParticipant->getInCallFlags(),
			'readOnly' => $room->getReadOnly(),
			'count' => 0, // Deprecated, remove in future API version
			'hasCall' => $room->getActiveSince() instanceof \DateTimeInterface,
			'lastActivity' => $lastActivity,
			'isFavorite' => $currentParticipant->isFavorite(),
			'notificationLevel' => $currentParticipant->getNotificationLevel(),
			'lobbyState' => $room->getLobbyState(),
			'lobbyTimer' => $lobbyTimer,
			'lastPing' => $currentParticipant->getLastPing(),
			'sessionId' => $currentParticipant->getSessionId(),
		]);

		if ($roomData['notificationLevel'] === Participant::NOTIFY_DEFAULT) {
			if ($currentParticipant->isGuest()) {
				$roomData['notificationLevel'] = Participant::NOTIFY_NEVER;
			} elseif ($room->getType() === Room::ONE_TO_ONE_CALL) {
				$roomData['notificationLevel'] = Participant::NOTIFY_ALWAYS;
			} else {
				$adminSetting = (int) $this->config->getAppValue('spreed', 'default_group_notification', Participant::NOTIFY_DEFAULT);
				if ($adminSetting === Participant::NOTIFY_DEFAULT) {
					$roomData['notificationLevel'] = Participant::NOTIFY_MENTION;
				} else {
					$roomData['notificationLevel'] = $adminSetting;
				}
			}
		}

		if ($room->getLobbyState() === Webinary::LOBBY_NON_MODERATORS &&
			!$currentParticipant->hasModeratorPermissions()) {
			// No participants and chat messages for users in the lobby.
			return $roomData;
		}

		$roomData['canStartCall'] = $currentParticipant->canStartCall();

		$currentUser = $this->userManager->get($currentParticipant->getUser());
		if ($currentUser instanceof IUser) {
			$lastReadMessage = $currentParticipant->getLastReadMessage();
			if ($lastReadMessage === -1) {
				/*
				 * Because the migration from the old comment_read_markers was
				 * not possible in a programmatic way with a reasonable O(1) or O(n)
				 * but only with O(user×chat), we do the conversion here.
				 */
				$lastReadMessage = $this->chatManager->getLastReadMessageFromLegacy($room, $currentUser);
				$currentParticipant->setLastReadMessage($lastReadMessage);
			}
			$roomData['unreadMessages'] = $this->chatManager->getUnreadCount($room, $lastReadMessage);

			$lastMention = $currentParticipant->getLastMentionMessage();
			$roomData['unreadMention'] = $lastMention !== 0 && $lastReadMessage < $lastMention;
			$roomData['lastReadMessage'] = $lastReadMessage;
		}

		$numActiveGuests = 0;
		$cleanGuests = false;
		$participantList = [];
		$participants = $room->getParticipants();
		uasort($participants, function (Participant $participant1, Participant $participant2) {
			return $participant2->getLastPing() - $participant1->getLastPing();
		});

		foreach ($participants as $participant) {
			if ($participant->isGuest()) {
				if ($participant->getLastPing() <= $this->timeFactory->getTime() - 100) {
					$cleanGuests = true;
				} else {
					$numActiveGuests++;
				}
			} else {
				$user = $this->userManager->get($participant->getUser());
				if ($user instanceof IUser) {
					$participantList[(string)$user->getUID()] = [
						'name' => $user->getDisplayName(),
						'type' => $participant->getParticipantType(),
						'call' => $participant->getInCallFlags(),
						'sessionId' => $participant->getSessionId(),
					];

					if ($room->getType() === Room::ONE_TO_ONE_CALL &&
						  $user->getUID() !== $currentParticipant->getUser()) {
						// FIXME This should not be done, but currently all the clients use it to get the avatar of the user …
						$roomData['name'] = $user->getUID();
					}
				}

				if ($participant->getSessionId() !== '0' && $participant->getLastPing() <= $this->timeFactory->getTime() - 100) {
					$room->leaveRoom($participant->getUser());
				}
			}
		}

		if ($cleanGuests) {
			$room->cleanGuestParticipants();
		}

		$lastMessage = $room->getLastMessage();
		if ($lastMessage instanceof IComment) {
			$lastMessage = $this->formatLastMessage($room, $currentParticipant, $lastMessage);
		} else {
			$lastMessage = [];
		}

		$roomData = array_merge($roomData, [
			'participants' => $participantList,
			'numGuests' => $numActiveGuests,
			'lastMessage' => $lastMessage,
		]);

		return $roomData;
	}

	/**
	 * @param Room $room
	 * @param Participant $currentParticipant
	 * @return array
	 * @throws RoomNotFoundException
	 */
	protected function formatRoomV2(Room $room, ?Participant $currentParticipant): array {
		$roomData = [
			'id' => $room->getId(),
			'token' => $room->getToken(),
			'type' => $room->getType(),
			'name' => '',
			'displayName' => '',
			'objectType' => '',
			'objectId' => '',
			'participantType' => Participant::GUEST,
			'participantFlags' => Participant::FLAG_DISCONNECTED,
			'readOnly' => Room::READ_WRITE,
			'hasPassword' => $room->hasPassword(),
			'hasCall' => false,
			'canStartCall' => false,
			'lastActivity' => 0,
			'lastReadMessage' => 0,
			'unreadMessages' => 0,
			'unreadMention' => false,
			'isFavorite' => false,
			'canLeaveConversation' => false,
			'canDeleteConversation' => false,
			'notificationLevel' => Participant::NOTIFY_NEVER,
			'lastPing' => 0,
			'sessionId' => '0',
			'guestList' => '',
			'lastMessage' => [],
		];

		if (!$currentParticipant instanceof Participant) {
			return $roomData;
		}

		$lastActivity = $room->getLastActivity();
		if ($lastActivity instanceof \DateTimeInterface) {
			$lastActivity = $lastActivity->getTimestamp();
		} else {
			$lastActivity = 0;
		}

		$lobbyTimer = $room->getLobbyTimer();
		if ($lobbyTimer instanceof \DateTimeInterface) {
			$lobbyTimer = $lobbyTimer->getTimestamp();
		} else {
			$lobbyTimer = 0;
		}

		$roomData = array_merge($roomData, [
			'name' => $room->getName(),
			'displayName' => $room->getDisplayName($currentParticipant->getUser()),
			'objectType' => $room->getObjectType(),
			'objectId' => $room->getObjectId(),
			'participantType' => $currentParticipant->getParticipantType(),
			'participantFlags' => $currentParticipant->getInCallFlags(),
			'readOnly' => $room->getReadOnly(),
			'hasCall' => $room->getActiveSince() instanceof \DateTimeInterface,
			'lastActivity' => $lastActivity,
			'isFavorite' => $currentParticipant->isFavorite(),
			'notificationLevel' => $currentParticipant->getNotificationLevel(),
			'lobbyState' => $room->getLobbyState(),
			'lobbyTimer' => $lobbyTimer,
			'lastPing' => $currentParticipant->getLastPing(),
			'sessionId' => $currentParticipant->getSessionId(),
		]);

		if ($roomData['notificationLevel'] === Participant::NOTIFY_DEFAULT) {
			if ($currentParticipant->isGuest()) {
				$roomData['notificationLevel'] = Participant::NOTIFY_NEVER;
			} elseif ($room->getType() === Room::ONE_TO_ONE_CALL) {
				$roomData['notificationLevel'] = Participant::NOTIFY_ALWAYS;
			} else {
				$adminSetting = (int) $this->config->getAppValue('spreed', 'default_group_notification', Participant::NOTIFY_DEFAULT);
				if ($adminSetting === Participant::NOTIFY_DEFAULT) {
					$roomData['notificationLevel'] = Participant::NOTIFY_MENTION;
				} else {
					$roomData['notificationLevel'] = $adminSetting;
				}
			}
		}

		if ($room->getLobbyState() === Webinary::LOBBY_NON_MODERATORS &&
			!$currentParticipant->hasModeratorPermissions()) {
			// No participants and chat messages for users in the lobby.
			return $roomData;
		}

		$roomData['canStartCall'] = $currentParticipant->canStartCall();

		$currentUser = $this->userManager->get($currentParticipant->getUser());
		if ($currentUser instanceof IUser) {
			$lastReadMessage = $currentParticipant->getLastReadMessage();
			if ($lastReadMessage === -1) {
				/*
				 * Because the migration from the old comment_read_markers was
				 * not possible in a programmatic way with a reasonable O(1) or O(n)
				 * but only with O(user×chat), we do the conversion here.
				 */
				$lastReadMessage = $this->chatManager->getLastReadMessageFromLegacy($room, $currentUser);
				$currentParticipant->setLastReadMessage($lastReadMessage);
			}
			$roomData['unreadMessages'] = $this->chatManager->getUnreadCount($room, $lastReadMessage);

			$lastMention = $currentParticipant->getLastMentionMessage();
			$roomData['unreadMention'] = $lastMention !== 0 && $lastReadMessage < $lastMention;
			$roomData['lastReadMessage'] = $lastReadMessage;

			$roomData['canDeleteConversation'] = $room->getType() !== Room::ONE_TO_ONE_CALL
				&& $currentParticipant->hasModeratorPermissions(false);
			$roomData['canLeaveConversation'] = !$roomData['canDeleteConversation']<commit_msg>Save the "count unread messages" query, when the last read is the last room messages<commit_after><?php

declare(strict_types=1);
/**
 * @copyright Copyright (c) 2016 <NAME> <<EMAIL>>
 * @copyright Copyright (c) 2016 <NAME> <<EMAIL>>
 *
 * @author <NAME> <<EMAIL>>
 * @author <NAME> <<EMAIL>>
 *
 * @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\Controller;

use InvalidArgumentException;
use OCA\Circles\Api\v1\Circles;
use OCA\Circles\Model\Member;
use OCA\Talk\Chat\ChatManager;
use OCA\Talk\Chat\MessageParser;
use OCA\Talk\Config;
use OCA\Talk\Events\UserEvent;
use OCA\Talk\Exceptions\InvalidPasswordException;
use OCA\Talk\Exceptions\ParticipantNotFoundException;
use OCA\Talk\Exceptions\RoomNotFoundException;
use OCA\Talk\Exceptions\UnauthorizedException;
use OCA\Talk\GuestManager;
use OCA\Talk\Manager;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\RoomService;
use OCA\Talk\TalkSession;
use OCA\Talk\Webinary;
use OCP\App\IAppManager;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Comments\IComment;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IL10N;
use OCP\IRequest;
use OCP\IUser;
use OCP\IUserManager;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\IConfig;
use OCP\UserStatus\IManager as IUserStatusManager;

class RoomController extends AEnvironmentAwareController {
	public const EVENT_BEFORE_ROOMS_GET = self::class . '::preGetRooms';

	/** @var string|null */
	protected $userId;
	/** @var IAppManager */
	protected $appManager;
	/** @var TalkSession */
	protected $session;
	/** @var IUserManager */
	protected $userManager;
	/** @var IGroupManager */
	protected $groupManager;
	/** @var Manager */
	protected $manager;
	/** @var RoomService */
	protected $roomService;
	/** @var GuestManager */
	protected $guestManager;
	/** @var IUserStatusManager */
	protected $statusManager;
	/** @var ChatManager */
	protected $chatManager;
	/** @var IEventDispatcher */
	protected $dispatcher;
	/** @var MessageParser */
	protected $messageParser;
	/** @var ITimeFactory */
	protected $timeFactory;
	/** @var IL10N */
	protected $l10n;
	/** @var IConfig */
	protected $config;
	/** @var Config */
	protected $talkConfig;

	public function __construct(string $appName,
								?string $UserId,
								IRequest $request,
								IAppManager $appManager,
								TalkSession $session,
								IUserManager $userManager,
								IGroupManager $groupManager,
								Manager $manager,
								RoomService $roomService,
								GuestManager $guestManager,
								IUserStatusManager $statusManager,
								ChatManager $chatManager,
								IEventDispatcher $dispatcher,
								MessageParser $messageParser,
								ITimeFactory $timeFactory,
								IL10N $l10n,
								IConfig $config,
								Config $talkConfig) {
		parent::__construct($appName, $request);
		$this->session = $session;
		$this->appManager = $appManager;
		$this->userId = $UserId;
		$this->userManager = $userManager;
		$this->groupManager = $groupManager;
		$this->manager = $manager;
		$this->roomService = $roomService;
		$this->guestManager = $guestManager;
		$this->statusManager = $statusManager;
		$this->chatManager = $chatManager;
		$this->dispatcher = $dispatcher;
		$this->messageParser = $messageParser;
		$this->timeFactory = $timeFactory;
		$this->l10n = $l10n;
		$this->config = $config;
		$this->talkConfig = $talkConfig;
	}

	protected function getTalkHashHeader(): array {
		return [
			'X-Nextcloud-Talk-Hash' => sha1(
				$this->config->getSystemValueString('version') . '#' .
				$this->config->getAppValue('spreed', 'installed_version', '') . '#' .
				$this->config->getAppValue('spreed', 'stun_servers', '') . '#' .
				$this->config->getAppValue('spreed', 'turn_servers', '') . '#' .
				$this->config->getAppValue('spreed', 'signaling_servers', '') . '#' .
				$this->config->getAppValue('spreed', 'signaling_mode', '') . '#' .
				$this->config->getAppValue('spreed', 'allowed_groups', '') . '#' .
				$this->config->getAppValue('spreed', 'start_conversations', '') . '#' .
				$this->config->getAppValue('spreed', 'has_reference_id', '') . '#' .
				$this->config->getAppValue('theming', 'cachebuster', '1')
		)];
	}

	/**
	 * Get all currently existent rooms which the user has joined
	 *
	 * @NoAdminRequired
	 *
	 * @return DataResponse
	 */
	public function getRooms(): DataResponse {
		$event = new UserEvent($this->userId);
		$this->dispatcher->dispatch(self::EVENT_BEFORE_ROOMS_GET, $event);

		$rooms = $this->manager->getRoomsForParticipant($this->userId, true);

		$return = [];
		foreach ($rooms as $room) {
			try {
				$return[] = $this->formatRoom($room, $room->getParticipant($this->userId));
			} catch (RoomNotFoundException $e) {
			} catch (\RuntimeException $e) {
			}
		}

		return new DataResponse($return, Http::STATUS_OK, $this->getTalkHashHeader());
	}

	/**
	 * @PublicPage
	 *
	 * @param string $token
	 * @return DataResponse
	 */
	public function getSingleRoom(string $token): DataResponse {
		try {
			$room = $this->manager->getRoomForParticipantByToken($token, $this->userId, true);

			$participant = null;
			try {
				$participant = $room->getParticipant($this->userId);
			} catch (ParticipantNotFoundException $e) {
				try {
					$participant = $room->getParticipantBySession($this->session->getSessionForRoom($token));
				} catch (ParticipantNotFoundException $e) {
				}
			}

			return new DataResponse($this->formatRoom($room, $participant), Http::STATUS_OK, $this->getTalkHashHeader());
		} catch (RoomNotFoundException $e) {
			return new DataResponse([], Http::STATUS_NOT_FOUND);
		}
	}

	/**
	 * @param string $apiVersion
	 * @param Room $room
	 * @param Participant $currentParticipant
	 * @return array
	 * @throws RoomNotFoundException
	 */
	protected function formatRoom(Room $room, ?Participant $currentParticipant): array {
		if ($this->getAPIVersion() === 2) {
			return $this->formatRoomV2($room, $currentParticipant);
		}

		return $this->formatRoomV1($room, $currentParticipant);
	}

	/**
	 * @param Room $room
	 * @param Participant $currentParticipant
	 * @return array
	 * @throws RoomNotFoundException
	 */
	protected function formatRoomV1(Room $room, ?Participant $currentParticipant): array {
		$roomData = [
			'id' => $room->getId(),
			'token' => $room->getToken(),
			'type' => $room->getType(),
			'name' => '',
			'displayName' => '',
			'objectType' => '',
			'objectId' => '',
			'participantType' => Participant::GUEST,
			// Deprecated, use participantFlags instead.
			'participantInCall' => false,
			'participantFlags' => Participant::FLAG_DISCONNECTED,
			'readOnly' => Room::READ_WRITE,
			'count' => 0,
			'hasPassword' => $room->hasPassword(),
			'hasCall' => false,
			'canStartCall' => false,
			'lastActivity' => 0,
			'lastReadMessage' => 0,
			'unreadMessages' => 0,
			'unreadMention' => false,
			'isFavorite' => false,
			'notificationLevel' => Participant::NOTIFY_NEVER,
			'lastPing' => 0,
			'sessionId' => '0',
			'participants' => [],
			'numGuests' => 0,
			'guestList' => '',
			'lastMessage' => [],
		];

		if (!$currentParticipant instanceof Participant) {
			return $roomData;
		}

		$lastActivity = $room->getLastActivity();
		if ($lastActivity instanceof \DateTimeInterface) {
			$lastActivity = $lastActivity->getTimestamp();
		} else {
			$lastActivity = 0;
		}

		$lobbyTimer = $room->getLobbyTimer();
		if ($lobbyTimer instanceof \DateTimeInterface) {
			$lobbyTimer = $lobbyTimer->getTimestamp();
		} else {
			$lobbyTimer = 0;
		}

		$roomData = array_merge($roomData, [
			'name' => $room->getName(),
			'displayName' => $room->getDisplayName($currentParticipant->getUser()),
			'objectType' => $room->getObjectType(),
			'objectId' => $room->getObjectId(),
			'participantType' => $currentParticipant->getParticipantType(),
			// Deprecated, use participantFlags instead.
			'participantInCall' => ($currentParticipant->getInCallFlags() & Participant::FLAG_IN_CALL) !== 0,
			'participantFlags' => $currentParticipant->getInCallFlags(),
			'readOnly' => $room->getReadOnly(),
			'count' => 0, // Deprecated, remove in future API version
			'hasCall' => $room->getActiveSince() instanceof \DateTimeInterface,
			'lastActivity' => $lastActivity,
			'isFavorite' => $currentParticipant->isFavorite(),
			'notificationLevel' => $currentParticipant->getNotificationLevel(),
			'lobbyState' => $room->getLobbyState(),
			'lobbyTimer' => $lobbyTimer,
			'lastPing' => $currentParticipant->getLastPing(),
			'sessionId' => $currentParticipant->getSessionId(),
		]);

		if ($roomData['notificationLevel'] === Participant::NOTIFY_DEFAULT) {
			if ($currentParticipant->isGuest()) {
				$roomData['notificationLevel'] = Participant::NOTIFY_NEVER;
			} elseif ($room->getType() === Room::ONE_TO_ONE_CALL) {
				$roomData['notificationLevel'] = Participant::NOTIFY_ALWAYS;
			} else {
				$adminSetting = (int) $this->config->getAppValue('spreed', 'default_group_notification', Participant::NOTIFY_DEFAULT);
				if ($adminSetting === Participant::NOTIFY_DEFAULT) {
					$roomData['notificationLevel'] = Participant::NOTIFY_MENTION;
				} else {
					$roomData['notificationLevel'] = $adminSetting;
				}
			}
		}

		if ($room->getLobbyState() === Webinary::LOBBY_NON_MODERATORS &&
			!$currentParticipant->hasModeratorPermissions()) {
			// No participants and chat messages for users in the lobby.
			return $roomData;
		}

		$roomData['canStartCall'] = $currentParticipant->canStartCall();

		$currentUser = $this->userManager->get($currentParticipant->getUser());
		if ($currentUser instanceof IUser) {
			$lastReadMessage = $currentParticipant->getLastReadMessage();
			if ($lastReadMessage === -1) {
				/*
				 * Because the migration from the old comment_read_markers was
				 * not possible in a programmatic way with a reasonable O(1) or O(n)
				 * but only with O(user×chat), we do the conversion here.
				 */
				$lastReadMessage = $this->chatManager->getLastReadMessageFromLegacy($room, $currentUser);
				$currentParticipant->setLastReadMessage($lastReadMessage);
			}
			$roomData['unreadMessages'] = $this->chatManager->getUnreadCount($room, $lastReadMessage);

			$lastMention = $currentParticipant->getLastMentionMessage();
			$roomData['unreadMention'] = $lastMention !== 0 && $lastReadMessage < $lastMention;
			$roomData['lastReadMessage'] = $lastReadMessage;
		}

		$numActiveGuests = 0;
		$cleanGuests = false;
		$participantList = [];
		$participants = $room->getParticipants();
		uasort($participants, function (Participant $participant1, Participant $participant2) {
			return $participant2->getLastPing() - $participant1->getLastPing();
		});

		foreach ($participants as $participant) {
			if ($participant->isGuest()) {
				if ($participant->getLastPing() <= $this->timeFactory->getTime() - 100) {
					$cleanGuests = true;
				} else {
					$numActiveGuests++;
				}
			} else {
				$user = $this->userManager->get($participant->getUser());
				if ($user instanceof IUser) {
					$participantList[(string)$user->getUID()] = [
						'name' => $user->getDisplayName(),
						'type' => $participant->getParticipantType(),
						'call' => $participant->getInCallFlags(),
						'sessionId' => $participant->getSessionId(),
					];

					if ($room->getType() === Room::ONE_TO_ONE_CALL &&
						  $user->getUID() !== $currentParticipant->getUser()) {
						// FIXME This should not be done, but currently all the clients use it to get the avatar of the user …
						$roomData['name'] = $user->getUID();
					}
				}

				if ($participant->getSessionId() !== '0' && $participant->getLastPing() <= $this->timeFactory->getTime() - 100) {
					$room->leaveRoom($participant->getUser());
				}
			}
		}

		if ($cleanGuests) {
			$room->cleanGuestParticipants();
		}

		$lastMessage = $room->getLastMessage();
		if ($lastMessage instanceof IComment) {
			$lastMessage = $this->formatLastMessage($room, $currentParticipant, $lastMessage);
		} else {
			$lastMessage = [];
		}

		$roomData = array_merge($roomData, [
			'participants' => $participantList,
			'numGuests' => $numActiveGuests,
			'lastMessage' => $lastMessage,
		]);

		return $roomData;
	}

	/**
	 * @param Room $room
	 * @param Participant $currentParticipant
	 * @return array
	 * @throws RoomNotFoundException
	 */
	protected function formatRoomV2(Room $room, ?Participant $currentParticipant): array {
		$roomData = [
			'id' => $room->getId(),
			'token' => $room->getToken(),
			'type' => $room->getType(),
			'name' => '',
			'displayName' => '',
			'objectType' => '',
			'objectId' => '',
			'participantType' => Participant::GUEST,
			'participantFlags' => Participant::FLAG_DISCONNECTED,
			'readOnly' => Room::READ_WRITE,
			'hasPassword' => $room->hasPassword(),
			'hasCall' => false,
			'canStartCall' => false,
			'lastActivity' => 0,
			'lastReadMessage' => 0,
			'unreadMessages' => 0,
			'unreadMention' => false,
			'isFavorite' => false,
			'canLeaveConversation' => false,
			'canDeleteConversation' => false,
			'notificationLevel' => Participant::NOTIFY_NEVER,
			'lastPing' => 0,
			'sessionId' => '0',
			'guestList' => '',
			'lastMessage' => [],
		];

		if (!$currentParticipant instanceof Participant) {
			return $roomData;
		}

		$lastActivity = $room->getLastActivity();
		if ($lastActivity instanceof \DateTimeInterface) {
			$lastActivity = $lastActivity->getTimestamp();
		} else {
			$lastActivity = 0;
		}

		$lobbyTimer = $room->getLobbyTimer();
		if ($lobbyTimer instanceof \DateTimeInterface) {
			$lobbyTimer = $lobbyTimer->getTimestamp();
		} else {
			$lobbyTimer = 0;
		}

		$roomData = array_merge($roomData, [
			'name' => $room->getName(),
			'displayName' => $room->getDisplayName($currentParticipant->getUser()),
			'objectType' => $room->getObjectType(),
			'objectId' => $room->getObjectId(),
			'participantType' => $currentParticipant->getParticipantType(),
			'participantFlags' => $currentParticipant->getInCallFlags(),
			'readOnly' => $room->getReadOnly(),
			'hasCall' => $room->getActiveSince() instanceof \DateTimeInterface,
			'lastActivity' => $lastActivity,
			'isFavorite' => $currentParticipant->isFavorite(),
			'notificationLevel' => $currentParticipant->getNotificationLevel(),
			'lobbyState' => $room->getLobbyState(),
			'lobbyTimer' => $lobbyTimer,
			'lastPing' => $currentParticipant->getLastPing(),
			'sessionId' => $currentParticipant->getSessionId(),
		]);

		if ($roomData['notificationLevel'] === Participant::NOTIFY_DEFAULT) {
			if ($currentParticipant->isGuest()) {
				$roomData['notificationLevel'] = Participant::NOTIFY_NEVER;
			} elseif ($room->getType() === Room::ONE_TO_ONE_CALL) {
				$roomData['notificationLevel'] = Participant::NOTIFY_ALWAYS;
			} else {
				$adminSetting = (int) $this->config->getAppValue('spreed', 'default_group_notification', Participant::NOTIFY_DEFAULT);
				if ($adminSetting === Participant::NOTIFY_DEFAULT) {
					$roomData['notificationLevel'] = Participant::NOTIFY_MENTION;
				} else {
					$roomData['notificationLevel'] = $adminSetting;
				}
			}
		}

		if ($room->getLobbyState() === Webinary::LOBBY_NON_MODERATORS &&
			!$currentParticipant->hasModeratorPermissions()) {
			// No participants and chat messages for users in the lobby.
			return $roomData;
		}

		$roomData['canStartCall'] = $currentParticipant->canStartCall();

		$currentUser = $this->userManager->get($currentParticipant->getUser());
		if ($currentUser instanceof IUser) {
			$lastReadMessage = $currentParticipant->getLastReadMessage();
			if ($lastReadMessage === -1) {
				/*
				 * Because the migration from the old comment_read_markers was
				 * not possible in a programmatic way with a reasonable O(1) or O(n)
				 * but only with O(user×chat), we do the conversion here.
				 */
				$lastReadMessage = $this->chatManager->getLastReadMessageFromLegacy($room, $currentUser);
				$currentParticipant->setLastReadMessage($lastReadMessage);
			}
			if ($room->getLastMessage() && $lastReadMessage === (int) $room->getLastMessage()->getId()) {
				// When the last message is the last read message, there are no unread messages,
				// so we can save the query.
				$roomData['unreadMessages'] = 0;
			} else {
				$roomData['unreadMessages'] = $this->chatManager->getUnreadCount($room, $lastReadMessage);
			}

			$lastMention = $currentParticipant->getLastMentionMessage();
			$roomData['unreadMention'] = $lastMention !== 0 && $lastReadMessage < $lastMention;
			$roomData['lastReadMessage'] = $lastReadMessage;

			$roomData['canDeleteConversation'] = $room->getType() !== Room::ONE_TO_ONE_CALL
				&& $currentParticipant->hasModeratorPermissions(false);
			$roomData['canLeaveConversation'] = !$roomData['canDeleteConversation']
{'fraction_non_alphanumeric': 0.15948163395471546, 'fraction_numerical': 0.0040298031484649055, 'mean_word_length': 4.382958801498128, 'pattern_counts': {'":': 0, '<': 39, '<?xml version=': 0, '>': 552, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2626810', 'n_tokens_mistral': 12768, 'n_tokens_neox': 10835, 'n_words': 2183}