mirror of
https://github.com/nextcloud/spreed.git
synced 2025-08-20 13:15:42 +00:00
feat(conversations): Add new sample conversation "💡 Let's get started!"
Signed-off-by: Joas Schilling <coding@schilljs.com>
This commit is contained in:
@ -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
|
||||
|
72
docs/samples/en/lets-get-started.md
Normal file
72
docs/samples/en/lets-get-started.md
Normal 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!
|
@ -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. |
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
30
lib/Listener/SampleConversationsListener.php
Normal file
30
lib/Listener/SampleConversationsListener.php
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
238
lib/Service/SampleConversationsService.php
Normal file
238
lib/Service/SampleConversationsService.php
Normal 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);
|
||||
}
|
||||
}
|
@ -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'],
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -132,6 +132,7 @@ export const ATTENDEE = {
|
||||
BRIDGE_BOT_ID: 'bridge-bot',
|
||||
|
||||
CHANGELOG_BOT_ID: 'changelog',
|
||||
SAMPLE_BOT_ID: 'sample',
|
||||
}
|
||||
|
||||
export const MESSAGE = {
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
||||
|
Reference in New Issue
Block a user