Start with system chat messages

Signed-off-by: Joas Schilling <coding@schilljs.com>
This commit is contained in:
Joas Schilling 2018-07-20 17:34:51 +02:00
Родитель a87de565af
Коммит ee4ba11a53
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 7076EA9751AACDDA
13 изменённых файлов: 336 добавлений и 16 удалений

Просмотреть файл

@ -246,15 +246,23 @@ body:not(#body-public) #commentsTabView .comment .authorRow:not(.currentUser):no
/* Make the mention the positioning context of its child contacts menu */
position: relative;
font-weight: bold;
background-color: nc-lighten($color-main-text, 90%);
padding: 2px 5px;
border-radius: 10px;
}
#commentsTabView .comment .message .mention-user.current-user {
background-color: $color-primary;
color: $color-primary-text;
#commentsTabView .comment:not(.systemMessage) .message .mention-user {
font-weight: bold;
.current-user {
background-color: $color-primary;
color: $color-primary-text;
}
}
#commentsTabView .comment.systemMessage .message {
opacity: .5;
}
body:not(#body-public) #commentsTabView .comment:not(.newCommentRow) .message .mention-user:not(.current-user) {

Просмотреть файл

@ -70,6 +70,7 @@ Base endpoint is: `/ocs/v2.php/apps/spreed/api/v1`
* `favorites` - Rooms can be marked as favorites which will pin them to the top of the room list.
* `last-room-activity` - Rooms have the `lastActivity` attribute and should be sorted by that instead of the last ping of the user.
* `no-ping` - The ping endpoint has been removed. Ping is updated with a call to fetch the signaling or chat messages instead.
* `system-messages` - Chat messages have a "verb" and can be generated by the system
## Room management
@ -491,6 +492,7 @@ Base endpoint is: `/ocs/v2.php/apps/spreed/api/v1`
`actorId` | string | User id of the message author
`actorDisplayName` | string | Display name of the message author
`timestamp` | int | Timestamp in seconds and UTC time zone
`verb` | string | `comment` for normal chat message or `system` as activity report
`message` | string | Message string with placeholders (see [Rich Object String](https://github.com/nextcloud/server/issues/1706))
`messageParameters` | array | Message parameters for `message` (see [Rich Object String](https://github.com/nextcloud/server/issues/1706))

Просмотреть файл

@ -52,10 +52,12 @@
'</div>';
var COMMENT_TEMPLATE =
'<li class="comment" data-id="{{id}}">' +
'<li class="comment{{#if isNotSystemMessage}}{{else}} systemMessage{{/if}}" data-id="{{id}}">' +
' <div class="authorRow{{#if isUserAuthor}} currentUser{{/if}}{{#if isGuest}} guestUser{{/if}}">' +
' {{#if isNotSystemMessage}}' +
' <div class="avatar" data-user-id="{{actorId}}" data-displayname="{{actorDisplayName}}"> </div>' +
' <div class="author">{{actorDisplayName}}</div>' +
' {{/if}}' +
' <div class="date has-tooltip{{#if relativeDate}} live-relative-timestamp{{/if}}" data-timestamp="{{timestamp}}" title="{{altDate}}">{{date}}</div>' +
' </div>' +
' <div class="message">{{{formattedMessage}}}</div>' +
@ -355,6 +357,7 @@
date: relativeDate ? OC.Util.relativeModifiedDate(timestamp) : OC.Util.formatDate(timestamp, 'LTS'),
relativeDate: relativeDate,
altDate: OC.Util.formatDate(timestamp),
isNotSystemMessage: commentModel.get('verb') !== 'system',
formattedMessage: formattedMessage
});
return data;
@ -471,7 +474,8 @@
return false;
}
return model1.get('actorId') === model2.get('actorId') &&
return model1.get('verb') === model2.get('verb') &&
model1.get('actorId') === model2.get('actorId') &&
model1.get('actorType') === model2.get('actorType');
},

Просмотреть файл

@ -24,6 +24,7 @@ namespace OCA\Spreed\AppInfo;
use OCA\Spreed\Activity\Hooks;
use OCA\Spreed\Capabilities;
use OCA\Spreed\Chat\ChatManager;
use OCA\Spreed\Chat\SystemMessage\Listener;
use OCA\Spreed\Config;
use OCA\Spreed\GuestManager;
use OCA\Spreed\HookListener;
@ -70,6 +71,10 @@ class Application extends App {
$this->registerCallNotificationHook($dispatcher);
$this->registerChatHooks($dispatcher);
$this->registerClientLinks($server);
/** @var Listener $systemMessageListener */
$systemMessageListener = $this->getContainer()->query(Listener::class);
$systemMessageListener->register();
}
protected function registerNotifier(IServerContainer $server) {

Просмотреть файл

@ -51,6 +51,7 @@ class Capabilities implements IPublicCapability {
'favorites',
'last-room-activity',
'no-ping',
'system-messages',
],
],
];

Просмотреть файл

@ -23,9 +23,11 @@
namespace OCA\Spreed\Chat;
use OCA\Spreed\Chat\SystemMessage\Parser;
use OCA\Spreed\Room;
use OCP\Comments\IComment;
use OCP\Comments\ICommentsManager;
use OCP\Comments\NotFoundException;
use OCP\IUser;
/**
@ -56,6 +58,29 @@ class ChatManager {
$this->notifier = $notifier;
}
/**
* Sends a new message to the given chat.
*
* @param Room $chat
* @param string $actorType
* @param string $actorId
* @param string $message
* @param \DateTime $creationDateTime
* @return IComment
*/
public function addSystemMessage(Room $chat, $actorType, $actorId, $message, \DateTime $creationDateTime) {
$comment = $this->commentsManager->create($actorType, $actorId, 'chat', (string) $chat->getId());
$comment->setMessage($message);
$comment->setCreationDateTime($creationDateTime);
$comment->setVerb('system');
try {
$this->commentsManager->save($comment);
} catch (NotFoundException $e) {
}
return $comment;
}
/**
* Sends a new message to the given chat.
*
@ -74,7 +99,10 @@ class ChatManager {
// comment
$comment->setVerb('comment');
$this->commentsManager->save($comment);
try {
$this->commentsManager->save($comment);
} catch (NotFoundException $e) {
}
// Update last_message
$chat->setLastMessage($comment);

Просмотреть файл

@ -53,7 +53,7 @@ class RichMessageHelper {
* not be resolved.
*
* @param IComment $comment
* @return Array first element, the rich message; second element, the
* @return array first element, the rich message; second element, the
* parameters of the rich message (or an empty array if there are no
* parameters).
*/

Просмотреть файл

@ -0,0 +1,143 @@
<?php
/**
* @copyright Copyright (c) 2018 Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Spreed\Chat\SystemMessage;
use OCA\Spreed\Chat\ChatManager;
use OCA\Spreed\Participant;
use OCA\Spreed\Room;
use OCA\Spreed\TalkSession;
use OCP\IUser;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\GenericEvent;
class Listener {
/** @var EventDispatcherInterface */
protected $dispatcher;
/** @var ChatManager */
protected $chatManager;
/** @var TalkSession */
protected $session;
/** @var string */
protected $userId;
public function __construct(EventDispatcherInterface $dispatcher, ChatManager $chatManager, TalkSession $session, $userId) {
$this->dispatcher = $dispatcher;
$this->chatManager = $chatManager;
$this->session = $session;
$this->userId = $userId;
}
public function register() {
$this->dispatcher->addListener(Room::class . '::postSessionJoinCall', function(GenericEvent $event) {
/** @var Room $room */
$room = $event->getSubject();
$this->sendSystemMessage($room, 'joined_call');
});
$this->dispatcher->addListener(Room::class . '::postSessionLeaveCall', function(GenericEvent $event) {
/** @var Room $room */
$room = $event->getSubject();
$this->sendSystemMessage($room, 'left_call');
});
$this->dispatcher->addListener(Room::class . '::createRoom', function(GenericEvent $event) {
/** @var Room $room */
$room = $event->getSubject();
$this->sendSystemMessage($room, 'created_conversation');
});
$this->dispatcher->addListener(Room::class . '::postSetName', function(GenericEvent $event) {
/** @var Room $room */
$room = $event->getSubject();
$this->sendSystemMessage($room, 'renamed_conversation', $event->getArguments());
});
$this->dispatcher->addListener(Room::class . '::postSetPassword', function(GenericEvent $event) {
/** @var Room $room */
$room = $event->getSubject();
if ($event->getArgument('password')) {
$this->sendSystemMessage($room, 'set_password');
} else {
$this->sendSystemMessage($room, 'removed_password');
}
});
$this->dispatcher->addListener(Room::class . '::postChangeType', function(GenericEvent $event) {
$arguments = $event->getArguments();
if ($arguments['newType'] !== Room::PUBLIC_CALL && $arguments['newType'] !== Room::PUBLIC_CALL) {
// one2one => group: Only added a user
return;
}
/** @var Room $room */
$room = $event->getSubject();
$this->sendSystemMessage($room, 'change_type', $event->getArguments());
});
$this->dispatcher->addListener(Room::class . '::postAddUsers', function(GenericEvent $event) {
$participants = $event->getArgument('users');
/** @var Room $room */
$room = $event->getSubject();
foreach ($participants as $participant) {
if ($this->userId !== $participant['userId']) {
$this->sendSystemMessage($room, 'user_added', ['user' => $participant['userId']]);
}
}
});
$this->dispatcher->addListener(Room::class . '::postRemoveUser', function(GenericEvent $event) {
/** @var IUser $user */
$user = $event->getArgument('user');
/** @var Room $room */
$room = $event->getSubject();
$this->sendSystemMessage($room, 'user_removed', ['user' => $user->getUID()]);
});
$this->dispatcher->addListener(Room::class . '::postSetParticipantType', function(GenericEvent $event) {
/** @var Room $room */
$room = $event->getSubject();
if ($event->getArgument('newType') === Participant::MODERATOR) {
$this->sendSystemMessage($room, 'moderator_promoted', ['user' => $event->getArgument('user')]);
} else if ($event->getArgument('newType') === Participant::USER) {
$this->sendSystemMessage($room, 'moderator_demoted', ['user' => $event->getArgument('user')]);
}
});
}
protected function sendSystemMessage(Room $room, string $message, array $parameters = []) {
if ($this->userId === null) {
$actorType = 'guests';
$sessionId = $this->session->getSessionForRoom($room->getToken());
$actorId = $sessionId ? sha1($sessionId) : '';
} else {
$actorType = 'users';
$actorId = $this->userId;
}
$this->chatManager->addSystemMessage(
$room, $actorType, $actorId,
json_encode(['message' => $message, 'parameters' => $parameters]),
new \DateTime()
);
}
}

Просмотреть файл

@ -0,0 +1,117 @@
<?php
/**
* @copyright Copyright (c) 2018 Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Spreed\Chat\SystemMessage;
use OCA\Spreed\Room;
use OCP\Comments\IComment;
use OCP\IL10N;
use OCP\IUser;
use OCP\IUserManager;
class Parser {
/** @var IUserManager */
protected $userManager;
/** @var IL10N */
protected $l;
/** @var string[] */
protected $displayNames = [];
public function __construct(IUserManager $userManager, IL10N $l) {
$this->userManager = $userManager;
$this->l = $l;
}
public function parseMessage(IComment $comment, string $displayName): array {
$data = json_decode($comment->getMessage(), true);
$message = $data['message'];
$parameters = $data['parameters'];
$parsedParameters = ['actor' => $this->getActor($comment, $displayName)];
$parsedMessage = $comment->getMessage();
if ($message === 'joined_call') {
$parsedMessage = $this->l->t('{actor} joined the call');
} else if ($message === 'left_call') {
$parsedMessage = $this->l->t('{actor} left the call');
} else if ($message === 'created_conversation') {
$parsedMessage = $this->l->t('{actor} created the conversation');
} else if ($message === 'renamed_conversation') {
$parsedMessage = $this->l->t('{actor} renamed the conversation from "%1$s" to "%2$s"', [$parameters['oldName'], $parameters['newName']]);
} else if ($message === 'change_type') {
if ($parameters['newType'] === Room::PUBLIC_CALL) {
$parsedMessage = $this->l->t('{actor} allowed guests in the conversation');
} else {
$parsedMessage = $this->l->t('{actor} disallowed guests in the conversation');
}
} else if ($message === 'set_password') {
$parsedMessage = $this->l->t('{actor} set a password for the conversation');
} else if ($message === 'removed_password') {
$parsedMessage = $this->l->t('{actor} removed the password for the conversation');
} else if ($message === 'user_added') {
$parsedParameters['user'] = $this->getUser($parameters['user']);
$parsedMessage = $this->l->t('{actor} added {user} to the conversation');
} else if ($message === 'user_removed') {
$parsedParameters['user'] = $this->getUser($parameters['user']);
$parsedMessage = $this->l->t('{actor} removed {user} from the conversation');
} else if ($message === 'moderator_promoted') {
$parsedParameters['user'] = $this->getUser($parameters['user']);
$parsedMessage = $this->l->t('{actor} promoted {user} to moderator');
} else if ($message === 'moderator_demoted') {
$parsedParameters['user'] = $this->getUser($parameters['user']);
$parsedMessage = $this->l->t('{actor} demoted {user} from moderator');
}
return [$parsedMessage, $parsedParameters];
}
protected function getActor(IComment $comment, string $displayName): array {
return [
'type' => 'user',
'id' => $comment->getActorId(),
'name' => $displayName,
];
}
protected function getUser(string $uid): array {
if (!isset($this->displayNames[$uid])) {
$this->displayNames[$uid] = $this->getDisplayName($uid);
}
return [
'type' => 'user',
'id' => $uid,
'name' => $this->displayNames[$uid],
];
}
protected function getDisplayName(string $uid): string {
$user = $this->userManager->get($uid);
if ($user instanceof IUser) {
return $user->getDisplayName();
}
return $uid;
}
}

Просмотреть файл

@ -28,6 +28,7 @@ use OCA\Spreed\Chat\AutoComplete\SearchPlugin;
use OCA\Spreed\Chat\AutoComplete\Sorter;
use OCA\Spreed\Chat\ChatManager;
use OCA\Spreed\Chat\RichMessageHelper;
use OCA\Spreed\Chat\SystemMessage\Parser;
use OCA\Spreed\Exceptions\ParticipantNotFoundException;
use OCA\Spreed\Exceptions\RoomNotFoundException;
use OCA\Spreed\GuestManager;
@ -84,6 +85,9 @@ class ChatController extends OCSController {
/** @var ISearchResult */
private $searchResult;
/** @var Parser */
private $parser;
/** @var EventDispatcherInterface */
private $dispatcher;
@ -99,7 +103,7 @@ class ChatController extends OCSController {
* @param RichMessageHelper $richMessageHelper
* @param IManager $autoCompleteManager
* @param SearchPlugin $searchPlugin
* @param SearchResult $searchResult
* @param ISearchResult $searchResult
* @param EventDispatcherInterface $dispatcher
*/
public function __construct($appName,
@ -113,7 +117,8 @@ class ChatController extends OCSController {
RichMessageHelper $richMessageHelper,
IManager $autoCompleteManager,
SearchPlugin $searchPlugin,
SearchResult $searchResult, // FIXME for 14 ISearchResult is injectable
ISearchResult $searchResult,
Parser $parser,
EventDispatcherInterface $dispatcher) {
parent::__construct($appName, $request);
@ -127,6 +132,7 @@ class ChatController extends OCSController {
$this->autoCompleteManager = $autoCompleteManager;
$this->searchPlugin = $searchPlugin;
$this->searchResult = $searchResult;
$this->parser = $parser;
$this->dispatcher = $dispatcher;
}
@ -299,7 +305,11 @@ class ChatController extends OCSController {
$displayName = $guestNames[$comment->getActorId()];
}
list($message, $messageParameters) = $this->richMessageHelper->getRichMessage($comment);
if ($comment->getVerb() === 'system') {
list($message, $messageParameters) = $this->parser->parseMessage($comment, $displayName);
} else {
list($message, $messageParameters) = $this->richMessageHelper->getRichMessage($comment);
}
return [
'id' => $comment->getId(),
@ -310,6 +320,7 @@ class ChatController extends OCSController {
'timestamp' => $comment->getCreationDateTime()->getTimestamp(),
'message' => $message,
'messageParameters' => $messageParameters,
'verb' => $comment->getVerb(),
];
}, $comments), Http::STATUS_OK);

Просмотреть файл

@ -398,7 +398,11 @@ class Manager {
$query->execute();
$roomId = $query->getLastInsertId();
return $this->getRoomById($roomId);
$room = $this->getRoomById($roomId);
$this->dispatcher->dispatch(Room::class . '::createRoom', new GenericEvent($room));
return $room;
}
/**

Просмотреть файл

@ -389,9 +389,6 @@ class Room {
$query->execute();
}
/**
* @return bool
*/
public function resetActiveSince() {
$query = $this->db->getQueryBuilder();
$query->update('talk_rooms')
@ -399,7 +396,6 @@ class Room {
->set('active_since', $query->createNamedParameter(null, 'datetime'))
->where($query->expr()->eq('id', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)));
$query->execute();
}
/**

Просмотреть файл

@ -46,6 +46,7 @@ class CapabilitiesTest extends TestCase {
'favorites',
'last-room-activity',
'no-ping',
'system-messages',
],
],
], $capabilities->getCapabilities());