| 1 | // SPDX-FileCopyrightText: 2015 Felix Rohrbach <kde@fxrh.de> |
| 2 | // SPDX-FileCopyrightText: 2016 Kitsune Ral <Kitsune-Ral@users.sf.net> |
| 3 | // SPDX-FileCopyrightText: 2017 Roman Plášil <me@rplasil.name> |
| 4 | // SPDX-License-Identifier: LGPL-2.1-or-later |
| 5 | |
| 6 | #pragma once |
| 7 | |
| 8 | #include "eventcontent.h" |
| 9 | #include "eventrelation.h" |
| 10 | #include "roomevent.h" |
| 11 | |
| 12 | class QFileInfo; |
| 13 | |
| 14 | namespace Quotient { |
| 15 | namespace MessageEventContent = EventContent; // Back-compatibility |
| 16 | |
| 17 | /** |
| 18 | * The event class corresponding to m.room.message events |
| 19 | */ |
| 20 | class QUOTIENT_API RoomMessageEvent : public RoomEvent { |
| 21 | Q_GADGET |
| 22 | public: |
| 23 | QUO_EVENT(RoomMessageEvent, "m.room.message" ) |
| 24 | |
| 25 | enum class MsgType { |
| 26 | Text, |
| 27 | Emote, |
| 28 | Notice, |
| 29 | Image, |
| 30 | File, |
| 31 | Location, |
| 32 | Video, |
| 33 | Audio, |
| 34 | Unknown |
| 35 | }; |
| 36 | |
| 37 | RoomMessageEvent(const QString& plainBody, const QString& jsonMsgType, |
| 38 | EventContent::TypedBase* content = nullptr); |
| 39 | explicit RoomMessageEvent(const QString& plainBody, |
| 40 | MsgType msgType = MsgType::Text, |
| 41 | EventContent::TypedBase* content = nullptr); |
| 42 | #if QT_VERSION_MAJOR < 6 |
| 43 | [[deprecated("Create an EventContent object on the client side" |
| 44 | " and pass it to other constructors" )]] // |
| 45 | explicit RoomMessageEvent(const QString& plainBody, const QFileInfo& file, |
| 46 | bool asGenericFile = false); |
| 47 | #endif |
| 48 | explicit RoomMessageEvent(const QJsonObject& obj); |
| 49 | |
| 50 | MsgType msgtype() const; |
| 51 | QString rawMsgtype() const; |
| 52 | QString plainBody() const; |
| 53 | const EventContent::TypedBase* content() const { return _content.data(); } |
| 54 | template <typename VisitorT> |
| 55 | void editContent(VisitorT&& visitor) |
| 56 | { |
| 57 | visitor(*_content); |
| 58 | editJson()[ContentKey] = assembleContentJson(plainBody: plainBody(), jsonMsgType: rawMsgtype(), |
| 59 | content: _content.data()); |
| 60 | } |
| 61 | QMimeType mimeType() const; |
| 62 | //! \brief Determine whether the message has text content |
| 63 | //! |
| 64 | //! \return true, if the message type is one of m.text, m.notice, m.emote, |
| 65 | //! or the message type is unspecified (in which case plainBody() |
| 66 | //! can still be examined); false otherwise |
| 67 | bool hasTextContent() const; |
| 68 | //! \brief Determine whether the message has a file/attachment |
| 69 | //! |
| 70 | //! \return true, if the message has a data structure corresponding to |
| 71 | //! a file (such as m.file or m.audio); false otherwise |
| 72 | bool hasFileContent() const; |
| 73 | //! \brief Determine whether the message has a thumbnail |
| 74 | //! |
| 75 | //! \return true, if the message has a data structure corresponding to |
| 76 | //! a thumbnail (the message type may be one for visual content, |
| 77 | //! such as m.image, or generic binary content, i.e. m.file); |
| 78 | //! false otherwise |
| 79 | bool hasThumbnail() const; |
| 80 | |
| 81 | //! \brief Obtain id of an event replaced by the current one |
| 82 | //! \sa RoomEvent::isReplaced, RoomEvent::replacedBy |
| 83 | QString replacedEvent() const; |
| 84 | |
| 85 | //! \brief Determine whether the event has been replaced |
| 86 | //! |
| 87 | //! \return true if this event has been overridden by another event |
| 88 | //! with `"rel_type": "m.replace"`; false otherwise |
| 89 | bool isReplaced() const; |
| 90 | |
| 91 | QString replacedBy() const; |
| 92 | |
| 93 | static QString rawMsgTypeForUrl(const QUrl& url); |
| 94 | static QString rawMsgTypeForFile(const QFileInfo& fi); |
| 95 | |
| 96 | private: |
| 97 | QScopedPointer<EventContent::TypedBase> _content; |
| 98 | |
| 99 | // FIXME: should it really be static? |
| 100 | static QJsonObject assembleContentJson(const QString& plainBody, |
| 101 | const QString& jsonMsgType, |
| 102 | EventContent::TypedBase* content); |
| 103 | |
| 104 | Q_ENUM(MsgType) |
| 105 | }; |
| 106 | |
| 107 | using MessageEventType = RoomMessageEvent::MsgType; |
| 108 | |
| 109 | namespace EventContent { |
| 110 | |
| 111 | struct [[deprecated("Use Quotient::EventRelation instead" )]] RelatesTo |
| 112 | : EventRelation { |
| 113 | static constexpr auto ReplyTypeId() { return ReplyType; } |
| 114 | static constexpr auto ReplacementTypeId() { return ReplacementType; } |
| 115 | }; |
| 116 | [[deprecated("Use EventRelation::replyTo() instead" )]] |
| 117 | inline auto replyTo(QString eventId) |
| 118 | { |
| 119 | return EventRelation::replyTo(eventId: std::move(eventId)); |
| 120 | } |
| 121 | [[deprecated("Use EventRelation::replace() instead" )]] |
| 122 | inline auto replacementOf(QString eventId) |
| 123 | { |
| 124 | return EventRelation::replace(eventId: std::move(eventId)); |
| 125 | } |
| 126 | |
| 127 | // Additional event content types |
| 128 | |
| 129 | /** |
| 130 | * Rich text content for m.text, m.emote, m.notice |
| 131 | * |
| 132 | * Available fields: mimeType, body. The body can be either rich text |
| 133 | * or plain text, depending on what mimeType specifies. |
| 134 | */ |
| 135 | class QUOTIENT_API TextContent : public TypedBase { |
| 136 | public: |
| 137 | TextContent(QString text, const QString& contentType, |
| 138 | Omittable<EventRelation> relatesTo = none); |
| 139 | explicit TextContent(const QJsonObject& json); |
| 140 | |
| 141 | QMimeType type() const override { return mimeType; } |
| 142 | |
| 143 | QMimeType mimeType; |
| 144 | QString body; |
| 145 | Omittable<EventRelation> relatesTo; |
| 146 | |
| 147 | protected: |
| 148 | void fillJson(QJsonObject& json) const override; |
| 149 | }; |
| 150 | |
| 151 | /** |
| 152 | * Content class for m.location |
| 153 | * |
| 154 | * Available fields: |
| 155 | * - corresponding to the top-level JSON: |
| 156 | * - geoUri ("geo_uri" in JSON) |
| 157 | * - corresponding to the "info" subobject: |
| 158 | * - thumbnail.url ("thumbnail_url" in JSON) |
| 159 | * - corresponding to the "info/thumbnail_info" subobject: |
| 160 | * - thumbnail.payloadSize |
| 161 | * - thumbnail.mimeType |
| 162 | * - thumbnail.imageSize |
| 163 | */ |
| 164 | class QUOTIENT_API LocationContent : public TypedBase { |
| 165 | public: |
| 166 | LocationContent(const QString& geoUri, const Thumbnail& thumbnail = {}); |
| 167 | explicit LocationContent(const QJsonObject& json); |
| 168 | |
| 169 | QMimeType type() const override; |
| 170 | |
| 171 | public: |
| 172 | QString geoUri; |
| 173 | Thumbnail thumbnail; |
| 174 | |
| 175 | protected: |
| 176 | void fillJson(QJsonObject& o) const override; |
| 177 | }; |
| 178 | |
| 179 | /** |
| 180 | * A base class for info types that include duration: audio and video |
| 181 | */ |
| 182 | template <typename InfoT> |
| 183 | class PlayableContent : public UrlBasedContent<InfoT> { |
| 184 | public: |
| 185 | using UrlBasedContent<InfoT>::UrlBasedContent; |
| 186 | PlayableContent(const QJsonObject& json) |
| 187 | : UrlBasedContent<InfoT>(json) |
| 188 | , duration(FileInfo::originalInfoJson["duration"_ls ].toInt()) |
| 189 | {} |
| 190 | |
| 191 | protected: |
| 192 | void fillInfoJson(QJsonObject& infoJson) const override |
| 193 | { |
| 194 | infoJson.insert(QStringLiteral("duration" ), value: duration); |
| 195 | } |
| 196 | |
| 197 | public: |
| 198 | int duration; |
| 199 | }; |
| 200 | |
| 201 | /** |
| 202 | * Content class for m.video |
| 203 | * |
| 204 | * Available fields: |
| 205 | * - corresponding to the top-level JSON: |
| 206 | * - url |
| 207 | * - filename (extension to the CS API spec) |
| 208 | * - corresponding to the "info" subobject: |
| 209 | * - payloadSize ("size" in JSON) |
| 210 | * - mimeType ("mimetype" in JSON) |
| 211 | * - duration |
| 212 | * - imageSize (QSize for a combination of "h" and "w" in JSON) |
| 213 | * - thumbnail.url ("thumbnail_url" in JSON) |
| 214 | * - corresponding to the "info/thumbnail_info" subobject: contents of |
| 215 | * thumbnail field, in the same vein as for "info": |
| 216 | * - payloadSize |
| 217 | * - mimeType |
| 218 | * - imageSize |
| 219 | */ |
| 220 | using VideoContent = PlayableContent<ImageInfo>; |
| 221 | |
| 222 | /** |
| 223 | * Content class for m.audio |
| 224 | * |
| 225 | * Available fields: |
| 226 | * - corresponding to the top-level JSON: |
| 227 | * - url |
| 228 | * - filename (extension to the CS API spec) |
| 229 | * - corresponding to the "info" subobject: |
| 230 | * - payloadSize ("size" in JSON) |
| 231 | * - mimeType ("mimetype" in JSON) |
| 232 | * - duration |
| 233 | * - thumbnail.url ("thumbnail_url" in JSON - extension to the spec) |
| 234 | * - corresponding to the "info/thumbnail_info" subobject: contents of |
| 235 | * thumbnail field (extension to the spec): |
| 236 | * - payloadSize |
| 237 | * - mimeType |
| 238 | * - imageSize |
| 239 | */ |
| 240 | using AudioContent = PlayableContent<FileInfo>; |
| 241 | } // namespace EventContent |
| 242 | } // namespace Quotient |
| 243 | |