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