Browse Source

feat: 🍱 Talk Dashboard - ⚙️ API

Signed-off-by: Anna Larch <anna@nextcloud.com>
pull/15019/head
Anna Larch 7 months ago
parent
commit
07c0fe5c6d
  1. 6
      REUSE.toml
  2. 1
      appinfo/routes.php
  3. 18
      appinfo/routes/routesDashboardController.php
  4. 1
      docs/capabilities.md
  5. 2
      lib/Capabilities.php
  6. 55
      lib/Controller/DashboardController.php
  7. 274
      lib/Dashboard/Event.php
  8. 87
      lib/ResponseDefinitions.php
  9. 180
      lib/Service/DashboardService.php
  10. 22
      lib/Service/RoomService.php
  11. 156
      openapi-backend-sipbridge.json
  12. 156
      openapi-federation.json
  13. 384
      openapi-full.json
  14. 384
      openapi.json
  15. 152
      src/types/openapi/openapi-backend-sipbridge.ts
  16. 152
      src/types/openapi/openapi-federation.ts
  17. 243
      src/types/openapi/openapi-full.ts
  18. 243
      src/types/openapi/openapi.ts
  19. 413
      tests/integration/features/bootstrap/FeatureContext.php
  20. 2
      tests/integration/features/integration/dashboard-server.feature
  21. 15
      tests/integration/features/integration/dashboard-talk.feature
  22. 2
      tests/integration/spreedcheats/appinfo/routes.php
  23. 18
      tests/integration/spreedcheats/calendars/0_event_single.txt
  24. 20
      tests/integration/spreedcheats/calendars/1_event_attachment.txt
  25. 23
      tests/integration/spreedcheats/calendars/2_event_attendees.txt
  26. 19
      tests/integration/spreedcheats/calendars/3_event_recurring_weekly.txt
  27. 36
      tests/integration/spreedcheats/lib/Calendar/EventGenerator.php
  28. 102
      tests/integration/spreedcheats/lib/Controller/ApiController.php

6
REUSE.toml

@ -41,6 +41,12 @@ precedence = "aggregate"
SPDX-FileCopyrightText = "2016 Nextcloud GmbH and Nextcloud contributors"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = "tests/integration/spreedcheats/calendars/**.txt"
precedence = "aggregate"
SPDX-FileCopyrightText = "none"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = ["package.json", "package-lock.json", ".git-blame-ignore-revs", "**/package.json", "**/package-lock.json", "composer.json", "composer.patches.json", "composer.lock", "**/composer.json", "**/composer.lock", ".gitignore", ".l10nignore", "psalm.xml", "tests/psalm-baseline.xml", "vendor-bin/**/composer.json", "vendor-bin/**/composer.lock", ".tx/config", "**/phpunit.xml", "tsconfig.json", "redocly.yaml"]
precedence = "aggregate"

1
appinfo/routes.php

@ -15,6 +15,7 @@ return array_merge_recursive(
include(__DIR__ . '/routes/routesCallController.php'),
include(__DIR__ . '/routes/routesCertificateController.php'),
include(__DIR__ . '/routes/routesChatController.php'),
include(__DIR__ . '/routes/routesDashboardController.php'),
include(__DIR__ . '/routes/routesFederationController.php'),
include(__DIR__ . '/routes/routesFilesIntegrationController.php'),
include(__DIR__ . '/routes/routesGuestController.php'),

18
appinfo/routes/routesDashboardController.php

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
$requirements = [
'apiVersion' => '(v4)',
];
return [
'ocs' => [
/** @see \OCA\Talk\Controller\DashboardController::getEventRooms() */
['name' => 'Dashboard#getEventRooms', 'url' => '/api/{apiVersion}/dashboard/events', 'verb' => 'GET', 'requirements' => $requirements],
],
];

1
docs/capabilities.md

@ -184,3 +184,4 @@
* `sip-direct-dialin` (local) - Whether the SIP bridge can create conversations when an external participant calls a mapped phone number
* `important-conversations` (local) - Whether important conversations are supported
* `config => call => predefined-backgrounds-v2` (local) - Whether virtual backgrounds should be read from the theming directory
* `dashboard-event-rooms` (local) - Whether Talk APIs offer functionality for Dashboard requests

2
lib/Capabilities.php

@ -117,6 +117,7 @@ class Capabilities implements IPublicCapability {
'conversation-creation-all',
'important-conversations',
'sip-direct-dialin',
'dashboard-event-rooms',
];
public const CONDITIONAL_FEATURES = [
@ -143,6 +144,7 @@ class Capabilities implements IPublicCapability {
'conversation-creation-all',
'important-conversations',
'sip-direct-dialin',
'dashboard-event-rooms',
];
public const LOCAL_CONFIGS = [

55
lib/Controller/DashboardController.php

@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Controller;
use OCA\Talk\ResponseDefinitions;
use OCA\Talk\Service\DashboardService;
use OCA\Talk\Service\ParticipantService;
use OCA\Talk\Service\RoomFormatter;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest;
use OCP\IUserSession;
use Psr\Log\LoggerInterface;
/**
* @psalm-import-type TalkDashboardEvent from ResponseDefinitions
*/
class DashboardController extends AEnvironmentAwareOCSController {
public function __construct(
string $appName,
IRequest $request,
protected IUserSession $userSession,
protected LoggerInterface $logger,
protected DashboardService $service,
protected ParticipantService $participantService,
protected RoomFormatter $formatter,
) {
parent::__construct($appName, $request);
}
/**
* Get up to 10 rooms that have events in the next 7 days
* sorted by their start timestamp ascending
*
* Required capability: `dashboard-event-rooms`
*
* @return DataResponse<Http::STATUS_OK, list<TalkDashboardEvent>, array{}>
*
* 200: A list of dashboard entries or an empty array
*/
#[NoAdminRequired]
public function getEventRooms(): DataResponse {
$userId = $this->userSession->getUser()?->getUID();
$entries = $this->service->getEvents($userId);
return new DataResponse($entries);
}
}

274
lib/Dashboard/Event.php

@ -0,0 +1,274 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Dashboard;
use OCA\Talk\ResponseDefinitions;
/**
* @psalm-import-type TalkDashboardEvent from ResponseDefinitions
* @psalm-import-type TalkDashboardEventCalendar from ResponseDefinitions
* @psalm-import-type TalkDashboardEventAttachment from ResponseDefinitions
*/
class Event implements \JsonSerializable {
/** @var list<TalkDashboardEventCalendar> */
protected array $calendars = [];
protected string $eventName = '';
protected string $eventLink = '';
protected int $start = 0;
protected int $end = 0;
protected string $roomToken = '';
protected string $roomAvatarVersion = '';
protected string $roomName = '';
protected string $roomDisplayName = '';
protected int $roomType = 0;
protected ?string $eventDescription = null;
/** @var list<TalkDashboardEventAttachment> */
protected array $eventAttachments = [];
protected ?int $roomActiveSince = null;
protected ?int $accepted = null;
protected ?int $tentative = null;
protected ?int $declined = null;
protected ?int $invited = null;
public function __construct() {
}
/**
* @return list<TalkDashboardEventCalendar>
*/
public function getCalendars(): array {
return $this->calendars;
}
public function getEventName(): string {
return $this->eventName;
}
public function setEventName(string $eventName): void {
$this->eventName = $eventName;
}
public function getEventDescription(): ?string {
return $this->eventDescription;
}
public function setEventDescription(?string $eventDescription): void {
$this->eventDescription = $eventDescription;
}
/**
* @return list<TalkDashboardEventAttachment>
*/
public function getEventAttachments(): array {
return $this->eventAttachments;
}
public function setEventLink(string $eventLink): void {
$this->eventLink = $eventLink;
}
public function getStart(): int {
return $this->start;
}
public function setStart(int $start): void {
$this->start = $start;
}
public function getEnd(): int {
return $this->end;
}
public function setEnd(int $end): void {
$this->end = $end;
}
public function setRoomToken(string $roomToken): void {
$this->roomToken = $roomToken;
}
public function setRoomAvatarVersion(string $roomAvatarVersion): void {
$this->roomAvatarVersion = $roomAvatarVersion;
}
public function setRoomName(string $roomName): void {
$this->roomName = $roomName;
}
public function setRoomDisplayName(string $roomDisplayName): void {
$this->roomDisplayName = $roomDisplayName;
}
public function setRoomType(int $roomType): void {
$this->roomType = $roomType;
}
public function setRoomActiveSince(?int $roomActiveSince): void {
$this->roomActiveSince = $roomActiveSince;
}
public function generateAttendance(array $attendees): void {
foreach ($attendees as $attendee) {
switch ($attendee[1]['PARTSTAT']->getValue()) {
case 'ACCEPTED':
(int)$this->accepted++;
break;
case 'TENTATIVE':
(int)$this->tentative++;
break;
case 'DECLINED':
(int)$this->declined++;
break;
case 'NEEDS-ACTION':
(int)$this->invited++;
break;
default:
break;
}
}
}
/**
* Takes the room token, start and end time and attendees to build an identifier
* If the identifier already exists, another event is happening at the same time
* in the same room
*
* We only return duplicates if the attendees are different
*
* @return string
*/
public function generateEventIdentifier(): string {
return $this->roomToken . '#' . $this->start . '#' . $this->end . '#' . (int)$this->accepted . '#' . (int)$this->tentative . '#' . (int)$this->declined;
}
/**
* @param string $calendarName
* @param array $attachments
* @return void
*/
public function handleCalendarAttachments(string $calendarName, array $attachments): void {
foreach ($attachments as $attachment) {
$params = $attachment[1];
$this->eventAttachments[$attachment[0]] = [
'calendars' => [$calendarName],
'fmttype' => $params['FMTTYPE']?->getValue(),
'filename' => $params['FILENAME']?->getValue(),
'fileid' => $params['X-NC-FILE-ID']?->getValue(),
'preview' => $params['X-NC-HAS-PREVIEW']?->getValue(),
'previewLink' => (bool)$params['X-NC-HAS-PREVIEW']?->getValue() ? $attachment[0] : null,
];
}
}
/**
* @param string $principalUri
* @param string $calendarName
* @param string|null $calendarColor
* @return void
*/
public function addCalendar(string $principalUri, string $calendarName, ?string $calendarColor): void {
$this->calendars[] = [
'principalUri' => $principalUri,
'calendarName' => $calendarName,
'calendarColor' => $calendarColor,
];
}
public function mergeAttachments(self $event): void {
$attachments = $event->getEventAttachments();
if (empty($attachments) === true) {
return;
}
if (empty($this->eventAttachments) === true) {
$this->eventAttachments = $attachments;
return;
}
foreach ($attachments as $filename => $attachment) {
if (isset($this->eventAttachments[$filename])) {
$this->eventAttachments[$filename]['calendars'] =
array_merge($this->eventAttachments[$filename]['calendars'], $attachment['calendars']);
} else {
$this->eventAttachments[$filename] = $attachment;
}
}
}
public function getEventLink(): string {
return $this->eventLink;
}
public function getRoomToken(): string {
return $this->roomToken;
}
public function getRoomAvatarVersion(): string {
return $this->roomAvatarVersion;
}
public function getRoomName(): string {
return $this->roomName;
}
public function getRoomDisplayName(): string {
return $this->roomDisplayName;
}
public function getRoomType(): int {
return $this->roomType;
}
public function getRoomActiveSince(): ?int {
return $this->roomActiveSince;
}
public function getAccepted(): ?int {
return $this->accepted;
}
public function getTentative(): ?int {
return $this->tentative;
}
public function getDeclined(): ?int {
return $this->declined;
}
public function getInvited(): ?int {
return $this->invited;
}
/**
* @return TalkDashboardEvent
*/
#[\Override]
public function jsonSerialize(): array {
return [
'calendars' => $this->getCalendars(),
'eventName' => $this->getEventName(),
'eventLink' => $this->getEventLink(),
'start' => $this->getStart(),
'end' => $this->getEnd(),
'roomToken' => $this->getRoomToken(),
'roomAvatarVersion' => $this->getRoomAvatarVersion(),
'roomName' => $this->getRoomName(),
'roomDisplayName' => $this->getRoomDisplayName(),
'roomType' => $this->getRoomType(),
'eventDescription' => $this->getEventDescription(),
'eventAttachments' => $this->getEventAttachments(),
'roomActiveSince' => $this->getRoomActiveSince(),
'accepted' => $this->getAccepted(),
'tentative' => $this->getTentative(),
'declined' => $this->getDeclined(),
'invited' => $this->getInvited(),
];
}
}

87
lib/ResponseDefinitions.php

@ -232,69 +232,156 @@ namespace OCA\Talk;
* }
*
* @psalm-type TalkRoom = array{
* // The unique identifier for the given actor type
* actorId: string,
* // The cloud id of the invited user
* invitedActorId?: string,
* // Actor type of the current user (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-types))
* actorType: string,
* // Unique attendee id
* attendeeId: int,
* // Dedicated permissions for the current participant, if not `Custom` this are not the resulting permissions (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))
* attendeePermissions: int,
* // Unique dial-in authentication code for this user, when the conversation has SIP enabled (see `sipEnabled` attribute)
* attendeePin: ?string,
* // Version of conversation avatar used to easier expiration of the avatar in case a moderator updates it, since the avatar endpoint should be cached for 24 hours. (only available with `avatar` capability)
* avatarVersion: string,
* // Breakout room configuration mode (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#breakout-room-modes)) (only available with `breakout-rooms-v1` capability)
* breakoutRoomMode: int,
* // Breakout room status (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#breakout-room-status)) (only available with `breakout-rooms-v1` capability)
* breakoutRoomStatus: int,
* // Combined flag of all participants in the current call (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#participant-in-call-flag), only available with `conversation-call-flags` capability)
* callFlag: int,
* // Call permissions, if not `Custom` this are not the resulting permissions, if set they will reset after the end of the call (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))
* callPermissions: int,
* callRecording: int,
* callStartTime: int,
* // Flag if the user can delete the conversation for everyone (not possible without moderator permissions or in one-to-one conversations)
* canDeleteConversation: bool,
* // Whether the given user can enable SIP for this conversation. Note that when the token is not-numeric only, SIP can not be enabled even if the user is permitted and a moderator of the conversation
* canEnableSIP: bool,
* // Flag if the user can leave the conversation (not possible for the last user with moderator permissions)
* canLeaveConversation: bool,
* // Flag if the user can start a new call in this conversation (joining is always possible) (only available with `start-call-flag` capability)
* canStartCall: bool,
* // Default permissions for new participants (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))
* defaultPermissions: int,
* // Description of the conversation (can also be empty) (only available with `room-description` capability)
* description: string,
* // `name` if non-empty, otherwise it falls back to a list of participants
* displayName: string,
* // Flag if the conversation has an active call
* hasCall: bool,
* // Flag if the conversation has a password
* hasPassword: bool,
* // Numeric identifier of the conversation
* id: int,
* // Flag if the conversation has a custom avatar (only available with `avatar` capability)
* isCustomAvatar: bool,
* // Flag if the conversation is favorited by the user
* isFavorite: bool,
* // Timestamp of the last activity in the conversation, in seconds and UTC time zone
* lastActivity: int,
* // ID of the last message read by every user that has read privacy set to public in a room. When the user themself has it set to private the value is `0` (only available with `chat-read-status` capability)
* lastCommonReadMessage: int,
* // Last message in a conversation if available, otherwise empty. **Note:** Even when given the message will not contain the `parent` or `reactionsSelf` attribute due to performance reasons
* lastMessage?: TalkRoomLastMessage,
* // Timestamp of the user's session making the request
* lastPing: int,
* // ID of the last read message in a room (only available with `chat-read-marker` capability)
* lastReadMessage: int,
* // Listable scope for the room (only available with `listable-rooms` capability)
* listable: int,
* // Webinar lobby restriction (0-1), if the participant is a moderator they can always join the conversation (only available with `webinary-lobby` capability) (See [Webinar lobby states](https://nextcloud-talk.readthedocs.io/en/latest/constants#webinar-lobby-states))
* lobbyState: int,
* // Timestamp when the lobby will be automatically disabled (only available with `webinary-lobby` capability)
* lobbyTimer: int,
* mentionPermissions: int,
* messageExpiration: int,
* // Name of the conversation (can also be empty)
* name: string,
* notificationCalls: int,
* // The notification level for the user (See [Participant notification levels](https://nextcloud-talk.readthedocs.io/en/latest/constants#participant-notification-levels))
* notificationLevel: int,
* // See [Object types](https://nextcloud-talk.readthedocs.io/en/latest/constants#object-types) documentation for explanation
* objectId: string,
* // The type of object that the conversation is associated with (See [Object types](https://nextcloud-talk.readthedocs.io/en/latest/constants#object-types))
* objectType: string,
* // "In call" flags of the user's session making the request (only available with `in-call-flags` capability)
* participantFlags: int,
* // Permissions level of the current user
* participantType: int,
* // Combined final permissions for the current participant, permissions are picked in order of attendee then call then default and the first which is `Custom` will apply (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))
* permissions: int,
* // Read-only state for the current user (only available with `read-only-rooms` capability)
* readOnly: int,
* // Whether recording consent is required before joining a call (Only 0 and 1 will be returned, see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#recording-consent-required)) (only available with `recording-consent` capability)
* recordingConsent: int,
* remoteServer?: string,
* remoteToken?: string,
* // `'0'` if not connected, otherwise an up to 512 character long string that is the identifier of the user's session making the request. Should only be used to pre-check if the user joined already with this session, but this might be outdated by the time of usage, so better check via [Get list of participants in a conversation](https://nextcloud-talk.readthedocs.io/en/latest/participant/#get-list-of-participants-in-a-conversation)
* sessionId: string,
* // SIP enable status (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#sip-states))
* sipEnabled: int,
* // Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status
* status?: string,
* // Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status
* statusClearAt?: ?int,
* // Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status
* statusIcon?: ?string,
* // Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status
* statusMessage?: ?string,
* // Token identifier of the conversation which is used for further interaction
* token: string,
* // See list of conversation types in the [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants/#conversation-types)
* type: int,
* // Flag if the user was mentioned since their last visit
* unreadMention: bool,
* // Flag if the user was mentioned directly (ignoring `@all` mentions) since their last visit (only available with `direct-mention-flag` capability)
* unreadMentionDirect: bool,
* // Number of unread chat messages in the conversation (only available with `chat-v2` capability)
* unreadMessages: int,
* // Flag if the conversation is archived by the user (only available with `archived-conversations-v2` capability)
* isArchived: bool,
* // Required capability: `important-conversations`
* isImportant: bool,
* }
*
* @psalm-type TalkDashboardEventAttachment = array{
* calendars: list<string>,
* fmttype: string,
* filename: string,
* fileId: int,
* hasPreview: boolean,
* previewUrl: ?string,
* }
*
* @psalm-type TalkDashboardEventCalendar = array{
* principalUri: string,
* calendarName: string,
* calendarColor: ?string,
* }
*
* @psalm-type TalkDashboardEvent = array{
* calendars: list<TalkDashboardEventCalendar>,
* eventName: string,
* eventDescription: ?string,
* eventAttachments: list<TalkDashboardEventAttachment>,
* eventLink: string,
* start: int,
* end: int,
* roomToken: string,
* roomAvatarVersion: string,
* roomName: string,
* roomDisplayName: string,
* roomType: int,
* roomActiveSince: ?int,
* invited: ?int,
* accepted: ?int,
* tentative: ?int,
* declined: ?int,
* }
*
* @psalm-type TalkRoomWithInvalidInvitations = TalkRoom&array{
* invalidParticipants: TalkInvitationList,
* }

180
lib/Service/DashboardService.php

@ -0,0 +1,180 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Service;
use OCA\Talk\Dashboard\Event;
use OCA\Talk\Exceptions\RoomNotFoundException;
use OCA\Talk\Manager;
use OCA\Talk\ResponseDefinitions;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Calendar\ICalendar;
use OCP\Calendar\IManager;
use OCP\IDateTimeZone;
use OCP\IURLGenerator;
use Psr\Log\LoggerInterface;
/**
* @psalm-import-type TalkDashboardEvent from ResponseDefinitions
*/
class DashboardService {
public function __construct(
private Manager $manager,
private IManager $calendarManager,
private ITimeFactory $timeFactory,
private LoggerInterface $logger,
private RoomService $roomService,
private IDateTimeZone $dateTimeZone,
private AvatarService $avatarService,
private IURLGenerator $urlGenerator,
) {
}
/**
* @param string $userId
* @return list<TalkDashboardEvent>
*/
public function getEvents(string $userId): array {
$principaluri = 'principals/users/' . $userId;
$calendars = $this->calendarManager->getCalendarsForPrincipal($principaluri);
if (count($calendars) === 0) {
return [];
}
// Only use personal calendars
// Events for shared calendars where you are an ATTENDEE will be in your personal calendar
$calendars = array_filter($calendars, static function (ICalendar $calendar) {
if (method_exists($calendar, 'isShared')) {
return $calendar->isShared() === false;
}
return true;
});
$userTimezone = $this->dateTimeZone->getTimezone();
// Midnight for the current user so we also include ongoing events (might be all day events)
$start = $this->timeFactory->getDateTime()->setTimezone($userTimezone)->setTime(0, 0);
$start = $start->setTimezone(new \DateTimeZone('UTC'));
$end = clone($start);
$end = $end->add(\DateInterval::createFromDateString('1 week'));
$options = [
'timerange' => [
'start' => $start,
'end' => $end,
],
];
$pattern = '/call/';
$searchProperties = ['LOCATION'];
$events = [];
/** @var ICalendar $calendar */
foreach ($calendars as $calendar) {
$searchResult = $calendar->search($pattern, $searchProperties, $options, 10);
foreach ($searchResult as $calendarEvent) {
// Find first recurrence in the future
$event = null;
$dashboardEvent = new Event();
foreach ($calendarEvent['objects'] as $object) {
$dashboardEvent->setStart(\DateTime::createFromImmutable($object['DTSTART'][0])->setTimezone($userTimezone)->getTimestamp());
$dashboardEvent->setEnd(\DateTime::createFromImmutable($object['DTEND'][0])->setTimezone($userTimezone)->getTimestamp());
// Filter out events in the past
if ($dashboardEvent->getEnd() <= $this->timeFactory->getDateTime('now', $userTimezone)->getTimestamp()) {
continue;
}
$event = $object;
break;
}
$location = $event['LOCATION'][0] ?? null;
if ($event === null || $location === null) {
continue;
}
try {
$token = $this->roomService->parseRoomTokenFromUrl($location);
// Already returns public / open conversations
$room = $this->manager->getRoomForUserByToken($token, $userId);
} catch (RoomNotFoundException) {
$this->logger->debug("Room for url $location not found in dashboard service");
continue;
}
$dashboardEvent->setRoomToken($token);
$dashboardEvent->setRoomType($room->getType());
$dashboardEvent->setRoomName($room->getName());
$dashboardEvent->setRoomDisplayName($room->getDisplayName($userId));
if (isset($event['ATTENDEE'])) {
$dashboardEvent->generateAttendance($event['ATTENDEE']);
}
$dashboardEvent->setEventName($event['SUMMARY'][0] ?? '');
$dashboardEvent->setEventDescription($event['DESCRIPTION'][0] ?? null);
if (isset($event['ATTACH'])) {
$dashboardEvent->handleCalendarAttachments($calendar->getUri(), $event['ATTACH']);
}
if (isset($events[$dashboardEvent->generateEventIdentifier()])) {
/** @var Event $existing */
$existing = $events[$dashboardEvent->generateEventIdentifier()];
$existing->addCalendar($calendar->getUri(), $calendar->getDisplayName(), $calendar->getDisplayColor());
// Merge attachments
$existing->mergeAttachments($dashboardEvent);
// If original SUMMARY is empty, use the duplicate content if it exists
if ($existing->getEventDescription() === null) {
$existing->setEventDescription($dashboardEvent->getEventDescription() ?? '');
}
// We continue here as the same event already exists in a different calendar
$events[$existing->generateEventIdentifier()] = $existing;
continue;
}
$dashboardEvent->addCalendar($calendar->getUri(), $calendar->getDisplayName(), $calendar->getDisplayColor());
$dashboardEvent->setRoomAvatarVersion($this->avatarService->getAvatarVersion($room));
$dashboardEvent->setRoomActiveSince($room->getActiveSince()?->getTimestamp());
$objectId = base64_encode($this->urlGenerator->getWebroot() . '/remote.php/dav/calendars/' . $userId . '/' . $calendar->getUri() . '/' . $calendarEvent['uri']);
if (isset($event['RECURRENCE-ID'])) {
$dashboardEvent->setEventLink(
$this->urlGenerator->linkToRouteAbsolute(
'calendar.view.indexdirect.edit',
[
'objectId' => $objectId,
'recurrenceId' => $event['RECURRENCE-ID'][0],
]
)
);
} else {
$dashboardEvent->setEventLink(
$this->urlGenerator->linkToRouteAbsolute('calendar.view.indexdirect.edit', ['objectId' => $objectId])
);
}
$events[$dashboardEvent->generateEventIdentifier()] = $dashboardEvent;
}
}
if (empty($events)) {
return $events;
}
usort($events, static function (Event $a, Event $b) {
return $a->getStart() - $b->getStart();
});
return array_map(static function (Event $event) {
return $event->jsonSerialize();
}, array_slice($events, 0, 10));
}
}

22
lib/Service/RoomService.php

@ -1460,6 +1460,28 @@ class RoomService {
$room->setObjectType($objectType);
}
/**
* @param string $url
* @return string
*/
public function parseRoomTokenFromUrl(string $url): string {
// Check if room exists and check if user is part of room
$array = explode('/', $url);
$token = end($array);
// Cut off any excess characters from the room token
if (str_contains($token, '?')) {
$token = substr($token, 0, strpos($token, '?'));
}
if (str_contains($token, '#')) {
$token = substr($token, 0, strpos($token, '#'));
}
if (!$token) {
throw new RoomNotFoundException();
}
return $token;
}
public function hasExistingCalendarEvents(Room $room, string $userId, string $eventUid) : bool {
$calendars = $this->calendarManager->getCalendarsForPrincipal('principals/users/' . $userId);
if (!empty($calendars)) {

156
openapi-backend-sipbridge.json

@ -602,44 +602,55 @@
],
"properties": {
"actorId": {
"type": "string"
"type": "string",
"description": "The unique identifier for the given actor type"
},
"invitedActorId": {
"type": "string"
"type": "string",
"description": "The cloud id of the invited user"
},
"actorType": {
"type": "string"
"type": "string",
"description": "Actor type of the current user (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-types))"
},
"attendeeId": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Unique attendee id"
},
"attendeePermissions": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Dedicated permissions for the current participant, if not `Custom` this are not the resulting permissions (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))"
},
"attendeePin": {
"type": "string",
"nullable": true
"nullable": true,
"description": "Unique dial-in authentication code for this user, when the conversation has SIP enabled (see `sipEnabled` attribute)"
},
"avatarVersion": {
"type": "string"
"type": "string",
"description": "Version of conversation avatar used to easier expiration of the avatar in case a moderator updates it, since the avatar endpoint should be cached for 24 hours. (only available with `avatar` capability)"
},
"breakoutRoomMode": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Breakout room configuration mode (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#breakout-room-modes)) (only available with `breakout-rooms-v1` capability)"
},
"breakoutRoomStatus": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Breakout room status (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#breakout-room-status)) (only available with `breakout-rooms-v1` capability)"
},
"callFlag": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Combined flag of all participants in the current call (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#participant-in-call-flag), only available with `conversation-call-flags` capability)"
},
"callPermissions": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Call permissions, if not `Custom` this are not the resulting permissions, if set they will reset after the end of the call (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))"
},
"callRecording": {
"type": "integer",
@ -650,73 +661,93 @@
"format": "int64"
},
"canDeleteConversation": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the user can delete the conversation for everyone (not possible without moderator permissions or in one-to-one conversations)"
},
"canEnableSIP": {
"type": "boolean"
"type": "boolean",
"description": "Whether the given user can enable SIP for this conversation. Note that when the token is not-numeric only, SIP can not be enabled even if the user is permitted and a moderator of the conversation"
},
"canLeaveConversation": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the user can leave the conversation (not possible for the last user with moderator permissions)"
},
"canStartCall": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the user can start a new call in this conversation (joining is always possible) (only available with `start-call-flag` capability)"
},
"defaultPermissions": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Default permissions for new participants (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))"
},
"description": {
"type": "string"
"type": "string",
"description": "Description of the conversation (can also be empty) (only available with `room-description` capability)"
},
"displayName": {
"type": "string"
"type": "string",
"description": "`name` if non-empty, otherwise it falls back to a list of participants"
},
"hasCall": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the conversation has an active call"
},
"hasPassword": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the conversation has a password"
},
"id": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Numeric identifier of the conversation"
},
"isCustomAvatar": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the conversation has a custom avatar (only available with `avatar` capability)"
},
"isFavorite": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the conversation is favorited by the user"
},
"lastActivity": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Timestamp of the last activity in the conversation, in seconds and UTC time zone"
},
"lastCommonReadMessage": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "ID of the last message read by every user that has read privacy set to public in a room. When the user themself has it set to private the value is `0` (only available with `chat-read-status` capability)"
},
"lastMessage": {
"$ref": "#/components/schemas/RoomLastMessage"
"$ref": "#/components/schemas/RoomLastMessage",
"description": "Last message in a conversation if available, otherwise empty. **Note:** Even when given the message will not contain the `parent` or `reactionsSelf` attribute due to performance reasons"
},
"lastPing": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Timestamp of the user's session making the request"
},
"lastReadMessage": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "ID of the last read message in a room (only available with `chat-read-marker` capability)"
},
"listable": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Listable scope for the room (only available with `listable-rooms` capability)"
},
"lobbyState": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Webinar lobby restriction (0-1), if the participant is a moderator they can always join the conversation (only available with `webinary-lobby` capability) (See [Webinar lobby states](https://nextcloud-talk.readthedocs.io/en/latest/constants#webinar-lobby-states))"
},
"lobbyTimer": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Timestamp when the lobby will be automatically disabled (only available with `webinary-lobby` capability)"
},
"mentionPermissions": {
"type": "integer",
@ -727,7 +758,8 @@
"format": "int64"
},
"name": {
"type": "string"
"type": "string",
"description": "Name of the conversation (can also be empty)"
},
"notificationCalls": {
"type": "integer",
@ -735,33 +767,41 @@
},
"notificationLevel": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "The notification level for the user (See [Participant notification levels](https://nextcloud-talk.readthedocs.io/en/latest/constants#participant-notification-levels))"
},
"objectId": {
"type": "string"
"type": "string",
"description": "See [Object types](https://nextcloud-talk.readthedocs.io/en/latest/constants#object-types) documentation for explanation"
},
"objectType": {
"type": "string"
"type": "string",
"description": "The type of object that the conversation is associated with (See [Object types](https://nextcloud-talk.readthedocs.io/en/latest/constants#object-types))"
},
"participantFlags": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "\"In call\" flags of the user's session making the request (only available with `in-call-flags` capability)"
},
"participantType": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Permissions level of the current user"
},
"permissions": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Combined final permissions for the current participant, permissions are picked in order of attendee then call then default and the first which is `Custom` will apply (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))"
},
"readOnly": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Read-only state for the current user (only available with `read-only-rooms` capability)"
},
"recordingConsent": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Whether recording consent is required before joining a call (Only 0 and 1 will be returned, see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#recording-consent-required)) (only available with `recording-consent` capability)"
},
"remoteServer": {
"type": "string"
@ -770,47 +810,59 @@
"type": "string"
},
"sessionId": {
"type": "string"
"type": "string",
"description": "`'0'` if not connected, otherwise an up to 512 character long string that is the identifier of the user's session making the request. Should only be used to pre-check if the user joined already with this session, but this might be outdated by the time of usage, so better check via [Get list of participants in a conversation](https://nextcloud-talk.readthedocs.io/en/latest/participant/#get-list-of-participants-in-a-conversation)"
},
"sipEnabled": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "SIP enable status (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#sip-states))"
},
"status": {
"type": "string"
"type": "string",
"description": "Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status"
},
"statusClearAt": {
"type": "integer",
"format": "int64",
"nullable": true
"nullable": true,
"description": "Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status"
},
"statusIcon": {
"type": "string",
"nullable": true
"nullable": true,
"description": "Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status"
},
"statusMessage": {
"type": "string",
"nullable": true
"nullable": true,
"description": "Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status"
},
"token": {
"type": "string"
"type": "string",
"description": "Token identifier of the conversation which is used for further interaction"
},
"type": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "See list of conversation types in the [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants/#conversation-types)"
},
"unreadMention": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the user was mentioned since their last visit"
},
"unreadMentionDirect": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the user was mentioned directly (ignoring `@all` mentions) since their last visit (only available with `direct-mention-flag` capability)"
},
"unreadMessages": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Number of unread chat messages in the conversation (only available with `chat-v2` capability)"
},
"isArchived": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the conversation is archived by the user (only available with `archived-conversations-v2` capability)"
},
"isImportant": {
"type": "boolean",

156
openapi-federation.json

@ -656,44 +656,55 @@
],
"properties": {
"actorId": {
"type": "string"
"type": "string",
"description": "The unique identifier for the given actor type"
},
"invitedActorId": {
"type": "string"
"type": "string",
"description": "The cloud id of the invited user"
},
"actorType": {
"type": "string"
"type": "string",
"description": "Actor type of the current user (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-types))"
},
"attendeeId": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Unique attendee id"
},
"attendeePermissions": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Dedicated permissions for the current participant, if not `Custom` this are not the resulting permissions (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))"
},
"attendeePin": {
"type": "string",
"nullable": true
"nullable": true,
"description": "Unique dial-in authentication code for this user, when the conversation has SIP enabled (see `sipEnabled` attribute)"
},
"avatarVersion": {
"type": "string"
"type": "string",
"description": "Version of conversation avatar used to easier expiration of the avatar in case a moderator updates it, since the avatar endpoint should be cached for 24 hours. (only available with `avatar` capability)"
},
"breakoutRoomMode": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Breakout room configuration mode (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#breakout-room-modes)) (only available with `breakout-rooms-v1` capability)"
},
"breakoutRoomStatus": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Breakout room status (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#breakout-room-status)) (only available with `breakout-rooms-v1` capability)"
},
"callFlag": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Combined flag of all participants in the current call (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#participant-in-call-flag), only available with `conversation-call-flags` capability)"
},
"callPermissions": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Call permissions, if not `Custom` this are not the resulting permissions, if set they will reset after the end of the call (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))"
},
"callRecording": {
"type": "integer",
@ -704,73 +715,93 @@
"format": "int64"
},
"canDeleteConversation": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the user can delete the conversation for everyone (not possible without moderator permissions or in one-to-one conversations)"
},
"canEnableSIP": {
"type": "boolean"
"type": "boolean",
"description": "Whether the given user can enable SIP for this conversation. Note that when the token is not-numeric only, SIP can not be enabled even if the user is permitted and a moderator of the conversation"
},
"canLeaveConversation": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the user can leave the conversation (not possible for the last user with moderator permissions)"
},
"canStartCall": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the user can start a new call in this conversation (joining is always possible) (only available with `start-call-flag` capability)"
},
"defaultPermissions": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Default permissions for new participants (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))"
},
"description": {
"type": "string"
"type": "string",
"description": "Description of the conversation (can also be empty) (only available with `room-description` capability)"
},
"displayName": {
"type": "string"
"type": "string",
"description": "`name` if non-empty, otherwise it falls back to a list of participants"
},
"hasCall": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the conversation has an active call"
},
"hasPassword": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the conversation has a password"
},
"id": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Numeric identifier of the conversation"
},
"isCustomAvatar": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the conversation has a custom avatar (only available with `avatar` capability)"
},
"isFavorite": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the conversation is favorited by the user"
},
"lastActivity": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Timestamp of the last activity in the conversation, in seconds and UTC time zone"
},
"lastCommonReadMessage": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "ID of the last message read by every user that has read privacy set to public in a room. When the user themself has it set to private the value is `0` (only available with `chat-read-status` capability)"
},
"lastMessage": {
"$ref": "#/components/schemas/RoomLastMessage"
"$ref": "#/components/schemas/RoomLastMessage",
"description": "Last message in a conversation if available, otherwise empty. **Note:** Even when given the message will not contain the `parent` or `reactionsSelf` attribute due to performance reasons"
},
"lastPing": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Timestamp of the user's session making the request"
},
"lastReadMessage": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "ID of the last read message in a room (only available with `chat-read-marker` capability)"
},
"listable": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Listable scope for the room (only available with `listable-rooms` capability)"
},
"lobbyState": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Webinar lobby restriction (0-1), if the participant is a moderator they can always join the conversation (only available with `webinary-lobby` capability) (See [Webinar lobby states](https://nextcloud-talk.readthedocs.io/en/latest/constants#webinar-lobby-states))"
},
"lobbyTimer": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Timestamp when the lobby will be automatically disabled (only available with `webinary-lobby` capability)"
},
"mentionPermissions": {
"type": "integer",
@ -781,7 +812,8 @@
"format": "int64"
},
"name": {
"type": "string"
"type": "string",
"description": "Name of the conversation (can also be empty)"
},
"notificationCalls": {
"type": "integer",
@ -789,33 +821,41 @@
},
"notificationLevel": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "The notification level for the user (See [Participant notification levels](https://nextcloud-talk.readthedocs.io/en/latest/constants#participant-notification-levels))"
},
"objectId": {
"type": "string"
"type": "string",
"description": "See [Object types](https://nextcloud-talk.readthedocs.io/en/latest/constants#object-types) documentation for explanation"
},
"objectType": {
"type": "string"
"type": "string",
"description": "The type of object that the conversation is associated with (See [Object types](https://nextcloud-talk.readthedocs.io/en/latest/constants#object-types))"
},
"participantFlags": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "\"In call\" flags of the user's session making the request (only available with `in-call-flags` capability)"
},
"participantType": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Permissions level of the current user"
},
"permissions": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Combined final permissions for the current participant, permissions are picked in order of attendee then call then default and the first which is `Custom` will apply (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))"
},
"readOnly": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Read-only state for the current user (only available with `read-only-rooms` capability)"
},
"recordingConsent": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Whether recording consent is required before joining a call (Only 0 and 1 will be returned, see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#recording-consent-required)) (only available with `recording-consent` capability)"
},
"remoteServer": {
"type": "string"
@ -824,47 +864,59 @@
"type": "string"
},
"sessionId": {
"type": "string"
"type": "string",
"description": "`'0'` if not connected, otherwise an up to 512 character long string that is the identifier of the user's session making the request. Should only be used to pre-check if the user joined already with this session, but this might be outdated by the time of usage, so better check via [Get list of participants in a conversation](https://nextcloud-talk.readthedocs.io/en/latest/participant/#get-list-of-participants-in-a-conversation)"
},
"sipEnabled": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "SIP enable status (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#sip-states))"
},
"status": {
"type": "string"
"type": "string",
"description": "Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status"
},
"statusClearAt": {
"type": "integer",
"format": "int64",
"nullable": true
"nullable": true,
"description": "Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status"
},
"statusIcon": {
"type": "string",
"nullable": true
"nullable": true,
"description": "Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status"
},
"statusMessage": {
"type": "string",
"nullable": true
"nullable": true,
"description": "Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status"
},
"token": {
"type": "string"
"type": "string",
"description": "Token identifier of the conversation which is used for further interaction"
},
"type": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "See list of conversation types in the [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants/#conversation-types)"
},
"unreadMention": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the user was mentioned since their last visit"
},
"unreadMentionDirect": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the user was mentioned directly (ignoring `@all` mentions) since their last visit (only available with `direct-mention-flag` capability)"
},
"unreadMessages": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Number of unread chat messages in the conversation (only available with `chat-v2` capability)"
},
"isArchived": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the conversation is archived by the user (only available with `archived-conversations-v2` capability)"
},
"isImportant": {
"type": "boolean",

384
openapi-full.json

@ -648,6 +648,157 @@
}
}
},
"DashboardEvent": {
"type": "object",
"required": [
"calendars",
"eventName",
"eventDescription",
"eventAttachments",
"eventLink",
"start",
"end",
"roomToken",
"roomAvatarVersion",
"roomName",
"roomDisplayName",
"roomType",
"roomActiveSince",
"invited",
"accepted",
"tentative",
"declined"
],
"properties": {
"calendars": {
"type": "array",
"items": {
"$ref": "#/components/schemas/DashboardEventCalendar"
}
},
"eventName": {
"type": "string"
},
"eventDescription": {
"type": "string",
"nullable": true
},
"eventAttachments": {
"type": "array",
"items": {
"$ref": "#/components/schemas/DashboardEventAttachment"
}
},
"eventLink": {
"type": "string"
},
"start": {
"type": "integer",
"format": "int64"
},
"end": {
"type": "integer",
"format": "int64"
},
"roomToken": {
"type": "string"
},
"roomAvatarVersion": {
"type": "string"
},
"roomName": {
"type": "string"
},
"roomDisplayName": {
"type": "string"
},
"roomType": {
"type": "integer",
"format": "int64"
},
"roomActiveSince": {
"type": "integer",
"format": "int64",
"nullable": true
},
"invited": {
"type": "integer",
"format": "int64",
"nullable": true
},
"accepted": {
"type": "integer",
"format": "int64",
"nullable": true
},
"tentative": {
"type": "integer",
"format": "int64",
"nullable": true
},
"declined": {
"type": "integer",
"format": "int64",
"nullable": true
}
}
},
"DashboardEventAttachment": {
"type": "object",
"required": [
"calendars",
"fmttype",
"filename",
"fileId",
"hasPreview",
"previewUrl"
],
"properties": {
"calendars": {
"type": "array",
"items": {
"type": "string"
}
},
"fmttype": {
"type": "string"
},
"filename": {
"type": "string"
},
"fileId": {
"type": "integer",
"format": "int64"
},
"hasPreview": {
"type": "boolean"
},
"previewUrl": {
"type": "string",
"nullable": true
}
}
},
"DashboardEventCalendar": {
"type": "object",
"required": [
"principalUri",
"calendarName",
"calendarColor"
],
"properties": {
"principalUri": {
"type": "string"
},
"calendarName": {
"type": "string"
},
"calendarColor": {
"type": "string",
"nullable": true
}
}
},
"DeletedChatMessage": {
"type": "object",
"required": [
@ -1256,44 +1407,55 @@
],
"properties": {
"actorId": {
"type": "string"
"type": "string",
"description": "The unique identifier for the given actor type"
},
"invitedActorId": {
"type": "string"
"type": "string",
"description": "The cloud id of the invited user"
},
"actorType": {
"type": "string"
"type": "string",
"description": "Actor type of the current user (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-types))"
},
"attendeeId": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Unique attendee id"
},
"attendeePermissions": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Dedicated permissions for the current participant, if not `Custom` this are not the resulting permissions (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))"
},
"attendeePin": {
"type": "string",
"nullable": true
"nullable": true,
"description": "Unique dial-in authentication code for this user, when the conversation has SIP enabled (see `sipEnabled` attribute)"
},
"avatarVersion": {
"type": "string"
"type": "string",
"description": "Version of conversation avatar used to easier expiration of the avatar in case a moderator updates it, since the avatar endpoint should be cached for 24 hours. (only available with `avatar` capability)"
},
"breakoutRoomMode": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Breakout room configuration mode (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#breakout-room-modes)) (only available with `breakout-rooms-v1` capability)"
},
"breakoutRoomStatus": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Breakout room status (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#breakout-room-status)) (only available with `breakout-rooms-v1` capability)"
},
"callFlag": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Combined flag of all participants in the current call (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#participant-in-call-flag), only available with `conversation-call-flags` capability)"
},
"callPermissions": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Call permissions, if not `Custom` this are not the resulting permissions, if set they will reset after the end of the call (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))"
},
"callRecording": {
"type": "integer",
@ -1304,73 +1466,93 @@
"format": "int64"
},
"canDeleteConversation": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the user can delete the conversation for everyone (not possible without moderator permissions or in one-to-one conversations)"
},
"canEnableSIP": {
"type": "boolean"
"type": "boolean",
"description": "Whether the given user can enable SIP for this conversation. Note that when the token is not-numeric only, SIP can not be enabled even if the user is permitted and a moderator of the conversation"
},
"canLeaveConversation": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the user can leave the conversation (not possible for the last user with moderator permissions)"
},
"canStartCall": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the user can start a new call in this conversation (joining is always possible) (only available with `start-call-flag` capability)"
},
"defaultPermissions": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Default permissions for new participants (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))"
},
"description": {
"type": "string"
"type": "string",
"description": "Description of the conversation (can also be empty) (only available with `room-description` capability)"
},
"displayName": {
"type": "string"
"type": "string",
"description": "`name` if non-empty, otherwise it falls back to a list of participants"
},
"hasCall": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the conversation has an active call"
},
"hasPassword": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the conversation has a password"
},
"id": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Numeric identifier of the conversation"
},
"isCustomAvatar": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the conversation has a custom avatar (only available with `avatar` capability)"
},
"isFavorite": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the conversation is favorited by the user"
},
"lastActivity": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Timestamp of the last activity in the conversation, in seconds and UTC time zone"
},
"lastCommonReadMessage": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "ID of the last message read by every user that has read privacy set to public in a room. When the user themself has it set to private the value is `0` (only available with `chat-read-status` capability)"
},
"lastMessage": {
"$ref": "#/components/schemas/RoomLastMessage"
"$ref": "#/components/schemas/RoomLastMessage",
"description": "Last message in a conversation if available, otherwise empty. **Note:** Even when given the message will not contain the `parent` or `reactionsSelf` attribute due to performance reasons"
},
"lastPing": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Timestamp of the user's session making the request"
},
"lastReadMessage": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "ID of the last read message in a room (only available with `chat-read-marker` capability)"
},
"listable": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Listable scope for the room (only available with `listable-rooms` capability)"
},
"lobbyState": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Webinar lobby restriction (0-1), if the participant is a moderator they can always join the conversation (only available with `webinary-lobby` capability) (See [Webinar lobby states](https://nextcloud-talk.readthedocs.io/en/latest/constants#webinar-lobby-states))"
},
"lobbyTimer": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Timestamp when the lobby will be automatically disabled (only available with `webinary-lobby` capability)"
},
"mentionPermissions": {
"type": "integer",
@ -1381,7 +1563,8 @@
"format": "int64"
},
"name": {
"type": "string"
"type": "string",
"description": "Name of the conversation (can also be empty)"
},
"notificationCalls": {
"type": "integer",
@ -1389,33 +1572,41 @@
},
"notificationLevel": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "The notification level for the user (See [Participant notification levels](https://nextcloud-talk.readthedocs.io/en/latest/constants#participant-notification-levels))"
},
"objectId": {
"type": "string"
"type": "string",
"description": "See [Object types](https://nextcloud-talk.readthedocs.io/en/latest/constants#object-types) documentation for explanation"
},
"objectType": {
"type": "string"
"type": "string",
"description": "The type of object that the conversation is associated with (See [Object types](https://nextcloud-talk.readthedocs.io/en/latest/constants#object-types))"
},
"participantFlags": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "\"In call\" flags of the user's session making the request (only available with `in-call-flags` capability)"
},
"participantType": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Permissions level of the current user"
},
"permissions": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Combined final permissions for the current participant, permissions are picked in order of attendee then call then default and the first which is `Custom` will apply (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))"
},
"readOnly": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Read-only state for the current user (only available with `read-only-rooms` capability)"
},
"recordingConsent": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Whether recording consent is required before joining a call (Only 0 and 1 will be returned, see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#recording-consent-required)) (only available with `recording-consent` capability)"
},
"remoteServer": {
"type": "string"
@ -1424,47 +1615,59 @@
"type": "string"
},
"sessionId": {
"type": "string"
"type": "string",
"description": "`'0'` if not connected, otherwise an up to 512 character long string that is the identifier of the user's session making the request. Should only be used to pre-check if the user joined already with this session, but this might be outdated by the time of usage, so better check via [Get list of participants in a conversation](https://nextcloud-talk.readthedocs.io/en/latest/participant/#get-list-of-participants-in-a-conversation)"
},
"sipEnabled": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "SIP enable status (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#sip-states))"
},
"status": {
"type": "string"
"type": "string",
"description": "Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status"
},
"statusClearAt": {
"type": "integer",
"format": "int64",
"nullable": true
"nullable": true,
"description": "Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status"
},
"statusIcon": {
"type": "string",
"nullable": true
"nullable": true,
"description": "Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status"
},
"statusMessage": {
"type": "string",
"nullable": true
"nullable": true,
"description": "Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status"
},
"token": {
"type": "string"
"type": "string",
"description": "Token identifier of the conversation which is used for further interaction"
},
"type": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "See list of conversation types in the [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants/#conversation-types)"
},
"unreadMention": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the user was mentioned since their last visit"
},
"unreadMentionDirect": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the user was mentioned directly (ignoring `@all` mentions) since their last visit (only available with `direct-mention-flag` capability)"
},
"unreadMessages": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Number of unread chat messages in the conversation (only available with `chat-v2` capability)"
},
"isArchived": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the conversation is archived by the user (only available with `archived-conversations-v2` capability)"
},
"isImportant": {
"type": "boolean",
@ -8497,6 +8700,83 @@
}
}
},
"/ocs/v2.php/apps/spreed/api/{apiVersion}/dashboard/events": {
"get": {
"operationId": "dashboard-get-event-rooms",
"summary": "Get up to 10 rooms that have events in the next 7 days sorted by their start timestamp ascending",
"description": "Required capability: `dashboard-event-rooms`",
"tags": [
"dashboard"
],
"security": [
{
"bearer_auth": []
},
{
"basic_auth": []
}
],
"parameters": [
{
"name": "apiVersion",
"in": "path",
"required": true,
"schema": {
"type": "string",
"enum": [
"v4"
],
"default": "v4"
}
},
{
"name": "OCS-APIRequest",
"in": "header",
"description": "Required to be true for the API request to pass",
"required": true,
"schema": {
"type": "boolean",
"default": true
}
}
],
"responses": {
"200": {
"description": "A list of dashboard entries or an empty array",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ocs"
],
"properties": {
"ocs": {
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"$ref": "#/components/schemas/OCSMeta"
},
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/DashboardEvent"
}
}
}
}
}
}
}
}
}
}
}
},
"/ocs/v2.php/apps/spreed/api/{apiVersion}/file/{fileId}": {
"get": {
"operationId": "files_integration-get-room-by-file-id",

384
openapi.json

@ -607,6 +607,157 @@
}
}
},
"DashboardEvent": {
"type": "object",
"required": [
"calendars",
"eventName",
"eventDescription",
"eventAttachments",
"eventLink",
"start",
"end",
"roomToken",
"roomAvatarVersion",
"roomName",
"roomDisplayName",
"roomType",
"roomActiveSince",
"invited",
"accepted",
"tentative",
"declined"
],
"properties": {
"calendars": {
"type": "array",
"items": {
"$ref": "#/components/schemas/DashboardEventCalendar"
}
},
"eventName": {
"type": "string"
},
"eventDescription": {
"type": "string",
"nullable": true
},
"eventAttachments": {
"type": "array",
"items": {
"$ref": "#/components/schemas/DashboardEventAttachment"
}
},
"eventLink": {
"type": "string"
},
"start": {
"type": "integer",
"format": "int64"
},
"end": {
"type": "integer",
"format": "int64"
},
"roomToken": {
"type": "string"
},
"roomAvatarVersion": {
"type": "string"
},
"roomName": {
"type": "string"
},
"roomDisplayName": {
"type": "string"
},
"roomType": {
"type": "integer",
"format": "int64"
},
"roomActiveSince": {
"type": "integer",
"format": "int64",
"nullable": true
},
"invited": {
"type": "integer",
"format": "int64",
"nullable": true
},
"accepted": {
"type": "integer",
"format": "int64",
"nullable": true
},
"tentative": {
"type": "integer",
"format": "int64",
"nullable": true
},
"declined": {
"type": "integer",
"format": "int64",
"nullable": true
}
}
},
"DashboardEventAttachment": {
"type": "object",
"required": [
"calendars",
"fmttype",
"filename",
"fileId",
"hasPreview",
"previewUrl"
],
"properties": {
"calendars": {
"type": "array",
"items": {
"type": "string"
}
},
"fmttype": {
"type": "string"
},
"filename": {
"type": "string"
},
"fileId": {
"type": "integer",
"format": "int64"
},
"hasPreview": {
"type": "boolean"
},
"previewUrl": {
"type": "string",
"nullable": true
}
}
},
"DashboardEventCalendar": {
"type": "object",
"required": [
"principalUri",
"calendarName",
"calendarColor"
],
"properties": {
"principalUri": {
"type": "string"
},
"calendarName": {
"type": "string"
},
"calendarColor": {
"type": "string",
"nullable": true
}
}
},
"DeletedChatMessage": {
"type": "object",
"required": [
@ -1161,44 +1312,55 @@
],
"properties": {
"actorId": {
"type": "string"
"type": "string",
"description": "The unique identifier for the given actor type"
},
"invitedActorId": {
"type": "string"
"type": "string",
"description": "The cloud id of the invited user"
},
"actorType": {
"type": "string"
"type": "string",
"description": "Actor type of the current user (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-types))"
},
"attendeeId": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Unique attendee id"
},
"attendeePermissions": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Dedicated permissions for the current participant, if not `Custom` this are not the resulting permissions (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))"
},
"attendeePin": {
"type": "string",
"nullable": true
"nullable": true,
"description": "Unique dial-in authentication code for this user, when the conversation has SIP enabled (see `sipEnabled` attribute)"
},
"avatarVersion": {
"type": "string"
"type": "string",
"description": "Version of conversation avatar used to easier expiration of the avatar in case a moderator updates it, since the avatar endpoint should be cached for 24 hours. (only available with `avatar` capability)"
},
"breakoutRoomMode": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Breakout room configuration mode (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#breakout-room-modes)) (only available with `breakout-rooms-v1` capability)"
},
"breakoutRoomStatus": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Breakout room status (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#breakout-room-status)) (only available with `breakout-rooms-v1` capability)"
},
"callFlag": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Combined flag of all participants in the current call (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#participant-in-call-flag), only available with `conversation-call-flags` capability)"
},
"callPermissions": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Call permissions, if not `Custom` this are not the resulting permissions, if set they will reset after the end of the call (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))"
},
"callRecording": {
"type": "integer",
@ -1209,73 +1371,93 @@
"format": "int64"
},
"canDeleteConversation": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the user can delete the conversation for everyone (not possible without moderator permissions or in one-to-one conversations)"
},
"canEnableSIP": {
"type": "boolean"
"type": "boolean",
"description": "Whether the given user can enable SIP for this conversation. Note that when the token is not-numeric only, SIP can not be enabled even if the user is permitted and a moderator of the conversation"
},
"canLeaveConversation": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the user can leave the conversation (not possible for the last user with moderator permissions)"
},
"canStartCall": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the user can start a new call in this conversation (joining is always possible) (only available with `start-call-flag` capability)"
},
"defaultPermissions": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Default permissions for new participants (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))"
},
"description": {
"type": "string"
"type": "string",
"description": "Description of the conversation (can also be empty) (only available with `room-description` capability)"
},
"displayName": {
"type": "string"
"type": "string",
"description": "`name` if non-empty, otherwise it falls back to a list of participants"
},
"hasCall": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the conversation has an active call"
},
"hasPassword": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the conversation has a password"
},
"id": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Numeric identifier of the conversation"
},
"isCustomAvatar": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the conversation has a custom avatar (only available with `avatar` capability)"
},
"isFavorite": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the conversation is favorited by the user"
},
"lastActivity": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Timestamp of the last activity in the conversation, in seconds and UTC time zone"
},
"lastCommonReadMessage": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "ID of the last message read by every user that has read privacy set to public in a room. When the user themself has it set to private the value is `0` (only available with `chat-read-status` capability)"
},
"lastMessage": {
"$ref": "#/components/schemas/RoomLastMessage"
"$ref": "#/components/schemas/RoomLastMessage",
"description": "Last message in a conversation if available, otherwise empty. **Note:** Even when given the message will not contain the `parent` or `reactionsSelf` attribute due to performance reasons"
},
"lastPing": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Timestamp of the user's session making the request"
},
"lastReadMessage": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "ID of the last read message in a room (only available with `chat-read-marker` capability)"
},
"listable": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Listable scope for the room (only available with `listable-rooms` capability)"
},
"lobbyState": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Webinar lobby restriction (0-1), if the participant is a moderator they can always join the conversation (only available with `webinary-lobby` capability) (See [Webinar lobby states](https://nextcloud-talk.readthedocs.io/en/latest/constants#webinar-lobby-states))"
},
"lobbyTimer": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Timestamp when the lobby will be automatically disabled (only available with `webinary-lobby` capability)"
},
"mentionPermissions": {
"type": "integer",
@ -1286,7 +1468,8 @@
"format": "int64"
},
"name": {
"type": "string"
"type": "string",
"description": "Name of the conversation (can also be empty)"
},
"notificationCalls": {
"type": "integer",
@ -1294,33 +1477,41 @@
},
"notificationLevel": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "The notification level for the user (See [Participant notification levels](https://nextcloud-talk.readthedocs.io/en/latest/constants#participant-notification-levels))"
},
"objectId": {
"type": "string"
"type": "string",
"description": "See [Object types](https://nextcloud-talk.readthedocs.io/en/latest/constants#object-types) documentation for explanation"
},
"objectType": {
"type": "string"
"type": "string",
"description": "The type of object that the conversation is associated with (See [Object types](https://nextcloud-talk.readthedocs.io/en/latest/constants#object-types))"
},
"participantFlags": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "\"In call\" flags of the user's session making the request (only available with `in-call-flags` capability)"
},
"participantType": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Permissions level of the current user"
},
"permissions": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Combined final permissions for the current participant, permissions are picked in order of attendee then call then default and the first which is `Custom` will apply (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))"
},
"readOnly": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Read-only state for the current user (only available with `read-only-rooms` capability)"
},
"recordingConsent": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Whether recording consent is required before joining a call (Only 0 and 1 will be returned, see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#recording-consent-required)) (only available with `recording-consent` capability)"
},
"remoteServer": {
"type": "string"
@ -1329,47 +1520,59 @@
"type": "string"
},
"sessionId": {
"type": "string"
"type": "string",
"description": "`'0'` if not connected, otherwise an up to 512 character long string that is the identifier of the user's session making the request. Should only be used to pre-check if the user joined already with this session, but this might be outdated by the time of usage, so better check via [Get list of participants in a conversation](https://nextcloud-talk.readthedocs.io/en/latest/participant/#get-list-of-participants-in-a-conversation)"
},
"sipEnabled": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "SIP enable status (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#sip-states))"
},
"status": {
"type": "string"
"type": "string",
"description": "Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status"
},
"statusClearAt": {
"type": "integer",
"format": "int64",
"nullable": true
"nullable": true,
"description": "Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status"
},
"statusIcon": {
"type": "string",
"nullable": true
"nullable": true,
"description": "Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status"
},
"statusMessage": {
"type": "string",
"nullable": true
"nullable": true,
"description": "Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status"
},
"token": {
"type": "string"
"type": "string",
"description": "Token identifier of the conversation which is used for further interaction"
},
"type": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "See list of conversation types in the [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants/#conversation-types)"
},
"unreadMention": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the user was mentioned since their last visit"
},
"unreadMentionDirect": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the user was mentioned directly (ignoring `@all` mentions) since their last visit (only available with `direct-mention-flag` capability)"
},
"unreadMessages": {
"type": "integer",
"format": "int64"
"format": "int64",
"description": "Number of unread chat messages in the conversation (only available with `chat-v2` capability)"
},
"isArchived": {
"type": "boolean"
"type": "boolean",
"description": "Flag if the conversation is archived by the user (only available with `archived-conversations-v2` capability)"
},
"isImportant": {
"type": "boolean",
@ -8402,6 +8605,83 @@
}
}
},
"/ocs/v2.php/apps/spreed/api/{apiVersion}/dashboard/events": {
"get": {
"operationId": "dashboard-get-event-rooms",
"summary": "Get up to 10 rooms that have events in the next 7 days sorted by their start timestamp ascending",
"description": "Required capability: `dashboard-event-rooms`",
"tags": [
"dashboard"
],
"security": [
{
"bearer_auth": []
},
{
"basic_auth": []
}
],
"parameters": [
{
"name": "apiVersion",
"in": "path",
"required": true,
"schema": {
"type": "string",
"enum": [
"v4"
],
"default": "v4"
}
},
{
"name": "OCS-APIRequest",
"in": "header",
"description": "Required to be true for the API request to pass",
"required": true,
"schema": {
"type": "boolean",
"default": true
}
}
],
"responses": {
"200": {
"description": "A list of dashboard entries or an empty array",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ocs"
],
"properties": {
"ocs": {
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"$ref": "#/components/schemas/OCSMeta"
},
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/DashboardEvent"
}
}
}
}
}
}
}
}
}
}
}
},
"/ocs/v2.php/apps/spreed/api/{apiVersion}/file/{fileId}": {
"get": {
"operationId": "files_integration-get-room-by-file-id",

152
src/types/openapi/openapi-backend-sipbridge.ts

@ -281,94 +281,196 @@ export type components = {
blurhash?: string;
};
Room: {
/** @description The unique identifier for the given actor type */
actorId: string;
/** @description The cloud id of the invited user */
invitedActorId?: string;
/** @description Actor type of the current user (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-types)) */
actorType: string;
/** Format: int64 */
/**
* Format: int64
* @description Unique attendee id
*/
attendeeId: number;
/** Format: int64 */
/**
* Format: int64
* @description Dedicated permissions for the current participant, if not `Custom` this are not the resulting permissions (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))
*/
attendeePermissions: number;
/** @description Unique dial-in authentication code for this user, when the conversation has SIP enabled (see `sipEnabled` attribute) */
attendeePin: string | null;
/** @description Version of conversation avatar used to easier expiration of the avatar in case a moderator updates it, since the avatar endpoint should be cached for 24 hours. (only available with `avatar` capability) */
avatarVersion: string;
/** Format: int64 */
/**
* Format: int64
* @description Breakout room configuration mode (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#breakout-room-modes)) (only available with `breakout-rooms-v1` capability)
*/
breakoutRoomMode: number;
/** Format: int64 */
/**
* Format: int64
* @description Breakout room status (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#breakout-room-status)) (only available with `breakout-rooms-v1` capability)
*/
breakoutRoomStatus: number;
/** Format: int64 */
/**
* Format: int64
* @description Combined flag of all participants in the current call (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#participant-in-call-flag), only available with `conversation-call-flags` capability)
*/
callFlag: number;
/** Format: int64 */
/**
* Format: int64
* @description Call permissions, if not `Custom` this are not the resulting permissions, if set they will reset after the end of the call (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))
*/
callPermissions: number;
/** Format: int64 */
callRecording: number;
/** Format: int64 */
callStartTime: number;
/** @description Flag if the user can delete the conversation for everyone (not possible without moderator permissions or in one-to-one conversations) */
canDeleteConversation: boolean;
/** @description Whether the given user can enable SIP for this conversation. Note that when the token is not-numeric only, SIP can not be enabled even if the user is permitted and a moderator of the conversation */
canEnableSIP: boolean;
/** @description Flag if the user can leave the conversation (not possible for the last user with moderator permissions) */
canLeaveConversation: boolean;
/** @description Flag if the user can start a new call in this conversation (joining is always possible) (only available with `start-call-flag` capability) */
canStartCall: boolean;
/** Format: int64 */
/**
* Format: int64
* @description Default permissions for new participants (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))
*/
defaultPermissions: number;
/** @description Description of the conversation (can also be empty) (only available with `room-description` capability) */
description: string;
/** @description `name` if non-empty, otherwise it falls back to a list of participants */
displayName: string;
/** @description Flag if the conversation has an active call */
hasCall: boolean;
/** @description Flag if the conversation has a password */
hasPassword: boolean;
/** Format: int64 */
/**
* Format: int64
* @description Numeric identifier of the conversation
*/
id: number;
/** @description Flag if the conversation has a custom avatar (only available with `avatar` capability) */
isCustomAvatar: boolean;
/** @description Flag if the conversation is favorited by the user */
isFavorite: boolean;
/** Format: int64 */
/**
* Format: int64
* @description Timestamp of the last activity in the conversation, in seconds and UTC time zone
*/
lastActivity: number;
/** Format: int64 */
/**
* Format: int64
* @description ID of the last message read by every user that has read privacy set to public in a room. When the user themself has it set to private the value is `0` (only available with `chat-read-status` capability)
*/
lastCommonReadMessage: number;
/** @description Last message in a conversation if available, otherwise empty. **Note:** Even when given the message will not contain the `parent` or `reactionsSelf` attribute due to performance reasons */
lastMessage?: components["schemas"]["RoomLastMessage"];
/** Format: int64 */
/**
* Format: int64
* @description Timestamp of the user's session making the request
*/
lastPing: number;
/** Format: int64 */
/**
* Format: int64
* @description ID of the last read message in a room (only available with `chat-read-marker` capability)
*/
lastReadMessage: number;
/** Format: int64 */
/**
* Format: int64
* @description Listable scope for the room (only available with `listable-rooms` capability)
*/
listable: number;
/** Format: int64 */
/**
* Format: int64
* @description Webinar lobby restriction (0-1), if the participant is a moderator they can always join the conversation (only available with `webinary-lobby` capability) (See [Webinar lobby states](https://nextcloud-talk.readthedocs.io/en/latest/constants#webinar-lobby-states))
*/
lobbyState: number;
/** Format: int64 */
/**
* Format: int64
* @description Timestamp when the lobby will be automatically disabled (only available with `webinary-lobby` capability)
*/
lobbyTimer: number;
/** Format: int64 */
mentionPermissions: number;
/** Format: int64 */
messageExpiration: number;
/** @description Name of the conversation (can also be empty) */
name: string;
/** Format: int64 */
notificationCalls: number;
/** Format: int64 */
/**
* Format: int64
* @description The notification level for the user (See [Participant notification levels](https://nextcloud-talk.readthedocs.io/en/latest/constants#participant-notification-levels))
*/
notificationLevel: number;
/** @description See [Object types](https://nextcloud-talk.readthedocs.io/en/latest/constants#object-types) documentation for explanation */
objectId: string;
/** @description The type of object that the conversation is associated with (See [Object types](https://nextcloud-talk.readthedocs.io/en/latest/constants#object-types)) */
objectType: string;
/** Format: int64 */
/**
* Format: int64
* @description "In call" flags of the user's session making the request (only available with `in-call-flags` capability)
*/
participantFlags: number;
/** Format: int64 */
/**
* Format: int64
* @description Permissions level of the current user
*/
participantType: number;
/** Format: int64 */
/**
* Format: int64
* @description Combined final permissions for the current participant, permissions are picked in order of attendee then call then default and the first which is `Custom` will apply (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))
*/
permissions: number;
/** Format: int64 */
/**
* Format: int64
* @description Read-only state for the current user (only available with `read-only-rooms` capability)
*/
readOnly: number;
/** Format: int64 */
/**
* Format: int64
* @description Whether recording consent is required before joining a call (Only 0 and 1 will be returned, see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#recording-consent-required)) (only available with `recording-consent` capability)
*/
recordingConsent: number;
remoteServer?: string;
remoteToken?: string;
/** @description `'0'` if not connected, otherwise an up to 512 character long string that is the identifier of the user's session making the request. Should only be used to pre-check if the user joined already with this session, but this might be outdated by the time of usage, so better check via [Get list of participants in a conversation](https://nextcloud-talk.readthedocs.io/en/latest/participant/#get-list-of-participants-in-a-conversation) */
sessionId: string;
/** Format: int64 */
/**
* Format: int64
* @description SIP enable status (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#sip-states))
*/
sipEnabled: number;
/** @description Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status */
status?: string;
/** Format: int64 */
/**
* Format: int64
* @description Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status
*/
statusClearAt?: number | null;
/** @description Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status */
statusIcon?: string | null;
/** @description Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status */
statusMessage?: string | null;
/** @description Token identifier of the conversation which is used for further interaction */
token: string;
/** Format: int64 */
/**
* Format: int64
* @description See list of conversation types in the [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants/#conversation-types)
*/
type: number;
/** @description Flag if the user was mentioned since their last visit */
unreadMention: boolean;
/** @description Flag if the user was mentioned directly (ignoring `@all` mentions) since their last visit (only available with `direct-mention-flag` capability) */
unreadMentionDirect: boolean;
/** Format: int64 */
/**
* Format: int64
* @description Number of unread chat messages in the conversation (only available with `chat-v2` capability)
*/
unreadMessages: number;
/** @description Flag if the conversation is archived by the user (only available with `archived-conversations-v2` capability) */
isArchived: boolean;
/** @description Required capability: `important-conversations` */
isImportant: boolean;

152
src/types/openapi/openapi-federation.ts

@ -308,94 +308,196 @@ export type components = {
blurhash?: string;
};
Room: {
/** @description The unique identifier for the given actor type */
actorId: string;
/** @description The cloud id of the invited user */
invitedActorId?: string;
/** @description Actor type of the current user (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-types)) */
actorType: string;
/** Format: int64 */
/**
* Format: int64
* @description Unique attendee id
*/
attendeeId: number;
/** Format: int64 */
/**
* Format: int64
* @description Dedicated permissions for the current participant, if not `Custom` this are not the resulting permissions (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))
*/
attendeePermissions: number;
/** @description Unique dial-in authentication code for this user, when the conversation has SIP enabled (see `sipEnabled` attribute) */
attendeePin: string | null;
/** @description Version of conversation avatar used to easier expiration of the avatar in case a moderator updates it, since the avatar endpoint should be cached for 24 hours. (only available with `avatar` capability) */
avatarVersion: string;
/** Format: int64 */
/**
* Format: int64
* @description Breakout room configuration mode (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#breakout-room-modes)) (only available with `breakout-rooms-v1` capability)
*/
breakoutRoomMode: number;
/** Format: int64 */
/**
* Format: int64
* @description Breakout room status (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#breakout-room-status)) (only available with `breakout-rooms-v1` capability)
*/
breakoutRoomStatus: number;
/** Format: int64 */
/**
* Format: int64
* @description Combined flag of all participants in the current call (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#participant-in-call-flag), only available with `conversation-call-flags` capability)
*/
callFlag: number;
/** Format: int64 */
/**
* Format: int64
* @description Call permissions, if not `Custom` this are not the resulting permissions, if set they will reset after the end of the call (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))
*/
callPermissions: number;
/** Format: int64 */
callRecording: number;
/** Format: int64 */
callStartTime: number;
/** @description Flag if the user can delete the conversation for everyone (not possible without moderator permissions or in one-to-one conversations) */
canDeleteConversation: boolean;
/** @description Whether the given user can enable SIP for this conversation. Note that when the token is not-numeric only, SIP can not be enabled even if the user is permitted and a moderator of the conversation */
canEnableSIP: boolean;
/** @description Flag if the user can leave the conversation (not possible for the last user with moderator permissions) */
canLeaveConversation: boolean;
/** @description Flag if the user can start a new call in this conversation (joining is always possible) (only available with `start-call-flag` capability) */
canStartCall: boolean;
/** Format: int64 */
/**
* Format: int64
* @description Default permissions for new participants (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))
*/
defaultPermissions: number;
/** @description Description of the conversation (can also be empty) (only available with `room-description` capability) */
description: string;
/** @description `name` if non-empty, otherwise it falls back to a list of participants */
displayName: string;
/** @description Flag if the conversation has an active call */
hasCall: boolean;
/** @description Flag if the conversation has a password */
hasPassword: boolean;
/** Format: int64 */
/**
* Format: int64
* @description Numeric identifier of the conversation
*/
id: number;
/** @description Flag if the conversation has a custom avatar (only available with `avatar` capability) */
isCustomAvatar: boolean;
/** @description Flag if the conversation is favorited by the user */
isFavorite: boolean;
/** Format: int64 */
/**
* Format: int64
* @description Timestamp of the last activity in the conversation, in seconds and UTC time zone
*/
lastActivity: number;
/** Format: int64 */
/**
* Format: int64
* @description ID of the last message read by every user that has read privacy set to public in a room. When the user themself has it set to private the value is `0` (only available with `chat-read-status` capability)
*/
lastCommonReadMessage: number;
/** @description Last message in a conversation if available, otherwise empty. **Note:** Even when given the message will not contain the `parent` or `reactionsSelf` attribute due to performance reasons */
lastMessage?: components["schemas"]["RoomLastMessage"];
/** Format: int64 */
/**
* Format: int64
* @description Timestamp of the user's session making the request
*/
lastPing: number;
/** Format: int64 */
/**
* Format: int64
* @description ID of the last read message in a room (only available with `chat-read-marker` capability)
*/
lastReadMessage: number;
/** Format: int64 */
/**
* Format: int64
* @description Listable scope for the room (only available with `listable-rooms` capability)
*/
listable: number;
/** Format: int64 */
/**
* Format: int64
* @description Webinar lobby restriction (0-1), if the participant is a moderator they can always join the conversation (only available with `webinary-lobby` capability) (See [Webinar lobby states](https://nextcloud-talk.readthedocs.io/en/latest/constants#webinar-lobby-states))
*/
lobbyState: number;
/** Format: int64 */
/**
* Format: int64
* @description Timestamp when the lobby will be automatically disabled (only available with `webinary-lobby` capability)
*/
lobbyTimer: number;
/** Format: int64 */
mentionPermissions: number;
/** Format: int64 */
messageExpiration: number;
/** @description Name of the conversation (can also be empty) */
name: string;
/** Format: int64 */
notificationCalls: number;
/** Format: int64 */
/**
* Format: int64
* @description The notification level for the user (See [Participant notification levels](https://nextcloud-talk.readthedocs.io/en/latest/constants#participant-notification-levels))
*/
notificationLevel: number;
/** @description See [Object types](https://nextcloud-talk.readthedocs.io/en/latest/constants#object-types) documentation for explanation */
objectId: string;
/** @description The type of object that the conversation is associated with (See [Object types](https://nextcloud-talk.readthedocs.io/en/latest/constants#object-types)) */
objectType: string;
/** Format: int64 */
/**
* Format: int64
* @description "In call" flags of the user's session making the request (only available with `in-call-flags` capability)
*/
participantFlags: number;
/** Format: int64 */
/**
* Format: int64
* @description Permissions level of the current user
*/
participantType: number;
/** Format: int64 */
/**
* Format: int64
* @description Combined final permissions for the current participant, permissions are picked in order of attendee then call then default and the first which is `Custom` will apply (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))
*/
permissions: number;
/** Format: int64 */
/**
* Format: int64
* @description Read-only state for the current user (only available with `read-only-rooms` capability)
*/
readOnly: number;
/** Format: int64 */
/**
* Format: int64
* @description Whether recording consent is required before joining a call (Only 0 and 1 will be returned, see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#recording-consent-required)) (only available with `recording-consent` capability)
*/
recordingConsent: number;
remoteServer?: string;
remoteToken?: string;
/** @description `'0'` if not connected, otherwise an up to 512 character long string that is the identifier of the user's session making the request. Should only be used to pre-check if the user joined already with this session, but this might be outdated by the time of usage, so better check via [Get list of participants in a conversation](https://nextcloud-talk.readthedocs.io/en/latest/participant/#get-list-of-participants-in-a-conversation) */
sessionId: string;
/** Format: int64 */
/**
* Format: int64
* @description SIP enable status (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#sip-states))
*/
sipEnabled: number;
/** @description Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status */
status?: string;
/** Format: int64 */
/**
* Format: int64
* @description Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status
*/
statusClearAt?: number | null;
/** @description Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status */
statusIcon?: string | null;
/** @description Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status */
statusMessage?: string | null;
/** @description Token identifier of the conversation which is used for further interaction */
token: string;
/** Format: int64 */
/**
* Format: int64
* @description See list of conversation types in the [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants/#conversation-types)
*/
type: number;
/** @description Flag if the user was mentioned since their last visit */
unreadMention: boolean;
/** @description Flag if the user was mentioned directly (ignoring `@all` mentions) since their last visit (only available with `direct-mention-flag` capability) */
unreadMentionDirect: boolean;
/** Format: int64 */
/**
* Format: int64
* @description Number of unread chat messages in the conversation (only available with `chat-v2` capability)
*/
unreadMessages: number;
/** @description Flag if the conversation is archived by the user (only available with `archived-conversations-v2` capability) */
isArchived: boolean;
/** @description Required capability: `important-conversations` */
isImportant: boolean;

243
src/types/openapi/openapi-full.ts

@ -528,6 +528,26 @@ export type paths = {
patch?: never;
trace?: never;
};
"/ocs/v2.php/apps/spreed/api/{apiVersion}/dashboard/events": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* Get up to 10 rooms that have events in the next 7 days sorted by their start timestamp ascending
* @description Required capability: `dashboard-event-rooms`
*/
get: operations["dashboard-get-event-rooms"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/ocs/v2.php/apps/spreed/api/{apiVersion}/file/{fileId}": {
parameters: {
query?: never;
@ -2176,6 +2196,47 @@ export type components = {
token: string;
userId: string;
};
DashboardEvent: {
calendars: components["schemas"]["DashboardEventCalendar"][];
eventName: string;
eventDescription: string | null;
eventAttachments: components["schemas"]["DashboardEventAttachment"][];
eventLink: string;
/** Format: int64 */
start: number;
/** Format: int64 */
end: number;
roomToken: string;
roomAvatarVersion: string;
roomName: string;
roomDisplayName: string;
/** Format: int64 */
roomType: number;
/** Format: int64 */
roomActiveSince: number | null;
/** Format: int64 */
invited: number | null;
/** Format: int64 */
accepted: number | null;
/** Format: int64 */
tentative: number | null;
/** Format: int64 */
declined: number | null;
};
DashboardEventAttachment: {
calendars: string[];
fmttype: string;
filename: string;
/** Format: int64 */
fileId: number;
hasPreview: boolean;
previewUrl: string | null;
};
DashboardEventCalendar: {
principalUri: string;
calendarName: string;
calendarColor: string | null;
};
DeletedChatMessage: {
/** Format: int64 */
id: number;
@ -2337,94 +2398,196 @@ export type components = {
blurhash?: string;
};
Room: {
/** @description The unique identifier for the given actor type */
actorId: string;
/** @description The cloud id of the invited user */
invitedActorId?: string;
/** @description Actor type of the current user (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-types)) */
actorType: string;
/** Format: int64 */
/**
* Format: int64
* @description Unique attendee id
*/
attendeeId: number;
/** Format: int64 */
/**
* Format: int64
* @description Dedicated permissions for the current participant, if not `Custom` this are not the resulting permissions (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))
*/
attendeePermissions: number;
/** @description Unique dial-in authentication code for this user, when the conversation has SIP enabled (see `sipEnabled` attribute) */
attendeePin: string | null;
/** @description Version of conversation avatar used to easier expiration of the avatar in case a moderator updates it, since the avatar endpoint should be cached for 24 hours. (only available with `avatar` capability) */
avatarVersion: string;
/** Format: int64 */
/**
* Format: int64
* @description Breakout room configuration mode (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#breakout-room-modes)) (only available with `breakout-rooms-v1` capability)
*/
breakoutRoomMode: number;
/** Format: int64 */
/**
* Format: int64
* @description Breakout room status (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#breakout-room-status)) (only available with `breakout-rooms-v1` capability)
*/
breakoutRoomStatus: number;
/** Format: int64 */
/**
* Format: int64
* @description Combined flag of all participants in the current call (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#participant-in-call-flag), only available with `conversation-call-flags` capability)
*/
callFlag: number;
/** Format: int64 */
/**
* Format: int64
* @description Call permissions, if not `Custom` this are not the resulting permissions, if set they will reset after the end of the call (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))
*/
callPermissions: number;
/** Format: int64 */
callRecording: number;
/** Format: int64 */
callStartTime: number;
/** @description Flag if the user can delete the conversation for everyone (not possible without moderator permissions or in one-to-one conversations) */
canDeleteConversation: boolean;
/** @description Whether the given user can enable SIP for this conversation. Note that when the token is not-numeric only, SIP can not be enabled even if the user is permitted and a moderator of the conversation */
canEnableSIP: boolean;
/** @description Flag if the user can leave the conversation (not possible for the last user with moderator permissions) */
canLeaveConversation: boolean;
/** @description Flag if the user can start a new call in this conversation (joining is always possible) (only available with `start-call-flag` capability) */
canStartCall: boolean;
/** Format: int64 */
/**
* Format: int64
* @description Default permissions for new participants (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))
*/
defaultPermissions: number;
/** @description Description of the conversation (can also be empty) (only available with `room-description` capability) */
description: string;
/** @description `name` if non-empty, otherwise it falls back to a list of participants */
displayName: string;
/** @description Flag if the conversation has an active call */
hasCall: boolean;
/** @description Flag if the conversation has a password */
hasPassword: boolean;
/** Format: int64 */
/**
* Format: int64
* @description Numeric identifier of the conversation
*/
id: number;
/** @description Flag if the conversation has a custom avatar (only available with `avatar` capability) */
isCustomAvatar: boolean;
/** @description Flag if the conversation is favorited by the user */
isFavorite: boolean;
/** Format: int64 */
/**
* Format: int64
* @description Timestamp of the last activity in the conversation, in seconds and UTC time zone
*/
lastActivity: number;
/** Format: int64 */
/**
* Format: int64
* @description ID of the last message read by every user that has read privacy set to public in a room. When the user themself has it set to private the value is `0` (only available with `chat-read-status` capability)
*/
lastCommonReadMessage: number;
/** @description Last message in a conversation if available, otherwise empty. **Note:** Even when given the message will not contain the `parent` or `reactionsSelf` attribute due to performance reasons */
lastMessage?: components["schemas"]["RoomLastMessage"];
/** Format: int64 */
/**
* Format: int64
* @description Timestamp of the user's session making the request
*/
lastPing: number;
/** Format: int64 */
/**
* Format: int64
* @description ID of the last read message in a room (only available with `chat-read-marker` capability)
*/
lastReadMessage: number;
/** Format: int64 */
/**
* Format: int64
* @description Listable scope for the room (only available with `listable-rooms` capability)
*/
listable: number;
/** Format: int64 */
/**
* Format: int64
* @description Webinar lobby restriction (0-1), if the participant is a moderator they can always join the conversation (only available with `webinary-lobby` capability) (See [Webinar lobby states](https://nextcloud-talk.readthedocs.io/en/latest/constants#webinar-lobby-states))
*/
lobbyState: number;
/** Format: int64 */
/**
* Format: int64
* @description Timestamp when the lobby will be automatically disabled (only available with `webinary-lobby` capability)
*/
lobbyTimer: number;
/** Format: int64 */
mentionPermissions: number;
/** Format: int64 */
messageExpiration: number;
/** @description Name of the conversation (can also be empty) */
name: string;
/** Format: int64 */
notificationCalls: number;
/** Format: int64 */
/**
* Format: int64
* @description The notification level for the user (See [Participant notification levels](https://nextcloud-talk.readthedocs.io/en/latest/constants#participant-notification-levels))
*/
notificationLevel: number;
/** @description See [Object types](https://nextcloud-talk.readthedocs.io/en/latest/constants#object-types) documentation for explanation */
objectId: string;
/** @description The type of object that the conversation is associated with (See [Object types](https://nextcloud-talk.readthedocs.io/en/latest/constants#object-types)) */
objectType: string;
/** Format: int64 */
/**
* Format: int64
* @description "In call" flags of the user's session making the request (only available with `in-call-flags` capability)
*/
participantFlags: number;
/** Format: int64 */
/**
* Format: int64
* @description Permissions level of the current user
*/
participantType: number;
/** Format: int64 */
/**
* Format: int64
* @description Combined final permissions for the current participant, permissions are picked in order of attendee then call then default and the first which is `Custom` will apply (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))
*/
permissions: number;
/** Format: int64 */
/**
* Format: int64
* @description Read-only state for the current user (only available with `read-only-rooms` capability)
*/
readOnly: number;
/** Format: int64 */
/**
* Format: int64
* @description Whether recording consent is required before joining a call (Only 0 and 1 will be returned, see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#recording-consent-required)) (only available with `recording-consent` capability)
*/
recordingConsent: number;
remoteServer?: string;
remoteToken?: string;
/** @description `'0'` if not connected, otherwise an up to 512 character long string that is the identifier of the user's session making the request. Should only be used to pre-check if the user joined already with this session, but this might be outdated by the time of usage, so better check via [Get list of participants in a conversation](https://nextcloud-talk.readthedocs.io/en/latest/participant/#get-list-of-participants-in-a-conversation) */
sessionId: string;
/** Format: int64 */
/**
* Format: int64
* @description SIP enable status (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#sip-states))
*/
sipEnabled: number;
/** @description Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status */
status?: string;
/** Format: int64 */
/**
* Format: int64
* @description Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status
*/
statusClearAt?: number | null;
/** @description Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status */
statusIcon?: string | null;
/** @description Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status */
statusMessage?: string | null;
/** @description Token identifier of the conversation which is used for further interaction */
token: string;
/** Format: int64 */
/**
* Format: int64
* @description See list of conversation types in the [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants/#conversation-types)
*/
type: number;
/** @description Flag if the user was mentioned since their last visit */
unreadMention: boolean;
/** @description Flag if the user was mentioned directly (ignoring `@all` mentions) since their last visit (only available with `direct-mention-flag` capability) */
unreadMentionDirect: boolean;
/** Format: int64 */
/**
* Format: int64
* @description Number of unread chat messages in the conversation (only available with `chat-v2` capability)
*/
unreadMessages: number;
/** @description Flag if the conversation is archived by the user (only available with `archived-conversations-v2` capability) */
isArchived: boolean;
/** @description Required capability: `important-conversations` */
isImportant: boolean;
@ -5166,6 +5329,36 @@ export interface operations {
};
};
};
"dashboard-get-event-rooms": {
parameters: {
query?: never;
header: {
/** @description Required to be true for the API request to pass */
"OCS-APIRequest": boolean;
};
path: {
apiVersion: "v4";
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description A list of dashboard entries or an empty array */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": {
ocs: {
meta: components["schemas"]["OCSMeta"];
data: components["schemas"]["DashboardEvent"][];
};
};
};
};
};
};
"files_integration-get-room-by-file-id": {
parameters: {
query?: never;

243
src/types/openapi/openapi.ts

@ -528,6 +528,26 @@ export type paths = {
patch?: never;
trace?: never;
};
"/ocs/v2.php/apps/spreed/api/{apiVersion}/dashboard/events": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* Get up to 10 rooms that have events in the next 7 days sorted by their start timestamp ascending
* @description Required capability: `dashboard-event-rooms`
*/
get: operations["dashboard-get-event-rooms"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/ocs/v2.php/apps/spreed/api/{apiVersion}/file/{fileId}": {
parameters: {
query?: never;
@ -1654,6 +1674,47 @@ export type components = {
token: string;
userId: string;
};
DashboardEvent: {
calendars: components["schemas"]["DashboardEventCalendar"][];
eventName: string;
eventDescription: string | null;
eventAttachments: components["schemas"]["DashboardEventAttachment"][];
eventLink: string;
/** Format: int64 */
start: number;
/** Format: int64 */
end: number;
roomToken: string;
roomAvatarVersion: string;
roomName: string;
roomDisplayName: string;
/** Format: int64 */
roomType: number;
/** Format: int64 */
roomActiveSince: number | null;
/** Format: int64 */
invited: number | null;
/** Format: int64 */
accepted: number | null;
/** Format: int64 */
tentative: number | null;
/** Format: int64 */
declined: number | null;
};
DashboardEventAttachment: {
calendars: string[];
fmttype: string;
filename: string;
/** Format: int64 */
fileId: number;
hasPreview: boolean;
previewUrl: string | null;
};
DashboardEventCalendar: {
principalUri: string;
calendarName: string;
calendarColor: string | null;
};
DeletedChatMessage: {
/** Format: int64 */
id: number;
@ -1799,94 +1860,196 @@ export type components = {
blurhash?: string;
};
Room: {
/** @description The unique identifier for the given actor type */
actorId: string;
/** @description The cloud id of the invited user */
invitedActorId?: string;
/** @description Actor type of the current user (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-types)) */
actorType: string;
/** Format: int64 */
/**
* Format: int64
* @description Unique attendee id
*/
attendeeId: number;
/** Format: int64 */
/**
* Format: int64
* @description Dedicated permissions for the current participant, if not `Custom` this are not the resulting permissions (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))
*/
attendeePermissions: number;
/** @description Unique dial-in authentication code for this user, when the conversation has SIP enabled (see `sipEnabled` attribute) */
attendeePin: string | null;
/** @description Version of conversation avatar used to easier expiration of the avatar in case a moderator updates it, since the avatar endpoint should be cached for 24 hours. (only available with `avatar` capability) */
avatarVersion: string;
/** Format: int64 */
/**
* Format: int64
* @description Breakout room configuration mode (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#breakout-room-modes)) (only available with `breakout-rooms-v1` capability)
*/
breakoutRoomMode: number;
/** Format: int64 */
/**
* Format: int64
* @description Breakout room status (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#breakout-room-status)) (only available with `breakout-rooms-v1` capability)
*/
breakoutRoomStatus: number;
/** Format: int64 */
/**
* Format: int64
* @description Combined flag of all participants in the current call (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#participant-in-call-flag), only available with `conversation-call-flags` capability)
*/
callFlag: number;
/** Format: int64 */
/**
* Format: int64
* @description Call permissions, if not `Custom` this are not the resulting permissions, if set they will reset after the end of the call (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))
*/
callPermissions: number;
/** Format: int64 */
callRecording: number;
/** Format: int64 */
callStartTime: number;
/** @description Flag if the user can delete the conversation for everyone (not possible without moderator permissions or in one-to-one conversations) */
canDeleteConversation: boolean;
/** @description Whether the given user can enable SIP for this conversation. Note that when the token is not-numeric only, SIP can not be enabled even if the user is permitted and a moderator of the conversation */
canEnableSIP: boolean;
/** @description Flag if the user can leave the conversation (not possible for the last user with moderator permissions) */
canLeaveConversation: boolean;
/** @description Flag if the user can start a new call in this conversation (joining is always possible) (only available with `start-call-flag` capability) */
canStartCall: boolean;
/** Format: int64 */
/**
* Format: int64
* @description Default permissions for new participants (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))
*/
defaultPermissions: number;
/** @description Description of the conversation (can also be empty) (only available with `room-description` capability) */
description: string;
/** @description `name` if non-empty, otherwise it falls back to a list of participants */
displayName: string;
/** @description Flag if the conversation has an active call */
hasCall: boolean;
/** @description Flag if the conversation has a password */
hasPassword: boolean;
/** Format: int64 */
/**
* Format: int64
* @description Numeric identifier of the conversation
*/
id: number;
/** @description Flag if the conversation has a custom avatar (only available with `avatar` capability) */
isCustomAvatar: boolean;
/** @description Flag if the conversation is favorited by the user */
isFavorite: boolean;
/** Format: int64 */
/**
* Format: int64
* @description Timestamp of the last activity in the conversation, in seconds and UTC time zone
*/
lastActivity: number;
/** Format: int64 */
/**
* Format: int64
* @description ID of the last message read by every user that has read privacy set to public in a room. When the user themself has it set to private the value is `0` (only available with `chat-read-status` capability)
*/
lastCommonReadMessage: number;
/** @description Last message in a conversation if available, otherwise empty. **Note:** Even when given the message will not contain the `parent` or `reactionsSelf` attribute due to performance reasons */
lastMessage?: components["schemas"]["RoomLastMessage"];
/** Format: int64 */
/**
* Format: int64
* @description Timestamp of the user's session making the request
*/
lastPing: number;
/** Format: int64 */
/**
* Format: int64
* @description ID of the last read message in a room (only available with `chat-read-marker` capability)
*/
lastReadMessage: number;
/** Format: int64 */
/**
* Format: int64
* @description Listable scope for the room (only available with `listable-rooms` capability)
*/
listable: number;
/** Format: int64 */
/**
* Format: int64
* @description Webinar lobby restriction (0-1), if the participant is a moderator they can always join the conversation (only available with `webinary-lobby` capability) (See [Webinar lobby states](https://nextcloud-talk.readthedocs.io/en/latest/constants#webinar-lobby-states))
*/
lobbyState: number;
/** Format: int64 */
/**
* Format: int64
* @description Timestamp when the lobby will be automatically disabled (only available with `webinary-lobby` capability)
*/
lobbyTimer: number;
/** Format: int64 */
mentionPermissions: number;
/** Format: int64 */
messageExpiration: number;
/** @description Name of the conversation (can also be empty) */
name: string;
/** Format: int64 */
notificationCalls: number;
/** Format: int64 */
/**
* Format: int64
* @description The notification level for the user (See [Participant notification levels](https://nextcloud-talk.readthedocs.io/en/latest/constants#participant-notification-levels))
*/
notificationLevel: number;
/** @description See [Object types](https://nextcloud-talk.readthedocs.io/en/latest/constants#object-types) documentation for explanation */
objectId: string;
/** @description The type of object that the conversation is associated with (See [Object types](https://nextcloud-talk.readthedocs.io/en/latest/constants#object-types)) */
objectType: string;
/** Format: int64 */
/**
* Format: int64
* @description "In call" flags of the user's session making the request (only available with `in-call-flags` capability)
*/
participantFlags: number;
/** Format: int64 */
/**
* Format: int64
* @description Permissions level of the current user
*/
participantType: number;
/** Format: int64 */
/**
* Format: int64
* @description Combined final permissions for the current participant, permissions are picked in order of attendee then call then default and the first which is `Custom` will apply (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#attendee-permissions))
*/
permissions: number;
/** Format: int64 */
/**
* Format: int64
* @description Read-only state for the current user (only available with `read-only-rooms` capability)
*/
readOnly: number;
/** Format: int64 */
/**
* Format: int64
* @description Whether recording consent is required before joining a call (Only 0 and 1 will be returned, see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#recording-consent-required)) (only available with `recording-consent` capability)
*/
recordingConsent: number;
remoteServer?: string;
remoteToken?: string;
/** @description `'0'` if not connected, otherwise an up to 512 character long string that is the identifier of the user's session making the request. Should only be used to pre-check if the user joined already with this session, but this might be outdated by the time of usage, so better check via [Get list of participants in a conversation](https://nextcloud-talk.readthedocs.io/en/latest/participant/#get-list-of-participants-in-a-conversation) */
sessionId: string;
/** Format: int64 */
/**
* Format: int64
* @description SIP enable status (see [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants#sip-states))
*/
sipEnabled: number;
/** @description Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status */
status?: string;
/** Format: int64 */
/**
* Format: int64
* @description Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status
*/
statusClearAt?: number | null;
/** @description Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status */
statusIcon?: string | null;
/** @description Optional: Only available for one-to-one conversations, when `includeStatus=true` is set and the user has a status, can still be null even with a status */
statusMessage?: string | null;
/** @description Token identifier of the conversation which is used for further interaction */
token: string;
/** Format: int64 */
/**
* Format: int64
* @description See list of conversation types in the [constants list](https://nextcloud-talk.readthedocs.io/en/latest/constants/#conversation-types)
*/
type: number;
/** @description Flag if the user was mentioned since their last visit */
unreadMention: boolean;
/** @description Flag if the user was mentioned directly (ignoring `@all` mentions) since their last visit (only available with `direct-mention-flag` capability) */
unreadMentionDirect: boolean;
/** Format: int64 */
/**
* Format: int64
* @description Number of unread chat messages in the conversation (only available with `chat-v2` capability)
*/
unreadMessages: number;
/** @description Flag if the conversation is archived by the user (only available with `archived-conversations-v2` capability) */
isArchived: boolean;
/** @description Required capability: `important-conversations` */
isImportant: boolean;
@ -4628,6 +4791,36 @@ export interface operations {
};
};
};
"dashboard-get-event-rooms": {
parameters: {
query?: never;
header: {
/** @description Required to be true for the API request to pass */
"OCS-APIRequest": boolean;
};
path: {
apiVersion: "v4";
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description A list of dashboard entries or an empty array */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": {
ocs: {
meta: components["schemas"]["OCSMeta"];
data: components["schemas"]["DashboardEvent"][];
};
};
};
};
};
};
"files_integration-get-room-by-file-id": {
parameters: {
query?: never;

413
tests/integration/features/bootstrap/FeatureContext.php

@ -77,6 +77,8 @@ class FeatureContext implements Context, SnippetAcceptingContext {
/** @var array<string, int> */
protected static array $userToBanId;
/** @var array<string, string> */
protected static array $identifierToObjectId = [];
protected static array $permissionsMap = [
'D' => 0, // PERMISSIONS_DEFAULT
@ -135,6 +137,10 @@ class FeatureContext implements Context, SnippetAcceptingContext {
return self::$createdTeams[$server][$label] ?? self::$renamedTeams[$server][$label] ?? throw new \RuntimeException('Unknown team: ' . $label);
}
public static function getRoomLocationForToken(string $identifier): string {
return getenv('TEST_SERVER_URL') . '/call/' . self::$identifierToToken[$identifier] ?? throw new \RuntimeException('Unknown token: ' . $identifier);
}
public static function getMessageIdForText(string $text): int {
return self::$textToMessageId[$text];
}
@ -382,6 +388,13 @@ class FeatureContext implements Context, SnippetAcceptingContext {
Assert::assertCount(count($formData->getHash()), $rooms, 'Room count does not match');
$expected = $formData->getHash();
$count = count($expected);
for ($i = 0; $i < $count; $i++) {
if (isset($expected[$i]['objectId']) && preg_match('/OBJECT_ID\(([^)]+)\)/', $expected[$i]['objectId'], $matches)) {
$expected[$i]['objectId'] = self::$identifierToObjectId[$matches[1]];
}
}
if ($shouldOrder) {
$sorter = static function (array $roomA, array $roomB): int {
if (str_starts_with($roomA['name'], '/')) {
@ -425,141 +438,147 @@ class FeatureContext implements Context, SnippetAcceptingContext {
usort($expected, $sorter);
}
Assert::assertEquals($expected, array_map(function (array $room, array $expectedRoom): array {
if (!isset(self::$identifierToToken[$room['name']])) {
self::$identifierToToken[$room['name']] = $room['token'];
}
if (!isset(self::$tokenToIdentifier[$room['token']])) {
self::$tokenToIdentifier[$room['token']] = $room['name'];
}
Assert::assertEquals($expected,
array_map(function (array $room, array $expectedRoom): array {
if (!isset(self::$identifierToToken[$room['name']])) {
self::$identifierToToken[$room['name']] = $room['token'];
}
if (!isset(self::$tokenToIdentifier[$room['token']])) {
self::$tokenToIdentifier[$room['token']] = $room['name'];
}
$data = [];
if (isset($expectedRoom['id'])) {
$data['id'] = self::$tokenToIdentifier[$room['token']];
}
if (isset($expectedRoom['name'])) {
$data['name'] = $room['name'];
$data = [];
if (isset($expectedRoom['id'])) {
$data['id'] = self::$tokenToIdentifier[$room['token']];
}
if (isset($expectedRoom['name'])) {
$data['name'] = $room['name'];
// Breakout room regex
if (str_starts_with($expectedRoom['name'], '/') && preg_match($expectedRoom['name'], $room['name'])) {
$data['name'] = $expectedRoom['name'];
// Breakout room regex
if (str_starts_with($expectedRoom['name'], '/') && preg_match($expectedRoom['name'], $room['name'])) {
$data['name'] = $expectedRoom['name'];
}
}
}
if (isset($expectedRoom['description'])) {
$data['description'] = $room['description'];
}
if (isset($expectedRoom['type'])) {
$data['type'] = (string)$room['type'];
}
if (isset($expectedRoom['remoteServer'])) {
$data['remoteServer'] = isset($room['remoteServer']) ? self::translateRemoteServer($room['remoteServer']) : '';
}
if (isset($expectedRoom['remoteToken'])) {
if (isset($room['remoteToken'])) {
$data['remoteToken'] = self::$tokenToIdentifier[$room['remoteToken']] ?? 'unknown-token';
} else {
$data['remoteToken'] = '';
if (isset($expectedRoom['description'])) {
$data['description'] = $room['description'];
}
}
if (isset($expectedRoom['hasPassword'])) {
$data['hasPassword'] = (string)$room['hasPassword'];
}
if (isset($expectedRoom['readOnly'])) {
$data['readOnly'] = (string)$room['readOnly'];
}
if (isset($expectedRoom['listable'])) {
$data['listable'] = (string)$room['listable'];
}
if (isset($expectedRoom['isArchived'])) {
$data['isArchived'] = (int)$room['isArchived'];
}
if (isset($expectedRoom['participantType'])) {
$data['participantType'] = (string)$room['participantType'];
}
if (isset($expectedRoom['sipEnabled'])) {
$data['sipEnabled'] = (string)$room['sipEnabled'];
}
if (isset($expectedRoom['callFlag'])) {
$data['callFlag'] = (int)$room['callFlag'];
}
if (isset($expectedRoom['lobbyState'])) {
$data['lobbyState'] = (int)$room['lobbyState'];
}
if (!empty($expectedRoom['lobbyTimer'])) {
$data['lobbyTimer'] = (int)$room['lobbyTimer'];
}
if (isset($expectedRoom['lobbyTimer'])) {
$data['lobbyTimer'] = (int)$room['lobbyTimer'];
if ($expectedRoom['lobbyTimer'] === 'GREATER_THAN_ZERO' && $room['lobbyTimer'] > 0) {
$data['lobbyTimer'] = 'GREATER_THAN_ZERO';
if (isset($expectedRoom['type'])) {
$data['type'] = (string)$room['type'];
}
}
if (isset($expectedRoom['breakoutRoomMode'])) {
$data['breakoutRoomMode'] = (int)$room['breakoutRoomMode'];
}
if (isset($expectedRoom['breakoutRoomStatus'])) {
$data['breakoutRoomStatus'] = (int)$room['breakoutRoomStatus'];
}
if (isset($expectedRoom['attendeePin'])) {
$data['attendeePin'] = $room['attendeePin'] ? '**PIN**' : '';
}
if (isset($expectedRoom['lastMessage'])) {
if (isset($room['lastMessage'])) {
$data['lastMessage'] = $room['lastMessage'] ? $room['lastMessage']['message'] : '';
} else {
$data['lastMessage'] = 'UNSET';
if (isset($expectedRoom['remoteServer'])) {
$data['remoteServer'] = isset($room['remoteServer']) ? self::translateRemoteServer($room['remoteServer']) : '';
}
}
if (isset($expectedRoom['lastMessageActorType'])) {
$data['lastMessageActorType'] = $room['lastMessage'] ? $room['lastMessage']['actorType'] : '';
}
if (isset($expectedRoom['lastMessageActorId'])) {
$data['lastMessageActorId'] = $room['lastMessage'] ? $room['lastMessage']['actorId'] : '';
$data['lastMessageActorId'] = str_replace(rtrim($this->localServerUrl, '/'), '{$LOCAL_URL}', $data['lastMessageActorId']);
$data['lastMessageActorId'] = str_replace(rtrim($this->remoteServerUrl, '/'), '{$REMOTE_URL}', $data['lastMessageActorId']);
}
if (isset($expectedRoom['lastReadMessage'])) {
$data['lastReadMessage'] = self::$messageIdToText[(int)$room['lastReadMessage']] ?? ($room['lastReadMessage'] === -2 ? 'FIRST_MESSAGE_UNREAD': 'UNKNOWN_MESSAGE');
}
if (isset($expectedRoom['unreadMessages'])) {
$data['unreadMessages'] = (int)$room['unreadMessages'];
}
if (isset($expectedRoom['unreadMention'])) {
$data['unreadMention'] = (int)$room['unreadMention'];
}
if (isset($expectedRoom['unreadMentionDirect'])) {
$data['unreadMentionDirect'] = (int)$room['unreadMentionDirect'];
}
if (isset($expectedRoom['messageExpiration'])) {
$data['messageExpiration'] = (int)$room['messageExpiration'];
}
if (isset($expectedRoom['callRecording'])) {
$data['callRecording'] = (int)$room['callRecording'];
}
if (isset($expectedRoom['recordingConsent'])) {
$data['recordingConsent'] = (int)$room['recordingConsent'];
}
if (isset($expectedRoom['permissions'])) {
$data['permissions'] = $this->mapPermissionsAPIOutput($room['permissions']);
}
if (isset($expectedRoom['attendeePermissions'])) {
$data['attendeePermissions'] = $this->mapPermissionsAPIOutput($room['attendeePermissions']);
}
if (isset($expectedRoom['callPermissions'])) {
$data['callPermissions'] = $this->mapPermissionsAPIOutput($room['callPermissions']);
}
if (isset($expectedRoom['defaultPermissions'])) {
$data['defaultPermissions'] = $this->mapPermissionsAPIOutput($room['defaultPermissions']);
}
if (isset($expectedRoom['mentionPermissions'])) {
$data['mentionPermissions'] = (int)$room['mentionPermissions'];
}
if (isset($expectedRoom['participants'])) {
throw new \Exception('participants key needs to be checked via participants endpoint');
}
return $data;
}, $rooms, $expected));
if (isset($expectedRoom['remoteToken'])) {
if (isset($room['remoteToken'])) {
$data['remoteToken'] = self::$tokenToIdentifier[$room['remoteToken']] ?? 'unknown-token';
} else {
$data['remoteToken'] = '';
}
}
if (isset($expectedRoom['hasPassword'])) {
$data['hasPassword'] = (string)$room['hasPassword'];
}
if (isset($expectedRoom['readOnly'])) {
$data['readOnly'] = (string)$room['readOnly'];
}
if (isset($expectedRoom['listable'])) {
$data['listable'] = (string)$room['listable'];
}
if (isset($expectedRoom['isArchived'])) {
$data['isArchived'] = (int)$room['isArchived'];
}
if (isset($expectedRoom['participantType'])) {
$data['participantType'] = (string)$room['participantType'];
}
if (isset($expectedRoom['sipEnabled'])) {
$data['sipEnabled'] = (string)$room['sipEnabled'];
}
if (isset($expectedRoom['callFlag'])) {
$data['callFlag'] = (int)$room['callFlag'];
}
if (isset($expectedRoom['lobbyState'])) {
$data['lobbyState'] = (int)$room['lobbyState'];
}
if (!empty($expectedRoom['lobbyTimer'])) {
$data['lobbyTimer'] = (int)$room['lobbyTimer'];
}
if (isset($expectedRoom['lobbyTimer'])) {
$data['lobbyTimer'] = (int)$room['lobbyTimer'];
if ($expectedRoom['lobbyTimer'] === 'GREATER_THAN_ZERO' && $room['lobbyTimer'] > 0) {
$data['lobbyTimer'] = 'GREATER_THAN_ZERO';
}
}
if (isset($expectedRoom['breakoutRoomMode'])) {
$data['breakoutRoomMode'] = (int)$room['breakoutRoomMode'];
}
if (isset($expectedRoom['breakoutRoomStatus'])) {
$data['breakoutRoomStatus'] = (int)$room['breakoutRoomStatus'];
}
if (isset($expectedRoom['attendeePin'])) {
$data['attendeePin'] = $room['attendeePin'] ? '**PIN**' : '';
}
if (isset($expectedRoom['lastMessage'])) {
if (isset($room['lastMessage'])) {
$data['lastMessage'] = $room['lastMessage'] ? $room['lastMessage']['message'] : '';
} else {
$data['lastMessage'] = 'UNSET';
}
}
if (isset($expectedRoom['lastMessageActorType'])) {
$data['lastMessageActorType'] = $room['lastMessage'] ? $room['lastMessage']['actorType'] : '';
}
if (isset($expectedRoom['lastMessageActorId'])) {
$data['lastMessageActorId'] = $room['lastMessage'] ? $room['lastMessage']['actorId'] : '';
$data['lastMessageActorId'] = str_replace(rtrim($this->localServerUrl, '/'), '{$LOCAL_URL}', $data['lastMessageActorId']);
$data['lastMessageActorId'] = str_replace(rtrim($this->remoteServerUrl, '/'), '{$REMOTE_URL}', $data['lastMessageActorId']);
}
if (isset($expectedRoom['lastReadMessage'])) {
$data['lastReadMessage'] = self::$messageIdToText[(int)$room['lastReadMessage']] ?? ($room['lastReadMessage'] === -2 ? 'FIRST_MESSAGE_UNREAD': 'UNKNOWN_MESSAGE');
}
if (isset($expectedRoom['unreadMessages'])) {
$data['unreadMessages'] = (int)$room['unreadMessages'];
}
if (isset($expectedRoom['unreadMention'])) {
$data['unreadMention'] = (int)$room['unreadMention'];
}
if (isset($expectedRoom['unreadMentionDirect'])) {
$data['unreadMentionDirect'] = (int)$room['unreadMentionDirect'];
}
if (isset($expectedRoom['messageExpiration'])) {
$data['messageExpiration'] = (int)$room['messageExpiration'];
}
if (isset($expectedRoom['callRecording'])) {
$data['callRecording'] = (int)$room['callRecording'];
}
if (isset($expectedRoom['recordingConsent'])) {
$data['recordingConsent'] = (int)$room['recordingConsent'];
}
if (isset($expectedRoom['permissions'])) {
$data['permissions'] = $this->mapPermissionsAPIOutput($room['permissions']);
}
if (isset($expectedRoom['attendeePermissions'])) {
$data['attendeePermissions'] = $this->mapPermissionsAPIOutput($room['attendeePermissions']);
}
if (isset($expectedRoom['callPermissions'])) {
$data['callPermissions'] = $this->mapPermissionsAPIOutput($room['callPermissions']);
}
if (isset($expectedRoom['defaultPermissions'])) {
$data['defaultPermissions'] = $this->mapPermissionsAPIOutput($room['defaultPermissions']);
}
if (isset($expectedRoom['mentionPermissions'])) {
$data['mentionPermissions'] = (int)$room['mentionPermissions'];
}
if (isset($expectedRoom['participants'])) {
throw new \Exception('participants key needs to be checked via participants endpoint');
}
if (isset($expectedRoom['objectId'])) {
$data['objectId'] = $room['objectId'];
}
if (isset($expectedRoom['objectType'])) {
$data['objectType'] = $room['objectType'];
}
return $data;
}, $rooms, $expected));
}
#[Then('/^user "([^"]*)" has the following invitations \((v1)\)$/')]
@ -1039,6 +1058,11 @@ class FeatureContext implements Context, SnippetAcceptingContext {
}
}
if (isset($body['objectType']) && $body['objectType'] === 'event') {
[$start, $end] = explode('#', $body['objectId']);
$body['objectId'] = (time() + (int)$start) . '#' . (time() + (int)$end);
}
if (isset($body['permissions'])) {
$body['permissions'] = $this->mapPermissionsTestInput($body['permissions']);
}
@ -4293,4 +4317,135 @@ class FeatureContext implements Context, SnippetAcceptingContext {
Assert::assertEquals($statusCode, $response->getStatusCode(), $message);
}
}
/**
* @param string $user
* @param string $identifier
* @param int $statusCode
* @param string $apiVersion
* @param TableNode|null $formData
* @return void
*
* @Given /^user "([^"]*)" creates conversation with event "([^"]*)" \((v4)\)$/
*/
public function createCalendarEventConversation(string $user, string $identifier, string $apiVersion = 'v1', ?TableNode $formData = null): void {
$body = $formData->getRowsHash();
$startTime = time() + 86400;
$endTime = time() + 90000;
if (isset($body['objectId'])) {
[$start, $end] = explode('#', $body['objectId']);
$startTime = time() + (int)$start;
$endTime = time() + (int)$end;
$body['objectId'] = $startTime . '#' . $endTime;
self::$identifierToObjectId[$identifier] = $body['objectId'];
}
$body['roomName'] = $identifier;
$this->setCurrentUser($user);
$this->sendRequest('POST', '/apps/spreed/api/' . $apiVersion . '/room', $body);
$this->assertStatusCode($this->response, 201);
$response = $this->getDataFromResponse($this->response);
self::$identifierToToken[$identifier] = $response['token'];
self::$identifierToId[$identifier] = $response['id'];
self::$tokenToIdentifier[$response['token']] = $identifier;
$location = self::getRoomLocationForToken($identifier);
$this->sendRequest('POST', '/apps/spreedcheats/calendar', [
'name' => $identifier,
'location' => $location,
'start' => $startTime,
'end' => $endTime,
]);
$this->assertStatusCode($this->response, 200);
}
/**
* @param string $user
* @param string $identifier
* @param int $statusCode
* @param string $apiVersion
* @param TableNode|null $formData
* @return void
*
* @Given /^user "([^"]*)" creates calendar events for a room "([^"]*)" \((v4)\)$/
*/
public function createCalendarEntriesWithRoom(string $user, string $identifier, string $apiVersion = 'v1', ?TableNode $formData = null): void {
$body = $formData->getRowsHash();
$body['roomName'] = $identifier;
if (!isset(self::$tokenToIdentifier[$identifier])) {
$this->setCurrentUser($user);
$this->sendRequest('POST', '/apps/spreed/api/' . $apiVersion . '/room', $body);
$this->assertStatusCode($this->response, 201);
$response = $this->getDataFromResponse($this->response);
self::$identifierToToken[$identifier] = $response['token'];
self::$identifierToId[$identifier] = $response['id'];
self::$tokenToIdentifier[$response['token']] = $identifier;
}
$location = self::getRoomLocationForToken($identifier);
$this->sendRequest('POST', '/apps/spreedcheats/dashboardEvents', [
'name' => $identifier,
'location' => $location,
]);
$this->assertStatusCode($this->response, 200);
}
#[Then('/^user "([^"]*)" sees the following entry when loading the dashboard conversations \((v4)\)$/')]
public function userGetsEventConversationsForTalkDashboard(string $user, string $apiVersion, ?TableNode $formData = null): void {
$this->setCurrentUser($user);
$this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/dashboard/events');
$this->assertStatusCode($this->response, 200);
$data = $this->getDataFromResponse($this->response);
if (!$formData instanceof TableNode) {
Assert::assertEmpty($data);
return;
}
$this->assertDashboardData($data, $formData);
}
/**
* @param array $dashboardEvents
* @param TableNode $formData
*/
private function assertDashboardData(array $dashboardEvents, TableNode $formData) : void {
Assert::assertCount(count($formData->getHash()), $dashboardEvents, 'Event count does not match');
$expected = $formData->getHash();
if (empty($expected)) {
return;
}
$missingKeys = array_diff(array_keys($dashboardEvents[0]), array_keys($expected[0]));
Assert::assertEquals(array_map(function ($event) {
foreach ($event as $key => $value) {
if ($value === 'null') {
$event[$key] = null;
}
}
$event['roomType'] = (int)$event['roomType'];
$event['eventAttachments'] = (int)$event['eventAttachments'];
$event['calendars'] = (int)$event['calendars'];
return $event;
}, $expected), array_map(static function (array $event) use ($missingKeys): array {
$data = $event;
foreach ($missingKeys as $key) {
unset($data[$key]);
}
$data['eventAttachments'] = count($event['eventAttachments']);
$data['calendars'] = count($event['calendars']);
return $data;
}, $dashboardEvents));
}
}

2
tests/integration/features/integration/dashboard.feature → tests/integration/features/integration/dashboard-server.feature

@ -1,4 +1,4 @@
Feature: integration/dashboard
Feature: integration/dashboard-server
Background:
Given user "participant1" exists
Given user "participant2" exists

15
tests/integration/features/integration/dashboard-talk.feature

@ -0,0 +1,15 @@
Feature: integration/dashboard-talk
Background:
Given user "participant1" exists
Scenario: User gets the events for the talk dashboard
Given user "participant1" creates room "dashboardRoom" (v4)
| roomType | 2 |
Given user "participant1" creates calendar events for a room "dashboardRoom" (v4)
| roomType | 2 |
Then user "participant1" sees the following entry when loading the dashboard conversations (v4)
|roomName | roomType| eventName | invited | accepted | declined | tentative | eventAttachments | calendars |
|dashboardRoom | 2 | dashboardRoom-single | null | null | null | null | 0 | 1 |
|dashboardRoom | 2 | dashboardRoom-attachment | null | null | null | null | 1 | 1 |
|dashboardRoom | 2 | dashboardRoom-attendees | 1 | 1 | null | null | 0 | 1 |
|dashboardRoom | 2 | dashboardRoom-recurring | null | null | null | null | 0 | 1 |

2
tests/integration/spreedcheats/appinfo/routes.php

@ -10,5 +10,7 @@ return [
'ocs' => [
['name' => 'Api#resetSpreed', 'url' => '/', 'verb' => 'DELETE'],
['name' => 'Api#ageChat', 'url' => '/age', 'verb' => 'POST'],
['name' => 'Api#createEventInCalendar', 'url' => '/calendar', 'verb' => 'POST'],
['name' => 'Api#createDashboardEvents', 'url' => '/dashboardEvents', 'verb' => 'POST'],
],
];

18
tests/integration/spreedcheats/calendars/0_event_single.txt

@ -0,0 +1,18 @@
BEGIN:VCALENDAR
PRODID:-//IDN nextcloud.com//Calendar app 5.2.0-dev.1//EN
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
CREATED:20250310T171800Z
DTSTAMP:20250310T171819Z
LAST-MODIFIED:20250310T171819Z
SEQUENCE:2
UID:{{{UID}}}
DTSTART:{{{START}}}
DTEND:{{{END}}}
STATUS:CONFIRMED
SUMMARY:{{{NAME}}}-single
LOCATION:{{{LOCATION}}}
DESCRIPTION:{{{NAME}}}
END:VEVENT
END:VCALENDAR

20
tests/integration/spreedcheats/calendars/1_event_attachment.txt

@ -0,0 +1,20 @@
BEGIN:VCALENDAR
CALSCALE:GREGORIAN
VERSION:2.0
PRODID:-//IDN nextcloud.com//Calendar app 5.3.0-dev.1//EN
BEGIN:VEVENT
CREATED:20250428T161339Z
DTSTAMP:20250428T172521Z
LAST-MODIFIED:20250428T172521Z
SEQUENCE:3
UID:{{{UID}}}
DTSTART:{{{START}}}
DTEND:{{{END}}}
STATUS:CONFIRMED
SUMMARY:{{{NAME}}}-attachment
LOCATION:{{{LOCATION}}}
DESCRIPTION:{{{NAME}}}
ATTACH;FMTTYPE=image/jpeg;FILENAME=/Calendar/signal-2025-03-21-121810_004.j
peg;X-NC-FILE-ID=566;X-NC-HAS-PREVIEW=true:/index.php/f/566
END:VEVENT
END:VCALENDAR

23
tests/integration/spreedcheats/calendars/2_event_attendees.txt

@ -0,0 +1,23 @@
BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//IDN nextcloud.com//Calendar app 5.3.0-dev.1//EN
BEGIN:VEVENT
CREATED:20250428T163259Z
DTSTAMP:20250428T163714Z
LAST-MODIFIED:20250428T163714Z
SEQUENCE:2
UID:{{{UID}}}
DTSTART:{{{START}}}
DTEND:{{{END}}}
STATUS:CONFIRMED
SUMMARY:{{{NAME}}}-attendees
LOCATION:{{{LOCATION}}}
DESCRIPTION:{{{NAME}}}
ATTENDEE;CN=bob;CUTYPE=INDIVIDUAL;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPAN
T;RSVP=TRUE;LANGUAGE=en_GB;SCHEDULE-STATUS=1.1:mailto:bob@domain.tld
ATTENDEE;CN=Alice Alice;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED;ROLE=REQ-PARTIC
IPANT;LANGUAGE=en_GB;SCHEDULE-STATUS=2.0:mailto:alice@domain.tld
ORGANIZER;CN=admin:mailto:admin@example.net
END:VEVENT
END:VCALENDAR

19
tests/integration/spreedcheats/calendars/3_event_recurring_weekly.txt

@ -0,0 +1,19 @@
BEGIN:VCALENDAR
PRODID:-//IDN nextcloud.com//Calendar app 5.3.0-dev.1//EN
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
CREATED:20250430T095901Z
DTSTAMP:20250430T095947Z
LAST-MODIFIED:20250430T095947Z
SEQUENCE:2
UID:{{{UID}}}
DTSTART:{{{START}}}
DTEND:{{{END}}}
STATUS:CONFIRMED
SUMMARY:{{{NAME}}}-recurring
LOCATION:{{{LOCATION}}}
DESCRIPTION:{{{NAME}}}
RRULE:FREQ=WEEKLY;COUNT=3;BYDAY=TH
END:VEVENT
END:VCALENDAR

36
tests/integration/spreedcheats/lib/Calendar/EventGenerator.php

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\SpreedCheats\Calendar;
use Sabre\VObject\UUIDUtil;
class EventGenerator {
public static function generateEvents(string $name, string $location, int $start, int $end): array {
$files = scandir(__DIR__ . '/../../calendars/');
$events = [];
$startDate = (new \DateTime())->setTimestamp($start);
$endDate = (new \DateTime())->setTimestamp($end);
foreach ($files as $file) {
if ($file === '.' || $file === '..') {
continue;
}
$calData = file_get_contents(__DIR__ . '/../../calendars/' . $file);
if ($calData === false) {
continue;
}
$interval = new \DateInterval('P1D');
$startDate->add($interval);
$endDate->add($interval);
$uid = UUIDUtil::getUUID();
$events[] = str_replace(['{{{NAME}}}', '{{{START}}}', '{{{END}}}', '{{{UID}}}', '{{{LOCATION}}}'], [$name, $startDate->format('Ymd\THis'), $endDate->format('Ymd\THis'), $uid, $location], $calData);
}
return $events;
}
}

102
tests/integration/spreedcheats/lib/Controller/ApiController.php

@ -8,19 +8,27 @@ declare(strict_types=1);
namespace OCA\SpreedCheats\Controller;
use OCA\SpreedCheats\Calendar\EventGenerator;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\Calendar\Exceptions\CalendarException;
use OCP\Calendar\ICreateFromString;
use OCP\Calendar\IManager;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\IRequest;
use OCP\Share\IShare;
use Sabre\VObject\UUIDUtil;
class ApiController extends OCSController {
public function __construct(
string $appName,
IRequest $request,
protected IDBConnection $db,
private IManager $calendarManager,
private ?string $userId,
) {
parent::__construct($appName, $request);
}
@ -104,6 +112,13 @@ class ApiController extends OCSController {
// Ignore
}
$delete = $this->db->getQueryBuilder();
$delete->delete('calendarobjects')->executeStatement();
$delete = $this->db->getQueryBuilder();
$delete->delete('calendarobjects_props')->executeStatement();
return new DataResponse();
}
@ -164,4 +179,91 @@ class ApiController extends OCSController {
return new DataResponse();
}
#[NoAdminRequired]
public function createEventInCalendar(string $name, string $location, string $start, string $end): DataResponse {
if ($this->userId === null) {
return new DataResponse(null, Http::STATUS_UNAUTHORIZED);
}
$calendar = null;
// Create a calendar event with LOCATION and time via OCP
$calendars = $this->calendarManager->getCalendarsForPrincipal('principals/users/' . $this->userId);
foreach ($calendars as $c) {
if ($c instanceof ICreateFromString) {
$calendar = $c;
}
}
if ($calendar === null) {
return new DataResponse(null, Http::STATUS_NOT_FOUND);
}
$calData = <<<EOF
BEGIN:VCALENDAR
PRODID:-//IDN nextcloud.com//Calendar app 5.2.0-dev.1//EN
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
CREATED:20250310T171800Z
DTSTAMP:20250310T171819Z
LAST-MODIFIED:20250310T171819Z
SEQUENCE:2
UID:{{{UID}}}
DTSTART:{{{START}}}
DTEND:{{{END}}}
STATUS:CONFIRMED
SUMMARY:{{{NAME}}}
LOCATION:{{{LOCATION}}}
END:VEVENT
END:VCALENDAR
EOF;
$start = (new \DateTime())->setTimestamp((int)$start)->format('Ymd\THis');
$end = (new \DateTime())->setTimestamp((int)$end)->format('Ymd\THis');
$uid = UUIDUtil::getUUID();
$calData = str_replace(['{{{NAME}}}', '{{{START}}}', '{{{END}}}', '{{{UID}}}', '{{{LOCATION}}}'], [$name, $start, $end, $uid, $location], $calData);
try {
/** @var ICreateFromString $calendar */
$calendar->createFromString((string)random_int(0, 10000), $calData);
} catch (CalendarException) {
return new DataResponse(null, Http::STATUS_FORBIDDEN);
}
return new DataResponse();
}
#[NoAdminRequired]
public function createDashboardEvents(string $name, string $location): DataResponse {
if ($this->userId === null) {
return new DataResponse(null, Http::STATUS_UNAUTHORIZED);
}
$calendar = null;
$calendars = $this->calendarManager->getCalendarsForPrincipal('principals/users/' . $this->userId);
foreach ($calendars as $c) {
if ($c instanceof ICreateFromString) {
$calendar = $c;
}
}
if ($calendar === null) {
return new DataResponse(null, Http::STATUS_NOT_FOUND);
}
$start = time();
$end = time() + 3600;
$events = EventGenerator::generateEvents($name, $location, $start, $end);
foreach ($events as $event) {
try {
/** @var ICreateFromString $calendar */
$calendar->createFromString((string)random_int(0, 10000), $event);
} catch (CalendarException) {
return new DataResponse(null, Http::STATUS_FORBIDDEN);
}
}
return new DataResponse();
}
}
Loading…
Cancel
Save