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