diff options
Diffstat (limited to 'lib/events')
-rw-r--r-- | lib/events/accountdataevents.h | 78 | ||||
-rw-r--r-- | lib/events/directchatevent.cpp | 36 | ||||
-rw-r--r-- | lib/events/directchatevent.h | 34 | ||||
-rw-r--r-- | lib/events/event.cpp | 182 | ||||
-rw-r--r-- | lib/events/event.h | 314 | ||||
-rw-r--r-- | lib/events/eventcontent.cpp | 85 | ||||
-rw-r--r-- | lib/events/eventcontent.h | 314 | ||||
-rw-r--r-- | lib/events/receiptevent.cpp | 70 | ||||
-rw-r--r-- | lib/events/receiptevent.h | 50 | ||||
-rw-r--r-- | lib/events/redactionevent.cpp | 1 | ||||
-rw-r--r-- | lib/events/redactionevent.h | 43 | ||||
-rw-r--r-- | lib/events/roomavatarevent.cpp | 23 | ||||
-rw-r--r-- | lib/events/roomavatarevent.h | 43 | ||||
-rw-r--r-- | lib/events/roommemberevent.cpp | 69 | ||||
-rw-r--r-- | lib/events/roommemberevent.h | 78 | ||||
-rw-r--r-- | lib/events/roommessageevent.cpp | 193 | ||||
-rw-r--r-- | lib/events/roommessageevent.h | 194 | ||||
-rw-r--r-- | lib/events/simplestateevents.h | 53 | ||||
-rw-r--r-- | lib/events/typingevent.cpp | 32 | ||||
-rw-r--r-- | lib/events/typingevent.h | 39 |
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 |