feat(conversations): Add new sample conversation "💡 Let's get started!"

Signed-off-by: Joas Schilling <coding@schilljs.com>
This commit is contained in:
Joas Schilling
2025-01-13 17:29:53 +01:00
parent a4db9bfbbb
commit 07e6368d88
17 changed files with 433 additions and 17 deletions

View File

@ -18,6 +18,7 @@
| `share:password` | No | Video verification to verify the identity of the share recipient | Share token |
| `room` | Yes | Room is a breakout room | Token of the main/parent conversation |
| `phone` | Yes | Room is created when calling a phone number with SIP dial-out | `phone` (not set atm, just used for the default avatar) |
| `sample` | No | Room is a sample conversation | User ID the sample |
### Read-only states
* `0` Read-write

View File

@ -0,0 +1,72 @@
NAME: Let´s get started!
EMOJI: 💡
COLOR: #0082c9
---
{DESCRIPTION}
**Nextcloud Talk** is a secure, self-hosted communication platform that integrates seamlessly with the Nextcloud ecosystem.
#### Key Features of Nextcloud Talk:
* Chat and messaging in private and group chats
* Voice and video calls
* File sharing and integration with other Nextcloud apps
* Customizable conversation settings, moderation and privacy controls
* Web, desktop and mobile (iOS and Android)
* Private & secure communication
Find out more in the [user documentation](https://docs.nextcloud.com/server/latest/user_manual/en/talk/index.html).
---
# Welcome to Nextcloud Talk
Nextcloud Talk is a private and powerful messaging app that integrates with Nextcloud. Chat in private or group conversations, collaborate over voice and video calls, organize webinars and events, customize your conversations and more.
---
## 🎨 Format texts to create rich messages
In Nextcloud Talk, you can use Markdown syntax to format your messages. For example, apply **bold** or *italic* formatting, or `highlight texts as code`. You can even create tables and add headings to your text.
Need to fix a typo or change formatting? Edit your message by clicking "Edit message" in the message menu.
---
## 🔗 Add attachments and links
Attach files from your Nextcloud Hub using the "+" button. Share items from Files and various Nextcloud apps. Some apps even support interactive widgets, for example, the Text app.
{FILE:Readme.md}
---
## 💭 Let the conversations flow: mention users, react to messages and more
You can mention everybody in the conversation @all or mention specific participants by typing "@" and picking their name from the list.
{REACTION:😍}
{REACTION:👍}
---
{REPLY}
You can reply to messages, forward them to other chats and people, or copy message content.
---
## ✨ Do more with Smart Picker
Simply type "/" or go to the "+" menu to open the Smart Picker where you can attach various content to your messages. You can configure the Smart Picker to be able to add items from Nextcloud apps, GIFs, map locations, AI generated content and much more.
---
## ⚙️ Manage conversation settings
In the conversation menu, you can access various settings to manage your conversations, such as:
* Edit conversation info
* Manage notifications
* Apply numerous moderation rules
* Configure access and security
* Enable bots
* and more!

View File

@ -88,6 +88,7 @@ Legend:
| `matterbridge_binary` | string | | No | | Path to the matterbridge binary file |
| `bridge_bot_password` | string | | No | | Automatically generated password of the matterbridge bot user profile |
| `default_attachment_folder` | string | `/Talk` | No | | Specify default attachment folder location |
| `samples_directory` | string | | No | | Specify a readable directory that contains other sample conversation data |
| `start_calls` | int | `0` | Yes | 🖌️ | Who can start a call, see [constants list](constants.md#start-call) |
| `max_call_duration` | int | `0` | No | | Maximum duration of a call in seconds, 0 for unlimited. Federated calls will be terminated based on the setting of the host server. Calls are ended via a background job, so system cron should be used and calls will last a bit longer (until the next execution of the cron). |
| `max-gif-size` | int | `3145728` | No | | Maximum file size for clients to render gifs previews with animation |
@ -116,5 +117,6 @@ Legend:
| `delete_one_to_one_conversations` | string<br>`1` or `0` | `0` | No | | Whether one-to-one conversations can be left by either participant or should be deleted when one participant leaves |
| `enable_matterbridge` | string<br>`1` or `0` | `0` | No | 🖌️ | Whether the Matterbridge integration is enabled and can be configured |
| `force_passwords` | string<br>`1` or `0` | `0` | No | | Whether public chats are forced to use a password |
| `create_samples` | string<br>`1` or `0` | `1` | No | | Create sample conversations (the content can be overwritten by providing files in a provided `samples_directory` app config) |
| `inactivity_lock_after_days` | int | `0` | No | | A duration (in days) after which rooms are locked. Calculated from the last activity in the room. |
| `inactivity_enable_lobby` | string<br>`1` or `0` | `0` | No | | Additionally enable the lobby for inactive rooms so they can only be read by moderators. |

View File

@ -88,6 +88,7 @@ use OCA\Talk\Listener\GroupDeletedListener;
use OCA\Talk\Listener\GroupMembershipListener;
use OCA\Talk\Listener\NoteToSelfListener;
use OCA\Talk\Listener\RestrictStartingCalls as RestrictStartingCallsListener;
use OCA\Talk\Listener\SampleConversationsListener;
use OCA\Talk\Listener\UserDeletedListener;
use OCA\Talk\Maps\MapsPluginLoader;
use OCA\Talk\Middleware\CanUseTalkMiddleware;
@ -199,6 +200,7 @@ class Application extends App implements IBootstrap {
$context->registerEventListener(BeforeRoomsFetchEvent::class, ChangelogListener::class);
$context->registerEventListener(RoomDeletedEvent::class, ChatListener::class);
$context->registerEventListener(BeforeRoomsFetchEvent::class, NoteToSelfListener::class);
$context->registerEventListener(BeforeRoomsFetchEvent::class, SampleConversationsListener::class);
$context->registerEventListener(AttendeesAddedEvent::class, SystemMessageListener::class);
$context->registerEventListener(AttendeeRemovedEvent::class, SystemMessageListener::class);
$context->registerEventListener(AttendeesRemovedEvent::class, SystemMessageListener::class);

View File

@ -290,6 +290,45 @@ class ChatManager {
return $comment;
}
/**
* Post a new message to the given chat.
*
* @param Room $chat
* @param string $message
* @return IComment
*/
public function postSampleMessage(Room $chat, string $message, string $replyTo): IComment {
$comment = $this->commentsManager->create(Attendee::ACTOR_GUESTS, Attendee::ACTOR_ID_SAMPLE, 'chat', (string)$chat->getId());
if ($replyTo) {
$comment->setParentId($replyTo);
}
$comment->setMessage($message, self::MAX_CHAT_LENGTH);
$comment->setCreationDateTime($this->timeFactory->getDateTime());
$comment->setVerb(self::VERB_MESSAGE); // Has to be 'comment', so it counts as unread message
$metaData = [
Message::METADATA_CAN_MENTION_ALL => true,
];
$comment->setMetaData($metaData);
$event = new BeforeSystemMessageSentEvent($chat, $comment);
$this->dispatcher->dispatchTyped($event);
try {
$this->commentsManager->save($comment);
// Update last_message
$this->roomService->setLastMessage($chat, $comment);
$this->unreadCountCache->clear($chat->getId() . '-');
$event = new SystemMessageSentEvent($chat, $comment);
$this->dispatcher->dispatchTyped($event);
} catch (NotFoundException $e) {
}
$this->cache->remove($chat->getToken());
return $comment;
}
/**
* Sends a new message to the given chat.
*

View File

@ -11,8 +11,10 @@ namespace OCA\Talk\Chat\Parser;
use OCA\Talk\Chat\ChatManager;
use OCA\Talk\Events\MessageParseEvent;
use OCA\Talk\Model\Attendee;
use OCP\Defaults;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Server;
/**
* @template-implements IEventListener<Event>
@ -28,13 +30,19 @@ class Changelog implements IEventListener {
return;
}
if ($chatMessage->getActorType() !== Attendee::ACTOR_GUESTS ||
$chatMessage->getActorId() !== Attendee::ACTOR_ID_CHANGELOG) {
if ($chatMessage->getActorType() !== Attendee::ACTOR_GUESTS) {
return;
}
$l = $chatMessage->getL10n();
$chatMessage->setActor(Attendee::ACTOR_BOTS, Attendee::ACTOR_ID_CHANGELOG, $l->t('Talk updates ✅'));
$event->stopPropagation();
if ($chatMessage->getActorId() === Attendee::ACTOR_ID_CHANGELOG) {
$l = $chatMessage->getL10n();
$chatMessage->setActor(Attendee::ACTOR_BOTS, Attendee::ACTOR_ID_CHANGELOG, $l->t('Talk updates ✅'));
$event->stopPropagation();
}
if ($chatMessage->getActorId() === Attendee::ACTOR_ID_SAMPLE) {
$theme = Server::get(Defaults::class);
$chatMessage->setActor(Attendee::ACTOR_BOTS, Attendee::ACTOR_ID_SAMPLE, $theme->getName());
}
}
}

View File

@ -33,6 +33,7 @@ use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\NoteToSelfService;
use OCA\Talk\Service\ParticipantService;
use OCA\Talk\Service\SampleConversationsService;
use OCA\Talk\TalkSession;
use OCA\Talk\Webinary;
use OCP\AppFramework\Utility\ITimeFactory;
@ -155,7 +156,7 @@ class Listener implements IEventListener {
}
protected function sendSystemMessageAboutConversationCreated(RoomCreatedEvent $event): void {
if ($event->getRoom()->getType() === Room::TYPE_CHANGELOG || $this->isCreatingNoteToSelfAutomatically($event)) {
if ($event->getRoom()->getType() === Room::TYPE_CHANGELOG || $this->isCreatingNoteToSelfAutomatically($event) || $this->isCreatingSample($event)) {
$this->sendSystemMessage($event->getRoom(), 'conversation_created', forceSystemAsActor: true);
} else {
$this->sendSystemMessage($event->getRoom(), 'conversation_created');
@ -176,7 +177,7 @@ class Listener implements IEventListener {
protected function sendSystemMessageAboutRoomDescriptionChanges(RoomModifiedEvent $event): void {
if ($event->getNewValue() !== '') {
if ($this->isCreatingNoteToSelf($event)) {
if ($this->isCreatingNoteToSelf($event) || $this->isCreatingSample($event)) {
return;
}
@ -562,7 +563,7 @@ class Listener implements IEventListener {
protected function avatarChanged(RoomModifiedEvent $event): void {
if ($event->getNewValue()) {
if ($this->isCreatingNoteToSelf($event)) {
if ($this->isCreatingNoteToSelf($event) || $this->isCreatingSample($event)) {
return;
}
@ -596,6 +597,24 @@ class Listener implements IEventListener {
return false;
}
protected function isCreatingSample(ARoomEvent $event): bool {
if ($event->getRoom()->getType() !== Room::TYPE_GROUP) {
return false;
}
$exception = new \Exception();
$trace = $exception->getTrace();
foreach ($trace as $step) {
if (isset($step['class']) && $step['class'] === SampleConversationsService::class &&
isset($step['function']) && $step['function'] === 'initialCreateSamples') {
return true;
}
}
return false;
}
protected function isCreatingNoteToSelfAutomatically(RoomCreatedEvent $event): bool {
if ($event->getRoom()->getType() !== Room::TYPE_NOTE_TO_SELF) {
return false;

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Listener;
use OCA\Talk\Events\BeforeRoomsFetchEvent;
use OCA\Talk\Service\SampleConversationsService;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
/**
* @template-implements IEventListener<Event>
*/
class SampleConversationsListener implements IEventListener {
public function __construct(
protected SampleConversationsService $service,
) {
}
public function handle(Event $event): void {
if ($event instanceof BeforeRoomsFetchEvent) {
$this->service->initialCreateSamples($event->getUserId());
}
}
}

View File

@ -76,6 +76,7 @@ class Attendee extends Entity {
public const ACTOR_BOT_PREFIX = 'bot-';
public const ACTOR_ID_CLI = 'cli';
public const ACTOR_ID_SYSTEM = 'system';
public const ACTOR_ID_SAMPLE = 'sample';
public const ACTOR_ID_CHANGELOG = 'changelog';
public const PERMISSIONS_DEFAULT = 0;

View File

@ -43,6 +43,7 @@ class Room {
public const OBJECT_TYPE_FILE = 'file';
public const OBJECT_TYPE_PHONE = 'phone';
public const OBJECT_TYPE_VIDEO_VERIFICATION = 'share:password';
public const OBJECT_TYPE_SAMPLE = 'sample';
public const RECORDING_NONE = 0;
public const RECORDING_VIDEO = 1;

View File

@ -0,0 +1,238 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Service;
use OCA\Talk\Chat\ChatManager;
use OCA\Talk\Chat\ReactionManager;
use OCA\Talk\Manager;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Room;
use OCP\AppFramework\Services\IAppConfig;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\L10N\IFactory;
use OCP\Security\ISecureRandom;
use Psr\Log\LoggerInterface;
class SampleConversationsService {
public function __construct(
protected IConfig $config,
protected IAppConfig $appConfig,
protected IUserManager $userManager,
protected Manager $manager,
protected ChatManager $chatManager,
protected ReactionManager $reactionManager,
protected RoomService $roomService,
protected AvatarService $avatarService,
protected ParticipantService $participantService,
protected ISecureRandom $secureRandom,
protected IRootFolder $rootFolder,
protected IURLGenerator $url,
protected ITimeFactory $timeFactory,
protected IFactory $l10nFactory,
protected IL10N $l,
protected LoggerInterface $logger,
) {
}
public function initialCreateSamples(string $userId): void {
if (!$this->appConfig->getAppValueBool('create_samples', true)) {
return;
}
$created = $this->config->getUserValue($userId, 'spreed', 'samples_created');
if ($created !== '') {
return;
}
$this->config->setUserValue($userId, 'spreed', 'samples_created', $this->timeFactory->now()->format(\DateTime::ATOM));
$user = $this->userManager->get($userId);
if (!$user instanceof IUser) {
throw new \InvalidArgumentException('User not found');
}
$sampleDirectory = $this->appConfig->getAppValueString('samples_directory');
if ($sampleDirectory !== '') {
$this->logger->debug('Creating custom sample conversations for user ' . $userId . ' from ' . $sampleDirectory);
$this->customSampleConversations($user, $sampleDirectory);
} else {
$this->logger->debug('Creating default sample conversations for user ' . $userId);
$this->defaultSampleConversation($user);
}
}
protected function defaultSampleConversation(IUser $user): void {
$room = $this->roomService->createConversation(
Room::TYPE_GROUP,
$this->l->t('Let´s get started!'),
$user,
Room::OBJECT_TYPE_SAMPLE,
$user->getUID()
);
$this->avatarService->setAvatarFromEmoji($room, '💡', null);
$this->roomService->setDescription($room, $this->l->t('**Nextcloud Talk** is a secure, self-hosted communication platform that integrates seamlessly with the Nextcloud ecosystem.
#### Key Features of Nextcloud Talk:
* Chat and messaging in private and group chats
* Voice and video calls
* File sharing and integration with other Nextcloud apps
* Customizable conversation settings, moderation and privacy controls
* Web, desktop and mobile (iOS and Android)
* Private & secure communication
Find out more in the [user documentation](https://docs.nextcloud.com/server/latest/user_manual/en/talk/index.html).'));
$messages = [
$this->l->t('# Welcome to Nextcloud Talk
Nextcloud Talk is a private and powerful messaging app that integrates with Nextcloud. Chat in private or group conversations, collaborate over voice and video calls, organize webinars and events, customize your conversations and more.'),
$this->l->t('## 🎨 Format texts to create rich messages
In Nextcloud Talk, you can use Markdown syntax to format your messages. For example, apply **bold** or *italic* formatting, or `highlight texts as code`. You can even create tables and add headings to your text.
Need to fix a typo or change formatting? Edit your message by clicking "Edit message" in the message menu.'),
$this->l->t('## 🔗 Add attachments and links
Attach files from your Nextcloud Hub using the "+" button. Share items from Files and various Nextcloud apps. Some apps even support interactive widgets, for example, the Text app.')
. "\n\n" . '{FILE:Readme.md}',
$this->l->t('## 💭 Let the conversations flow: mention users, react to messages and more
You can mention everybody in a the conversation @all or mention specific participants by typing "@" and picking their name from the list.')
. "\n" . '{REACTION:😍}{REACTION:👍}',
'{REPLY}' . $this->l->t('You can reply to messages, forward them to other chats and people, or copy message content.'),
$this->l->t('## ✨ Do more with Smart Picker
Simply type "/" or go to the "+" menu to open the Smart Picker where you can attach various content to your messages. You can configure the Smart Picker to be able to add items from Nextcloud apps, GIFs, map locations, AI generated content and much more.'),
$this->l->t('## ⚙️ Manage conversation settings
In the conversation menu, you can access various settings to manage your conversations, such as:
* Edit conversation info
* Manage notifications
* Apply numerous moderation rules
* Configure access and security
* Enable bots
* and more!'),
];
$this->fillConversation($user, $room, $messages);
}
protected function fillConversation(IUser $user, Room $room, array $messages): void {
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
$previous = null;
foreach ($messages as $message) {
$message = trim($message);
$replyTo = '';
if (str_starts_with($message, '{REPLY}')) {
$message = trim(str_replace('{REPLY}', '', $message));
$replyTo = $previous->getId();
}
if (str_contains($message, '{FILE:')) {
preg_match_all('/{FILE:([^}]*)}/', $message, $matches);
foreach ($matches[1] as $match) {
try {
$node = $userFolder->get($match);
$message = str_replace('{FILE:' . $match . '}', $this->url->linkToRouteAbsolute(
'files.view.showFile', ['fileid' => $node->getId()]
), $message);
} catch (NotFoundException|NotPermittedException) {
$message = trim(str_replace('{FILE:' . $match . '}', '', $message));
}
}
}
$reactions = [];
if (str_contains($message, '{REACTION:')) {
preg_match_all('/{REACTION:([^}]*)}/', $message, $matches);
$reactions = $matches[1];
$message = trim(preg_replace('/{REACTION:([^}]*)}/', '', $message));
}
$previous = $this->chatManager->postSampleMessage($room, $message, $replyTo);
foreach ($reactions as $reaction) {
$this->reactionManager->addReactionMessage($room, Attendee::ACTOR_GUESTS, Attendee::ACTOR_ID_SAMPLE, (int)$previous->getId(), $reaction);
}
}
}
protected function customSampleConversations(IUser $user, string $sampleDirectory): void {
$iterator = $this->l10nFactory->getLanguageIterator($user);
do {
$lang = $iterator->current();
if (file_exists($sampleDirectory . '/' . $lang)) {
break;
}
$iterator->next();
} while ($lang !== 'en' && $iterator->valid());
if (!file_exists($sampleDirectory . '/' . $lang)) {
return;
}
$directory = new \DirectoryIterator($sampleDirectory . '/' . $lang);
foreach ($directory as $file) {
if ($file->isDot() || $file->getExtension() !== 'md') {
continue;
}
$this->createSampleFromFile($user, $file->getPathname());
}
}
protected function createSampleFromFile(IUser $user, string $filePath): void {
$content = file_get_contents($filePath);
$messages = explode("\n---\n", $content);
$detailsBlock = array_shift($messages);
$details = explode("\n", $detailsBlock);
$name = $emoji = $color = null;
foreach ($details as $detail) {
if (str_starts_with($detail, 'NAME:')) {
$name = trim(substr($detail, strlen('NAME:')));
}
if (str_starts_with($detail, 'EMOJI:')) {
$emoji = trim(substr($detail, strlen('EMOJI:')));
}
if (str_starts_with($detail, 'COLOR:')) {
$color = substr(trim(substr($detail, strlen('COLOR:'))), 1);
}
}
if ($name === null) {
$this->logger->error('Sample conversation ' . $filePath . ' has no name defined');
return;
}
$room = $this->roomService->createConversation(Room::TYPE_GROUP, $name, $user, Room::OBJECT_TYPE_SAMPLE, $user->getUID());
if ($emoji !== null) {
$this->avatarService->setAvatarFromEmoji($room, $emoji, $color);
}
if (isset($messages[0]) && str_starts_with($messages[0], 'DESCRIPTION:')) {
$description = array_shift($messages);
$this->roomService->setDescription($room, trim(substr($description, strlen('DESCRIPTION:'))));
}
$this->fillConversation($user, $room, $messages);
}
}

View File

@ -94,6 +94,7 @@ describe('AvatarWrapper.vue', () => {
describe('render specific icons', () => {
const testCases = [
[null, ATTENDEE.CHANGELOG_BOT_ID, 'Talk updates', ATTENDEE.ACTOR_TYPE.BOTS, 'icon-changelog'],
[null, ATTENDEE.SAMPLE_BOT_ID, 'Nextcloud', ATTENDEE.ACTOR_TYPE.BOTS, 'icon-changelog'],
[null, 'federated_user/id', USER_NAME, ATTENDEE.ACTOR_TYPE.FEDERATED_USERS, 'icon-user'],
[null, 'guest/id', '', ATTENDEE.ACTOR_TYPE.GUESTS, 'icon-user'],
[null, 'guest/id', t('spreed', 'Guest'), ATTENDEE.ACTOR_TYPE.GUESTS, 'icon-user'],

View File

@ -162,7 +162,7 @@ export default {
case ATTENDEE.ACTOR_TYPE.PHONES:
return 'icon-phone'
case ATTENDEE.ACTOR_TYPE.BOTS:
return this.id === ATTENDEE.CHANGELOG_BOT_ID ? 'icon-changelog' : ''
return [ATTENDEE.CHANGELOG_BOT_ID, ATTENDEE.SAMPLE_BOT_ID].includes(this.id) ? 'icon-changelog' : ''
case ATTENDEE.ACTOR_TYPE.CIRCLES:
return 'icon-team'
case ATTENDEE.ACTOR_TYPE.GROUPS:
@ -188,7 +188,7 @@ export default {
return this.source === ATTENDEE.ACTOR_TYPE.FEDERATED_USERS
},
isBot() {
return this.source === ATTENDEE.ACTOR_TYPE.BOTS && this.id !== ATTENDEE.CHANGELOG_BOT_ID
return this.source === ATTENDEE.ACTOR_TYPE.BOTS && this.id !== ATTENDEE.CHANGELOG_BOT_ID && this.id !== ATTENDEE.SAMPLE_BOT_ID
},
isGuestUser() {
return [ATTENDEE.ACTOR_TYPE.GUESTS, ATTENDEE.ACTOR_TYPE.EMAILS].includes(this.source)

View File

@ -508,7 +508,8 @@ export default {
}
if (message1.actorType === ATTENDEE.ACTOR_TYPE.BOTS // Don't group messages of bots
&& message1.actorId !== ATTENDEE.CHANGELOG_BOT_ID) { // Apart from the changelog bot
&& message1.actorId !== ATTENDEE.CHANGELOG_BOT_ID // Apart from the changelog bot
&& message1.actorId !== ATTENDEE.SAMPLE_BOT_ID) { // Apart from the sample message
return false
}

View File

@ -132,6 +132,7 @@ export const ATTENDEE = {
BRIDGE_BOT_ID: 'bridge-bot',
CHANGELOG_BOT_ID: 'changelog',
SAMPLE_BOT_ID: 'sample',
}
export const MESSAGE = {

View File

@ -376,12 +376,12 @@ class FeatureContext implements Context, SnippetAcceptingContext {
if ($shouldFilter === '') {
$rooms = array_filter($rooms, static function (array $room) {
// Filter out "Talk updates" and "Note to self" conversations
return $room['type'] !== 4 && $room['type'] !== 6;
return $room['type'] !== 4 && $room['type'] !== 6 && $room['objectType'] !== 'sample';
});
} elseif ($shouldFilter === 'note-to-self ') {
$rooms = array_filter($rooms, static function (array $room) {
// Filter out "Talk updates" conversations
return $room['type'] !== 4;
return $room['type'] !== 4 && $room['objectType'] !== 'sample';
});
}
@ -416,7 +416,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
$rooms = array_filter($rooms, static function (array $room) {
// Filter out "Talk updates" and "Note to self" conversations
return $room['type'] !== 4 && $room['type'] !== 6;
return $room['type'] !== 4 && $room['type'] !== 6 && $room['objectType'] !== 'sample';
});
if ($formData === null) {
@ -750,7 +750,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
$rooms = array_filter($rooms, function ($room) {
// Filter out "Talk updates" and "Note to self" conversations
return $room['type'] !== 4 && $room['type'] !== 6;
return $room['type'] !== 4 && $room['type'] !== 6 && $room['objectType'] !== 'sample';
});
if ($isParticipant) {
@ -2778,7 +2778,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
$actualItems = $data[$widgetId]['items'];
}
$actualItems = array_values(array_filter($actualItems, static fn ($item) => $item['title'] !== 'Note to self' && $item['title'] !== 'Talk updates ✅'));
$actualItems = array_values(array_filter($actualItems, static fn ($item) => $item['title'] !== 'Note to self' && $item['title'] !== 'Talk updates ✅' && $item['title'] !== 'Let´s get started!'));
if (empty($expectedItems)) {
Assert::assertEmpty($actualItems);

View File

@ -88,7 +88,7 @@ class ApiController extends OCSController {
$delete = $this->db->getQueryBuilder();
$delete->delete('preferences')
->where($delete->expr()->in('configkey', $delete->createNamedParameter(['changelog', 'note_to_self'], IQueryBuilder::PARAM_STR_ARRAY)))
->where($delete->expr()->in('configkey', $delete->createNamedParameter(['changelog', 'note_to_self', 'samples_created'], IQueryBuilder::PARAM_STR_ARRAY)))
->andWhere($delete->expr()->eq('appid', $delete->createNamedParameter('spreed')))
->executeStatement();