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
12class QFileInfo;
13
14namespace Quotient {
15namespace MessageEventContent = EventContent; // Back-compatibility
16
17/**
18 * The event class corresponding to m.room.message events
19 */
20class QUOTIENT_API RoomMessageEvent : public RoomEvent {
21 Q_GADGET
22public:
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
96private:
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
107using MessageEventType = RoomMessageEvent::MsgType;
108
109namespace 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