1// SPDX-FileCopyrightText: 2016 Kitsune Ral <Kitsune-Ral@users.sf.net>
2// SPDX-FileCopyrightText: 2017 Roman Plášil <me@rplasil.name>
3// SPDX-FileCopyrightText: 2017 Marius Gripsgard <marius@ubports.com>
4// SPDX-FileCopyrightText: 2018 Josip Delic <delijati@googlemail.com>
5// SPDX-FileCopyrightText: 2018 Black Hat <bhat@encom.eu.org>
6// SPDX-FileCopyrightText: 2019 Alexey Andreyev <aa13q@ya.ru>
7// SPDX-FileCopyrightText: 2020 Ram Nad <ramnad1999@gmail.com>
8// SPDX-License-Identifier: LGPL-2.1-or-later
9
10#pragma once
11
12#include "connection.h"
13#include "roomstateview.h"
14#include "eventitem.h"
15#include "quotient_common.h"
16
17#include "csapi/message_pagination.h"
18
19#include "events/accountdataevents.h"
20#include "events/encryptedevent.h"
21#include "events/roomkeyevent.h"
22#include "events/roommessageevent.h"
23#include "events/roomcreateevent.h"
24#include "events/roomtombstoneevent.h"
25#include "events/eventrelation.h"
26
27#include <QtCore/QJsonObject>
28#include <QtGui/QImage>
29
30#include <deque>
31#include <memory>
32#include <utility>
33
34namespace Quotient {
35class Event;
36class Avatar;
37class SyncRoomData;
38class RoomMemberEvent;
39class User;
40class MemberSorter;
41class LeaveRoomJob;
42class SetRoomStateWithKeyJob;
43class RedactEventJob;
44
45/** The data structure used to expose file transfer information to views
46 *
47 * This is specifically tuned to work with QML exposing all traits as
48 * Q_PROPERTY values.
49 */
50class QUOTIENT_API FileTransferInfo {
51 Q_GADGET
52 Q_PROPERTY(bool isUpload MEMBER isUpload CONSTANT)
53 Q_PROPERTY(bool active READ active CONSTANT)
54 Q_PROPERTY(bool started READ started CONSTANT)
55 Q_PROPERTY(bool completed READ completed CONSTANT)
56 Q_PROPERTY(bool failed READ failed CONSTANT)
57 Q_PROPERTY(int progress MEMBER progress CONSTANT)
58 Q_PROPERTY(int total MEMBER total CONSTANT)
59 Q_PROPERTY(QUrl localDir MEMBER localDir CONSTANT)
60 Q_PROPERTY(QUrl localPath MEMBER localPath CONSTANT)
61public:
62 enum Status { None, Started, Completed, Failed, Cancelled };
63 Status status = None;
64 bool isUpload = false;
65 int progress = 0;
66 int total = -1;
67 QUrl localDir {};
68 QUrl localPath {};
69
70 bool started() const { return status == Started; }
71 bool completed() const { return status == Completed; }
72 bool active() const { return started() || completed(); }
73 bool failed() const { return status == Failed; }
74};
75
76//! \brief Data structure for a room member's read receipt
77//! \sa Room::lastReadReceipt
78class QUOTIENT_API ReadReceipt {
79 Q_GADGET
80 Q_PROPERTY(QString eventId MEMBER eventId CONSTANT)
81 Q_PROPERTY(QDateTime timestamp MEMBER timestamp CONSTANT)
82public:
83 QString eventId;
84 QDateTime timestamp = {};
85
86 bool operator==(const ReadReceipt& other) const
87 {
88 return eventId == other.eventId && timestamp == other.timestamp;
89 }
90 bool operator!=(const ReadReceipt& other) const
91 {
92 return !operator==(other);
93 }
94};
95inline void swap(ReadReceipt& lhs, ReadReceipt& rhs)
96{
97 swap(value1&: lhs.eventId, value2&: rhs.eventId);
98 swap(value1&: lhs.timestamp, value2&: rhs.timestamp);
99}
100
101struct EventStats;
102
103struct Notification
104{
105 enum Type { None = 0, Basic, Highlight };
106 Q_ENUM(Type)
107
108 Type type = None;
109
110private:
111 Q_GADGET
112 Q_PROPERTY(Type type MEMBER type CONSTANT)
113};
114
115class QUOTIENT_API Room : public QObject {
116 Q_OBJECT
117 Q_PROPERTY(Connection* connection READ connection CONSTANT)
118 Q_PROPERTY(User* localUser READ localUser CONSTANT)
119 Q_PROPERTY(QString id READ id CONSTANT)
120 Q_PROPERTY(QString version READ version NOTIFY baseStateLoaded)
121 Q_PROPERTY(bool isUnstable READ isUnstable NOTIFY stabilityUpdated)
122 Q_PROPERTY(QString predecessorId READ predecessorId NOTIFY baseStateLoaded)
123 Q_PROPERTY(QString successorId READ successorId NOTIFY upgraded)
124 Q_PROPERTY(QString name READ name NOTIFY namesChanged)
125 Q_PROPERTY(QStringList aliases READ aliases NOTIFY namesChanged)
126 Q_PROPERTY(QStringList altAliases READ altAliases NOTIFY namesChanged)
127 Q_PROPERTY(QString canonicalAlias READ canonicalAlias NOTIFY namesChanged)
128 Q_PROPERTY(QString displayName READ displayName NOTIFY displaynameChanged)
129 Q_PROPERTY(QStringList pinnedEventIds READ pinnedEventIds WRITE setPinnedEvents
130 NOTIFY pinnedEventsChanged)
131 Q_PROPERTY(QString displayNameForHtml READ displayNameForHtml NOTIFY displaynameChanged)
132 Q_PROPERTY(QString topic READ topic NOTIFY topicChanged)
133 Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged
134 STORED false)
135 Q_PROPERTY(QUrl avatarUrl READ avatarUrl NOTIFY avatarChanged)
136 Q_PROPERTY(bool usesEncryption READ usesEncryption NOTIFY encryption)
137
138 Q_PROPERTY(int timelineSize READ timelineSize NOTIFY addedMessages)
139 Q_PROPERTY(QStringList memberNames READ safeMemberNames NOTIFY memberListChanged)
140 Q_PROPERTY(int joinedCount READ joinedCount NOTIFY memberListChanged)
141 Q_PROPERTY(int invitedCount READ invitedCount NOTIFY memberListChanged)
142 Q_PROPERTY(int totalMemberCount READ totalMemberCount NOTIFY memberListChanged)
143
144 Q_PROPERTY(bool displayed READ displayed WRITE setDisplayed NOTIFY
145 displayedChanged)
146 Q_PROPERTY(QString firstDisplayedEventId READ firstDisplayedEventId WRITE
147 setFirstDisplayedEventId NOTIFY firstDisplayedEventChanged)
148 Q_PROPERTY(QString lastDisplayedEventId READ lastDisplayedEventId WRITE
149 setLastDisplayedEventId NOTIFY lastDisplayedEventChanged)
150 //! \deprecated since 0.7
151 Q_PROPERTY(QString readMarkerEventId READ readMarkerEventId WRITE
152 markMessagesAsRead NOTIFY readMarkerMoved)
153 Q_PROPERTY(QString lastFullyReadEventId READ lastFullyReadEventId WRITE
154 markMessagesAsRead NOTIFY fullyReadMarkerMoved)
155 //! \deprecated since 0.7
156 Q_PROPERTY(bool hasUnreadMessages READ hasUnreadMessages NOTIFY
157 partiallyReadStatsChanged STORED false)
158 //! \deprecated since 0.7
159 Q_PROPERTY(int unreadCount READ unreadCount NOTIFY partiallyReadStatsChanged
160 STORED false)
161 Q_PROPERTY(qsizetype highlightCount READ highlightCount
162 NOTIFY highlightCountChanged)
163 Q_PROPERTY(qsizetype notificationCount READ notificationCount
164 NOTIFY notificationCountChanged)
165 Q_PROPERTY(EventStats partiallyReadStats READ partiallyReadStats NOTIFY partiallyReadStatsChanged)
166 Q_PROPERTY(EventStats unreadStats READ unreadStats NOTIFY unreadStatsChanged)
167 Q_PROPERTY(bool allHistoryLoaded READ allHistoryLoaded NOTIFY addedMessages
168 STORED false)
169 Q_PROPERTY(QStringList tagNames READ tagNames NOTIFY tagsChanged)
170 Q_PROPERTY(bool isFavourite READ isFavourite NOTIFY tagsChanged STORED false)
171 Q_PROPERTY(bool isLowPriority READ isLowPriority NOTIFY tagsChanged STORED false)
172
173 Q_PROPERTY(GetRoomEventsJob* eventsHistoryJob READ eventsHistoryJob NOTIFY
174 eventsHistoryJobChanged)
175
176 Q_PROPERTY(QStringList accountDataEventTypes READ accountDataEventTypes NOTIFY accountDataChanged)
177
178public:
179 using Timeline = std::deque<TimelineItem>;
180 using PendingEvents = std::vector<PendingEventItem>;
181 using RelatedEvents = QVector<const RoomEvent*>;
182 using rev_iter_t = Timeline::const_reverse_iterator;
183 using timeline_iter_t = Timeline::const_iterator;
184
185 //! \brief Room changes that can be tracked using Room::changed() signal
186 //!
187 //! This enumeration lists kinds of changes that can be tracked with
188 //! a "cumulative" changed() signal instead of using individual signals for
189 //! each change. Specific enumerators mention these individual signals.
190 //! \sa changed
191 enum class Change : quint32 { // QFlags can't go more than 32-bit
192 None = 0x0, //!< No changes occurred in the room
193 RoomNames = 0x1, //!< \sa namesChanged, displaynameChanged
194 DECL_DEPRECATED_ENUMERATOR(Name, RoomNames),
195 DECL_DEPRECATED_ENUMERATOR(Aliases, RoomNames),
196 DECL_DEPRECATED_ENUMERATOR(CanonicalAlias, RoomNames),
197 // Aliases/CanonicalAlias pre-0.8 = 0x2,
198 Topic = 0x4, //!< \sa topicChanged
199 PartiallyReadStats = 0x8, //!< \sa partiallyReadStatsChanged
200 DECL_DEPRECATED_ENUMERATOR(UnreadNotifs, PartiallyReadStats),
201 Avatar = 0x10, //!< \sa avatarChanged
202 JoinState = 0x20, //!< \sa joinStateChanged
203 Tags = 0x40, //!< \sa tagsChanged
204 //! \sa userAdded, userRemoved, memberRenamed, memberListChanged,
205 //! displaynameChanged
206 Members = 0x80,
207 UnreadStats = 0x100, //!< \sa unreadStatsChanged
208 AccountData Q_DECL_ENUMERATOR_DEPRECATED_X(
209 "Change::AccountData will be merged into Change::Other in 0.8") =
210 0x200,
211 Summary = 0x400, //!< \sa summaryChanged, displaynameChanged
212 ReadMarker Q_DECL_ENUMERATOR_DEPRECATED_X(
213 "Change::ReadMarker will be merged into Change::Other in 0.8") =
214 0x800,
215 Highlights = 0x1000, //!< \sa highlightCountChanged
216 //! A catch-all value that covers changes not listed above (such as
217 //! encryption turned on or the room having been upgraded), as well as
218 //! changes in the room state that the library is not aware of (e.g.,
219 //! custom state events) and m.read/m.fully_read position changes.
220 //! \sa encryptionChanged, upgraded, accountDataChanged
221 Other = 0x8000,
222 //! This is intended to test a Change/Changes value for non-emptiness;
223 //! adding <tt>& Change::Any</tt> has the same meaning as
224 //! !testFlag(Change::None) or adding <tt>!= Change::None</tt>
225 //! \note testFlag(Change::Any) tests that _all_ bits are on and
226 //! will always return false.
227 Any = 0xFFFF
228 };
229 QUO_DECLARE_FLAGS(Changes, Change)
230
231 Room(Connection* connection, QString id, JoinState initialJoinState);
232 Q_DISABLE_COPY_MOVE(Room)
233 ~Room() override;
234
235 // Property accessors
236
237 Connection* connection() const;
238 User* localUser() const;
239 const QString& id() const;
240 QString version() const;
241 bool isUnstable() const;
242 QString predecessorId() const;
243 /// Room predecessor
244 /** This function validates that the predecessor has a tombstone and
245 * the tombstone refers to the current room. If that's not the case,
246 * or if the predecessor is in a join state not matching \p stateFilter,
247 * the function returns nullptr.
248 */
249 Room* predecessor(JoinStates statesFilter = JoinState::Invite
250 | JoinState::Join) const;
251 QString successorId() const;
252 /// Room successor
253 /** This function validates that the successor room's creation event
254 * refers to the current room. If that's not the case, or if the successor
255 * is in a join state not matching \p stateFilter, it returns nullptr.
256 */
257 Room* successor(JoinStates statesFilter = JoinState::Invite
258 | JoinState::Join) const;
259 QString name() const;
260 QString canonicalAlias() const;
261 QStringList altAliases() const;
262 //! Get a list of both canonical and alternative aliases
263 QStringList aliases() const;
264 QString displayName() const;
265 QStringList pinnedEventIds() const;
266 // Returns events available locally, use pinnedEventIds() for full list
267 QVector<const RoomEvent*> pinnedEvents() const;
268 QString displayNameForHtml() const;
269 QString topic() const;
270 QString avatarMediaId() const;
271 QUrl avatarUrl() const;
272 const Avatar& avatarObject() const;
273 Q_INVOKABLE JoinState joinState() const;
274 Q_INVOKABLE QList<Quotient::User*> usersTyping() const;
275 QList<User*> membersLeft() const;
276
277 Q_INVOKABLE QList<Quotient::User*> users() const;
278 Q_DECL_DEPRECATED_X("Use safeMemberNames() or htmlSafeMemberNames() instead") //
279 QStringList memberNames() const;
280 QStringList safeMemberNames() const;
281 QStringList htmlSafeMemberNames() const;
282 int timelineSize() const;
283 bool usesEncryption() const;
284 RoomEventPtr decryptMessage(const EncryptedEvent& encryptedEvent);
285 void handleRoomKeyEvent(const RoomKeyEvent& roomKeyEvent,
286 const QString& senderId,
287 const QByteArray& olmSessionId);
288 int joinedCount() const;
289 int invitedCount() const;
290 int totalMemberCount() const;
291
292 GetRoomEventsJob* eventsHistoryJob() const;
293
294 /**
295 * Returns a square room avatar with the given size and requests it
296 * from the network if needed
297 * \return a pixmap with the avatar or a placeholder if there's none
298 * available yet
299 */
300 Q_INVOKABLE QImage avatar(int dimension);
301 /**
302 * Returns a room avatar with the given dimensions and requests it
303 * from the network if needed
304 * \return a pixmap with the avatar or a placeholder if there's none
305 * available yet
306 */
307 Q_INVOKABLE QImage avatar(int width, int height);
308
309 /**
310 * \brief Get a user object for a given user id
311 * This is the recommended way to get a user object in a room
312 * context. The actual object type may be changed in further
313 * versions to provide room-specific user information (display name,
314 * avatar etc.).
315 * \note The method will return a valid user regardless of
316 * the membership.
317 */
318 Q_INVOKABLE Quotient::User* user(const QString& userId) const;
319
320 /**
321 * \brief Check the join state of a given user in this room
322 *
323 * \note Banned and invited users are not tracked separately for now (Leave
324 * will be returned for them).
325 *
326 * \return Join if the user is a room member; Leave otherwise
327 */
328 Q_DECL_DEPRECATED_X("Use isMember() instead")
329 Q_INVOKABLE Quotient::JoinState memberJoinState(Quotient::User* user) const;
330
331 //! \brief Check the join state of a given user in this room
332 //!
333 //! \return the given user's state with respect to the room
334 Q_INVOKABLE Quotient::Membership memberState(const QString& userId) const;
335
336 //! Check whether a user with the given id is a member of the room
337 Q_INVOKABLE bool isMember(const QString& userId) const;
338
339 //! \brief Get a display name (without disambiguation) for the given member
340 //!
341 //! \sa safeMemberName, htmlSafeMemberName
342 Q_INVOKABLE QString memberName(const QString& mxId) const;
343
344 //! \brief Get a disambiguated name for the given user in the room context
345 Q_DECL_DEPRECATED_X("Use safeMemberName() instead")
346 Q_INVOKABLE QString roomMembername(const Quotient::User* u) const;
347 //! \brief Get a disambiguated name for a user with this id in the room
348 Q_DECL_DEPRECATED_X("Use safeMemberName() instead")
349 Q_INVOKABLE QString roomMembername(const QString& userId) const;
350
351 /*!
352 * \brief Get a disambiguated name for the member with the given MXID
353 *
354 * This function should only be used for non-UI code; consider using
355 * safeMemberName() or htmlSafeMemberName() for displayed strings.
356 */
357 Q_INVOKABLE QString disambiguatedMemberName(const QString& mxId) const;
358
359 /*! Get a display-safe member name in the context of this room
360 *
361 * Display-safe means disambiguated and without RLO/LRO markers
362 * (see https://github.com/quotient-im/Quaternion/issues/545).
363 */
364 Q_INVOKABLE QString safeMemberName(const QString& userId) const;
365
366 /*! Get an HTML-safe member name in the context of this room
367 *
368 * This function adds HTML escaping on top of safeMemberName() safeguards.
369 */
370 Q_INVOKABLE QString htmlSafeMemberName(const QString& userId) const;
371
372 //! \brief Get an avatar for the member with the given MXID
373 QUrl memberAvatarUrl(const QString& mxId) const;
374
375 const Timeline& messageEvents() const;
376 const PendingEvents& pendingEvents() const;
377
378 /// Check whether all historical messages are already loaded
379 /**
380 * \return true if the "oldest" event in the timeline is
381 * a room creation event and there's no further history
382 * to load; false otherwise
383 */
384 bool allHistoryLoaded() const;
385 /**
386 * A convenience method returning the read marker to the position
387 * before the "oldest" event; same as messageEvents().crend()
388 */
389 rev_iter_t historyEdge() const;
390 /**
391 * A convenience method returning the iterator beyond the latest
392 * arrived event; same as messageEvents().cend()
393 */
394 Timeline::const_iterator syncEdge() const;
395 Q_INVOKABLE Quotient::TimelineItem::index_t minTimelineIndex() const;
396 Q_INVOKABLE Quotient::TimelineItem::index_t maxTimelineIndex() const;
397 Q_INVOKABLE bool
398 isValidIndex(Quotient::TimelineItem::index_t timelineIndex) const;
399
400 rev_iter_t findInTimeline(TimelineItem::index_t index) const;
401 rev_iter_t findInTimeline(const QString& evtId) const;
402 PendingEvents::iterator findPendingEvent(const QString& txnId);
403 PendingEvents::const_iterator findPendingEvent(const QString& txnId) const;
404
405 const RelatedEvents relatedEvents(const QString& evtId,
406 EventRelation::reltypeid_t relType) const;
407 const RelatedEvents relatedEvents(const RoomEvent& evt,
408 EventRelation::reltypeid_t relType) const;
409
410 const RoomCreateEvent* creation() const;
411 const RoomTombstoneEvent* tombstone() const;
412
413 bool displayed() const;
414 /// Mark the room as currently displayed to the user
415 /**
416 * Marking the room displayed causes the room to obtain the full
417 * list of members if it's been lazy-loaded before; in the future
418 * it may do more things bound to "screen time" of the room, e.g.
419 * measure that "screen time".
420 */
421 void setDisplayed(bool displayed = true);
422 QString firstDisplayedEventId() const;
423 rev_iter_t firstDisplayedMarker() const;
424 void setFirstDisplayedEventId(const QString& eventId);
425 void setFirstDisplayedEvent(TimelineItem::index_t index);
426 QString lastDisplayedEventId() const;
427 rev_iter_t lastDisplayedMarker() const;
428 void setLastDisplayedEventId(const QString& eventId);
429 void setLastDisplayedEvent(TimelineItem::index_t index);
430
431 //! \brief Obtain a read receipt of any user
432 //! \deprecated Use lastReadReceipt or fullyReadMarker instead.
433 //!
434 //! Historically, readMarker was returning a "converged" read marker
435 //! representing both the read receipt and the fully read marker, as
436 //! Quotient managed them together. Since 0.6.8, a single-argument call of
437 //! readMarker returns the last read receipt position (for any room member)
438 //! and a call without arguments returns the last _fully read_ position,
439 //! to provide access to both positions separately while maintaining API
440 //! stability guarantees. 0.7 has separate methods to return read receipts
441 //! and the fully read marker - use them instead.
442 //! \sa lastReadReceipt
443 [[deprecated("Use lastReadReceipt() to get m.read receipt or"
444 " fullyReadMarker() to get m.fully_read marker")]] //
445 rev_iter_t readMarker(const User* user) const;
446 //! \brief Obtain the local user's fully-read marker
447 //! \deprecated Use fullyReadMarker instead
448 //!
449 //! See the documentation for the single-argument overload.
450 //! \sa fullyReadMarker
451 [[deprecated("Use localReadReceiptMarker() or fullyReadMarker()")]] //
452 rev_iter_t readMarker() const;
453 //! \brief Get the event id for the local user's fully-read marker
454 //! \deprecated Use lastFullyReadEventId instead
455 //!
456 //! See the readMarker documentation
457 [[deprecated("Use lastReadReceipt() to get m.read receipt or"
458 " lastFullyReadEventId() to get an event id that"
459 " m.fully_read marker points to")]] //
460 QString readMarkerEventId() const;
461
462 //! \brief Get the latest read receipt from a user
463 //!
464 //! The user id must be valid. A read receipt with an empty event id
465 //! is returned if the user id is valid but there was no read receipt
466 //! from them.
467 //! \sa usersAtEventId
468 ReadReceipt lastReadReceipt(const QString& userId) const;
469
470 //! \brief Get the latest read receipt from the local user
471 //!
472 //! This is a shortcut for <tt>lastReadReceipt(localUserId)</tt>.
473 //! \sa lastReadReceipt
474 ReadReceipt lastLocalReadReceipt() const;
475
476 //! \brief Find the timeline item the local read receipt is at
477 //!
478 //! This is a shortcut for \code
479 //! room->findInTimeline(room->lastLocalReadReceipt().eventId);
480 //! \endcode
481 rev_iter_t localReadReceiptMarker() const;
482
483 //! \brief Get the latest event id marked as fully read
484 //!
485 //! This can be either the event id pointed to by the actual latest
486 //! m.fully_read event, or the latest event id marked locally as fully read
487 //! if markMessagesAsRead or markAllMessagesAsRead has been called and
488 //! the homeserver didn't return an updated m.fully_read event yet.
489 //! \sa markMessagesAsRead, markAllMessagesAsRead, fullyReadMarker
490 QString lastFullyReadEventId() const;
491
492 //! \brief Get the iterator to the latest timeline item marked as fully read
493 //!
494 //! This method calls findInTimeline on the result of lastFullyReadEventId.
495 //! If the fully read marker turns out to be outside the timeline (because
496 //! the event marked as fully read is too far back in the history) the
497 //! returned value will be equal to historyEdge.
498 //!
499 //! Be sure to read the caveats on iterators returned by findInTimeline.
500 //! \sa lastFullyReadEventId, findInTimeline
501 rev_iter_t fullyReadMarker() const;
502
503 //! \brief Get users whose latest read receipts point to the event
504 //!
505 //! This method is for cases when you need to show users who have read
506 //! an event. Calling it on inexistent or empty event id will return
507 //! an empty set.
508 //! \note The returned list may contain ids resolving to users that are
509 //! not loaded as room members yet (in particular, if members are not
510 //! yet lazy-loaded). For now this merely means that the user's
511 //! room-specific name and avatar will not be there; but generally
512 //! it's recommended to ensure that all room members are loaded
513 //! before operating on the result of this function.
514 //! \sa lastReadReceipt, allMembersLoaded
515 QSet<QString> userIdsAtEvent(const QString& eventId) const;
516
517 QSet<QString> userIdsAtEvent(const QString& eventId); // See #706
518
519 [[deprecated("Use userIdsAtEvent instead")]]
520 QSet<User*> usersAtEventId(const QString& eventId);
521
522 //! \brief Mark the event with uptoEventId as fully read
523 //!
524 //! Marks the event with the specified id as fully read locally and also
525 //! sends an update to m.fully_read account data to the server either
526 //! for this message or, if it's from the local user, for
527 //! the nearest non-local message before. uptoEventId must point to a known
528 //! event in the timeline; the method will do nothing if the event is behind
529 //! the current m.fully_read marker or is not loaded, to prevent
530 //! accidentally trying to move the marker back in the timeline.
531 //! \sa markAllMessagesAsRead, fullyReadMarker
532 Q_INVOKABLE void markMessagesAsRead(const QString& uptoEventId);
533
534 //! \brief Determine whether an event should be counted as unread
535 //!
536 //! The criteria of including an event in unread counters are described in
537 //! [MSC2654](https://github.com/matrix-org/matrix-doc/pull/2654); according
538 //! to these, the event should be counted as unread (or, in libQuotient
539 //! parlance, is "notable") if it is:
540 //! - either
541 //! - a message event that is not m.notice, or
542 //! - a state event with type being one of:
543 //! `m.room.topic`, `m.room.name`, `m.room.avatar`, `m.room.tombstone`;
544 //! - neither redacted, nor an edit (redactions cause the redacted event
545 //! to stop being notable, while edits are not notable themselves while
546 //! the original event usually is);
547 //! - from a non-local user (events from other devices of the local
548 //! user are not notable).
549 //! \sa partiallyReadStats, unreadStats
550 virtual bool isEventNotable(const TimelineItem& ti) const;
551
552 //! \brief Get notification details for an event
553 //!
554 //! This allows to get details on the kind of notification that should
555 //! generated for \p evt.
556 Notification notificationFor(const TimelineItem& ti) const;
557
558 //! \brief Get event statistics since the fully read marker
559 //!
560 //! This call returns a structure containing:
561 //! - the number of notable unread events since the fully read marker;
562 //! depending on the fully read marker state with respect to the local
563 //! timeline, this number may be either exact or estimated
564 //! (see EventStats::isEstimate);
565 //! - the number of highlights (TODO).
566 //!
567 //! Note that this is different from the unread count defined by MSC2654
568 //! and from the notification/highlight numbers defined by the spec in that
569 //! it counts events since the fully read marker, not since the last
570 //! read receipt position.
571 //!
572 //! As E2EE is not supported in the library, the returned result will always
573 //! be an estimate (<tt>isEstimate == true</tt>) for encrypted rooms;
574 //! moreover, since the library doesn't know how to tackle push rules yet
575 //! the number of highlights returned here will always be zero (there's no
576 //! good substitute for that now).
577 //!
578 //! \sa isEventNotable, fullyReadMarker, unreadStats, EventStats
579 EventStats partiallyReadStats() const;
580
581 //! \brief Get event statistics since the last read receipt
582 //!
583 //! This call returns a structure that contains the following three numbers,
584 //! all counted on the timeline segment between the event pointed to by
585 //! the m.fully_read marker and the sync edge:
586 //! - the number of unread events - depending on the read receipt state
587 //! with respect to the local timeline, this number may be either precise
588 //! or estimated (see EventStats::isEstimate);
589 //! - the number of highlights (TODO).
590 //!
591 //! As E2EE is not supported in the library, the returned result will always
592 //! be an estimate (<tt>isEstimate == true</tt>) for encrypted rooms;
593 //! moreover, since the library doesn't know how to tackle push rules yet
594 //! the number of highlights returned here will always be zero - use
595 //! highlightCount() for now.
596 //!
597 //! \sa isEventNotable, lastLocalReadReceipt, partiallyReadStats,
598 //! highlightCount
599 EventStats unreadStats() const;
600
601 [[deprecated(
602 "Use partiallyReadStats/unreadStats() and EventStats::empty()")]]
603 bool hasUnreadMessages() const;
604
605 //! \brief Get the number of notable events since the fully read marker
606 //!
607 //! \deprecated Since 0.7 there are two ways to count unread events: since
608 //! the fully read marker (used by libQuotient pre-0.7) and since the last
609 //! read receipt (as used by most of Matrix ecosystem, including the spec
610 //! and MSCs). This function currently returns a value derived from
611 //! partiallyReadStats() for compatibility with libQuotient 0.6; it will be
612 //! removed due to ambiguity. Use unreadStats() to obtain the spec-compliant
613 //! count of unread events and the highlight count; partiallyReadStats() to
614 //! obtain the unread events count since the fully read marker.
615 //!
616 //! \return -1 (_not 0_) when all messages are known to have been fully read,
617 //! i.e. the fully read marker points to _the latest notable_ event
618 //! loaded in the local timeline (which may be different from
619 //! the latest event in the local timeline as that might not be
620 //! notable);
621 //! 0 when there may be unread messages but the current local
622 //! timeline doesn't have any notable ones (often but not always
623 //! because it's entirely empty yet);
624 //! a positive integer when there is (or estimated to be) a number
625 //! of unread notable events as described above.
626 //!
627 //! \sa partiallyReadStats, unreadStats
628 [[deprecated("Use partiallyReadStats() or unreadStats() instead")]] //
629 int unreadCount() const;
630
631 //! \brief Get the number of notifications since the last read receipt
632 //!
633 //! This is the same as <tt>unreadStats().notableCount</tt>.
634 //!
635 //! \sa unreadStats, lastLocalReadReceipt
636 qsizetype notificationCount() const;
637
638 //! \brief Get the number of highlights since the last read receipt
639 //!
640 //! As of 0.7, this is defined by the homeserver as Quotient doesn't process
641 //! push rules.
642 //!
643 //! \sa unreadStats, lastLocalReadReceipt
644 qsizetype highlightCount() const;
645
646 /** Check whether the room has account data of the given type
647 * Tags and read markers are not supported by this method _yet_.
648 */
649 bool hasAccountData(const QString& type) const;
650
651 /** Get a generic account data event of the given type
652 * This returns a generic hash map for any room account data event
653 * stored on the server. Tags and read markers cannot be retrieved
654 * using this method _yet_.
655 */
656 const EventPtr& accountData(const QString& type) const;
657
658 //! Get a list of all room account data events
659 //! \return A list of event types that exist in the room
660 QStringList accountDataEventTypes() const;
661
662 QStringList tagNames() const;
663 TagsMap tags() const;
664 TagRecord tag(const QString& name) const;
665
666 /** Add a new tag to this room
667 * If this room already has this tag, nothing happens. If it's a new
668 * tag for the room, the respective tag record is added to the set
669 * of tags and the new set is sent to the server to update other
670 * clients.
671 */
672 void addTag(const QString& name, const TagRecord& record = {});
673 Q_INVOKABLE void addTag(const QString& name, float order);
674
675 /// Remove a tag from the room
676 Q_INVOKABLE void removeTag(const QString& name);
677
678 /// The scope to apply an action on
679 /*! This enumeration is used to pick a strategy to propagate certain
680 * actions on the room to its predecessors and successors.
681 */
682 enum ActionScope {
683 ThisRoomOnly, ///< Do not apply to predecessors and successors
684 WithinSameState, ///< Apply to predecessors and successors in the same
685 ///< state as the current one
686 OmitLeftState, ///< Apply to all reachable predecessors and successors
687 ///< except those in Leave state
688 WholeSequence ///< Apply to all reachable predecessors and successors
689 };
690
691 /** Overwrite the room's tags
692 * This completely replaces the existing room's tags with a set
693 * of new ones and updates the new set on the server. Unlike
694 * most other methods in Room, this one sends a signal about changes
695 * immediately, not waiting for confirmation from the server
696 * (because tags are saved in account data rather than in shared
697 * room state).
698 * \param applyOn setting this to Room::OnAllConversations will set tags
699 * on this and all _known_ predecessors and successors;
700 * by default only the current room is changed
701 */
702 void setTags(TagsMap newTags, ActionScope applyOn = ThisRoomOnly);
703
704 /// Check whether the list of tags has m.favourite
705 bool isFavourite() const;
706 /// Check whether the list of tags has m.lowpriority
707 bool isLowPriority() const;
708 /// Check whether this room is for server notices (MSC1452)
709 bool isServerNoticeRoom() const;
710
711 /// Check whether this room is a direct chat
712 Q_INVOKABLE bool isDirectChat() const;
713
714 /// Get the list of users this room is a direct chat with
715 QList<User*> directChatUsers() const;
716
717 Q_INVOKABLE QUrl makeMediaUrl(const QString& eventId,
718 const QUrl &mxcUrl) const;
719
720 Q_INVOKABLE QUrl urlToThumbnail(const QString& eventId) const;
721 Q_INVOKABLE QUrl urlToDownload(const QString& eventId) const;
722
723 /// Get a file name for downloading for a given event id
724 /*!
725 * The event MUST be RoomMessageEvent and have content
726 * for downloading. \sa RoomMessageEvent::hasContent
727 */
728 Q_INVOKABLE QString fileNameToDownload(const QString& eventId) const;
729
730 /// Get information on file upload/download
731 /*!
732 * \param id uploads are identified by the corresponding event's
733 * transactionId (because uploads are done before
734 * the event is even sent), while downloads are using
735 * the normal event id for identifier.
736 */
737 Q_INVOKABLE Quotient::FileTransferInfo
738 fileTransferInfo(const QString& id) const;
739
740 /// Get the URL to the actual file source in a unified way
741 /*!
742 * For uploads it will return a URL to a local file; for downloads
743 * the URL will be taken from the corresponding room event.
744 */
745 Q_INVOKABLE QUrl fileSource(const QString& id) const;
746
747 /** Pretty-prints plain text into HTML
748 * As of now, it's exactly the same as Quotient::prettyPrint();
749 * in the future, it will also linkify room aliases, mxids etc.
750 * using the room context.
751 */
752 Q_INVOKABLE QString prettyPrint(const QString& plainText) const;
753
754 MemberSorter memberSorter() const;
755
756 Q_INVOKABLE bool supportsCalls() const;
757
758 /// Whether the current user is allowed to upgrade the room
759 Q_INVOKABLE bool canSwitchVersions() const;
760
761 /// Get a state event with the given event type and state key
762 /*! This method returns a (potentially empty) state event corresponding
763 * to the pair of event type \p evtType and state key \p stateKey.
764 */
765 [[deprecated("Use currentState().get() instead; "
766 "make sure to check its result for nullptrs")]] //
767 const StateEvent* getCurrentState(const QString& evtType,
768 const QString& stateKey = {}) const;
769
770 /// Get a state event with the given event type and state key
771 /*! This is a typesafe overload that accepts a C++ event type instead of
772 * its Matrix name.
773 */
774 template <typename EvT>
775 [[deprecated("Use currentState().get() instead; "
776 "make sure to check its result for nullptrs")]] //
777 const EvT* getCurrentState(const QString& stateKey = {}) const
778 {
779 QT_IGNORE_DEPRECATIONS(const auto* evt = eventCast<const EvT>(
780 getCurrentState(EvT::TypeId, stateKey));)
781 Q_ASSERT(evt);
782 Q_ASSERT(evt->matrixType() == EvT::TypeId
783 && evt->stateKey() == stateKey);
784 return evt;
785 }
786
787 /// \brief Get the current room state
788 RoomStateView currentState() const;
789
790 //! Send a request to update the room state with the given event
791 SetRoomStateWithKeyJob* setState(const StateEvent& evt);
792
793 //! \brief Set a state event of the given type with the given arguments
794 //!
795 //! This typesafe overload attempts to send a state event with the type
796 //! \p EvT and the content defined by \p args. Specifically, the function
797 //! constructs a temporary object of type \p EvT with its content
798 //! list-initialised from \p args, and sends a request to the homeserver
799 //! using the Matrix event type defined by \p EvT and the event content
800 //! produced via EvT::contentJson().
801 //!
802 //! \note This call is not suitable for events that assume non-empty
803 //! stateKey, such as member events; for those you have to create
804 //! a temporary event object yourself and use the setState() overload
805 //! that accepts StateEvent const-ref.
806 template <typename EvT, typename... ArgTs>
807 auto setState(ArgTs&&... args)
808 {
809 return setState(EvT(std::forward<ArgTs>(args)...));
810 }
811
812public Q_SLOTS:
813 /** Check whether the room should be upgraded */
814 void checkVersion();
815
816 QString postMessage(const QString& plainText, MessageEventType type);
817 QString postPlainText(const QString& plainText);
818 QString postHtmlMessage(const QString& plainText, const QString& html,
819 MessageEventType type = MessageEventType::Text);
820 QString postHtmlText(const QString& plainText, const QString& html);
821 /// Send a reaction on a given event with a given key
822 QString postReaction(const QString& eventId, const QString& key);
823
824 QString postFile(const QString& plainText, EventContent::TypedBase* content);
825#if QT_VERSION_MAJOR < 6
826 Q_DECL_DEPRECATED_X("Use postFile(QString, MessageEventType, EventContent)") //
827 QString postFile(const QString& plainText, const QUrl& localPath,
828 bool asGenericFile = false);
829#endif
830 /** Post a pre-created room message event
831 *
832 * Takes ownership of the event, deleting it once the matching one
833 * arrives with the sync
834 * \return transaction id associated with the event.
835 */
836 QString postEvent(RoomEvent* event);
837 QString postJson(const QString& matrixType, const QJsonObject& eventContent);
838 QString retryMessage(const QString& txnId);
839 void discardMessage(const QString& txnId);
840
841 //! Send a request to update the room state based on freeform inputs
842 SetRoomStateWithKeyJob* setState(const QString& evtType,
843 const QString& stateKey,
844 const QJsonObject& contentJson);
845 void setName(const QString& newName);
846 void setCanonicalAlias(const QString& newAlias);
847 void setPinnedEvents(const QStringList& events);
848 /// Set room aliases on the user's current server
849 void setLocalAliases(const QStringList& aliases);
850 void setTopic(const QString& newTopic);
851
852 /// You shouldn't normally call this method; it's here for debugging
853 void refreshDisplayName();
854
855 void getPreviousContent(int limit = 10, const QString &filter = {});
856
857 void inviteToRoom(const QString& memberId);
858 LeaveRoomJob* leaveRoom();
859 void kickMember(const QString& memberId, const QString& reason = {});
860 void ban(const QString& userId, const QString& reason = {});
861 void unban(const QString& userId);
862 void redactEvent(const QString& eventId, const QString& reason = {});
863
864 void uploadFile(const QString& id, const QUrl& localFilename,
865 const QString& overrideContentType = {});
866 // If localFilename is empty a temporary file is created
867 void downloadFile(const QString& eventId, const QUrl& localFilename = {});
868 void cancelFileTransfer(const QString& id);
869
870 //! \brief Set a given event as last read and post a read receipt on it
871 //!
872 //! Does nothing if the event is behind the current read receipt.
873 //! \sa lastReadReceipt, markMessagesAsRead, markAllMessagesAsRead
874 void setReadReceipt(const QString& atEventId);
875 //! Put the fully-read marker at the latest message in the room
876 void markAllMessagesAsRead();
877
878 /// Switch the room's version (aka upgrade)
879 void switchVersion(QString newVersion);
880
881 void inviteCall(const QString& callId, const int lifetime,
882 const QString& sdp);
883 void sendCallCandidates(const QString& callId, const QJsonArray& candidates);
884 [[deprecated("Lifetime argument is no more passed; "
885 "use 2-arg Room::answerCall() instead")]]
886 void answerCall(const QString& callId, int lifetime, const QString& sdp);
887 void answerCall(const QString& callId, const QString& sdp);
888 void hangupCall(const QString& callId);
889
890 /**
891 * Activates encryption for this room.
892 * Warning: Cannot be undone
893 */
894 void activateEncryption();
895
896Q_SIGNALS:
897 /// Initial set of state events has been loaded
898 /**
899 * The initial set is what comes from the initial sync for the room.
900 * This includes all basic things like RoomCreateEvent,
901 * RoomNameEvent, a (lazy-loaded, not full) set of RoomMemberEvents
902 * etc. This is a per-room reflection of Connection::loadedRoomState
903 * \sa Connection::loadedRoomState
904 */
905 void baseStateLoaded();
906 void eventsHistoryJobChanged();
907 void aboutToAddHistoricalMessages(Quotient::RoomEventsRange events);
908 void aboutToAddNewMessages(Quotient::RoomEventsRange events);
909 void addedMessages(int fromIndex, int toIndex);
910 /// The event is about to be appended to the list of pending events
911 void pendingEventAboutToAdd(Quotient::RoomEvent* event);
912 /// An event has been appended to the list of pending events
913 void pendingEventAdded();
914 /// The remote echo has arrived with the sync and will be merged
915 /// with its local counterpart
916 /** NB: Requires a sync loop to be emitted */
917 void pendingEventAboutToMerge(Quotient::RoomEvent* serverEvent,
918 int pendingEventIndex);
919 /// The remote and local copies of the event have been merged
920 /** NB: Requires a sync loop to be emitted */
921 void pendingEventMerged();
922 /// An event will be removed from the list of pending events
923 void pendingEventAboutToDiscard(int pendingEventIndex);
924 /// An event has just been removed from the list of pending events
925 void pendingEventDiscarded();
926 /// The status of a pending event has changed
927 /** \sa PendingEventItem::deliveryStatus */
928 void pendingEventChanged(int pendingEventIndex);
929 /// The server accepted the message
930 /** This is emitted when an event sending request has successfully
931 * completed. This does not mean that the event is already in the
932 * local timeline, only that the server has accepted it.
933 * \param txnId transaction id assigned by the client during sending
934 * \param eventId event id assigned by the server upon acceptance
935 * \sa postEvent, postPlainText, postMessage, postHtmlMessage
936 * \sa pendingEventMerged, aboutToAddNewMessages
937 */
938 void messageSent(QString txnId, QString eventId);
939
940 /** A common signal for various kinds of changes in the room
941 * Aside from all changes in the room state
942 * @param changes a set of flags describing what changes occurred
943 * upon the last sync
944 * \sa Changes
945 */
946 void changed(Quotient::Room::Changes changes);
947 /**
948 * \brief The room name, the canonical alias or other aliases changed
949 *
950 * Not triggered when display name changes.
951 */
952 void namesChanged(Quotient::Room* room);
953 void displaynameAboutToChange(Quotient::Room* room);
954 void displaynameChanged(Quotient::Room* room, QString oldName);
955 void pinnedEventsChanged();
956 void topicChanged();
957 void avatarChanged();
958 void userAdded(Quotient::User* user);
959 void userRemoved(Quotient::User* user);
960 void memberAboutToRename(Quotient::User* user, QString newName);
961 void memberRenamed(Quotient::User* user);
962 void memberAvatarChanged(Quotient::User* user);
963 /// The list of members has changed
964 /** Emitted no more than once per sync, this is a good signal to
965 * for cases when some action should be done upon any change in
966 * the member list. If you need per-item granularity you should use
967 * userAdded, userRemoved and memberAboutToRename / memberRenamed
968 * instead.
969 */
970 void memberListChanged();
971 /// The previously lazy-loaded members list is now loaded entirely
972 /// \sa setDisplayed
973 void allMembersLoaded();
974 void encryption();
975
976 void joinStateChanged(Quotient::JoinState oldState,
977 Quotient::JoinState newState);
978 void typingChanged();
979
980 void highlightCountChanged(); ///< \sa highlightCount
981 void notificationCountChanged(); ///< \sa notificationCount
982
983 void displayedChanged(bool displayed);
984 void firstDisplayedEventChanged();
985 void lastDisplayedEventChanged();
986 //! The event the m.read receipt points to has changed for the listed users
987 //! \sa lastReadReceipt
988 void lastReadEventChanged(QVector<QString> userIds);
989 void fullyReadMarkerMoved(QString fromEventId, QString toEventId);
990 [[deprecated("Since 0.7, use fullyReadMarkerMoved")]]
991 void readMarkerMoved(QString fromEventId, QString toEventId);
992 [[deprecated("Since 0.7, use lastReadEventChanged")]]
993 void readMarkerForUserMoved(Quotient::User* user, QString fromEventId,
994 QString toEventId);
995 [[deprecated("Since 0.7, use either partiallyReadStatsChanged "
996 "or unreadStatsChanged")]]
997 void unreadMessagesChanged(Quotient::Room* room);
998 void partiallyReadStatsChanged();
999 void unreadStatsChanged();
1000
1001 void accountDataAboutToChange(QString type);
1002 void accountDataChanged(QString type);
1003 void tagsAboutToChange();
1004 void tagsChanged();
1005
1006 void updatedEvent(QString eventId);
1007 void replacedEvent(const Quotient::RoomEvent* newEvent,
1008 const Quotient::RoomEvent* oldEvent);
1009
1010 void newFileTransfer(QString id, QUrl localFile);
1011 void fileTransferProgress(QString id, qint64 progress, qint64 total);
1012 void fileTransferCompleted(QString id, QUrl localFile,
1013 FileSourceInfo fileMetadata);
1014 void fileTransferFailed(QString id, QString errorMessage = {});
1015 // fileTransferCancelled() is no more here; use fileTransferFailed() and
1016 // check the transfer status instead
1017
1018 void callEvent(Quotient::Room* room, const Quotient::RoomEvent* event);
1019
1020 /// The room's version stability may have changed
1021 void stabilityUpdated(QString recommendedDefault,
1022 QStringList stableVersions);
1023 /// This room has been upgraded and won't receive updates any more
1024 void upgraded(QString serverMessage, Quotient::Room* successor);
1025 /// An attempted room upgrade has failed
1026 void upgradeFailed(QString errorMessage);
1027
1028 /// The room is about to be deleted
1029 void beforeDestruction(Quotient::Room*);
1030
1031protected:
1032 virtual Changes processStateEvent(const RoomEvent& e);
1033 virtual Changes processEphemeralEvent(EventPtr&& event);
1034 virtual Changes processAccountDataEvent(EventPtr&& event);
1035 virtual void onAddNewTimelineEvents(timeline_iter_t /*from*/) {}
1036 virtual void onAddHistoricalTimelineEvents(rev_iter_t /*from*/) {}
1037 virtual void onRedaction(const RoomEvent& /*prevEvent*/,
1038 const RoomEvent& /*after*/)
1039 {}
1040 virtual QJsonObject toJson() const;
1041 virtual void updateData(SyncRoomData&& data, bool fromCache = false);
1042 virtual Notification checkForNotifications(const TimelineItem& ti);
1043
1044private:
1045 friend class Connection;
1046
1047 class Private;
1048 Private* d;
1049
1050 // This is called from Connection, reflecting a state change that
1051 // arrived from the server. Clients should use
1052 // Connection::joinRoom() and Room::leaveRoom() to change the state.
1053 void setJoinState(JoinState state);
1054};
1055
1056class QUOTIENT_API MemberSorter {
1057public:
1058 explicit MemberSorter(const Room* r) : room(r) {}
1059
1060 bool operator()(User* u1, User* u2) const;
1061 bool operator()(User* u1, QStringView u2name) const;
1062
1063 template <typename ContT, typename ValT>
1064 typename ContT::size_type lowerBoundIndex(const ContT& c, const ValT& v) const
1065 {
1066 return std::lower_bound(c.begin(), c.end(), v, *this) - c.begin();
1067 }
1068
1069private:
1070 const Room* room;
1071};
1072} // namespace Quotient
1073Q_DECLARE_METATYPE(Quotient::FileTransferInfo)
1074Q_DECLARE_METATYPE(Quotient::ReadReceipt)
1075Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::Room::Changes)
1076