aboutsummaryrefslogtreecommitdiff
path: root/lib/events
diff options
context:
space:
mode:
authorKitsune Ral <Kitsune-Ral@users.sf.net>2018-03-31 13:16:02 +0900
committerKitsune Ral <Kitsune-Ral@users.sf.net>2018-03-31 14:23:55 +0900
commitefeb50a46ad824aa258472f6ac8da74810f05a55 (patch)
treea89c6f35d56986c60e73f870530c9d6ee0527e6d /lib/events
parent29093379b707bfe620234c2968b37aa86666542a (diff)
downloadlibquotient-efeb50a46ad824aa258472f6ac8da74810f05a55.tar.gz
libquotient-efeb50a46ad824aa258472f6ac8da74810f05a55.zip
Move source files to a separate folder
It's been long overdue to separate them from the rest of the stuff (docs etc.). Also, this allows installing to a directory within the checked out git tree (say, ./install/, similar to ./build/).
Diffstat (limited to 'lib/events')
-rw-r--r--lib/events/accountdataevents.h78
-rw-r--r--lib/events/directchatevent.cpp36
-rw-r--r--lib/events/directchatevent.h34
-rw-r--r--lib/events/event.cpp182
-rw-r--r--lib/events/event.h314
-rw-r--r--lib/events/eventcontent.cpp85
-rw-r--r--lib/events/eventcontent.h314
-rw-r--r--lib/events/receiptevent.cpp70
-rw-r--r--lib/events/receiptevent.h50
-rw-r--r--lib/events/redactionevent.cpp1
-rw-r--r--lib/events/redactionevent.h43
-rw-r--r--lib/events/roomavatarevent.cpp23
-rw-r--r--lib/events/roomavatarevent.h43
-rw-r--r--lib/events/roommemberevent.cpp69
-rw-r--r--lib/events/roommemberevent.h78
-rw-r--r--lib/events/roommessageevent.cpp193
-rw-r--r--lib/events/roommessageevent.h194
-rw-r--r--lib/events/simplestateevents.h53
-rw-r--r--lib/events/typingevent.cpp32
-rw-r--r--lib/events/typingevent.h39
20 files changed, 1931 insertions, 0 deletions
diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h
new file mode 100644
index 00000000..f3ba27bb
--- /dev/null
+++ b/lib/events/accountdataevents.h
@@ -0,0 +1,78 @@
+#include <utility>
+
+/******************************************************************************
+ * Copyright (C) 2018 Kitsune Ral <kitsune-ral@users.sf.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#pragma once
+
+#include "event.h"
+#include "eventcontent.h"
+
+namespace QMatrixClient
+{
+ static constexpr const char* FavouriteTag = "m.favourite";
+ static constexpr const char* LowPriorityTag = "m.lowpriority";
+
+ struct TagRecord
+ {
+ TagRecord (QString order = {}) : order(std::move(order)) { }
+ explicit TagRecord(const QJsonValue& jv)
+ : order(jv.toObject().value("order").toString())
+ { }
+
+ QString order;
+
+ bool operator==(const TagRecord& other) const
+ { return order == other.order; }
+ bool operator!=(const TagRecord& other) const
+ { return !operator==(other); }
+ };
+
+ inline QJsonValue toJson(const TagRecord& rec)
+ {
+ return QJsonObject {{ QStringLiteral("order"), rec.order }};
+ }
+
+ using TagsMap = QHash<QString, TagRecord>;
+
+#define DEFINE_SIMPLE_EVENT(_Name, _TypeId, _EnumType, _ContentType, _ContentKey) \
+ class _Name : public Event \
+ { \
+ public: \
+ static constexpr const char* TypeId = _TypeId; \
+ static const char* typeId() { return TypeId; } \
+ explicit _Name(const QJsonObject& obj) \
+ : Event((_EnumType), obj) \
+ , _content(contentJson(), QStringLiteral(#_ContentKey)) \
+ { } \
+ template <typename... Ts> \
+ explicit _Name(Ts&&... contentArgs) \
+ : Event(_EnumType) \
+ , _content(QStringLiteral(#_ContentKey), \
+ std::forward<Ts>(contentArgs)...) \
+ { } \
+ const _ContentType& _ContentKey() const { return _content.value; } \
+ QJsonObject toJson() const { return _content.toJson(); } \
+ protected: \
+ EventContent::SimpleContent<_ContentType> _content; \
+ };
+
+ DEFINE_SIMPLE_EVENT(TagEvent, "m.tag", EventType::Tag, TagsMap, tags)
+ DEFINE_SIMPLE_EVENT(ReadMarkerEvent, "m.fully_read", EventType::ReadMarker,
+ QString, event_id)
+}
diff --git a/lib/events/directchatevent.cpp b/lib/events/directchatevent.cpp
new file mode 100644
index 00000000..7049d967
--- /dev/null
+++ b/lib/events/directchatevent.cpp
@@ -0,0 +1,36 @@
+/******************************************************************************
+ * Copyright (C) 2018 Kitsune Ral <kitsune-ral@users.sf.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "directchatevent.h"
+
+#include "converters.h"
+
+using namespace QMatrixClient;
+
+DirectChatEvent::DirectChatEvent(const QJsonObject& obj)
+ : Event(Type::DirectChat, obj)
+{ }
+
+QMultiHash<QString, QString> DirectChatEvent::usersToDirectChats() const
+{
+ QMultiHash<QString, QString> result;
+ for (auto it = contentJson().begin(); it != contentJson().end(); ++it)
+ for (auto roomIdValue: it.value().toArray())
+ result.insert(it.key(), roomIdValue.toString());
+ return result;
+}
diff --git a/lib/events/directchatevent.h b/lib/events/directchatevent.h
new file mode 100644
index 00000000..2b0ad0a0
--- /dev/null
+++ b/lib/events/directchatevent.h
@@ -0,0 +1,34 @@
+/******************************************************************************
+ * Copyright (C) 2018 Kitsune Ral <kitsune-ral@users.sf.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#pragma once
+
+#include "event.h"
+
+namespace QMatrixClient
+{
+ class DirectChatEvent : public Event
+ {
+ public:
+ explicit DirectChatEvent(const QJsonObject& obj);
+
+ QMultiHash<QString, QString> usersToDirectChats() const;
+
+ static constexpr const char * TypeId = "m.direct";
+ };
+}
diff --git a/lib/events/event.cpp b/lib/events/event.cpp
new file mode 100644
index 00000000..8ddf3945
--- /dev/null
+++ b/lib/events/event.cpp
@@ -0,0 +1,182 @@
+/******************************************************************************
+ * Copyright (C) 2015 Felix Rohrbach <kde@fxrh.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "event.h"
+
+#include "roommessageevent.h"
+#include "simplestateevents.h"
+#include "roommemberevent.h"
+#include "roomavatarevent.h"
+#include "typingevent.h"
+#include "receiptevent.h"
+#include "accountdataevents.h"
+#include "directchatevent.h"
+#include "redactionevent.h"
+#include "logging.h"
+
+#include <QtCore/QJsonDocument>
+
+using namespace QMatrixClient;
+
+Event::Event(Type type, const QJsonObject& rep)
+ : _type(type), _originalJson(rep)
+{
+ if (!rep.contains("content") &&
+ !rep.value("unsigned").toObject().contains("redacted_because"))
+ {
+ qCWarning(EVENTS) << "Event without 'content' node";
+ qCWarning(EVENTS) << formatJson << rep;
+ }
+}
+
+Event::~Event() = default;
+
+QString Event::jsonType() const
+{
+ return originalJsonObject().value("type").toString();
+}
+
+QByteArray Event::originalJson() const
+{
+ return QJsonDocument(_originalJson).toJson();
+}
+
+QJsonObject Event::originalJsonObject() const
+{
+ return _originalJson;
+}
+
+const QJsonObject Event::contentJson() const
+{
+ return _originalJson["content"].toObject();
+}
+
+template <typename BaseEventT>
+inline BaseEventT* makeIfMatches(const QJsonObject&, const QString&)
+{
+ return nullptr;
+}
+
+template <typename BaseEventT, typename EventT, typename... EventTs>
+inline BaseEventT* makeIfMatches(const QJsonObject& o, const QString& selector)
+{
+ if (selector == EventT::TypeId)
+ return new EventT(o);
+
+ return makeIfMatches<BaseEventT, EventTs...>(o, selector);
+}
+
+template <>
+EventPtr _impl::doMakeEvent<Event>(const QJsonObject& obj)
+{
+ // Check more specific event types first
+ if (auto e = doMakeEvent<RoomEvent>(obj))
+ return EventPtr(move(e));
+
+ return EventPtr { makeIfMatches<Event,
+ TypingEvent, ReceiptEvent, TagEvent, ReadMarkerEvent, DirectChatEvent>(
+ obj, obj["type"].toString()) };
+}
+
+RoomEvent::RoomEvent(Event::Type type) : Event(type) { }
+
+RoomEvent::RoomEvent(Type type, const QJsonObject& rep)
+ : Event(type, rep)
+ , _id(rep["event_id"].toString())
+// , _roomId(rep["room_id"].toString())
+// , _senderId(rep["sender"].toString())
+// , _serverTimestamp(
+// QMatrixClient::fromJson<QDateTime>(rep["origin_server_ts"]))
+{
+// if (_id.isEmpty())
+// {
+// qCWarning(EVENTS) << "Can't find event_id in a room event";
+// qCWarning(EVENTS) << formatJson << rep;
+// }
+// if (!rep.contains("origin_server_ts"))
+// {
+// qCWarning(EVENTS) << "Can't find server timestamp in a room event";
+// qCWarning(EVENTS) << formatJson << rep;
+// }
+// if (_senderId.isEmpty())
+// {
+// qCWarning(EVENTS) << "Can't find sender in a room event";
+// qCWarning(EVENTS) << formatJson << rep;
+// }
+ auto unsignedData = rep["unsigned"].toObject();
+ auto redaction = unsignedData.value("redacted_because");
+ if (redaction.isObject())
+ {
+ _redactedBecause =
+ std::make_unique<RedactionEvent>(redaction.toObject());
+ return;
+ }
+
+ _txnId = unsignedData.value("transactionId").toString();
+ if (!_txnId.isEmpty())
+ qCDebug(EVENTS) << "Event transactionId:" << _txnId;
+}
+
+RoomEvent::~RoomEvent() = default; // Let the smart pointer do its job
+
+QDateTime RoomEvent::timestamp() const
+{
+ return QMatrixClient::fromJson<QDateTime>(
+ originalJsonObject().value("origin_server_ts"));
+}
+
+QString RoomEvent::roomId() const
+{
+ return originalJsonObject().value("room_id").toString();
+}
+
+QString RoomEvent::senderId() const
+{
+ return originalJsonObject().value("sender").toString();
+}
+
+QString RoomEvent::redactionReason() const
+{
+ return isRedacted() ? _redactedBecause->reason() : QString{};
+}
+
+void RoomEvent::addId(const QString& id)
+{
+ Q_ASSERT(_id.isEmpty()); Q_ASSERT(!id.isEmpty());
+ _id = id;
+}
+
+template <>
+RoomEventPtr _impl::doMakeEvent(const QJsonObject& obj)
+{
+ return RoomEventPtr { makeIfMatches<RoomEvent,
+ RoomMessageEvent, RoomNameEvent, RoomAliasesEvent,
+ RoomCanonicalAliasEvent, RoomMemberEvent, RoomTopicEvent,
+ RoomAvatarEvent, EncryptionEvent, RedactionEvent>
+ (obj, obj["type"].toString()) };
+}
+
+StateEventBase::~StateEventBase() = default;
+
+bool StateEventBase::repeatsState() const
+{
+ auto contentJson = originalJsonObject().value("content");
+ auto prevContentJson = originalJsonObject().value("unsigned")
+ .toObject().value("prev_content");
+ return contentJson == prevContentJson;
+}
diff --git a/lib/events/event.h b/lib/events/event.h
new file mode 100644
index 00000000..eccfec41
--- /dev/null
+++ b/lib/events/event.h
@@ -0,0 +1,314 @@
+/******************************************************************************
+ * Copyright (C) 2015 Felix Rohrbach <kde@fxrh.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#pragma once
+
+#include <QtCore/QString>
+#include <QtCore/QDateTime>
+#include <QtCore/QJsonObject>
+#include <QtCore/QJsonArray>
+
+#include "util.h"
+
+#include <memory>
+
+namespace QMatrixClient
+{
+ template <typename EventT>
+ using event_ptr_tt = std::unique_ptr<EventT>;
+
+ namespace _impl
+ {
+ template <typename EventT>
+ event_ptr_tt<EventT> doMakeEvent(const QJsonObject& obj);
+ }
+
+ class Event
+ {
+ Q_GADGET
+ public:
+ enum class Type : quint16
+ {
+ Unknown = 0,
+ Typing, Receipt, Tag, DirectChat, ReadMarker,
+ RoomEventBase = 0x1000,
+ RoomMessage = RoomEventBase + 1,
+ RoomEncryptedMessage, Redaction,
+ RoomStateEventBase = 0x1800,
+ RoomName = RoomStateEventBase + 1,
+ RoomAliases, RoomCanonicalAlias, RoomMember, RoomTopic,
+ RoomAvatar, RoomEncryption, RoomCreate, RoomJoinRules,
+ RoomPowerLevels,
+ Reserved = 0x2000
+ };
+
+ explicit Event(Type type) : _type(type) { }
+ Event(Type type, const QJsonObject& rep);
+ Event(const Event&) = delete;
+ virtual ~Event();
+
+ Type type() const { return _type; }
+ QString jsonType() const;
+ bool isStateEvent() const
+ {
+ return (quint16(_type) & 0x1800) == 0x1800;
+ }
+ QByteArray originalJson() const;
+ QJsonObject originalJsonObject() const;
+
+ // According to the CS API spec, every event also has
+ // a "content" object; but since its structure is different for
+ // different types, we're implementing it per-event type
+ // (and in most cases it will be a combination of other fields
+ // instead of "content" field).
+
+ const QJsonObject contentJson() const;
+
+ private:
+ Type _type;
+ QJsonObject _originalJson;
+
+ REGISTER_ENUM(Type)
+ Q_PROPERTY(Type type READ type CONSTANT)
+ Q_PROPERTY(QJsonObject contentJson READ contentJson CONSTANT)
+ };
+ using EventType = Event::Type;
+ using EventPtr = event_ptr_tt<Event>;
+
+ /** Create an event with proper type from a JSON object
+ * Use this factory template to detect the type from the JSON object
+ * contents (the detected event type should derive from the template
+ * parameter type) and create an event object of that type.
+ */
+ template <typename EventT>
+ event_ptr_tt<EventT> makeEvent(const QJsonObject& obj)
+ {
+ auto e = _impl::doMakeEvent<EventT>(obj);
+ if (!e)
+ e = std::make_unique<EventT>(EventType::Unknown, obj);
+ return e;
+ }
+
+ namespace _impl
+ {
+ template <>
+ EventPtr doMakeEvent<Event>(const QJsonObject& obj);
+ }
+
+ /**
+ * \brief A vector of pointers to events with deserialisation capabilities
+ *
+ * This is a simple wrapper over a generic vector type that adds
+ * a convenience method to deserialise events from QJsonArray.
+ * \tparam EventT base type of all events in the vector
+ */
+ template <typename EventT>
+ class EventsBatch : public std::vector<event_ptr_tt<EventT>>
+ {
+ public:
+ /**
+ * \brief Deserialise events from an array
+ *
+ * Given the following JSON construct, creates events from
+ * the array stored at key "node":
+ * \code
+ * "container": {
+ * "node": [ { "event_id": "!evt1:srv.org", ... }, ... ]
+ * }
+ * \endcode
+ * \param container - the wrapping JSON object
+ * \param node - the key in container that holds the array of events
+ */
+ void fromJson(const QJsonObject& container, const QString& node)
+ {
+ const auto objs = container.value(node).toArray();
+ using size_type = typename std::vector<event_ptr_tt<EventT>>::size_type;
+ // The below line accommodates the difference in size types of
+ // STL and Qt containers.
+ this->reserve(static_cast<size_type>(objs.size()));
+ for (auto objValue: objs)
+ this->emplace_back(makeEvent<EventT>(objValue.toObject()));
+ }
+ };
+ using Events = EventsBatch<Event>;
+
+ class RedactionEvent;
+
+ /** This class corresponds to m.room.* events */
+ class RoomEvent : public Event
+ {
+ Q_GADGET
+ Q_PROPERTY(QString id READ id)
+ Q_PROPERTY(QDateTime timestamp READ timestamp CONSTANT)
+ Q_PROPERTY(QString roomId READ roomId CONSTANT)
+ Q_PROPERTY(QString senderId READ senderId CONSTANT)
+ Q_PROPERTY(QString redactionReason READ redactionReason)
+ Q_PROPERTY(bool isRedacted READ isRedacted)
+ Q_PROPERTY(QString transactionId READ transactionId)
+ public:
+ // RedactionEvent is an incomplete type here so we cannot inline
+ // constructors and destructors
+ explicit RoomEvent(Type type);
+ RoomEvent(Type type, const QJsonObject& rep);
+ ~RoomEvent();
+
+ QString id() const { return _id; }
+ QDateTime timestamp() const;
+ QString roomId() const;
+ QString senderId() const;
+ bool isRedacted() const { return bool(_redactedBecause); }
+ const RedactionEvent* redactedBecause() const
+ {
+ return _redactedBecause.get();
+ }
+ QString redactionReason() const;
+ const QString& transactionId() const { return _txnId; }
+
+ /**
+ * Sets the transaction id for locally created events. This should be
+ * done before the event is exposed to any code using the respective
+ * Q_PROPERTY.
+ *
+ * \param txnId - transaction id, normally obtained from
+ * Connection::generateTxnId()
+ */
+ void setTransactionId(const QString& txnId) { _txnId = txnId; }
+
+ /**
+ * Sets event id for locally created events
+ *
+ * When a new event is created locally, it has no server id yet.
+ * This function allows to add the id once the confirmation from
+ * the server is received. There should be no id set previously
+ * in the event. It's the responsibility of the code calling addId()
+ * to notify clients that use Q_PROPERTY(id) about its change
+ */
+ void addId(const QString& id);
+
+ private:
+ QString _id;
+// QString _roomId;
+// QString _senderId;
+// QDateTime _serverTimestamp;
+ event_ptr_tt<RedactionEvent> _redactedBecause;
+ QString _txnId;
+ };
+ using RoomEvents = EventsBatch<RoomEvent>;
+ using RoomEventPtr = event_ptr_tt<RoomEvent>;
+
+ namespace _impl
+ {
+ template <>
+ RoomEventPtr doMakeEvent<RoomEvent>(const QJsonObject& obj);
+ }
+
+ /**
+ * Conceptually similar to QStringView (but much more primitive), it's a
+ * simple abstraction over a pair of RoomEvents::const_iterator values
+ * referring to the beginning and the end of a range in a RoomEvents
+ * container.
+ */
+ struct RoomEventsRange
+ {
+ RoomEvents::iterator from;
+ RoomEvents::iterator to;
+
+ RoomEvents::size_type size() const
+ {
+ Q_ASSERT(std::distance(from, to) >= 0);
+ return RoomEvents::size_type(std::distance(from, to));
+ }
+ bool empty() const { return from == to; }
+ RoomEvents::const_iterator begin() const { return from; }
+ RoomEvents::const_iterator end() const { return to; }
+ RoomEvents::iterator begin() { return from; }
+ RoomEvents::iterator end() { return to; }
+ };
+
+ class StateEventBase: public RoomEvent
+ {
+ public:
+ explicit StateEventBase(Type type, const QJsonObject& obj)
+ : RoomEvent(obj.contains("state_key") ? type : Type::Unknown,
+ obj)
+ { }
+ explicit StateEventBase(Type type)
+ : RoomEvent(type)
+ { }
+ ~StateEventBase() override = 0;
+
+ virtual bool repeatsState() const;
+ };
+
+ template <typename ContentT>
+ struct Prev
+ {
+ template <typename... ContentParamTs>
+ explicit Prev(const QJsonObject& unsignedJson,
+ ContentParamTs&&... contentParams)
+ : senderId(unsignedJson.value("prev_sender").toString())
+ , content(unsignedJson.value("prev_content").toObject(),
+ std::forward<ContentParamTs>(contentParams)...)
+ { }
+
+ QString senderId;
+ ContentT content;
+ };
+
+ template <typename ContentT>
+ class StateEvent: public StateEventBase
+ {
+ public:
+ using content_type = ContentT;
+
+ template <typename... ContentParamTs>
+ explicit StateEvent(Type type, const QJsonObject& obj,
+ ContentParamTs&&... contentParams)
+ : StateEventBase(type, obj)
+ , _content(contentJson(),
+ std::forward<ContentParamTs>(contentParams)...)
+ {
+ auto unsignedData = obj.value("unsigned").toObject();
+ if (unsignedData.contains("prev_content"))
+ _prev = std::make_unique<Prev<ContentT>>(unsignedData,
+ std::forward<ContentParamTs>(contentParams)...);
+ }
+ template <typename... ContentParamTs>
+ explicit StateEvent(Type type, ContentParamTs&&... contentParams)
+ : StateEventBase(type)
+ , _content(std::forward<ContentParamTs>(contentParams)...)
+ { }
+
+ QJsonObject toJson() const { return _content.toJson(); }
+
+ const ContentT& content() const { return _content; }
+ /** @deprecated Use prevContent instead */
+ const ContentT* prev_content() const { return prevContent(); }
+ const ContentT* prevContent() const
+ { return _prev ? &_prev->content : nullptr; }
+ QString prevSenderId() const { return _prev ? _prev->senderId : ""; }
+
+ protected:
+ ContentT _content;
+ std::unique_ptr<Prev<ContentT>> _prev;
+ };
+} // namespace QMatrixClient
+Q_DECLARE_METATYPE(QMatrixClient::Event*)
+Q_DECLARE_METATYPE(QMatrixClient::RoomEvent*)
+Q_DECLARE_METATYPE(const QMatrixClient::Event*)
+Q_DECLARE_METATYPE(const QMatrixClient::RoomEvent*)
diff --git a/lib/events/eventcontent.cpp b/lib/events/eventcontent.cpp
new file mode 100644
index 00000000..f5974b46
--- /dev/null
+++ b/lib/events/eventcontent.cpp
@@ -0,0 +1,85 @@
+/******************************************************************************
+ * Copyright (C) 2017 Kitsune Ral <kitsune-ral@users.sf.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "eventcontent.h"
+
+#include <QtCore/QUrl>
+#include <QtCore/QMimeDatabase>
+
+using namespace QMatrixClient::EventContent;
+
+QJsonObject Base::toJson() const
+{
+ QJsonObject o;
+ fillJson(&o);
+ return o;
+}
+
+FileInfo::FileInfo(const QUrl& u, int payloadSize, const QMimeType& mimeType,
+ const QString& originalFilename)
+ : mimeType(mimeType), url(u), payloadSize(payloadSize)
+ , originalName(originalFilename)
+{ }
+
+FileInfo::FileInfo(const QUrl& u, const QJsonObject& infoJson,
+ const QString& originalFilename)
+ : originalInfoJson(infoJson)
+ , mimeType(QMimeDatabase().mimeTypeForName(infoJson["mimetype"].toString()))
+ , url(u)
+ , payloadSize(infoJson["size"].toInt())
+ , originalName(originalFilename)
+{
+ if (!mimeType.isValid())
+ mimeType = QMimeDatabase().mimeTypeForData(QByteArray());
+}
+
+void FileInfo::fillInfoJson(QJsonObject* infoJson) const
+{
+ Q_ASSERT(infoJson);
+ infoJson->insert("size", payloadSize);
+ infoJson->insert("mimetype", mimeType.name());
+}
+
+ImageInfo::ImageInfo(const QUrl& u, int fileSize, QMimeType mimeType,
+ const QSize& imageSize)
+ : FileInfo(u, fileSize, mimeType), imageSize(imageSize)
+{ }
+
+ImageInfo::ImageInfo(const QUrl& u, const QJsonObject& infoJson,
+ const QString& originalFilename)
+ : FileInfo(u, infoJson, originalFilename)
+ , imageSize(infoJson["w"].toInt(), infoJson["h"].toInt())
+{ }
+
+void ImageInfo::fillInfoJson(QJsonObject* infoJson) const
+{
+ FileInfo::fillInfoJson(infoJson);
+ infoJson->insert("w", imageSize.width());
+ infoJson->insert("h", imageSize.height());
+}
+
+Thumbnail::Thumbnail(const QJsonObject& infoJson)
+ : ImageInfo(infoJson["thumbnail_url"].toString(),
+ infoJson["thumbnail_info"].toObject())
+{ }
+
+void Thumbnail::fillInfoJson(QJsonObject* infoJson) const
+{
+ infoJson->insert("thumbnail_url", url.toString());
+ infoJson->insert("thumbnail_info", toInfoJson<ImageInfo>(*this));
+}
diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h
new file mode 100644
index 00000000..9d44aec0
--- /dev/null
+++ b/lib/events/eventcontent.h
@@ -0,0 +1,314 @@
+/******************************************************************************
+ * Copyright (C) 2017 Kitsune Ral <kitsune-ral@users.sf.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#pragma once
+
+// This file contains generic event content definitions, applicable to room
+// message events as well as other events (e.g., avatars).
+
+#include "converters.h"
+
+#include <QtCore/QMimeType>
+#include <QtCore/QUrl>
+#include <QtCore/QSize>
+
+#include <functional>
+
+namespace QMatrixClient
+{
+ namespace EventContent
+ {
+ /**
+ * A base class for all content types that can be stored
+ * in a RoomMessageEvent
+ *
+ * Each content type class should have a constructor taking
+ * a QJsonObject and override fillJson() with an implementation
+ * that will fill the target QJsonObject with stored values. It is
+ * assumed but not required that a content object can also be created
+ * from plain data.
+ */
+ class Base
+ {
+ public:
+ explicit Base (const QJsonObject& o = {}) : originalJson(o) { }
+ virtual ~Base() = default;
+
+ QJsonObject toJson() const;
+
+ public:
+ QJsonObject originalJson;
+
+ protected:
+ virtual void fillJson(QJsonObject* o) const = 0;
+ };
+
+ template <typename T = QString>
+ class SimpleContent: public Base
+ {
+ public:
+ using value_type = T;
+
+ // The constructor is templated to enable perfect forwarding
+ template <typename TT>
+ SimpleContent(QString keyName, TT&& value)
+ : value(std::forward<TT>(value)), key(std::move(keyName))
+ { }
+ SimpleContent(const QJsonObject& json, QString keyName)
+ : Base(json)
+ , value(QMatrixClient::fromJson<T>(json[keyName]))
+ , key(std::move(keyName))
+ { }
+
+ public:
+ T value;
+
+ protected:
+ QString key;
+
+ private:
+ void fillJson(QJsonObject* json) const override
+ {
+ Q_ASSERT(json);
+ json->insert(key, QMatrixClient::toJson(value));
+ }
+ };
+
+ // The below structures fairly follow CS spec 11.2.1.6. The overall
+ // set of attributes for each content types is a superset of the spec
+ // but specific aggregation structure is altered. See doc comments to
+ // each type for the list of available attributes.
+
+ // A quick classes inheritance structure follows:
+ // FileInfo
+ // FileContent : UrlBasedContent<FileInfo, Thumbnail>
+ // AudioContent : UrlBasedContent<FileInfo, Duration>
+ // ImageInfo : FileInfo + imageSize attribute
+ // ImageContent : UrlBasedContent<ImageInfo, Thumbnail>
+ // VideoContent : UrlBasedContent<ImageInfo, Thumbnail, Duration>
+
+ /**
+ * A base/mixin class for structures representing an "info" object for
+ * some content types. These include most attachment types currently in
+ * the CS API spec.
+ *
+ * In order to use it in a content class, derive both from TypedBase
+ * (or Base) and from FileInfo (or its derivative, such as \p ImageInfo)
+ * and call fillInfoJson() to fill the "info" subobject. Make sure
+ * to pass an "info" part of JSON to FileInfo constructor, not the whole
+ * JSON content, as well as contents of "url" (or a similar key) and
+ * optionally "filename" node from the main JSON content. Assuming you
+ * don't do unusual things, you should use \p UrlBasedContent<> instead
+ * of doing multiple inheritance and overriding Base::fillJson() by hand.
+ *
+ * This class is not polymorphic.
+ */
+ class FileInfo
+ {
+ public:
+ explicit FileInfo(const QUrl& u, int payloadSize = -1,
+ const QMimeType& mimeType = {},
+ const QString& originalFilename = {});
+ FileInfo(const QUrl& u, const QJsonObject& infoJson,
+ const QString& originalFilename = {});
+
+ void fillInfoJson(QJsonObject* infoJson) const;
+
+ /**
+ * \brief Extract media id from the URL
+ *
+ * This can be used, e.g., to construct a QML-facing image://
+ * URI as follows:
+ * \code "image://provider/" + info.mediaId() \endcode
+ */
+ QString mediaId() const { return url.authority() + url.path(); }
+
+ public:
+ QJsonObject originalInfoJson;
+ QMimeType mimeType;
+ QUrl url;
+ int payloadSize;
+ QString originalName;
+ };
+
+ template <typename InfoT>
+ QJsonObject toInfoJson(const InfoT& info)
+ {
+ QJsonObject infoJson;
+ info.fillInfoJson(&infoJson);
+ return infoJson;
+ }
+
+ /**
+ * A content info class for image content types: image, thumbnail, video
+ */
+ class ImageInfo : public FileInfo
+ {
+ public:
+ explicit ImageInfo(const QUrl& u, int fileSize = -1,
+ QMimeType mimeType = {},
+ const QSize& imageSize = {});
+ ImageInfo(const QUrl& u, const QJsonObject& infoJson,
+ const QString& originalFilename = {});
+
+ void fillInfoJson(QJsonObject* infoJson) const;
+
+ public:
+ QSize imageSize;
+ };
+
+ /**
+ * An auxiliary class for an info type that carries a thumbnail
+ *
+ * This class saves/loads a thumbnail to/from "info" subobject of
+ * the JSON representation of event content; namely,
+ * "info/thumbnail_url" and "info/thumbnail_info" fields are used.
+ */
+ class Thumbnail : public ImageInfo
+ {
+ public:
+ Thumbnail(const QJsonObject& infoJson);
+ Thumbnail(const ImageInfo& info)
+ : ImageInfo(info)
+ { }
+
+ /**
+ * Writes thumbnail information to "thumbnail_info" subobject
+ * and thumbnail URL to "thumbnail_url" node inside "info".
+ */
+ void fillInfoJson(QJsonObject* infoJson) const;
+ };
+
+ class TypedBase: public Base
+ {
+ public:
+ explicit TypedBase(const QJsonObject& o = {}) : Base(o) { }
+ virtual QMimeType type() const = 0;
+ virtual const FileInfo* fileInfo() const { return nullptr; }
+ virtual const Thumbnail* thumbnailInfo() const { return nullptr; }
+ };
+
+ /**
+ * A base class for content types that have a URL and additional info
+ *
+ * Types that derive from this class template take "url" and,
+ * optionally, "filename" values from the top-level JSON object and
+ * the rest of information from the "info" subobject, as defined by
+ * the parameter type.
+ *
+ * \tparam InfoT base info class
+ */
+ template <class InfoT>
+ class UrlBasedContent : public TypedBase, public InfoT
+ {
+ public:
+ UrlBasedContent(QUrl url, InfoT&& info, QString filename = {})
+ : InfoT(url, std::forward<InfoT>(info), filename)
+ { }
+ explicit UrlBasedContent(const QJsonObject& json)
+ : TypedBase(json)
+ , InfoT(json["url"].toString(), json["info"].toObject(),
+ json["filename"].toString())
+ {
+ // A small hack to facilitate links creation in QML.
+ originalJson.insert("mediaId", InfoT::mediaId());
+ }
+
+ QMimeType type() const override { return InfoT::mimeType; }
+ const FileInfo* fileInfo() const override { return this; }
+
+ protected:
+ void fillJson(QJsonObject* json) const override
+ {
+ Q_ASSERT(json);
+ json->insert("url", InfoT::url.toString());
+ if (!InfoT::originalName.isEmpty())
+ json->insert("filename", InfoT::originalName);
+ json->insert("info", toInfoJson<InfoT>(*this));
+ }
+ };
+
+ template <typename InfoT>
+ class UrlWithThumbnailContent : public UrlBasedContent<InfoT>
+ {
+ public:
+ // TODO: POD constructor
+ explicit UrlWithThumbnailContent(const QJsonObject& json)
+ : UrlBasedContent<InfoT>(json)
+ , thumbnail(InfoT::originalInfoJson)
+ {
+ // Another small hack, to simplify making a thumbnail link
+ UrlBasedContent<InfoT>::originalJson.insert(
+ "thumbnailMediaId", thumbnail.mediaId());
+ }
+
+ const Thumbnail* thumbnailInfo() const override
+ { return &thumbnail; }
+
+ public:
+ Thumbnail thumbnail;
+
+ protected:
+ void fillJson(QJsonObject* json) const override
+ {
+ UrlBasedContent<InfoT>::fillJson(json);
+ auto infoJson = json->take("info").toObject();
+ thumbnail.fillInfoJson(&infoJson);
+ json->insert("info", infoJson);
+ }
+ };
+
+ /**
+ * Content class for m.image
+ *
+ * Available fields:
+ * - corresponding to the top-level JSON:
+ * - url
+ * - filename (extension to the spec)
+ * - corresponding to the "info" subobject:
+ * - payloadSize ("size" in JSON)
+ * - mimeType ("mimetype" in JSON)
+ * - imageSize (QSize for a combination of "h" and "w" in JSON)
+ * - thumbnail.url ("thumbnail_url" in JSON)
+ * - corresponding to the "info/thumbnail_info" subobject: contents of
+ * thumbnail field, in the same vein as for the main image:
+ * - payloadSize
+ * - mimeType
+ * - imageSize
+ */
+ using ImageContent = UrlWithThumbnailContent<ImageInfo>;
+
+ /**
+ * Content class for m.file
+ *
+ * Available fields:
+ * - corresponding to the top-level JSON:
+ * - url
+ * - filename
+ * - corresponding to the "info" subobject:
+ * - payloadSize ("size" in JSON)
+ * - mimeType ("mimetype" in JSON)
+ * - thumbnail.url ("thumbnail_url" in JSON)
+ * - corresponding to the "info/thumbnail_info" subobject:
+ * - thumbnail.payloadSize
+ * - thumbnail.mimeType
+ * - thumbnail.imageSize (QSize for "h" and "w" in JSON)
+ */
+ using FileContent = UrlWithThumbnailContent<FileInfo>;
+ } // namespace EventContent
+} // namespace QMatrixClient
diff --git a/lib/events/receiptevent.cpp b/lib/events/receiptevent.cpp
new file mode 100644
index 00000000..7555db82
--- /dev/null
+++ b/lib/events/receiptevent.cpp
@@ -0,0 +1,70 @@
+/******************************************************************************
+ * Copyright (C) 2016 Felix Rohrbach <kde@fxrh.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+Example of a Receipt Event:
+{
+ "content": {
+ "$1435641916114394fHBLK:matrix.org": {
+ "m.read": {
+ "@rikj:jki.re": {
+ "ts": 1436451550453
+ }
+ }
+ }
+ },
+ "room_id": "!KpjVgQyZpzBwvMBsnT:matrix.org",
+ "type": "m.receipt"
+}
+*/
+
+#include "receiptevent.h"
+
+#include "converters.h"
+#include "logging.h"
+
+using namespace QMatrixClient;
+
+ReceiptEvent::ReceiptEvent(const QJsonObject& obj)
+ : Event(Type::Receipt, obj)
+{
+ Q_ASSERT(obj["type"].toString() == TypeId);
+
+ const QJsonObject contents = contentJson();
+ _eventsWithReceipts.reserve(contents.size());
+ for( auto eventIt = contents.begin(); eventIt != contents.end(); ++eventIt )
+ {
+ if (eventIt.key().isEmpty())
+ {
+ qCWarning(EPHEMERAL) << "ReceiptEvent has an empty event id, skipping";
+ qCDebug(EPHEMERAL) << "ReceiptEvent content follows:\n" << contents;
+ continue;
+ }
+ const QJsonObject reads = eventIt.value().toObject().value("m.read").toObject();
+ QVector<Receipt> receipts;
+ receipts.reserve(reads.size());
+ for( auto userIt = reads.begin(); userIt != reads.end(); ++userIt )
+ {
+ const QJsonObject user = userIt.value().toObject();
+ receipts.push_back({userIt.key(),
+ QMatrixClient::fromJson<QDateTime>(user["ts"])});
+ }
+ _eventsWithReceipts.push_back({eventIt.key(), std::move(receipts)});
+ }
+}
+
diff --git a/lib/events/receiptevent.h b/lib/events/receiptevent.h
new file mode 100644
index 00000000..5b99ae3f
--- /dev/null
+++ b/lib/events/receiptevent.h
@@ -0,0 +1,50 @@
+/******************************************************************************
+ * Copyright (C) 2016 Felix Rohrbach <kde@fxrh.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#pragma once
+
+#include "event.h"
+
+namespace QMatrixClient
+{
+ struct Receipt
+ {
+ QString userId;
+ QDateTime timestamp;
+ };
+ struct ReceiptsForEvent
+ {
+ QString evtId;
+ QVector<Receipt> receipts;
+ };
+ using EventsWithReceipts = QVector<ReceiptsForEvent>;
+
+ class ReceiptEvent: public Event
+ {
+ public:
+ explicit ReceiptEvent(const QJsonObject& obj);
+
+ EventsWithReceipts eventsWithReceipts() const
+ { return _eventsWithReceipts; }
+
+ static constexpr const char* const TypeId = "m.receipt";
+
+ private:
+ EventsWithReceipts _eventsWithReceipts;
+ };
+} // namespace QMatrixClient
diff --git a/lib/events/redactionevent.cpp b/lib/events/redactionevent.cpp
new file mode 100644
index 00000000..bf467718
--- /dev/null
+++ b/lib/events/redactionevent.cpp
@@ -0,0 +1 @@
+#include "redactionevent.h"
diff --git a/lib/events/redactionevent.h b/lib/events/redactionevent.h
new file mode 100644
index 00000000..fa6902ab
--- /dev/null
+++ b/lib/events/redactionevent.h
@@ -0,0 +1,43 @@
+/******************************************************************************
+ * Copyright (C) 2017 Kitsune Ral <kitsune-ral@users.sf.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#pragma once
+
+#include "event.h"
+
+namespace QMatrixClient
+{
+ class RedactionEvent : public RoomEvent
+ {
+ public:
+ static constexpr const char* const TypeId = "m.room.redaction";
+
+ RedactionEvent(const QJsonObject& obj)
+ : RoomEvent(Type::Redaction, obj)
+ , _redactedEvent(obj.value("redacts").toString())
+ , _reason(contentJson().value("reason").toString())
+ { }
+
+ const QString& redactedEvent() const { return _redactedEvent; }
+ const QString& reason() const { return _reason; }
+
+ private:
+ QString _redactedEvent;
+ QString _reason;
+ };
+} // namespace QMatrixClient
diff --git a/lib/events/roomavatarevent.cpp b/lib/events/roomavatarevent.cpp
new file mode 100644
index 00000000..7a5f82a1
--- /dev/null
+++ b/lib/events/roomavatarevent.cpp
@@ -0,0 +1,23 @@
+/******************************************************************************
+ * Copyright (C) 2017 Kitsune Ral <kitsune-ral@users.sf.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "roomavatarevent.h"
+
+using namespace QMatrixClient;
+
+
diff --git a/lib/events/roomavatarevent.h b/lib/events/roomavatarevent.h
new file mode 100644
index 00000000..ccfe8fbf
--- /dev/null
+++ b/lib/events/roomavatarevent.h
@@ -0,0 +1,43 @@
+/******************************************************************************
+ * Copyright (C) 2017 Kitsune Ral <kitsune-ral@users.sf.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#pragma once
+
+#include "event.h"
+
+#include <utility>
+
+#include "eventcontent.h"
+
+namespace QMatrixClient
+{
+ class RoomAvatarEvent: public StateEvent<EventContent::ImageContent>
+ {
+ // It's a bit of an overkill to use a full-fledged ImageContent
+ // because in reality m.room.avatar usually only has a single URL,
+ // without a thumbnail. But The Spec says there be thumbnails, and
+ // we follow The Spec.
+ public:
+ explicit RoomAvatarEvent(const QJsonObject& obj)
+ : StateEvent(Type::RoomAvatar, obj)
+ { }
+
+ static constexpr const char* TypeId = "m.room.avatar";
+ };
+
+} // namespace QMatrixClient
diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp
new file mode 100644
index 00000000..76b003c2
--- /dev/null
+++ b/lib/events/roommemberevent.cpp
@@ -0,0 +1,69 @@
+/******************************************************************************
+ * Copyright (C) 2015 Felix Rohrbach <kde@fxrh.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "roommemberevent.h"
+
+#include "logging.h"
+
+#include <array>
+
+using namespace QMatrixClient;
+
+static const std::array<QString, 5> membershipStrings = { {
+ QStringLiteral("invite"), QStringLiteral("join"),
+ QStringLiteral("knock"), QStringLiteral("leave"),
+ QStringLiteral("ban")
+} };
+
+namespace QMatrixClient
+{
+ template <>
+ struct FromJson<MembershipType>
+ {
+ MembershipType operator()(const QJsonValue& jv) const
+ {
+ const auto& membershipString = jv.toString();
+ for (auto it = membershipStrings.begin();
+ it != membershipStrings.end(); ++it)
+ if (membershipString == *it)
+ return MembershipType(it - membershipStrings.begin());
+
+ qCWarning(EVENTS) << "Unknown MembershipType: " << membershipString;
+ return MembershipType::Undefined;
+ }
+ };
+}
+
+MemberEventContent::MemberEventContent(const QJsonObject& json)
+ : membership(fromJson<MembershipType>(json["membership"]))
+ , isDirect(json["is_direct"].toBool())
+ , displayName(json["displayname"].toString())
+ , avatarUrl(json["avatar_url"].toString())
+{ }
+
+void MemberEventContent::fillJson(QJsonObject* o) const
+{
+ Q_ASSERT(o);
+ Q_ASSERT_X(membership != MembershipType::Undefined, __FUNCTION__,
+ "The key 'membership' must be explicit in MemberEventContent");
+ if (membership != MembershipType::Undefined)
+ o->insert("membership", membershipStrings[membership]);
+ o->insert("displayname", displayName);
+ if (avatarUrl.isValid())
+ o->insert("avatar_url", avatarUrl.toString());
+}
diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h
new file mode 100644
index 00000000..89b970c9
--- /dev/null
+++ b/lib/events/roommemberevent.h
@@ -0,0 +1,78 @@
+/******************************************************************************
+ * Copyright (C) 2015 Felix Rohrbach <kde@fxrh.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#pragma once
+
+#include "event.h"
+
+#include "eventcontent.h"
+
+#include <QtCore/QUrl>
+
+namespace QMatrixClient
+{
+ class MemberEventContent: public EventContent::Base
+ {
+ public:
+ enum MembershipType : size_t { Invite = 0, Join, Knock, Leave, Ban,
+ Undefined };
+
+ explicit MemberEventContent(MembershipType mt = MembershipType::Join)
+ : membership(mt)
+ { }
+ explicit MemberEventContent(const QJsonObject& json);
+
+ MembershipType membership;
+ bool isDirect = false;
+ QString displayName;
+ QUrl avatarUrl;
+
+ protected:
+ void fillJson(QJsonObject* o) const override;
+ };
+
+ using MembershipType = MemberEventContent::MembershipType;
+
+ class RoomMemberEvent: public StateEvent<MemberEventContent>
+ {
+ Q_GADGET
+ public:
+ static constexpr const char* TypeId = "m.room.member";
+
+ using MembershipType = MemberEventContent::MembershipType;
+
+ RoomMemberEvent(MemberEventContent&& c)
+ : StateEvent(Type::RoomMember, c)
+ { }
+ explicit RoomMemberEvent(const QJsonObject& obj)
+ : StateEvent(Type::RoomMember, obj)
+// , _userId(obj["state_key"].toString())
+ { }
+
+ MembershipType membership() const { return content().membership; }
+ QString userId() const
+ { return originalJsonObject().value("state_key").toString(); }
+ bool isDirect() const { return content().isDirect; }
+ QString displayName() const { return content().displayName; }
+ QUrl avatarUrl() const { return content().avatarUrl; }
+
+ private:
+// QString _userId;
+ REGISTER_ENUM(MembershipType)
+ };
+} // namespace QMatrixClient
diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp
new file mode 100644
index 00000000..dec0ca50
--- /dev/null
+++ b/lib/events/roommessageevent.cpp
@@ -0,0 +1,193 @@
+/******************************************************************************
+ * Copyright (C) 2015 Felix Rohrbach <kde@fxrh.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "roommessageevent.h"
+
+#include "logging.h"
+
+#include <QtCore/QMimeDatabase>
+
+using namespace QMatrixClient;
+using namespace EventContent;
+
+using MsgType = RoomMessageEvent::MsgType;
+
+template <typename ContentT>
+TypedBase* make(const QJsonObject& json)
+{
+ return new ContentT(json);
+}
+
+struct MsgTypeDesc
+{
+ QString jsonType;
+ MsgType enumType;
+ TypedBase* (*maker)(const QJsonObject&);
+};
+
+const std::vector<MsgTypeDesc> msgTypes =
+ { { QStringLiteral("m.text"), MsgType::Text, make<TextContent> }
+ , { QStringLiteral("m.emote"), MsgType::Emote, make<TextContent> }
+ , { QStringLiteral("m.notice"), MsgType::Notice, make<TextContent> }
+ , { QStringLiteral("m.image"), MsgType::Image, make<ImageContent> }
+ , { QStringLiteral("m.file"), MsgType::File, make<FileContent> }
+ , { QStringLiteral("m.location"), MsgType::Location, make<LocationContent> }
+ , { QStringLiteral("m.video"), MsgType::Video, make<VideoContent> }
+ , { QStringLiteral("m.audio"), MsgType::Audio, make<AudioContent> }
+ };
+
+QString msgTypeToJson(MsgType enumType)
+{
+ auto it = std::find_if(msgTypes.begin(), msgTypes.end(),
+ [=](const MsgTypeDesc& mtd) { return mtd.enumType == enumType; });
+ if (it != msgTypes.end())
+ return it->jsonType;
+
+ return {};
+}
+
+MsgType jsonToMsgType(const QString& jsonType)
+{
+ auto it = std::find_if(msgTypes.begin(), msgTypes.end(),
+ [=](const MsgTypeDesc& mtd) { return mtd.jsonType == jsonType; });
+ if (it != msgTypes.end())
+ return it->enumType;
+
+ return MsgType::Unknown;
+}
+
+RoomMessageEvent::RoomMessageEvent(const QString& plainBody,
+ MsgType msgType, TypedBase* content)
+ : RoomMessageEvent(plainBody, msgTypeToJson(msgType), content)
+{ }
+
+RoomMessageEvent::RoomMessageEvent(const QJsonObject& obj)
+ : RoomEvent(Type::RoomMessage, obj), _content(nullptr)
+{
+ if (isRedacted())
+ return;
+ const QJsonObject content = contentJson();
+ if ( content.contains("msgtype") && content.contains("body") )
+ {
+ _plainBody = content["body"].toString();
+
+ _msgtype = content["msgtype"].toString();
+ for (auto mt: msgTypes)
+ if (mt.jsonType == _msgtype)
+ _content.reset(mt.maker(content));
+
+ if (!_content)
+ {
+ qCWarning(EVENTS) << "RoomMessageEvent: couldn't load content,"
+ << " full content dump follows";
+ qCWarning(EVENTS) << formatJson << content;
+ }
+ }
+ else
+ {
+ qCWarning(EVENTS) << "No body or msgtype in room message event";
+ qCWarning(EVENTS) << formatJson << obj;
+ }
+}
+
+RoomMessageEvent::MsgType RoomMessageEvent::msgtype() const
+{
+ return jsonToMsgType(_msgtype);
+}
+
+QMimeType RoomMessageEvent::mimeType() const
+{
+ return _content ? _content->type() :
+ QMimeDatabase().mimeTypeForName("text/plain");
+}
+
+bool RoomMessageEvent::hasTextContent() const
+{
+ return content() &&
+ (msgtype() == MsgType::Text || msgtype() == MsgType::Emote ||
+ msgtype() == MsgType::Notice); // FIXME: Unbind from specific msgtypes
+}
+
+bool RoomMessageEvent::hasFileContent() const
+{
+ return content() && content()->fileInfo();
+}
+
+bool RoomMessageEvent::hasThumbnail() const
+{
+ return content() && content()->thumbnailInfo();
+}
+
+QJsonObject RoomMessageEvent::toJson() const
+{
+ QJsonObject obj = _content ? _content->toJson() : QJsonObject();
+ obj.insert("msgtype", msgTypeToJson(msgtype()));
+ obj.insert("body", plainBody());
+ return obj;
+}
+
+TextContent::TextContent(const QString& text, const QString& contentType)
+ : mimeType(QMimeDatabase().mimeTypeForName(contentType)), body(text)
+{ }
+
+TextContent::TextContent(const QJsonObject& json)
+{
+ QMimeDatabase db;
+
+ // Special-casing the custom matrix.org's (actually, Riot's) way
+ // of sending HTML messages.
+ if (json["format"].toString() == "org.matrix.custom.html")
+ {
+ mimeType = db.mimeTypeForName("text/html");
+ body = json["formatted_body"].toString();
+ } else {
+ // Falling back to plain text, as there's no standard way to describe
+ // rich text in messages.
+ mimeType = db.mimeTypeForName("text/plain");
+ body = json["body"].toString();
+ }
+}
+
+void TextContent::fillJson(QJsonObject* json) const
+{
+ Q_ASSERT(json);
+ json->insert("format", QStringLiteral("org.matrix.custom.html"));
+ json->insert("formatted_body", body);
+}
+
+LocationContent::LocationContent(const QString& geoUri, const ImageInfo& thumbnail)
+ : geoUri(geoUri), thumbnail(thumbnail)
+{ }
+
+LocationContent::LocationContent(const QJsonObject& json)
+ : TypedBase(json)
+ , geoUri(json["geo_uri"].toString())
+ , thumbnail(json["info"].toObject())
+{ }
+
+QMimeType LocationContent::type() const
+{
+ return QMimeDatabase().mimeTypeForData(geoUri.toLatin1());
+}
+
+void LocationContent::fillJson(QJsonObject* o) const
+{
+ Q_ASSERT(o);
+ o->insert("geo_uri", geoUri);
+ o->insert("info", toInfoJson(thumbnail));
+}
diff --git a/lib/events/roommessageevent.h b/lib/events/roommessageevent.h
new file mode 100644
index 00000000..a55564ed
--- /dev/null
+++ b/lib/events/roommessageevent.h
@@ -0,0 +1,194 @@
+/******************************************************************************
+ * Copyright (C) 2015 Felix Rohrbach <kde@fxrh.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#pragma once
+
+#include "event.h"
+
+#include "eventcontent.h"
+
+namespace QMatrixClient
+{
+ namespace MessageEventContent = EventContent; // Back-compatibility
+
+ /**
+ * The event class corresponding to m.room.message events
+ */
+ class RoomMessageEvent: public RoomEvent
+ {
+ Q_GADGET
+ Q_PROPERTY(QString msgType READ rawMsgtype CONSTANT)
+ Q_PROPERTY(QString plainBody READ plainBody CONSTANT)
+ Q_PROPERTY(QMimeType mimeType READ mimeType STORED false CONSTANT)
+ Q_PROPERTY(EventContent::TypedBase* content READ content CONSTANT)
+ public:
+ enum class MsgType
+ {
+ Text, Emote, Notice, Image, File, Location, Video, Audio, Unknown
+ };
+
+ RoomMessageEvent(const QString& plainBody,
+ const QString& jsonMsgType,
+ EventContent::TypedBase* content = nullptr)
+ : RoomEvent(Type::RoomMessage)
+ , _msgtype(jsonMsgType), _plainBody(plainBody), _content(content)
+ { }
+ explicit RoomMessageEvent(const QString& plainBody,
+ MsgType msgType = MsgType::Text,
+ EventContent::TypedBase* content = nullptr);
+ explicit RoomMessageEvent(const QJsonObject& obj);
+
+ MsgType msgtype() const;
+ QString rawMsgtype() const { return _msgtype; }
+ const QString& plainBody() const { return _plainBody; }
+ EventContent::TypedBase* content() const
+ { return _content.data(); }
+ QMimeType mimeType() const;
+ bool hasTextContent() const;
+ bool hasFileContent() const;
+ bool hasThumbnail() const;
+
+ QJsonObject toJson() const;
+
+ static constexpr const char* TypeId = "m.room.message";
+
+ private:
+ QString _msgtype;
+ QString _plainBody;
+ QScopedPointer<EventContent::TypedBase> _content;
+
+ REGISTER_ENUM(MsgType)
+ };
+ using MessageEventType = RoomMessageEvent::MsgType;
+
+ namespace EventContent
+ {
+ // Additional event content types
+
+ /**
+ * Rich text content for m.text, m.emote, m.notice
+ *
+ * Available fields: mimeType, body. The body can be either rich text
+ * or plain text, depending on what mimeType specifies.
+ */
+ class TextContent: public TypedBase
+ {
+ public:
+ TextContent(const QString& text, const QString& contentType);
+ explicit TextContent(const QJsonObject& json);
+
+ QMimeType type() const override { return mimeType; }
+
+ QMimeType mimeType;
+ QString body;
+
+ protected:
+ void fillJson(QJsonObject* json) const override;
+ };
+
+ /**
+ * Content class for m.location
+ *
+ * Available fields:
+ * - corresponding to the top-level JSON:
+ * - geoUri ("geo_uri" in JSON)
+ * - corresponding to the "info" subobject:
+ * - thumbnail.url ("thumbnail_url" in JSON)
+ * - corresponding to the "info/thumbnail_info" subobject:
+ * - thumbnail.payloadSize
+ * - thumbnail.mimeType
+ * - thumbnail.imageSize
+ */
+ class LocationContent: public TypedBase
+ {
+ public:
+ LocationContent(const QString& geoUri,
+ const ImageInfo& thumbnail);
+ explicit LocationContent(const QJsonObject& json);
+
+ QMimeType type() const override;
+
+ public:
+ QString geoUri;
+ Thumbnail thumbnail;
+
+ protected:
+ void fillJson(QJsonObject* o) const override;
+ };
+
+ /**
+ * A base class for info types that include duration: audio and video
+ */
+ template <typename ContentT>
+ class PlayableContent : public ContentT
+ {
+ public:
+ PlayableContent(const QJsonObject& json)
+ : ContentT(json)
+ , duration(ContentT::originalInfoJson["duration"].toInt())
+ { }
+
+ protected:
+ void fillJson(QJsonObject* json) const override
+ {
+ ContentT::fillJson(json);
+ auto infoJson = json->take("info").toObject();
+ infoJson.insert("duration", duration);
+ json->insert("info", infoJson);
+ }
+
+ public:
+ int duration;
+ };
+
+ /**
+ * Content class for m.video
+ *
+ * Available fields:
+ * - corresponding to the top-level JSON:
+ * - url
+ * - filename (extension to the CS API spec)
+ * - corresponding to the "info" subobject:
+ * - payloadSize ("size" in JSON)
+ * - mimeType ("mimetype" in JSON)
+ * - duration
+ * - imageSize (QSize for a combination of "h" and "w" in JSON)
+ * - thumbnail.url ("thumbnail_url" in JSON)
+ * - corresponding to the "info/thumbnail_info" subobject: contents of
+ * thumbnail field, in the same vein as for "info":
+ * - payloadSize
+ * - mimeType
+ * - imageSize
+ */
+ using VideoContent = PlayableContent<UrlWithThumbnailContent<ImageInfo>>;
+
+ /**
+ * Content class for m.audio
+ *
+ * Available fields:
+ * - corresponding to the top-level JSON:
+ * - url
+ * - filename (extension to the CS API spec)
+ * - corresponding to the "info" subobject:
+ * - payloadSize ("size" in JSON)
+ * - mimeType ("mimetype" in JSON)
+ * - duration
+ */
+ using AudioContent = PlayableContent<UrlBasedContent<FileInfo>>;
+ } // namespace EventContent
+} // namespace QMatrixClient
diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h
new file mode 100644
index 00000000..6b0cd51a
--- /dev/null
+++ b/lib/events/simplestateevents.h
@@ -0,0 +1,53 @@
+/******************************************************************************
+ * Copyright (C) 2017 Kitsune Ral <kitsune-ral@users.sf.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#pragma once
+
+#include "event.h"
+#include "eventcontent.h"
+
+namespace QMatrixClient
+{
+#define DEFINE_SIMPLE_STATE_EVENT(_Name, _TypeId, _EnumType, _ContentType, _ContentKey) \
+ class _Name \
+ : public StateEvent<EventContent::SimpleContent<_ContentType>> \
+ { \
+ public: \
+ static constexpr const char* TypeId = _TypeId; \
+ explicit _Name(const QJsonObject& obj) \
+ : StateEvent(_EnumType, obj, QStringLiteral(#_ContentKey)) \
+ { } \
+ template <typename T> \
+ explicit _Name(T&& value) \
+ : StateEvent(_EnumType, QStringLiteral(#_ContentKey), \
+ std::forward<T>(value)) \
+ { } \
+ const _ContentType& _ContentKey() const { return content().value; } \
+ };
+
+ DEFINE_SIMPLE_STATE_EVENT(RoomNameEvent, "m.room.name",
+ Event::Type::RoomName, QString, name)
+ DEFINE_SIMPLE_STATE_EVENT(RoomAliasesEvent, "m.room.aliases",
+ Event::Type::RoomAliases, QStringList, aliases)
+ DEFINE_SIMPLE_STATE_EVENT(RoomCanonicalAliasEvent, "m.room.canonical_alias",
+ Event::Type::RoomCanonicalAlias, QString, alias)
+ DEFINE_SIMPLE_STATE_EVENT(RoomTopicEvent, "m.room.topic",
+ Event::Type::RoomTopic, QString, topic)
+ DEFINE_SIMPLE_STATE_EVENT(EncryptionEvent, "m.room.encryption",
+ Event::Type::RoomEncryption, QString, algorithm)
+} // namespace QMatrixClient
diff --git a/lib/events/typingevent.cpp b/lib/events/typingevent.cpp
new file mode 100644
index 00000000..a4d3bae4
--- /dev/null
+++ b/lib/events/typingevent.cpp
@@ -0,0 +1,32 @@
+/******************************************************************************
+ * Copyright (C) 2015 Felix Rohrbach <kde@fxrh.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "typingevent.h"
+
+using namespace QMatrixClient;
+
+TypingEvent::TypingEvent(const QJsonObject& obj)
+ : Event(Type::Typing, obj)
+{
+ QJsonValue result;
+ result= contentJson()["user_ids"];
+ QJsonArray array = result.toArray();
+ for( const QJsonValue& user: array )
+ _users.push_back(user.toString());
+}
+
diff --git a/lib/events/typingevent.h b/lib/events/typingevent.h
new file mode 100644
index 00000000..8c9551a4
--- /dev/null
+++ b/lib/events/typingevent.h
@@ -0,0 +1,39 @@
+/******************************************************************************
+ * Copyright (C) 2015 Felix Rohrbach <kde@fxrh.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#pragma once
+
+#include "event.h"
+
+#include <QtCore/QStringList>
+
+namespace QMatrixClient
+{
+ class TypingEvent: public Event
+ {
+ public:
+ static constexpr const char* const TypeId = "m.typing";
+
+ TypingEvent(const QJsonObject& obj);
+
+ QStringList users() const { return _users; }
+
+ private:
+ QStringList _users;
+ };
+} // namespace QMatrixClient