diff --git a/appinfo/info.xml b/appinfo/info.xml
index 72bdcbd1c2..40d1e52ef2 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -18,7 +18,7 @@
* 🌉 **Sync with other chat solutions** With [Matterbridge](https://github.com/42wim/matterbridge/) being integrated in Talk, you can easily sync a lot of other chat solutions to Nextcloud Talk and vice-versa.
]]>
- 23.0.0-dev.2
+ 23.0.0-dev.3
agpl
Anna Larch
diff --git a/docs/capabilities.md b/docs/capabilities.md
index e9a377011a..fcb35a7efd 100644
--- a/docs/capabilities.md
+++ b/docs/capabilities.md
@@ -202,3 +202,4 @@
* `pinned-messages` - Whether messages can be pinned
* `federated-shared-items` - Whether shared items endpoints can be called in a federated conversation
* `config => chat => style` (local) - User selected chat style (split or unified for now)
+* `scheduled-messages` (local) - Whether a user can schedule messages
diff --git a/lib/Capabilities.php b/lib/Capabilities.php
index a46a19711b..b31e0a3803 100644
--- a/lib/Capabilities.php
+++ b/lib/Capabilities.php
@@ -126,6 +126,7 @@ class Capabilities implements IPublicCapability {
'threads',
'pinned-messages',
'federated-shared-items',
+ 'scheduled-messages',
];
public const CONDITIONAL_FEATURES = [
@@ -156,6 +157,7 @@ class Capabilities implements IPublicCapability {
'mutual-calendar-events',
'upcoming-reminders',
'sensitive-conversations',
+ 'scheduled-messages',
];
public const LOCAL_CONFIGS = [
diff --git a/lib/Chat/Listener.php b/lib/Chat/Listener.php
index c902ef66ca..7e75ae00ce 100644
--- a/lib/Chat/Listener.php
+++ b/lib/Chat/Listener.php
@@ -10,6 +10,7 @@ declare(strict_types=1);
namespace OCA\Talk\Chat;
use OCA\Talk\Events\RoomDeletedEvent;
+use OCA\Talk\Model\ScheduledMessageMapper;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
@@ -19,6 +20,7 @@ use OCP\EventDispatcher\IEventListener;
class Listener implements IEventListener {
public function __construct(
protected ChatManager $chatManager,
+ protected ScheduledMessageMapper $scheduledMessageMapper,
) {
}
@@ -26,6 +28,7 @@ class Listener implements IEventListener {
public function handle(Event $event): void {
if ($event instanceof RoomDeletedEvent) {
$this->chatManager->deleteMessages($event->getRoom());
+ $this->scheduledMessageMapper->deleteMessagesByRoom($event->getRoom());
}
}
}
diff --git a/lib/Controller/ChatController.php b/lib/Controller/ChatController.php
index 11cf11c975..c2f81294fb 100644
--- a/lib/Controller/ChatController.php
+++ b/lib/Controller/ChatController.php
@@ -35,6 +35,7 @@ use OCA\Talk\Model\Attendee;
use OCA\Talk\Model\Bot;
use OCA\Talk\Model\Message;
use OCA\Talk\Model\Reminder;
+use OCA\Talk\Model\ScheduledMessage;
use OCA\Talk\Model\Session;
use OCA\Talk\Model\Thread;
use OCA\Talk\Participant;
@@ -47,6 +48,7 @@ use OCA\Talk\Service\ParticipantService;
use OCA\Talk\Service\ProxyCacheMessageService;
use OCA\Talk\Service\ReminderService;
use OCA\Talk\Service\RoomFormatter;
+use OCA\Talk\Service\ScheduledMessageService;
use OCA\Talk\Service\SessionService;
use OCA\Talk\Service\ThreadService;
use OCA\Talk\Share\Helper\Preloader;
@@ -93,6 +95,7 @@ use Psr\Log\LoggerInterface;
* @psalm-import-type TalkChatReminderUpcoming from ResponseDefinitions
* @psalm-import-type TalkRichObjectParameter from ResponseDefinitions
* @psalm-import-type TalkRoom from ResponseDefinitions
+ * @psalm-import-type TalkScheduledMessage from ResponseDefinitions
*/
class ChatController extends AEnvironmentAwareOCSController {
/** @var string[] */
@@ -135,6 +138,7 @@ class ChatController extends AEnvironmentAwareOCSController {
protected ITaskProcessingManager $taskProcessingManager,
protected IAppConfig $appConfig,
protected LoggerInterface $logger,
+ protected ScheduledMessageService $scheduledMessageManager,
) {
parent::__construct($appName, $request);
}
@@ -332,6 +336,260 @@ class ChatController extends AEnvironmentAwareOCSController {
return $this->parseCommentToResponse($comment, $parentMessage);
}
+ /**
+ * Get all scheduled messages of a given room and participant
+ *
+ * The author and timestamp are automatically set to the current user
+ * and time.
+ *
+ * Required capability: `scheduled-messages`
+ *
+ * @return DataResponse, array{}>|DataResponse
+ *
+ * 200: All scheduled messages for this room and participant
+ * 404: Actor not found
+ */
+ #[NoAdminRequired]
+ #[RequireModeratorOrNoLobby]
+ #[RequireParticipant]
+ #[RequirePermission(permission: RequirePermission::CHAT)]
+ #[RequireReadWriteConversation]
+ #[ApiRoute(verb: 'GET', url: '/api/{apiVersion}/chat/{token}/schedule', requirements: [
+ 'apiVersion' => '(v1)',
+ 'token' => '[a-z0-9]{4,30}',
+ ])]
+ public function getScheduledMessages(): DataResponse {
+ if ($this->participant->isSelfJoinedOrGuest()) {
+ return new DataResponse(['error' => 'actor'], Http::STATUS_NOT_FOUND);
+ }
+
+ $scheduledMessages = $this->scheduledMessageManager->getMessages(
+ $this->room,
+ $this->participant,
+ );
+
+ return new DataResponse($scheduledMessages, Http::STATUS_OK);
+ }
+
+ /**
+ * Schedules the sending of a new chat message to the given room
+ *
+ * The author and timestamp are automatically set to the current user
+ * and time.
+ *
+ * Required capability: `scheduled-messages`
+ *
+ * @param string $message The message to send
+ * @param int $sendAt When to send the scheduled message
+ * @param int $replyTo Parent id which this scheduled message is a reply to
+ * @psalm-param non-negative-int $replyTo
+ * @param bool $silent If sent silent the scheduled message will not create any notifications when sent
+ * @param string $threadTitle Only supported when not replying, when given will create a thread (requires `threads` capability)
+ * @param int $threadId Thread id without quoting a specific message (requires `threads` capability)
+ * @return DataResponse|DataResponse|DataResponse|DataResponse
+ *
+ * 201: Message scheduled successfully
+ * 400: Scheduling the message is not possible
+ * 404: Actor not found
+ * 413: Message too long
+ */
+ #[NoAdminRequired]
+ #[RequireModeratorOrNoLobby]
+ #[RequireParticipant]
+ #[RequirePermission(permission: RequirePermission::CHAT)]
+ #[RequireReadWriteConversation]
+ #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/chat/{token}/schedule', requirements: [
+ 'apiVersion' => '(v1)',
+ 'token' => '[a-z0-9]{4,30}',
+ ])]
+ public function scheduleMessage(
+ string $message,
+ int $sendAt,
+ int $replyTo = 0,
+ bool $silent = false,
+ string $threadTitle = '',
+ int $threadId = 0,
+ ): DataResponse {
+ if ($this->participant->isSelfJoinedOrGuest()) {
+ return new DataResponse(['error' => 'actor'], Http::STATUS_NOT_FOUND);
+ }
+
+ if ($sendAt <= $this->timeFactory->getTime()) {
+ return new DataResponse(['error' => 'send-at'], Http::STATUS_BAD_REQUEST);
+ }
+
+ if (trim($message) === '') {
+ return new DataResponse(['error' => 'message'], Http::STATUS_BAD_REQUEST);
+ }
+
+ $parent = $parentMessage = null;
+ if ($replyTo !== 0) {
+ try {
+ $parent = $this->chatManager->getParentComment($this->room, (string)$replyTo);
+ } catch (NotFoundException $e) {
+ // Someone is trying to reply cross-rooms or to a non-existing message
+ return new DataResponse(['error' => 'reply-to'], Http::STATUS_BAD_REQUEST);
+ }
+
+ $parentMessage = $this->messageParser->createMessage($this->room, $this->participant, $parent, $this->l);
+ $this->messageParser->parseMessage($parentMessage);
+ if (!$parentMessage->isReplyable()) {
+ return new DataResponse(['error' => 'reply-to'], Http::STATUS_BAD_REQUEST);
+ }
+ }
+
+ if ($threadId !== 0 && !$this->threadService->validateThread($this->room->getId(), $threadId)) {
+ return new DataResponse(['error' => 'reply-to'], Http::STATUS_BAD_REQUEST);
+ }
+
+ $sendAtDateTime = $this->timeFactory->getDateTime('@' . $sendAt, new \DateTimeZone('UTC'));
+ try {
+ $createThread = $replyTo === 0 && $threadId === Thread::THREAD_NONE && $threadTitle !== '';
+ $threadId = $createThread ? Thread::THREAD_CREATE : $threadId;
+ $scheduledMessage = $this->scheduledMessageManager->scheduleMessage(
+ $this->room,
+ $this->participant,
+ $message,
+ ChatManager::VERB_MESSAGE,
+ $parent,
+ $threadId,
+ $sendAtDateTime,
+ [
+ ScheduledMessage::METADATA_THREAD_TITLE => $threadTitle,
+ ScheduledMessage::METADATA_SILENT => $silent,
+ ScheduledMessage::METADATA_THREAD_ID => $threadId,
+ ]
+ );
+ $this->participantService->setHasScheduledMessages($this->participant, true);
+ } catch (MessageTooLongException) {
+ return new DataResponse(['error' => 'message'], Http::STATUS_REQUEST_ENTITY_TOO_LARGE);
+ }
+
+ $data = $this->scheduledMessageManager->parseScheduledMessage($scheduledMessage, $parentMessage);
+ return new DataResponse($data, Http::STATUS_CREATED);
+ }
+
+ /**
+ * Update a scheduled message
+ *
+ * Required capability: `scheduled-messages`
+ *
+ * @param int $messageId The scheduled message id
+ * @param string $message The scheduled message to send
+ * @param int $sendAt When to send the scheduled message
+ * @param bool $silent If sent silent the scheduled message will not create any notifications
+ * @param string $threadTitle The thread title if scheduled message is creating a thread
+ * @return DataResponse|DataResponse|DataResponse|DataResponse
+ *
+ * 202: Message updated successfully
+ * 400: Editing scheduled message is not possible
+ * 404: Actor not found
+ * 413: Message too long
+ */
+ #[NoAdminRequired]
+ #[RequireModeratorOrNoLobby]
+ #[RequireLoggedInParticipant]
+ #[RequirePermission(permission: RequirePermission::CHAT)]
+ #[RequireReadWriteConversation]
+ #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/chat/{token}/schedule/{messageId}', requirements: [
+ 'apiVersion' => '(v1)',
+ 'token' => '[a-z0-9]{4,30}',
+ 'messageId' => '[0-9]{4,30}',
+ ])]
+ public function editScheduledMessage(
+ int $messageId,
+ string $message,
+ int $sendAt,
+ bool $silent = false,
+ string $threadTitle = '',
+ ): DataResponse {
+ if ($this->participant->isSelfJoinedOrGuest()) {
+ return new DataResponse(['error' => 'actor'], Http::STATUS_NOT_FOUND);
+ }
+
+ if ($sendAt <= $this->timeFactory->getTime()) {
+ return new DataResponse(['error' => 'send-at'], Http::STATUS_BAD_REQUEST);
+ }
+
+ if (trim($message) === '') {
+ return new DataResponse(['error' => 'message'], Http::STATUS_BAD_REQUEST);
+ }
+
+ $sendAtDateTime = $this->timeFactory->getDateTime('@' . $sendAt, new \DateTimeZone('UTC'));
+ try {
+ $scheduledMessage = $this->scheduledMessageManager->editMessage(
+ $this->room,
+ $messageId,
+ $this->participant,
+ $message,
+ $silent,
+ $sendAtDateTime,
+ $threadTitle
+ );
+ $this->participantService->setHasScheduledMessages($this->participant, true);
+ } catch (MessageTooLongException) {
+ return new DataResponse(['error' => 'message'], Http::STATUS_REQUEST_ENTITY_TOO_LARGE);
+ } catch (\InvalidArgumentException) {
+ return new DataResponse(['error' => 'thread-title'], Http::STATUS_BAD_REQUEST);
+ } catch (DoesNotExistException) {
+ return new DataResponse(['error' => 'message'], Http::STATUS_NOT_FOUND);
+ }
+
+ $parentMessage = null;
+ if ($scheduledMessage->getParentId() !== null) {
+ try {
+ $parent = $this->chatManager->getParentComment($this->room, (string)$scheduledMessage->getParentId());
+ $parentMessage = $this->messageParser->createMessage($this->room, $this->participant, $parent, $this->l);
+ $this->messageParser->parseMessage($parentMessage);
+ } catch (NotFoundException) {
+ }
+ }
+
+ $data = $this->scheduledMessageManager->parseScheduledMessage($scheduledMessage, $parentMessage);
+ return new DataResponse($data, Http::STATUS_ACCEPTED);
+ }
+
+ /**
+ * Delete a scheduled message
+ *
+ * Required capability: `scheduled-messages`
+ *
+ * @param int $messageId The scheduled message ud
+ * @return DataResponse|DataResponse
+ *
+ * 200: Message deleted
+ * 404: Message not found
+ */
+ #[NoAdminRequired]
+ #[RequireModeratorOrNoLobby]
+ #[RequireLoggedInParticipant]
+ #[RequirePermission(permission: RequirePermission::CHAT)]
+ #[RequireReadWriteConversation]
+ #[ApiRoute(verb: 'DELETE', url: '/api/{apiVersion}/chat/{token}/schedule/{messageId}', requirements: [
+ 'apiVersion' => '(v1)',
+ 'token' => '[a-z0-9]{4,30}',
+ 'messageId' => '[0-9]{4,30}',
+ ])]
+ public function deleteScheduleMessage(int $messageId): DataResponse {
+ if ($this->participant->isSelfJoinedOrGuest()) {
+ return new DataResponse(['error' => 'actor'], Http::STATUS_NOT_FOUND);
+ }
+
+ $deleted = $this->scheduledMessageManager->deleteMessage(
+ $this->room,
+ $messageId,
+ $this->participant,
+ );
+
+ if ($deleted === 0) {
+ return new DataResponse(['error' => 'message'], Http::STATUS_NOT_FOUND);
+ }
+
+ $hasScheduledMessages = $this->scheduledMessageManager->getScheduledMessageCount($this->room, $this->participant) > 0;
+ $this->participantService->setHasScheduledMessages($this->participant, $hasScheduledMessages);
+ return new DataResponse([], Http::STATUS_OK);
+ }
+
/**
* Sends a rich-object to the given room
*
diff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php
index ef0036338f..c7d7674065 100644
--- a/lib/Controller/RoomController.php
+++ b/lib/Controller/RoomController.php
@@ -8,7 +8,6 @@ declare(strict_types=1);
namespace OCA\Talk\Controller;
-use OCA\Circles\Model\Circle;
use OCA\Talk\Capabilities;
use OCA\Talk\Config;
use OCA\Talk\Events\AAttendeeRemovedEvent;
@@ -503,7 +502,7 @@ class RoomController extends AEnvironmentAwareOCSController {
$statuses = $this->statusManager->getUserStatuses($userIds);
}
return new DataResponse($this->formatRoom($room, $participant, $statuses, $isSIPBridgeRequest), Http::STATUS_OK, $this->getTalkHashHeader());
- } catch (RoomNotFoundException $e) {
+ } catch (RoomNotFoundException) {
/**
* A hack to fix type collision
* @var DataResponse $response
diff --git a/lib/Listener/UserDeletedListener.php b/lib/Listener/UserDeletedListener.php
index 7dbf4a1fca..68e3a3ef96 100644
--- a/lib/Listener/UserDeletedListener.php
+++ b/lib/Listener/UserDeletedListener.php
@@ -12,6 +12,7 @@ use OCA\Talk\Manager;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Service\ConsentService;
use OCA\Talk\Service\PollService;
+use OCA\Talk\Service\ScheduledMessageService;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\User\Events\UserDeletedEvent;
@@ -25,6 +26,7 @@ class UserDeletedListener implements IEventListener {
private Manager $manager,
private PollService $pollService,
private ConsentService $consentService,
+ private ScheduledMessageService $messageManager,
) {
}
@@ -41,5 +43,7 @@ class UserDeletedListener implements IEventListener {
$this->pollService->neutralizeDeletedUser(Attendee::ACTOR_USERS, $user->getUID());
$this->consentService->deleteByActor(Attendee::ACTOR_USERS, $user->getUID());
+
+ $this->messageManager->deleteByActor(Attendee::ACTOR_USERS, $user->getUID());
}
}
diff --git a/lib/Migration/Version23000Date20251105125333.php b/lib/Migration/Version23000Date20251105125333.php
new file mode 100644
index 0000000000..8eb9d9114c
--- /dev/null
+++ b/lib/Migration/Version23000Date20251105125333.php
@@ -0,0 +1,91 @@
+hasTable('talk_scheduled_msg')) {
+ $table = $schema->createTable('talk_scheduled_msg');
+ $table->addColumn('id', Types::BIGINT, [
+ 'notnull' => true,
+ 'unsigned' => true,
+ 'length' => 20
+ ]);
+ $table->addColumn('room_id', Types::BIGINT, [
+ 'notnull' => true,
+ 'length' => 20,
+ ]);
+ $table->addColumn('actor_id', Types::STRING, [
+ 'notnull' => true,
+ 'length' => 64,
+ ]);
+ $table->addColumn('actor_type', Types::STRING, [
+ 'notnull' => true,
+ 'length' => 64,
+ ]);
+ $table->addColumn('message', Types::TEXT, [
+ 'notnull' => false,
+ 'default' => '',
+ ]);
+ $table->addColumn('message_type', Types::STRING, [
+ 'notnull' => true,
+ 'length' => 64,
+ ]);
+ $table->addColumn('meta_data', Types::TEXT, [
+ 'notnull' => false,
+ ]);
+ $table->addColumn('thread_id', Types::BIGINT, [
+ 'notnull' => true,
+ 'default' => 0,
+ ]);
+ $table->addColumn('parent_id', Types::BIGINT, [
+ 'notnull' => false,
+ 'default' => null,
+ ]);
+ $table->addColumn('created_at', Types::DATETIME, [
+ 'notnull' => false,
+ 'default' => null,
+ ]);
+ $table->addColumn('send_at', Types::DATETIME, [
+ 'notnull' => false,
+ 'default' => null,
+ ]);
+ $table->setPrimaryKey(['id']);
+ $table->addIndex(['room_id'], 'tt_room_sched');
+ $table->addIndex(['room_id', 'actor_type', 'actor_id'], 'tt_actor_room_sched');
+ $table->addIndex(['send_at'], 'tt_send_at_sched');
+ }
+
+ $table = $schema->getTable('talk_attendees');
+ if (!$table->hasColumn('has_scheduled_messages')) {
+ $table->addColumn('has_scheduled_messages', Types::BOOLEAN, [
+ 'notnull' => true,
+ 'default' => false,
+ ]);
+ }
+
+ return $schema;
+ }
+}
diff --git a/lib/Model/Attendee.php b/lib/Model/Attendee.php
index caf34e5e57..3e4b0cc038 100644
--- a/lib/Model/Attendee.php
+++ b/lib/Model/Attendee.php
@@ -72,6 +72,8 @@ use OCP\DB\Types;
* @method bool getHasUnreadThreadDirects()
* @method void setHiddenPinnedId(int $hiddenPinnedId)
* @method int getHiddenPinnedId()
+ * @method void setHasScheduledMessages(bool $scheduledMessages)
+ * @method bool getHasScheduledMessages()
*/
class Attendee extends Entity {
public const ACTOR_USERS = 'users';
@@ -145,6 +147,7 @@ class Attendee extends Entity {
protected bool $hasUnreadThreadMentions = false;
protected bool $hasUnreadThreadDirects = false;
protected int $hiddenPinnedId = 0;
+ protected bool $hasScheduledMessages = false;
public function __construct() {
$this->addType('roomId', Types::BIGINT);
@@ -177,6 +180,8 @@ class Attendee extends Entity {
$this->addType('hasUnreadThreadMentions', Types::BOOLEAN);
$this->addType('hasUnreadThreadDirects', Types::BOOLEAN);
$this->addType('hiddenPinnedId', Types::BIGINT);
+ $this->addType('hasScheduledMessages', Types::BOOLEAN);
+
}
public function getDisplayName(): string {
diff --git a/lib/Model/Message.php b/lib/Model/Message.php
index a27bade533..2a7a1112a7 100644
--- a/lib/Model/Message.php
+++ b/lib/Model/Message.php
@@ -25,6 +25,7 @@ class Message {
public const METADATA_SILENT = 'silent';
public const METADATA_CAN_MENTION_ALL = 'can_mention_all';
public const METADATA_THREAD_ID = 'thread_id';
+ public const METADATA_THREAD_TITLE = 'thread_title';
public const METADATA_PINNED_BY_TYPE = 'pinned_by_type';
public const METADATA_PINNED_BY_ID = 'pinned_by_id';
public const METADATA_PINNED_BY_NAME = 'pinned_by_name';
diff --git a/lib/Model/ScheduledMessage.php b/lib/Model/ScheduledMessage.php
new file mode 100644
index 0000000000..d4398d47ee
--- /dev/null
+++ b/lib/Model/ScheduledMessage.php
@@ -0,0 +1,145 @@
+addType('room_id', Types::BIGINT);
+ $this->addType('actorId', Types::STRING);
+ $this->addType('actorType', Types::STRING);
+ $this->addType('threadId', Types::BIGINT);
+ $this->addType('parentId', Types::BIGINT);
+ $this->addType('message', Types::TEXT);
+ $this->addType('messageType', Types::STRING);
+ $this->addType('metaData', Types::TEXT);
+ $this->addType('sendAt', Types::DATETIME);
+ $this->addType('createdAt', Types::DATETIME);
+ }
+
+ /**
+ * @return TalkScheduledMessageMetaData
+ */
+ public function getDecodedMetaData(): array {
+ return json_decode($this->metaData, true, 512, JSON_THROW_ON_ERROR);
+ }
+
+ public function setMetaData(?array $metaData): void {
+ $this->metaData = json_encode($metaData, JSON_THROW_ON_ERROR);
+ $this->markFieldUpdated('metaData');
+ }
+
+ /**
+ * @throws MessageTooLongException When the message is too long (~32k characters)
+ */
+ public function setMessage(string $message): void {
+ $message = trim($message);
+ if (mb_strlen($message, 'UTF-8') > ChatManager::MAX_CHAT_LENGTH) {
+ throw new MessageTooLongException('Comment message must not exceed ' . ChatManager::MAX_CHAT_LENGTH . ' characters');
+ }
+ $this->message = $message;
+ $this->markFieldUpdated('message');
+ }
+
+ #[\Override]
+ public function jsonSerialize(): array {
+ return [
+ 'roomId' => $this->getRoomId(),
+ 'actorId' => $this->getActorId(),
+ 'actorType' => $this->getActorType(),
+ 'threadId' => $this->getThreadId(),
+ 'parentId' => $this->getParentId(),
+ 'message' => $this->getMessage(),
+ 'messageType' => $this->getMessageType(),
+ 'createdAt' => $this->getCreatedAt()->getTimestamp(),
+ 'sendAt' => $this->getSendAt()?->getTimestamp(),
+ 'metaData' => $this->getDecodedMetaData(),
+ ];
+ }
+
+ /**
+ * @return TalkScheduledMessage
+ */
+ public function toArray(?Message $parent, ?Thread $thread) : array {
+ $data = [
+ 'id' => $this->id,
+ 'roomId' => $this->getRoomId(),
+ 'actorId' => $this->getActorId(),
+ 'actorType' => $this->getActorType(),
+ 'threadId' => $this->getThreadId(),
+ 'parentId' => $this->getParentId(),
+ 'message' => $this->getMessage(),
+ 'messageType' => $this->getMessageType(),
+ 'createdAt' => $this->getCreatedAt()->getTimestamp(),
+ 'sendAt' => $this->getSendAt()?->getTimestamp(),
+ ];
+
+ if ($parent !== null) {
+ $data['parent'] = $parent->toArray('json', $thread);
+ }
+
+ $metaData = $this->getDecodedMetaData();
+ if ($thread !== null) {
+ $data['threadExists'] = true;
+ $data['threadTitle'] = $thread->getName();
+ $metaData[self::METADATA_THREAD_TITLE] = $thread->getName();
+ } elseif (isset($metaData[self::METADATA_THREAD_TITLE]) && $this->getThreadId() === Thread::THREAD_CREATE) {
+ $data['threadExists'] = false;
+ $data['threadTitle'] = (string)$metaData[self::METADATA_THREAD_TITLE];
+ }
+ $data['metaData'] = $metaData;
+ return $data;
+ }
+}
diff --git a/lib/Model/ScheduledMessageMapper.php b/lib/Model/ScheduledMessageMapper.php
new file mode 100644
index 0000000000..ecf73b1cee
--- /dev/null
+++ b/lib/Model/ScheduledMessageMapper.php
@@ -0,0 +1,137 @@
+ findEntities(IQueryBuilder $query)
+ * @method ScheduledMessage update(ScheduledMessage $scheduledMessage)
+ * @method ScheduledMessage delete(ScheduledMessage $scheduledMessage)
+ * @template-extends QBMapper
+ */
+class ScheduledMessageMapper extends QBMapper {
+ public function __construct(
+ IDBConnection $db,
+ protected IGenerator $generator,
+ ) {
+ parent::__construct($db, 'talk_scheduled_msg', ScheduledMessage::class);
+ }
+
+ /**
+ * @throws DoesNotExistException
+ */
+ public function findById(Room $chat, int $id, string $actorType, string $actorId): ScheduledMessage {
+ $query = $this->db->getQueryBuilder();
+ $query->select('*')
+ ->from($this->getTableName())
+ ->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
+ ->andWhere($query->expr()->eq('actor_type', $query->createNamedParameter($actorType, IQueryBuilder::PARAM_STR)))
+ ->andWhere($query->expr()->eq('actor_id', $query->createNamedParameter($actorId, IQueryBuilder::PARAM_STR)))
+ ->andWhere($query->expr()->eq('room_id', $query->createNamedParameter($chat->getId(), IQueryBuilder::PARAM_STR)));
+
+ return $this->findEntity($query);
+ }
+
+ public function findByRoomAndActor(Room $chat, string $actorType, string $actorId): array {
+ $query = $this->db->getQueryBuilder();
+ $query->select('s.*');
+ $helper = new SelectHelper();
+ $helper->selectThreadsTable($query, aliasAll: true);
+ $query->from($this->getTableName(), 's')
+ ->where($query->expr()->eq('s.room_id', $query->createNamedParameter($chat->getId(), IQueryBuilder::PARAM_STR)))
+ ->andWhere($query->expr()->eq('s.actor_type', $query->createNamedParameter($actorType, IQueryBuilder::PARAM_STR)))
+ ->andWhere($query->expr()->eq('s.actor_id', $query->createNamedParameter($actorId, IQueryBuilder::PARAM_STR)))
+ ->leftJoin('s', 'talk_threads', 'th', $query->expr()->eq('s.thread_id', 'th.id'))
+ ->orderBy('s.send_at', 'ASC');
+
+ $cursor = $query->executeQuery();
+ $result = $cursor->fetchAll();
+ $cursor->closeCursor();
+
+ return $result;
+ }
+
+ public function getCountByActorAndRoom(Room $chat, string $actorType, string $actorId): int {
+ $query = $this->db->getQueryBuilder();
+ $query->select(['room_id', $query->func()->count('*')])
+ ->from($this->getTableName())
+ ->where($query->expr()->eq('actor_type', $query->createNamedParameter($actorType, IQueryBuilder::PARAM_STR)))
+ ->andWhere($query->expr()->eq('actor_id', $query->createNamedParameter($actorId, IQueryBuilder::PARAM_STR)))
+ ->andWhere($query->expr()->eq('room_id', $query->createNamedParameter($chat->getId(), IQueryBuilder::PARAM_STR)))
+ ->groupBy('room_id');
+
+ $result = $query->executeQuery();
+ $count = $result->rowCount();
+ $result->closeCursor();
+
+ return $count;
+ }
+
+ public function deleteMessagesByRoomAndActor(Room $chat, string $actorType, string $actorId): int {
+ $query = $this->db->getQueryBuilder();
+ $query->delete($this->getTableName())
+ ->where($query->expr()->eq('room_id', $query->createNamedParameter($chat->getId(), IQueryBuilder::PARAM_STR)))
+ ->andWhere($query->expr()->eq('actor_type', $query->createNamedParameter($actorType, IQueryBuilder::PARAM_STR)))
+ ->andWhere($query->expr()->eq('actor_id', $query->createNamedParameter($actorId, IQueryBuilder::PARAM_STR)));
+
+ return $query->executeStatement();
+ }
+
+ public function deleteMessagesByRoom(Room $chat): int {
+ $query = $this->db->getQueryBuilder();
+ $query->delete($this->getTableName())
+ ->where($query->expr()->eq('room_id', $query->createNamedParameter($chat->getId(), IQueryBuilder::PARAM_STR)));
+ return $query->executeStatement();
+ }
+
+ public function deleteById(Room $chat, int $id, string $actorType, string $actorId): int {
+ $query = $this->db->getQueryBuilder();
+ $query->delete($this->getTableName())
+ ->where($query->expr()->eq('room_id', $query->createNamedParameter($chat->getId(), IQueryBuilder::PARAM_STR)))
+ ->andWhere($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_STR)))
+ ->andWhere($query->expr()->eq('actor_type', $query->createNamedParameter($actorType, IQueryBuilder::PARAM_STR)))
+ ->andWhere($query->expr()->eq('actor_id', $query->createNamedParameter($actorId, IQueryBuilder::PARAM_STR)));
+
+ return $query->executeStatement();
+ }
+
+ public function deleteByActor(string $actorType, string $actorId): int {
+ $query = $this->db->getQueryBuilder();
+ $query->delete($this->getTableName())
+ ->where($query->expr()->eq('actor_type', $query->createNamedParameter($actorType, IQueryBuilder::PARAM_STR)))
+ ->andWhere($query->expr()->eq('actor_id', $query->createNamedParameter($actorId, IQueryBuilder::PARAM_STR)));
+
+ return $query->executeStatement();
+ }
+
+ public function getMessagesDue(\DateTime $dateTime): array {
+ $query = $this->db->getQueryBuilder();
+ $query->select('*')
+ ->from($this->getTableName())
+ ->where($query->expr()->lt('send_at', $query->createNamedParameter($dateTime, IQueryBuilder::PARAM_DATETIME_MUTABLE)));
+
+ return $this->findEntities($query);
+ }
+
+ #[\Override]
+ public function insert(Entity $entity): Entity {
+ /** @psalm-suppress InvalidArgument */
+ $entity->setId($this->generator->nextId());
+ return parent::insert($entity);
+ }
+}
diff --git a/lib/Model/SelectHelper.php b/lib/Model/SelectHelper.php
index db6d97f977..ea0b512cd8 100644
--- a/lib/Model/SelectHelper.php
+++ b/lib/Model/SelectHelper.php
@@ -50,6 +50,33 @@ class SelectHelper {
])->selectAlias($alias . 'id', 'r_id');
}
+ public function selectThreadsTable(IQueryBuilder $query, string $alias = 'th', bool $aliasAll = false): void {
+ if ($alias !== '') {
+ $alias .= '.';
+ }
+
+ if ($aliasAll) {
+ $query
+ ->selectAlias($alias . 'room_id', 'th_room_id')
+ ->selectAlias($alias . 'last_message_id', 'th_last_message_id')
+ ->selectAlias($alias . 'num_replies', 'th_num_replies')
+ ->selectAlias($alias . 'last_activity', 'th_last_activity')
+ ->selectAlias($alias . 'name', 'th_name')
+ ->selectAlias($alias . 'id', 'th_id');
+ return;
+ }
+
+ $query->addSelect([
+ $alias . 'room_id',
+ $alias . 'last_message_id',
+ $alias . 'num_replies',
+ $alias . 'last_activity',
+ $alias . 'name',
+ ])->selectAlias($alias . 'id', 'th_id');
+
+
+ }
+
public function selectAttendeesTable(IQueryBuilder $query, string $alias = 'a'): void {
if ($alias !== '') {
$alias .= '.';
diff --git a/lib/Participant.php b/lib/Participant.php
index f56495491b..b5336cd893 100644
--- a/lib/Participant.php
+++ b/lib/Participant.php
@@ -66,6 +66,15 @@ class Participant {
return \in_array($participantType, [self::GUEST, self::GUEST_MODERATOR], true);
}
+ public function isSelfJoinedOrGuest(): bool {
+ $participantType = $this->attendee->getParticipantType();
+ return \in_array($participantType, [self::GUEST, self::GUEST_MODERATOR, self::USER_SELF_JOINED], true);
+ }
+
+ public function getHasScheduledMessages(): bool {
+ return $this->attendee->getHasScheduledMessages();
+ }
+
public function hasModeratorPermissions(bool $guestModeratorAllowed = true): bool {
$participantType = $this->attendee->getParticipantType();
if (!$guestModeratorAllowed) {
diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php
index f8c8028475..2318442d5f 100644
--- a/lib/ResponseDefinitions.php
+++ b/lib/ResponseDefinitions.php
@@ -387,6 +387,8 @@ namespace OCA\Talk;
* lastPinnedId: int,
* // Required capability: `pinned-messages`
* hiddenPinnedId: int,
+ * // Required capability: `scheduled-messages` (local)
+ * hasScheduledMessages: bool,
* }
*
* @psalm-type TalkDashboardEventAttachment = array{
@@ -563,6 +565,30 @@ namespace OCA\Talk;
* rtl: bool,
* },
* }
+ *
+ * @psalm-type TalkScheduledMessageMetaData = array{
+ * threadId: int,
+ * threadTitle: string,
+ * silent: bool,
+ * lastEditedTime?: int,
+ * }
+ *
+ * @psalm-type TalkScheduledMessage = array{
+ * id: int,
+ * roomId: int,
+ * actorId: string,
+ * actorType: string,
+ * threadId: int,
+ * threadExists?: boolean,
+ * threadTitle?: string,
+ * parentId: ?int,
+ * parent?: TalkChatMessage,
+ * message: string,
+ * messageType: string,
+ * createdAt: int,
+ * sendAt: ?int,
+ * metaData: TalkScheduledMessageMetaData,
+ * }
*/
class ResponseDefinitions {
}
diff --git a/lib/Service/ParticipantService.php b/lib/Service/ParticipantService.php
index 50df51fa03..a190a290cc 100644
--- a/lib/Service/ParticipantService.php
+++ b/lib/Service/ParticipantService.php
@@ -2381,4 +2381,10 @@ class ParticipantService {
$this->cacheParticipant($room, $participant);
return $participant;
}
+
+ public function setHasScheduledMessages(Participant $participant, bool $hasScheduledMessages): void {
+ $attendee = $participant->getAttendee();
+ $attendee->setHasScheduledMessages($hasScheduledMessages);
+ $this->attendeeMapper->update($attendee);
+ }
}
diff --git a/lib/Service/RoomFormatter.php b/lib/Service/RoomFormatter.php
index 1868e9c826..ba709b9cda 100644
--- a/lib/Service/RoomFormatter.php
+++ b/lib/Service/RoomFormatter.php
@@ -158,6 +158,7 @@ class RoomFormatter {
'isArchived' => false,
'isImportant' => false,
'isSensitive' => false,
+ 'hasScheduledMessages' => false,
];
if ($room->isFederatedConversation()) {
@@ -345,6 +346,7 @@ class RoomFormatter {
&& ($room->getType() === Room::TYPE_GROUP || $room->getType() === Room::TYPE_PUBLIC)
&& $currentParticipant->hasModeratorPermissions(false)
&& $this->talkConfig->canUserEnableSIP($currentUser);
+ $roomData['hasScheduledMessages'] = $currentParticipant->getHasScheduledMessages();
}
} elseif ($attendee->getActorType() === Attendee::ACTOR_FEDERATED_USERS) {
$lastReadMessage = $attendee->getLastReadMessage();
diff --git a/lib/Service/ScheduledMessageService.php b/lib/Service/ScheduledMessageService.php
new file mode 100644
index 0000000000..eb09427788
--- /dev/null
+++ b/lib/Service/ScheduledMessageService.php
@@ -0,0 +1,200 @@
+setRoomId($chat->getId());
+ $scheduledMessage->setActorId($participant->getAttendee()->getActorId());
+ $scheduledMessage->setActorType($participant->getAttendee()->getActorType());
+ $scheduledMessage->setSendAt($sendAt);
+ $scheduledMessage->setMessage($message);
+ $scheduledMessage->setMessageType($messageType);
+ if ($parent instanceof IComment) {
+ $scheduledMessage->setParentId((int)$parent->getId());
+ }
+ $scheduledMessage->setThreadId($threadId);
+ $scheduledMessage->setMetaData($metadata);
+ $scheduledMessage->setCreatedAt($this->timeFactory->getDateTime());
+
+ $this->scheduledMessageMapper->insert($scheduledMessage);
+
+ return $scheduledMessage;
+ }
+
+ public function deleteMessage(Room $chat, int $id, Participant $participant): int {
+ return $this->scheduledMessageMapper->deleteById(
+ $chat,
+ $id,
+ $participant->getAttendee()->getActorType(),
+ $participant->getAttendee()->getActorId()
+ );
+ }
+
+ /**
+ * @throws DoesNotExistException
+ * @throws MessageTooLongException
+ * @throws \InvalidArgumentException
+ */
+ public function editMessage(
+ Room $chat,
+ int $id,
+ Participant $participant,
+ string $text,
+ bool $isSilent,
+ \DateTime $sendAt,
+ string $threadTitle = '',
+ ): ScheduledMessage {
+ $message = $this->scheduledMessageMapper->findById(
+ $chat,
+ $id,
+ $participant->getAttendee()->getActorType(),
+ $participant->getAttendee()->getActorId()
+ );
+
+ $metaData = $message->getDecodedMetaData();
+ if ($metaData[ScheduledMessage::METADATA_THREAD_ID] !== Thread::THREAD_CREATE && $threadTitle !== '') {
+ throw new \InvalidArgumentException('thread-title');
+ }
+
+ if ($metaData[ScheduledMessage::METADATA_THREAD_ID] === Thread::THREAD_CREATE && $threadTitle !== '') {
+ $metaData[ScheduledMessage::METADATA_THREAD_TITLE] = $threadTitle;
+ }
+
+ $metaData[ScheduledMessage::METADATA_LAST_EDITED_TIME] = $this->timeFactory->getTime();
+ $metaData[ScheduledMessage::METADATA_SILENT] = $isSilent;
+ $message->setMetaData($metaData);
+ $message->setMessage($text);
+ $message->setSendAt($sendAt);
+ $this->scheduledMessageMapper->update($message);
+
+ return $message;
+ }
+
+ public function deleteByActor(string $actorType, string $actorId): void {
+ $this->scheduledMessageMapper->deleteByActor($actorType, $actorId);
+ }
+
+ /**
+ * @return list
+ */
+ public function getMessages(Room $chat, Participant $participant): array {
+ $result = $this->scheduledMessageMapper->findByRoomAndActor(
+ $chat,
+ $participant->getAttendee()->getActorType(),
+ $participant->getAttendee()->getActorId()
+ );
+
+ $commentIds = array_filter(array_map(static function (array $result) {
+ return $result['parent_id'];
+ }, $result));
+ try {
+ $comments = $this->commentsManager->getCommentsById($commentIds);
+ } catch (Exception) {
+ $comments = [];
+ }
+
+ $messages = [];
+ foreach ($result as $row) {
+ $parent = $thread = null;
+ $entity = [];
+ foreach ($row as $field => $value) {
+ if (str_starts_with($field, 'th_')) {
+ $thread[substr($field, 3)] = $value;
+ continue;
+ }
+ $entity[$field] = $value;
+ }
+
+ $scheduleMessage = ScheduledMessage::fromRow($entity);
+ if ($entity['parent_id'] !== null && isset($comments[$entity['parent_id']])) {
+ $parent = $this->messageParser->createMessage($chat, $participant, $comments[$entity['parent_id']], $this->l);
+ $this->messageParser->parseMessage($parent);
+ }
+ if (in_array($thread['id'], [null, Thread::THREAD_NONE, Thread::THREAD_CREATE], true)) {
+ $thread = null;
+ } else {
+ $thread = Thread::fromRow($thread);
+ }
+ $messages[] = $this->parseScheduledMessage($scheduleMessage, $parent, $thread);
+ }
+
+ return $messages;
+ }
+
+ public function parseScheduledMessage(ScheduledMessage $message, ?Message $parentMessage, ?Thread $thread = null): array {
+ if ($thread === null
+ && $message->getThreadId() !== Thread::THREAD_NONE
+ && $message->getThreadId() !== Thread::THREAD_CREATE
+ ) {
+ try {
+ $thread = $this->threadService->findByThreadId(
+ $message->getRoomId(),
+ $message->getThreadId(),
+ );
+ } catch (DoesNotExistException $e) {
+ $this->logger->warning('Could not find thread ' . (string)$message->getThreadId() . ' for scheduled message', ['exception' => $e]);
+ $thread = null;
+ }
+ }
+ return $message->toArray($parentMessage, $thread ?? null);
+ }
+
+ public function getScheduledMessageCount(Room $chat, Participant $participant): int {
+ return $this->scheduledMessageMapper->getCountByActorAndRoom(
+ $chat,
+ $participant->getAttendee()->getActorType(),
+ $participant->getAttendee()->getActorId(),
+ );
+ }
+}
diff --git a/openapi-backend-sipbridge.json b/openapi-backend-sipbridge.json
index 60325ee754..a2b42977e1 100644
--- a/openapi-backend-sipbridge.json
+++ b/openapi-backend-sipbridge.json
@@ -700,7 +700,8 @@
"isImportant",
"isSensitive",
"lastPinnedId",
- "hiddenPinnedId"
+ "hiddenPinnedId",
+ "hasScheduledMessages"
],
"properties": {
"actorId": {
@@ -1003,6 +1004,10 @@
"type": "integer",
"format": "int64",
"description": "Required capability: `pinned-messages`"
+ },
+ "hasScheduledMessages": {
+ "type": "boolean",
+ "description": "Required capability: `scheduled-messages` (local)"
}
}
},
diff --git a/openapi-federation.json b/openapi-federation.json
index ff4a4a6999..9ce93b86cf 100644
--- a/openapi-federation.json
+++ b/openapi-federation.json
@@ -754,7 +754,8 @@
"isImportant",
"isSensitive",
"lastPinnedId",
- "hiddenPinnedId"
+ "hiddenPinnedId",
+ "hasScheduledMessages"
],
"properties": {
"actorId": {
@@ -1057,6 +1058,10 @@
"type": "integer",
"format": "int64",
"description": "Required capability: `pinned-messages`"
+ },
+ "hasScheduledMessages": {
+ "type": "boolean",
+ "description": "Required capability: `scheduled-messages` (local)"
}
}
},
diff --git a/openapi-full.json b/openapi-full.json
index 48dd07be52..8337c07660 100644
--- a/openapi-full.json
+++ b/openapi-full.json
@@ -1578,7 +1578,8 @@
"isImportant",
"isSensitive",
"lastPinnedId",
- "hiddenPinnedId"
+ "hiddenPinnedId",
+ "hasScheduledMessages"
],
"properties": {
"actorId": {
@@ -1881,6 +1882,10 @@
"type": "integer",
"format": "int64",
"description": "Required capability: `pinned-messages`"
+ },
+ "hasScheduledMessages": {
+ "type": "boolean",
+ "description": "Required capability: `scheduled-messages` (local)"
}
}
},
@@ -1912,6 +1917,98 @@
}
]
},
+ "ScheduledMessage": {
+ "type": "object",
+ "required": [
+ "id",
+ "roomId",
+ "actorId",
+ "actorType",
+ "threadId",
+ "parentId",
+ "message",
+ "messageType",
+ "createdAt",
+ "sendAt",
+ "metaData"
+ ],
+ "properties": {
+ "id": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "roomId": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "actorId": {
+ "type": "string"
+ },
+ "actorType": {
+ "type": "string"
+ },
+ "threadId": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "threadExists": {
+ "type": "boolean"
+ },
+ "threadTitle": {
+ "type": "string"
+ },
+ "parentId": {
+ "type": "integer",
+ "format": "int64",
+ "nullable": true
+ },
+ "parent": {
+ "$ref": "#/components/schemas/ChatMessage"
+ },
+ "message": {
+ "type": "string"
+ },
+ "messageType": {
+ "type": "string"
+ },
+ "createdAt": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "sendAt": {
+ "type": "integer",
+ "format": "int64",
+ "nullable": true
+ },
+ "metaData": {
+ "$ref": "#/components/schemas/ScheduledMessageMetaData"
+ }
+ }
+ },
+ "ScheduledMessageMetaData": {
+ "type": "object",
+ "required": [
+ "threadId",
+ "threadTitle",
+ "silent"
+ ],
+ "properties": {
+ "threadId": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "threadTitle": {
+ "type": "string"
+ },
+ "silent": {
+ "type": "boolean"
+ },
+ "lastEditedTime": {
+ "type": "integer",
+ "format": "int64"
+ }
+ }
+ },
"SignalingFederationSettings": {
"type": "object",
"required": [
@@ -7634,6 +7731,926 @@
}
}
},
+ "/ocs/v2.php/apps/spreed/api/{apiVersion}/chat/{token}/schedule": {
+ "get": {
+ "operationId": "chat-scheduled-messages",
+ "summary": "Get all scheduled nessages of a given room and participant",
+ "description": "The author and timestamp are automatically set to the current user and time.\nRequired capability: `scheduled-messages`",
+ "tags": [
+ "chat"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "apiVersion",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "enum": [
+ "v1"
+ ],
+ "default": "v1"
+ }
+ },
+ {
+ "name": "token",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "pattern": "^[a-z0-9]{4,30}$"
+ }
+ },
+ {
+ "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": "All scheduled messages for this room and participant",
+ "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/ScheduledMessage"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Could not get scheduled messages",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "error"
+ ],
+ "properties": {
+ "error": {
+ "type": "string",
+ "enum": [
+ "message"
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Actor not found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "error"
+ ],
+ "properties": {
+ "error": {
+ "type": "string",
+ "enum": [
+ "actor"
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Current user is not logged in",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "post": {
+ "operationId": "chat-schedule-message",
+ "summary": "Schedules the sending of a new chat message to the given room",
+ "description": "The author and timestamp are automatically set to the current user and time.\nRequired capability: `scheduled-messages`",
+ "tags": [
+ "chat"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "message",
+ "sendAt"
+ ],
+ "properties": {
+ "message": {
+ "type": "string",
+ "description": "The message to send"
+ },
+ "sendAt": {
+ "type": "integer",
+ "format": "int64",
+ "description": "When to send the scheduled message"
+ },
+ "replyTo": {
+ "type": "integer",
+ "format": "int64",
+ "default": 0,
+ "description": "Parent id which this scheduled message is a reply to",
+ "minimum": 0
+ },
+ "silent": {
+ "type": "boolean",
+ "default": false,
+ "description": "If sent silent the scheduled message will not create any notifications when sent"
+ },
+ "threadTitle": {
+ "type": "string",
+ "default": "",
+ "description": "Only supported when not replying, when given will create a thread (requires `threads` capability)"
+ },
+ "threadId": {
+ "type": "integer",
+ "format": "int64",
+ "default": 0,
+ "description": "Thread id without quoting a specific message (requires `threads` capability)"
+ }
+ }
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "apiVersion",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "enum": [
+ "v1"
+ ],
+ "default": "v1"
+ }
+ },
+ {
+ "name": "token",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "pattern": "^[a-z0-9]{4,30}$"
+ }
+ },
+ {
+ "name": "OCS-APIRequest",
+ "in": "header",
+ "description": "Required to be true for the API request to pass",
+ "required": true,
+ "schema": {
+ "type": "boolean",
+ "default": true
+ }
+ }
+ ],
+ "responses": {
+ "201": {
+ "description": "Message scheduled successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "$ref": "#/components/schemas/ScheduledMessage"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Scheduling the message is not possible",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "error"
+ ],
+ "properties": {
+ "error": {
+ "type": "string",
+ "enum": [
+ "message",
+ "reply-to",
+ "send-at"
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "413": {
+ "description": "Message too long",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "error"
+ ],
+ "properties": {
+ "error": {
+ "type": "string",
+ "enum": [
+ "message"
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Actor not found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "error"
+ ],
+ "properties": {
+ "error": {
+ "type": "string",
+ "enum": [
+ "actor"
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Current user is not logged in",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/ocs/v2.php/apps/spreed/api/{apiVersion}/chat/{token}/schedule/{messageId}": {
+ "post": {
+ "operationId": "chat-edit-scheduled-message",
+ "summary": "Update a scheduled message",
+ "description": "Required capability: `scheduled-messages`",
+ "tags": [
+ "chat"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "message",
+ "sendAt"
+ ],
+ "properties": {
+ "message": {
+ "type": "string",
+ "description": "The scheduled message to send"
+ },
+ "sendAt": {
+ "type": "integer",
+ "format": "int64",
+ "description": "When to send the scheduled message"
+ },
+ "silent": {
+ "type": "boolean",
+ "default": false,
+ "description": "If sent silent the scheduled message will not create any notifications"
+ },
+ "threadTitle": {
+ "type": "string",
+ "default": "",
+ "description": "The thread title if scheduled message is creating a thread"
+ }
+ }
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "apiVersion",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "enum": [
+ "v1"
+ ],
+ "default": "v1"
+ }
+ },
+ {
+ "name": "token",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "pattern": "^[a-z0-9]{4,30}$"
+ }
+ },
+ {
+ "name": "messageId",
+ "in": "path",
+ "description": "The scheduled message id",
+ "required": true,
+ "schema": {
+ "type": "integer",
+ "format": "int64"
+ }
+ },
+ {
+ "name": "OCS-APIRequest",
+ "in": "header",
+ "description": "Required to be true for the API request to pass",
+ "required": true,
+ "schema": {
+ "type": "boolean",
+ "default": true
+ }
+ }
+ ],
+ "responses": {
+ "202": {
+ "description": "Message updated successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "$ref": "#/components/schemas/ScheduledMessage"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Editing scheduled message is not possible",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "error"
+ ],
+ "properties": {
+ "error": {
+ "type": "string",
+ "enum": [
+ "message",
+ "send-at",
+ "thread-title"
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "413": {
+ "description": "Message too long",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "error"
+ ],
+ "properties": {
+ "error": {
+ "type": "string",
+ "enum": [
+ "message"
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Actor not found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "error"
+ ],
+ "properties": {
+ "error": {
+ "type": "string",
+ "enum": [
+ "actor"
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Current user is not logged in",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "delete": {
+ "operationId": "chat-delete-schedule-message",
+ "summary": "Delete a scheduled message",
+ "description": "Required capability: `scheduled-messages`",
+ "tags": [
+ "chat"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "apiVersion",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "enum": [
+ "v1"
+ ],
+ "default": "v1"
+ }
+ },
+ {
+ "name": "token",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "pattern": "^[a-z0-9]{4,30}$"
+ }
+ },
+ {
+ "name": "messageId",
+ "in": "path",
+ "description": "The scheduled message ud",
+ "required": true,
+ "schema": {
+ "type": "integer",
+ "format": "int64"
+ }
+ },
+ {
+ "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": "Message deleted",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Message not found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "error"
+ ],
+ "properties": {
+ "error": {
+ "type": "string",
+ "enum": [
+ "actor",
+ "message"
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Current user is not logged in",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/ocs/v2.php/apps/spreed/api/{apiVersion}/chat/{token}/share": {
"post": {
"operationId": "chat-share-object-to-chat",
diff --git a/openapi.json b/openapi.json
index de72365fd1..6208978905 100644
--- a/openapi.json
+++ b/openapi.json
@@ -1483,7 +1483,8 @@
"isImportant",
"isSensitive",
"lastPinnedId",
- "hiddenPinnedId"
+ "hiddenPinnedId",
+ "hasScheduledMessages"
],
"properties": {
"actorId": {
@@ -1786,6 +1787,10 @@
"type": "integer",
"format": "int64",
"description": "Required capability: `pinned-messages`"
+ },
+ "hasScheduledMessages": {
+ "type": "boolean",
+ "description": "Required capability: `scheduled-messages` (local)"
}
}
},
@@ -1817,6 +1822,98 @@
}
]
},
+ "ScheduledMessage": {
+ "type": "object",
+ "required": [
+ "id",
+ "roomId",
+ "actorId",
+ "actorType",
+ "threadId",
+ "parentId",
+ "message",
+ "messageType",
+ "createdAt",
+ "sendAt",
+ "metaData"
+ ],
+ "properties": {
+ "id": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "roomId": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "actorId": {
+ "type": "string"
+ },
+ "actorType": {
+ "type": "string"
+ },
+ "threadId": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "threadExists": {
+ "type": "boolean"
+ },
+ "threadTitle": {
+ "type": "string"
+ },
+ "parentId": {
+ "type": "integer",
+ "format": "int64",
+ "nullable": true
+ },
+ "parent": {
+ "$ref": "#/components/schemas/ChatMessage"
+ },
+ "message": {
+ "type": "string"
+ },
+ "messageType": {
+ "type": "string"
+ },
+ "createdAt": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "sendAt": {
+ "type": "integer",
+ "format": "int64",
+ "nullable": true
+ },
+ "metaData": {
+ "$ref": "#/components/schemas/ScheduledMessageMetaData"
+ }
+ }
+ },
+ "ScheduledMessageMetaData": {
+ "type": "object",
+ "required": [
+ "threadId",
+ "threadTitle",
+ "silent"
+ ],
+ "properties": {
+ "threadId": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "threadTitle": {
+ "type": "string"
+ },
+ "silent": {
+ "type": "boolean"
+ },
+ "lastEditedTime": {
+ "type": "integer",
+ "format": "int64"
+ }
+ }
+ },
"SignalingFederationSettings": {
"type": "object",
"required": [
@@ -7539,6 +7636,926 @@
}
}
},
+ "/ocs/v2.php/apps/spreed/api/{apiVersion}/chat/{token}/schedule": {
+ "get": {
+ "operationId": "chat-scheduled-messages",
+ "summary": "Get all scheduled nessages of a given room and participant",
+ "description": "The author and timestamp are automatically set to the current user and time.\nRequired capability: `scheduled-messages`",
+ "tags": [
+ "chat"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "apiVersion",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "enum": [
+ "v1"
+ ],
+ "default": "v1"
+ }
+ },
+ {
+ "name": "token",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "pattern": "^[a-z0-9]{4,30}$"
+ }
+ },
+ {
+ "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": "All scheduled messages for this room and participant",
+ "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/ScheduledMessage"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Could not get scheduled messages",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "error"
+ ],
+ "properties": {
+ "error": {
+ "type": "string",
+ "enum": [
+ "message"
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Actor not found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "error"
+ ],
+ "properties": {
+ "error": {
+ "type": "string",
+ "enum": [
+ "actor"
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Current user is not logged in",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "post": {
+ "operationId": "chat-schedule-message",
+ "summary": "Schedules the sending of a new chat message to the given room",
+ "description": "The author and timestamp are automatically set to the current user and time.\nRequired capability: `scheduled-messages`",
+ "tags": [
+ "chat"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "message",
+ "sendAt"
+ ],
+ "properties": {
+ "message": {
+ "type": "string",
+ "description": "The message to send"
+ },
+ "sendAt": {
+ "type": "integer",
+ "format": "int64",
+ "description": "When to send the scheduled message"
+ },
+ "replyTo": {
+ "type": "integer",
+ "format": "int64",
+ "default": 0,
+ "description": "Parent id which this scheduled message is a reply to",
+ "minimum": 0
+ },
+ "silent": {
+ "type": "boolean",
+ "default": false,
+ "description": "If sent silent the scheduled message will not create any notifications when sent"
+ },
+ "threadTitle": {
+ "type": "string",
+ "default": "",
+ "description": "Only supported when not replying, when given will create a thread (requires `threads` capability)"
+ },
+ "threadId": {
+ "type": "integer",
+ "format": "int64",
+ "default": 0,
+ "description": "Thread id without quoting a specific message (requires `threads` capability)"
+ }
+ }
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "apiVersion",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "enum": [
+ "v1"
+ ],
+ "default": "v1"
+ }
+ },
+ {
+ "name": "token",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "pattern": "^[a-z0-9]{4,30}$"
+ }
+ },
+ {
+ "name": "OCS-APIRequest",
+ "in": "header",
+ "description": "Required to be true for the API request to pass",
+ "required": true,
+ "schema": {
+ "type": "boolean",
+ "default": true
+ }
+ }
+ ],
+ "responses": {
+ "201": {
+ "description": "Message scheduled successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "$ref": "#/components/schemas/ScheduledMessage"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Scheduling the message is not possible",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "error"
+ ],
+ "properties": {
+ "error": {
+ "type": "string",
+ "enum": [
+ "message",
+ "reply-to",
+ "send-at"
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "413": {
+ "description": "Message too long",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "error"
+ ],
+ "properties": {
+ "error": {
+ "type": "string",
+ "enum": [
+ "message"
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Actor not found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "error"
+ ],
+ "properties": {
+ "error": {
+ "type": "string",
+ "enum": [
+ "actor"
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Current user is not logged in",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/ocs/v2.php/apps/spreed/api/{apiVersion}/chat/{token}/schedule/{messageId}": {
+ "post": {
+ "operationId": "chat-edit-scheduled-message",
+ "summary": "Update a scheduled message",
+ "description": "Required capability: `scheduled-messages`",
+ "tags": [
+ "chat"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "message",
+ "sendAt"
+ ],
+ "properties": {
+ "message": {
+ "type": "string",
+ "description": "The scheduled message to send"
+ },
+ "sendAt": {
+ "type": "integer",
+ "format": "int64",
+ "description": "When to send the scheduled message"
+ },
+ "silent": {
+ "type": "boolean",
+ "default": false,
+ "description": "If sent silent the scheduled message will not create any notifications"
+ },
+ "threadTitle": {
+ "type": "string",
+ "default": "",
+ "description": "The thread title if scheduled message is creating a thread"
+ }
+ }
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "apiVersion",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "enum": [
+ "v1"
+ ],
+ "default": "v1"
+ }
+ },
+ {
+ "name": "token",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "pattern": "^[a-z0-9]{4,30}$"
+ }
+ },
+ {
+ "name": "messageId",
+ "in": "path",
+ "description": "The scheduled message id",
+ "required": true,
+ "schema": {
+ "type": "integer",
+ "format": "int64"
+ }
+ },
+ {
+ "name": "OCS-APIRequest",
+ "in": "header",
+ "description": "Required to be true for the API request to pass",
+ "required": true,
+ "schema": {
+ "type": "boolean",
+ "default": true
+ }
+ }
+ ],
+ "responses": {
+ "202": {
+ "description": "Message updated successfully",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "$ref": "#/components/schemas/ScheduledMessage"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Editing scheduled message is not possible",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "error"
+ ],
+ "properties": {
+ "error": {
+ "type": "string",
+ "enum": [
+ "message",
+ "send-at",
+ "thread-title"
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "413": {
+ "description": "Message too long",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "error"
+ ],
+ "properties": {
+ "error": {
+ "type": "string",
+ "enum": [
+ "message"
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Actor not found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "error"
+ ],
+ "properties": {
+ "error": {
+ "type": "string",
+ "enum": [
+ "actor"
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Current user is not logged in",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "delete": {
+ "operationId": "chat-delete-schedule-message",
+ "summary": "Delete a scheduled message",
+ "description": "Required capability: `scheduled-messages`",
+ "tags": [
+ "chat"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "apiVersion",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "enum": [
+ "v1"
+ ],
+ "default": "v1"
+ }
+ },
+ {
+ "name": "token",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "pattern": "^[a-z0-9]{4,30}$"
+ }
+ },
+ {
+ "name": "messageId",
+ "in": "path",
+ "description": "The scheduled message ud",
+ "required": true,
+ "schema": {
+ "type": "integer",
+ "format": "int64"
+ }
+ },
+ {
+ "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": "Message deleted",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Message not found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "error"
+ ],
+ "properties": {
+ "error": {
+ "type": "string",
+ "enum": [
+ "actor",
+ "message"
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Current user is not logged in",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/ocs/v2.php/apps/spreed/api/{apiVersion}/chat/{token}/share": {
"post": {
"operationId": "chat-share-object-to-chat",
diff --git a/src/types/openapi/openapi-backend-sipbridge.ts b/src/types/openapi/openapi-backend-sipbridge.ts
index 9b466d3ba2..1cb25eb3f2 100644
--- a/src/types/openapi/openapi-backend-sipbridge.ts
+++ b/src/types/openapi/openapi-backend-sipbridge.ts
@@ -542,6 +542,8 @@ export type components = {
* @description Required capability: `pinned-messages`
*/
hiddenPinnedId: number;
+ /** @description Required capability: `scheduled-messages` (local) */
+ hasScheduledMessages: boolean;
};
RoomLastMessage: components["schemas"]["ChatMessage"] | components["schemas"]["ChatProxyMessage"];
};
diff --git a/src/types/openapi/openapi-federation.ts b/src/types/openapi/openapi-federation.ts
index ff302c9885..6ec535cc63 100644
--- a/src/types/openapi/openapi-federation.ts
+++ b/src/types/openapi/openapi-federation.ts
@@ -569,6 +569,8 @@ export type components = {
* @description Required capability: `pinned-messages`
*/
hiddenPinnedId: number;
+ /** @description Required capability: `scheduled-messages` (local) */
+ hasScheduledMessages: boolean;
};
RoomLastMessage: components["schemas"]["ChatMessage"] | components["schemas"]["ChatProxyMessage"];
};
diff --git a/src/types/openapi/openapi-full.ts b/src/types/openapi/openapi-full.ts
index cde479c18a..8cdc9dcb2b 100644
--- a/src/types/openapi/openapi-full.ts
+++ b/src/types/openapi/openapi-full.ts
@@ -421,6 +421,56 @@ export type paths = {
patch?: never;
trace?: never;
};
+ "/ocs/v2.php/apps/spreed/api/{apiVersion}/chat/{token}/schedule": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /**
+ * Get all scheduled nessages of a given room and participant
+ * @description The author and timestamp are automatically set to the current user and time.
+ * Required capability: `scheduled-messages`
+ */
+ get: operations["chat-scheduled-messages"];
+ put?: never;
+ /**
+ * Schedules the sending of a new chat message to the given room
+ * @description The author and timestamp are automatically set to the current user and time.
+ * Required capability: `scheduled-messages`
+ */
+ post: operations["chat-schedule-message"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/ocs/v2.php/apps/spreed/api/{apiVersion}/chat/{token}/schedule/{messageId}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ /**
+ * Update a scheduled message
+ * @description Required capability: `scheduled-messages`
+ */
+ post: operations["chat-edit-scheduled-message"];
+ /**
+ * Delete a scheduled message
+ * @description Required capability: `scheduled-messages`
+ */
+ delete: operations["chat-delete-schedule-message"];
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
"/ocs/v2.php/apps/spreed/api/{apiVersion}/chat/{token}/share": {
parameters: {
query?: never;
@@ -2952,11 +3002,43 @@ export type components = {
* @description Required capability: `pinned-messages`
*/
hiddenPinnedId: number;
+ /** @description Required capability: `scheduled-messages` (local) */
+ hasScheduledMessages: boolean;
};
RoomLastMessage: components["schemas"]["ChatMessage"] | components["schemas"]["ChatProxyMessage"];
RoomWithInvalidInvitations: components["schemas"]["Room"] & {
invalidParticipants: components["schemas"]["InvitationList"];
};
+ ScheduledMessage: {
+ /** Format: int64 */
+ id: number;
+ /** Format: int64 */
+ roomId: number;
+ actorId: string;
+ actorType: string;
+ /** Format: int64 */
+ threadId: number;
+ threadExists?: boolean;
+ threadTitle?: string;
+ /** Format: int64 */
+ parentId: number | null;
+ parent?: components["schemas"]["ChatMessage"];
+ message: string;
+ messageType: string;
+ /** Format: int64 */
+ createdAt: number;
+ /** Format: int64 */
+ sendAt: number | null;
+ metaData: components["schemas"]["ScheduledMessageMetaData"];
+ };
+ ScheduledMessageMetaData: {
+ /** Format: int64 */
+ threadId: number;
+ threadTitle: string;
+ silent: boolean;
+ /** Format: int64 */
+ lastEditedTime?: number;
+ };
SignalingFederationSettings: {
server: string;
nextcloudServer: string;
@@ -5228,6 +5310,399 @@ export interface operations {
};
};
};
+ "chat-scheduled-messages": {
+ parameters: {
+ query?: never;
+ header: {
+ /** @description Required to be true for the API request to pass */
+ "OCS-APIRequest": boolean;
+ };
+ path: {
+ apiVersion: "v1";
+ token: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description All scheduled messages for this room and participant */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: components["schemas"]["ScheduledMessage"][];
+ };
+ };
+ };
+ };
+ /** @description Could not get scheduled messages */
+ 400: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: {
+ /** @enum {string} */
+ error: "message";
+ };
+ };
+ };
+ };
+ };
+ /** @description Current user is not logged in */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: unknown;
+ };
+ };
+ };
+ };
+ /** @description Actor not found */
+ 404: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: {
+ /** @enum {string} */
+ error: "actor";
+ };
+ };
+ };
+ };
+ };
+ };
+ };
+ "chat-schedule-message": {
+ parameters: {
+ query?: never;
+ header: {
+ /** @description Required to be true for the API request to pass */
+ "OCS-APIRequest": boolean;
+ };
+ path: {
+ apiVersion: "v1";
+ token: string;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": {
+ /** @description The message to send */
+ message: string;
+ /**
+ * Format: int64
+ * @description When to send the scheduled message
+ */
+ sendAt: number;
+ /**
+ * Format: int64
+ * @description Parent id which this scheduled message is a reply to
+ * @default 0
+ */
+ replyTo?: number;
+ /**
+ * @description If sent silent the scheduled message will not create any notifications when sent
+ * @default false
+ */
+ silent?: boolean;
+ /**
+ * @description Only supported when not replying, when given will create a thread (requires `threads` capability)
+ * @default
+ */
+ threadTitle?: string;
+ /**
+ * Format: int64
+ * @description Thread id without quoting a specific message (requires `threads` capability)
+ * @default 0
+ */
+ threadId?: number;
+ };
+ };
+ };
+ responses: {
+ /** @description Message scheduled successfully */
+ 201: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: components["schemas"]["ScheduledMessage"];
+ };
+ };
+ };
+ };
+ /** @description Scheduling the message is not possible */
+ 400: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: {
+ /** @enum {string} */
+ error: "message" | "reply-to" | "send-at";
+ };
+ };
+ };
+ };
+ };
+ /** @description Current user is not logged in */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: unknown;
+ };
+ };
+ };
+ };
+ /** @description Actor not found */
+ 404: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: {
+ /** @enum {string} */
+ error: "actor";
+ };
+ };
+ };
+ };
+ };
+ /** @description Message too long */
+ 413: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: {
+ /** @enum {string} */
+ error: "message";
+ };
+ };
+ };
+ };
+ };
+ };
+ };
+ "chat-edit-scheduled-message": {
+ parameters: {
+ query?: never;
+ header: {
+ /** @description Required to be true for the API request to pass */
+ "OCS-APIRequest": boolean;
+ };
+ path: {
+ apiVersion: "v1";
+ token: string;
+ /** @description The scheduled message id */
+ messageId: number;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": {
+ /** @description The scheduled message to send */
+ message: string;
+ /**
+ * Format: int64
+ * @description When to send the scheduled message
+ */
+ sendAt: number;
+ /**
+ * @description If sent silent the scheduled message will not create any notifications
+ * @default false
+ */
+ silent?: boolean;
+ /**
+ * @description The thread title if scheduled message is creating a thread
+ * @default
+ */
+ threadTitle?: string;
+ };
+ };
+ };
+ responses: {
+ /** @description Message updated successfully */
+ 202: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: components["schemas"]["ScheduledMessage"];
+ };
+ };
+ };
+ };
+ /** @description Editing scheduled message is not possible */
+ 400: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: {
+ /** @enum {string} */
+ error: "message" | "send-at" | "thread-title";
+ };
+ };
+ };
+ };
+ };
+ /** @description Current user is not logged in */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: unknown;
+ };
+ };
+ };
+ };
+ /** @description Actor not found */
+ 404: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: {
+ /** @enum {string} */
+ error: "actor";
+ };
+ };
+ };
+ };
+ };
+ /** @description Message too long */
+ 413: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: {
+ /** @enum {string} */
+ error: "message";
+ };
+ };
+ };
+ };
+ };
+ };
+ };
+ "chat-delete-schedule-message": {
+ parameters: {
+ query?: never;
+ header: {
+ /** @description Required to be true for the API request to pass */
+ "OCS-APIRequest": boolean;
+ };
+ path: {
+ apiVersion: "v1";
+ token: string;
+ /** @description The scheduled message ud */
+ messageId: number;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Message deleted */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: Record;
+ };
+ };
+ };
+ };
+ /** @description Current user is not logged in */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: unknown;
+ };
+ };
+ };
+ };
+ /** @description Message not found */
+ 404: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: {
+ /** @enum {string} */
+ error: "actor" | "message";
+ };
+ };
+ };
+ };
+ };
+ };
+ };
"chat-get-objects-shared-in-room": {
parameters: {
query: {
diff --git a/src/types/openapi/openapi.ts b/src/types/openapi/openapi.ts
index 3801d59fc7..916c719ece 100644
--- a/src/types/openapi/openapi.ts
+++ b/src/types/openapi/openapi.ts
@@ -421,6 +421,56 @@ export type paths = {
patch?: never;
trace?: never;
};
+ "/ocs/v2.php/apps/spreed/api/{apiVersion}/chat/{token}/schedule": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /**
+ * Get all scheduled nessages of a given room and participant
+ * @description The author and timestamp are automatically set to the current user and time.
+ * Required capability: `scheduled-messages`
+ */
+ get: operations["chat-scheduled-messages"];
+ put?: never;
+ /**
+ * Schedules the sending of a new chat message to the given room
+ * @description The author and timestamp are automatically set to the current user and time.
+ * Required capability: `scheduled-messages`
+ */
+ post: operations["chat-schedule-message"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/ocs/v2.php/apps/spreed/api/{apiVersion}/chat/{token}/schedule/{messageId}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ /**
+ * Update a scheduled message
+ * @description Required capability: `scheduled-messages`
+ */
+ post: operations["chat-edit-scheduled-message"];
+ /**
+ * Delete a scheduled message
+ * @description Required capability: `scheduled-messages`
+ */
+ delete: operations["chat-delete-schedule-message"];
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
"/ocs/v2.php/apps/spreed/api/{apiVersion}/chat/{token}/share": {
parameters: {
query?: never;
@@ -2414,11 +2464,43 @@ export type components = {
* @description Required capability: `pinned-messages`
*/
hiddenPinnedId: number;
+ /** @description Required capability: `scheduled-messages` (local) */
+ hasScheduledMessages: boolean;
};
RoomLastMessage: components["schemas"]["ChatMessage"] | components["schemas"]["ChatProxyMessage"];
RoomWithInvalidInvitations: components["schemas"]["Room"] & {
invalidParticipants: components["schemas"]["InvitationList"];
};
+ ScheduledMessage: {
+ /** Format: int64 */
+ id: number;
+ /** Format: int64 */
+ roomId: number;
+ actorId: string;
+ actorType: string;
+ /** Format: int64 */
+ threadId: number;
+ threadExists?: boolean;
+ threadTitle?: string;
+ /** Format: int64 */
+ parentId: number | null;
+ parent?: components["schemas"]["ChatMessage"];
+ message: string;
+ messageType: string;
+ /** Format: int64 */
+ createdAt: number;
+ /** Format: int64 */
+ sendAt: number | null;
+ metaData: components["schemas"]["ScheduledMessageMetaData"];
+ };
+ ScheduledMessageMetaData: {
+ /** Format: int64 */
+ threadId: number;
+ threadTitle: string;
+ silent: boolean;
+ /** Format: int64 */
+ lastEditedTime?: number;
+ };
SignalingFederationSettings: {
server: string;
nextcloudServer: string;
@@ -4690,6 +4772,399 @@ export interface operations {
};
};
};
+ "chat-scheduled-messages": {
+ parameters: {
+ query?: never;
+ header: {
+ /** @description Required to be true for the API request to pass */
+ "OCS-APIRequest": boolean;
+ };
+ path: {
+ apiVersion: "v1";
+ token: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description All scheduled messages for this room and participant */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: components["schemas"]["ScheduledMessage"][];
+ };
+ };
+ };
+ };
+ /** @description Could not get scheduled messages */
+ 400: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: {
+ /** @enum {string} */
+ error: "message";
+ };
+ };
+ };
+ };
+ };
+ /** @description Current user is not logged in */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: unknown;
+ };
+ };
+ };
+ };
+ /** @description Actor not found */
+ 404: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: {
+ /** @enum {string} */
+ error: "actor";
+ };
+ };
+ };
+ };
+ };
+ };
+ };
+ "chat-schedule-message": {
+ parameters: {
+ query?: never;
+ header: {
+ /** @description Required to be true for the API request to pass */
+ "OCS-APIRequest": boolean;
+ };
+ path: {
+ apiVersion: "v1";
+ token: string;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": {
+ /** @description The message to send */
+ message: string;
+ /**
+ * Format: int64
+ * @description When to send the scheduled message
+ */
+ sendAt: number;
+ /**
+ * Format: int64
+ * @description Parent id which this scheduled message is a reply to
+ * @default 0
+ */
+ replyTo?: number;
+ /**
+ * @description If sent silent the scheduled message will not create any notifications when sent
+ * @default false
+ */
+ silent?: boolean;
+ /**
+ * @description Only supported when not replying, when given will create a thread (requires `threads` capability)
+ * @default
+ */
+ threadTitle?: string;
+ /**
+ * Format: int64
+ * @description Thread id without quoting a specific message (requires `threads` capability)
+ * @default 0
+ */
+ threadId?: number;
+ };
+ };
+ };
+ responses: {
+ /** @description Message scheduled successfully */
+ 201: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: components["schemas"]["ScheduledMessage"];
+ };
+ };
+ };
+ };
+ /** @description Scheduling the message is not possible */
+ 400: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: {
+ /** @enum {string} */
+ error: "message" | "reply-to" | "send-at";
+ };
+ };
+ };
+ };
+ };
+ /** @description Current user is not logged in */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: unknown;
+ };
+ };
+ };
+ };
+ /** @description Actor not found */
+ 404: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: {
+ /** @enum {string} */
+ error: "actor";
+ };
+ };
+ };
+ };
+ };
+ /** @description Message too long */
+ 413: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: {
+ /** @enum {string} */
+ error: "message";
+ };
+ };
+ };
+ };
+ };
+ };
+ };
+ "chat-edit-scheduled-message": {
+ parameters: {
+ query?: never;
+ header: {
+ /** @description Required to be true for the API request to pass */
+ "OCS-APIRequest": boolean;
+ };
+ path: {
+ apiVersion: "v1";
+ token: string;
+ /** @description The scheduled message id */
+ messageId: number;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": {
+ /** @description The scheduled message to send */
+ message: string;
+ /**
+ * Format: int64
+ * @description When to send the scheduled message
+ */
+ sendAt: number;
+ /**
+ * @description If sent silent the scheduled message will not create any notifications
+ * @default false
+ */
+ silent?: boolean;
+ /**
+ * @description The thread title if scheduled message is creating a thread
+ * @default
+ */
+ threadTitle?: string;
+ };
+ };
+ };
+ responses: {
+ /** @description Message updated successfully */
+ 202: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: components["schemas"]["ScheduledMessage"];
+ };
+ };
+ };
+ };
+ /** @description Editing scheduled message is not possible */
+ 400: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: {
+ /** @enum {string} */
+ error: "message" | "send-at" | "thread-title";
+ };
+ };
+ };
+ };
+ };
+ /** @description Current user is not logged in */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: unknown;
+ };
+ };
+ };
+ };
+ /** @description Actor not found */
+ 404: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: {
+ /** @enum {string} */
+ error: "actor";
+ };
+ };
+ };
+ };
+ };
+ /** @description Message too long */
+ 413: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: {
+ /** @enum {string} */
+ error: "message";
+ };
+ };
+ };
+ };
+ };
+ };
+ };
+ "chat-delete-schedule-message": {
+ parameters: {
+ query?: never;
+ header: {
+ /** @description Required to be true for the API request to pass */
+ "OCS-APIRequest": boolean;
+ };
+ path: {
+ apiVersion: "v1";
+ token: string;
+ /** @description The scheduled message ud */
+ messageId: number;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Message deleted */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: Record;
+ };
+ };
+ };
+ };
+ /** @description Current user is not logged in */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: unknown;
+ };
+ };
+ };
+ };
+ /** @description Message not found */
+ 404: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: {
+ /** @enum {string} */
+ error: "actor" | "message";
+ };
+ };
+ };
+ };
+ };
+ };
+ };
"chat-get-objects-shared-in-room": {
parameters: {
query: {
diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php
index 84600943ee..78fcd39e72 100644
--- a/tests/integration/features/bootstrap/FeatureContext.php
+++ b/tests/integration/features/bootstrap/FeatureContext.php
@@ -1934,6 +1934,140 @@ class FeatureContext implements Context, SnippetAcceptingContext {
Assert::assertEquals(implode("\n", $expected) . "\n", $this->response->getBody()->getContents());
}
+ #[When('/^user "([^"]*)" schedules a message to room "([^"]*)" with (\d+)(?: \((v1)\))?$/')]
+ public function userSchedulesMessageToRoom(string $user, string $identifier, int $statusCode, string $apiVersion = 'v1', ?TableNode $formData = null): void {
+ $row = $formData->getRowsHash();
+ $row['sendAt'] = (int)$row['sendAt'];
+ if (isset($row['replyTo'])) {
+ $row['replyTo'] = self::$textToMessageId[$row['replyTo']];
+ }
+ if (isset($row['threadId']) && $row['threadId'] !== '0' && $row['threadId'] !== '-1') {
+ $row['threadId'] = self::$titleToThreadId[$row['threadId']];
+ }
+
+ $this->setCurrentUser($user);
+ $this->sendRequest(
+ 'POST',
+ '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '/schedule',
+ $row
+ );
+
+ $this->assertStatusCode($this->response, $statusCode);
+ sleep(1); // make sure Postgres manages the order of the messages
+
+ $response = $this->getDataFromResponse($this->response);
+ if (isset($response['id'])) {
+ self::$textToMessageId[$row['message']] = $response['id'];
+ self::$messageIdToText[$response['id']] = $row['message'];
+ }
+ }
+
+ #[When('/^user "([^"]*)" updates scheduled message "([^"]*)" in room "([^"]*)" with (\d+)(?: \((v1)\))?$/')]
+ public function userUpdatesScheduledMessageInRoom(string $user, string $message, string $identifier, int $statusCode, string $apiVersion = 'v1', ?TableNode $formData = null): void {
+ $row = $formData->getRowsHash();
+ $id = self::$textToMessageId[$message];
+ $row['sendAt'] = (int)$row['sendAt'];
+
+ $this->setCurrentUser($user);
+ $this->sendRequest(
+ 'POST',
+ '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '/schedule/' . $id,
+ $row
+ );
+
+ $this->assertStatusCode($this->response, $statusCode);
+ if ($this->response->getStatusCode() !== 202) {
+ return;
+ }
+ sleep(1); // make sure Postgres manages the order of the messages
+
+ $response = $this->getDataFromResponse($this->response);
+ self::$textToMessageId[$row['message']] = $response['id'];
+ self::$messageIdToText[$response['id']] = $row['message'];
+ Assert::assertEquals($row['message'], $response['message']);
+ Assert::assertEquals($row['sendAt'], $response['sendAt']);
+ Assert::assertArrayHasKey('metaData', $response);
+ $metaData = $response['metaData'];
+ Assert::assertArrayHasKey('silent', $metaData);
+ Assert::assertArrayHasKey('threadTitle', $metaData);
+ Assert::assertArrayHasKey('threadId', $metaData);
+ Assert::assertArrayHasKey('lastEditedTime', $metaData);
+ if (isset($row['silent'])) {
+ Assert::assertEquals($metaData['silent'], (bool)$row['silent']);
+ }
+ if (isset($row['threadTitle'])) {
+ Assert::assertEquals($metaData['threadTitle'], (bool)$row['threadTitle']);
+ }
+ }
+
+ #[When('/^user "([^"]*)" deletes scheduled message "([^"]*)" from room "([^"]*)" with (\d+)(?: \((v1)\))?$/')]
+ public function userDeletesScheduledMessageFromRoom(string $user, string $message, string $identifier, int $statusCode, string $apiVersion = 'v1', ?TableNode $formData = null): void {
+ $this->setCurrentUser($user);
+ $this->sendRequest(
+ 'DELETE',
+ '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '/schedule/' . self::$textToMessageId[$message],
+ );
+
+ $this->assertStatusCode($this->response, $statusCode);
+ }
+
+ #[Then('/^user "([^"]*)" sees the following scheduled messages in room "([^"]*)" with (\d+)(?: \((v1)\))?$/')]
+ public function userSeesTheFollowingScheduledMessagesInRoom(string $user, string $identifier, int $statusCode, string $apiVersion = 'v1', ?TableNode $formData = null): void {
+ $this->setCurrentUser($user);
+ $this->sendRequest(
+ 'GET',
+ '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '/schedule',
+ );
+ $this->assertStatusCode($this->response, $statusCode);
+
+ if ($statusCode === 304) {
+ return;
+ }
+
+ $data = $this->getDataFromResponse($this->response);
+ foreach ($data as &$message) {
+ Assert::assertArrayHasKey('createdAt', $message);
+ Assert::assertIsInt($message['createdAt']);
+ unset($message['createdAt']);
+ $metaData = $message['metaData'];
+ if (isset($metaData['lastEditedTime'])) {
+ $metaData['lastEditedTime'] = 0;
+ }
+ if (isset($message['parent'])) {
+ $parent = $message['parent'];
+ Assert::assertArrayHasKey('message', $parent);
+ Assert::assertArrayHasKey('actorId', $parent);
+ $message['parent'] = self::$messageIdToText[$parent['id']];
+ }
+ $message['metaData'] = $metaData;
+ }
+
+ $expected = $formData->getColumnsHash();
+ foreach ($expected as &$row) {
+ $row['id'] = self::$textToMessageId[$row['message']];
+ $row['sendAt'] = (int)$row['sendAt'];
+ $row['metaData'] = json_decode($row['metaData'], true);
+ $row['roomId'] = self::$identifierToId[$row['roomId']];
+ $row['parentId'] = ($row['parentId'] === 'null' ? null : self::$textToMessageId[$row['parentId']]);
+ if (isset($row['parent'])) {
+ $parent = [];
+ }
+ if ($row['threadId'] === '-1') {
+ $row['threadId'] = -1;
+ $row['threadExists'] = false;
+ $row['threadTitle'] = $row['metaData']['threadTitle'];
+ } elseif ($row['threadId'] !== '0') {
+ $row['threadId'] = self::$titleToThreadId[$row['threadId']];
+ $row['threadTitle'] = self::$threadIdToTitle[$row['threadId']];
+ $row['threadExists'] = true;
+ $row['metaData']['threadId'] = $row['threadId'];
+ } else {
+ $row['threadId'] = (int)$row['threadId'];
+ }
+ }
+ Assert::assertEquals($expected, $data);
+ }
+
#[Then('/^user "([^"]*)" (silent sends|sends) message ("[^"]*"|\'[^\']*\') to room "([^"]*)" with (\d+)(?: \((v1)\))?$/')]
public function userSendsMessageToRoom(string $user, string $sendingMode, string $message, string $identifier, int $statusCode, string $apiVersion = 'v1'): void {
$this->userPostThreadToRoom($user, $sendingMode, '', $message, $identifier, $statusCode, $apiVersion);
diff --git a/tests/integration/features/chat-4/scheduled-messages.feature b/tests/integration/features/chat-4/scheduled-messages.feature
new file mode 100644
index 0000000000..50f576dacb
--- /dev/null
+++ b/tests/integration/features/chat-4/scheduled-messages.feature
@@ -0,0 +1,111 @@
+Feature: chat-4/scheduling
+ Background:
+ Given user "participant1" exists
+ Given user "participant2" exists
+ Given user "participant1" creates room "room" (v4)
+ | roomType | 2 |
+ | roomName | room |
+ And user "participant1" adds user "participant2" to room "room" with 200 (v4)
+ And user "participant2" sends message "Message" to room "room" with 201
+
+ Scenario: Schedule a message
+ When user "participant1" schedules a message to room "room" with 201
+ | message | Message 1 |
+ | sendAt | 1985514582 |
+ Then user "participant1" sees the following scheduled messages in room "room" with 200
+ | id | roomId | actorType | actorId | threadId | parentId | message | messageType | sendAt | metaData |
+ | Message 1 | room | users | participant1 | 0 | null | Message 1 | comment | 1985514582 | {"threadTitle":"","silent":false,"threadId":0} |
+
+ Scenario: Schedule a silent message
+ When user "participant1" schedules a message to room "room" with 201
+ | message | Message 2 |
+ | sendAt | 1985514582 |
+ | silent | true |
+ Then user "participant1" sees the following scheduled messages in room "room" with 200
+ | id | roomId | actorType | actorId | threadId | parentId | message | messageType | sendAt | metaData |
+ | Message 2 | room | users | participant1 | 0 | null | Message 2 | comment | 1985514582 | {"threadTitle":"","silent":true,"threadId":0} |
+
+ Scenario: Schedule a message reply
+ When user "participant1" schedules a message to room "room" with 201
+ | message | Message 3 |
+ | sendAt | 1985514582 |
+ | replyTo | Message |
+ Then user "participant1" sees the following scheduled messages in room "room" with 200
+ | id | roomId | actorType | actorId | threadId | parentId | parent | message | messageType | sendAt | metaData |
+ | Message 3 | room | users | participant1 | 0 | Message | Message |Message 3 | comment | 1985514582 | {"threadTitle":"","silent":false,"threadId":0} |
+
+ Scenario: Schedule a thread
+ When user "participant1" schedules a message to room "room" with 201
+ | message | Message 4 |
+ | sendAt | 1985514582 |
+ | threadTitle | Scheduled Thread |
+ Then user "participant1" sees the following scheduled messages in room "room" with 200
+ | id | roomId | actorType | actorId | threadId | parentId | message | messageType | sendAt | metaData |
+ | Message 4 | room | users | participant1 | -1 | null | Message 4 | comment | 1985514582 | {"threadTitle":"Scheduled Thread","silent":false,"threadId":-1} |
+
+ Scenario: Schedule a thread reply
+ Given user "participant1" sends thread "Thread 1" with message "Message 0" to room "room" with 201
+ When user "participant1" schedules a message to room "room" with 201
+ | message | Message 5 |
+ | sendAt | 1985514582 |
+ | threadId | Thread 1 |
+ Then user "participant1" sees the following scheduled messages in room "room" with 200
+ | id | roomId | actorType | actorId | threadId | parentId | message | messageType | sendAt | metaData |
+ | Message 5 | room | users | participant1 | Thread 1 | null | Message 5 | comment | 1985514582 | {"threadTitle":"Thread 1","silent":false,"threadId":"Thread 1"} |
+
+ Scenario: Schedule a quoted thread reply
+ Given user "participant1" sends thread "Thread 1" with message "Message 0" to room "room" with 201
+ When user "participant1" schedules a message to room "room" with 201
+ | message | Message 5 |
+ | sendAt | 1985514582 |
+ | replyTo | Message 0 |
+ | threadId | Thread 1 |
+ Then user "participant1" sees the following scheduled messages in room "room" with 200
+ | id | roomId | actorType | actorId | threadId | parentId | parent | message | messageType | sendAt | metaData |
+ | Message 5 | room | users | participant1 | Thread 1 | Message 0 | Message 0 | Message 5 | comment | 1985514582 | {"threadTitle":"Thread 1","silent":false,"threadId":"Thread 1"} |
+
+ Scenario: Schedule two messages and delete the first
+ When user "participant1" schedules a message to room "room" with 201
+ | message | Message 1 |
+ | sendAt | 1985514582 |
+ | silent | false |
+ When user "participant1" schedules a message to room "room" with 201
+ | message |Message 2 |
+ | sendAt |1985514584 |
+ | silent | true |
+ Then user "participant1" sees the following scheduled messages in room "room" with 200
+ | id | roomId | actorType | actorId | threadId | parentId | message | messageType | sendAt | metaData |
+ | Message 1 | room | users | participant1 | 0 | null | Message 1 | comment | 1985514582 | {"threadTitle":"","silent":false,"threadId":0} |
+ | Message 2 | room | users | participant1 | 0 | null | Message 2 | comment | 1985514584 | {"threadTitle":"","silent":true,"threadId":0} |
+ When user "participant1" deletes scheduled message "Message 1" from room "room" with 200
+ Then user "participant1" sees the following scheduled messages in room "room" with 200
+ | id | roomId | actorType | actorId | threadId | parentId | message | messageType | sendAt | metaData |
+ | Message 2 | room | users | participant1 | 0 | null | Message 2 | comment | 1985514584 | {"threadTitle":"","silent":true,"threadId":0} |
+
+ Scenario: edit a scheduled message
+ When user "participant1" schedules a message to room "room" with 201
+ | message | Message 1 |
+ | sendAt | 1985514582 |
+ Then user "participant1" sees the following scheduled messages in room "room" with 200
+ | id | roomId | actorType | actorId | threadId | parentId | message | messageType | sendAt | metaData |
+ | Message 1 | room | users | participant1 | 0 | null | Message 1 | comment | 1985514582 | {"threadTitle":"","silent":false,"threadId":0} |
+ When user "participant1" updates scheduled message "Message 1" in room "room" with 202
+ | message | Message 1 edited |
+ | sendAt | 1985514582 |
+ Then user "participant1" sees the following scheduled messages in room "room" with 200
+ | id | roomId | actorType | actorId | threadId | parentId | message | messageType | sendAt | metaData |
+ | Message 1 edited | room | users | participant1 | 0 | null | Message 1 edited | comment | 1985514582 | {"threadTitle":"","silent":false,"threadId":0,"lastEditedTime":0} |
+ When user "participant1" updates scheduled message "Message 1" in room "room" with 202
+ | message | Message 1 edited |
+ | sendAt | 1985514582 |
+ | silent | true |
+ Then user "participant1" sees the following scheduled messages in room "room" with 200
+ | id | roomId | actorType | actorId | threadId | parentId | message | messageType | sendAt | metaData |
+ | Message 1 edited | room | users | participant1 | 0 | null | Message 1 edited | comment | 1985514582 | {"threadTitle":"","silent":true,"threadId":0,"lastEditedTime":0} |
+ When user "participant1" updates scheduled message "Message 1" in room "room" with 400
+ | message | Message 1 edited |
+ | sendAt | 1985514582 |
+ | threadTitle | Abcd |
+ Then user "participant1" sees the following scheduled messages in room "room" with 200
+ | id | roomId | actorType | actorId | threadId | parentId | message | messageType | sendAt | metaData |
+ | Message 1 edited | room | users | participant1 | 0 | null | Message 1 edited | comment | 1985514582 | {"threadTitle":"","silent":true,"threadId":0,"lastEditedTime":0} |
diff --git a/tests/integration/spreedcheats/lib/Controller/ApiController.php b/tests/integration/spreedcheats/lib/Controller/ApiController.php
index c9ef896e1c..c1050cd5a8 100644
--- a/tests/integration/spreedcheats/lib/Controller/ApiController.php
+++ b/tests/integration/spreedcheats/lib/Controller/ApiController.php
@@ -86,6 +86,9 @@ class ApiController extends OCSController {
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_rooms')->executeStatement();
+ $delete = $this->db->getQueryBuilder();
+ $delete->delete('talk_scheduled_msg')->executeStatement();
+
$delete = $this->db->getQueryBuilder();
$delete->delete('talk_sessions')->executeStatement();
diff --git a/tests/php/Controller/ChatControllerTest.php b/tests/php/Controller/ChatControllerTest.php
index 937ec6cb37..e2fc8e7bf9 100644
--- a/tests/php/Controller/ChatControllerTest.php
+++ b/tests/php/Controller/ChatControllerTest.php
@@ -29,6 +29,7 @@ use OCA\Talk\Service\ParticipantService;
use OCA\Talk\Service\ProxyCacheMessageService;
use OCA\Talk\Service\ReminderService;
use OCA\Talk\Service\RoomFormatter;
+use OCA\Talk\Service\ScheduledMessageService;
use OCA\Talk\Service\SessionService;
use OCA\Talk\Service\ThreadService;
use OCA\Talk\Share\Helper\Preloader;
@@ -97,6 +98,7 @@ class ChatControllerTest extends TestCase {
private ?ChatController $controller = null;
private Callback $newMessageDateTimeConstraint;
+ private MockObject&ScheduledMessageService $scheduledMessageService;
public function setUp(): void {
parent::setUp();
@@ -135,6 +137,7 @@ class ChatControllerTest extends TestCase {
$this->taskProcessingManager = $this->createMock(ITaskProcessingManager::class);
$this->appConfig = $this->createMock(IAppConfig::class);
$this->logger = $this->createMock(LoggerInterface::class);
+ $this->scheduledMessageService = $this->createMock(ScheduledMessageService::class);
$this->room = $this->createMock(Room::class);
@@ -148,7 +151,7 @@ class ChatControllerTest extends TestCase {
});
}
- private function recreateChatController() {
+ private function recreateChatController(): void {
$this->controller = new ChatController(
'spreed',
$this->userId,
@@ -186,10 +189,11 @@ class ChatControllerTest extends TestCase {
$this->taskProcessingManager,
$this->appConfig,
$this->logger,
+ $this->scheduledMessageService,
);
}
- private function newComment($id, $actorType, $actorId, $creationDateTime, $message) {
+ private function newComment($id, $actorType, $actorId, $creationDateTime, $message): IComment&MockObject {
$comment = $this->createMock(IComment::class);
$comment->method('getId')->willReturn($id);
@@ -209,7 +213,6 @@ class ChatControllerTest extends TestCase {
$this->timeFactory->expects($this->once())
->method('getDateTime')
->willReturn($date);
- /** @var IComment&MockObject $comment */
$comment = $this->newComment(42, 'user', $this->userId, $date, 'testMessage');
$this->chatManager->expects($this->once())
->method('sendMessage')
@@ -280,7 +283,6 @@ class ChatControllerTest extends TestCase {
$this->timeFactory->expects($this->once())
->method('getDateTime')
->willReturn($date);
- /** @var IComment&MockObject $comment */
$comment = $this->newComment(42, 'user', $this->userId, $date, 'testMessage');
$this->chatManager->expects($this->once())
->method('sendMessage')
@@ -355,7 +357,6 @@ class ChatControllerTest extends TestCase {
/** @var IComment&MockObject $comment */
$parent = $this->newComment(23, 'users', $this->userId . '2', $date, 'testMessage original');
- /** @var IComment&MockObject $comment */
$comment = $this->newComment(42, 'users', $this->userId, $date, 'testMessage');
$this->chatManager->expects($this->once())
->method('sendMessage')
@@ -513,7 +514,6 @@ class ChatControllerTest extends TestCase {
$this->timeFactory->expects($this->once())
->method('getDateTime')
->willReturn($date);
- /** @var IComment&MockObject $comment */
$comment = $this->newComment(23, 'user', $this->userId, $date, 'testMessage');
$this->chatManager->expects($this->once())
->method('sendMessage')
@@ -592,7 +592,6 @@ class ChatControllerTest extends TestCase {
$this->timeFactory->expects($this->once())
->method('getDateTime')
->willReturn($date);
- /** @var IComment&MockObject $comment */
$comment = $this->newComment(64, 'guest', sha1('testSpreedSession'), $date, 'testMessage');
$this->chatManager->expects($this->once())
->method('sendMessage')
@@ -674,7 +673,6 @@ class ChatControllerTest extends TestCase {
$this->timeFactory->expects($this->once())
->method('getDateTime')
->willReturn($date);
- /** @var IComment&MockObject $comment */
$comment = $this->newComment(42, 'user', $this->userId, $date, 'testMessage');
$this->chatManager->expects($this->once())
->method('addSystemMessage')
@@ -963,7 +961,7 @@ class ChatControllerTest extends TestCase {
public function testWaitForNewMessagesByUser(): void {
$testUser = $this->createMock(IUser::class);
- $testUser->expects($this->any())
+ $testUser
->method('getUID')
->willReturn('testUser');
@@ -1044,7 +1042,7 @@ class ChatControllerTest extends TestCase {
public function testWaitForNewMessagesTimeoutExpired(): void {
$participant = $this->createMock(Participant::class);
$testUser = $this->createMock(IUser::class);
- $testUser->expects($this->any())
+ $testUser
->method('getUID')
->willReturn('testUser');
@@ -1056,7 +1054,7 @@ class ChatControllerTest extends TestCase {
->with($this->room, $offset, $limit, $timeout, $testUser)
->willReturn([]);
- $this->userManager->expects($this->any())
+ $this->userManager
->method('get')
->with('testUser')
->willReturn($testUser);
@@ -1072,7 +1070,7 @@ class ChatControllerTest extends TestCase {
public function testWaitForNewMessagesTimeoutTooLarge(): void {
$participant = $this->createMock(Participant::class);
$testUser = $this->createMock(IUser::class);
- $testUser->expects($this->any())
+ $testUser
->method('getUID')
->willReturn('testUser');
@@ -1085,7 +1083,7 @@ class ChatControllerTest extends TestCase {
->with($this->room, $offset, $limit, $maximumTimeout, $testUser)
->willReturn([]);
- $this->userManager->expects($this->any())
+ $this->userManager
->method('get')
->with('testUser')
->willReturn($testUser);
@@ -1129,7 +1127,7 @@ class ChatControllerTest extends TestCase {
#[DataProvider('dataMentions')]
public function testMentions(string $search, int $limit, array $result, array $expected): void {
$participant = $this->createMock(Participant::class);
- $this->room->expects($this->any())
+ $this->room
->method('getId')
->willReturn(1234);