aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.appveyor.yml46
-rw-r--r--connection.cpp44
-rw-r--r--connection.h96
-rw-r--r--jobs/requestdata.cpp4
-rw-r--r--libqmatrixclient.pri7
-rw-r--r--room.cpp53
-rw-r--r--room.h3
-rw-r--r--user.cpp25
-rw-r--r--user.h35
9 files changed, 259 insertions, 54 deletions
diff --git a/.appveyor.yml b/.appveyor.yml
new file mode 100644
index 00000000..410ad12e
--- /dev/null
+++ b/.appveyor.yml
@@ -0,0 +1,46 @@
+image: Visual Studio 2015
+
+environment:
+ #DEPLOY_DIR: libqmatrixclient-%APPVEYOR_BUILD_VERSION%
+ matrix:
+ - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
+ QTDIR: C:\Qt\5.9\msvc2017_64
+ VCVARS: "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat"
+ PLATFORM:
+ MAKETOOL: cmake
+ - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
+ QTDIR: C:\Qt\5.9\msvc2017_64
+ VCVARS: "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat"
+ PLATFORM:
+ MAKETOOL: qmake
+ - QTDIR: C:\Qt\5.9\msvc2015
+ VCVARS: "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat"
+ PLATFORM: x86
+ MAKETOOL: cmake
+
+init:
+- call "%QTDIR%\bin\qtenv2.bat"
+- set PATH=C:\Qt\Tools\QtCreator\bin;%PATH%
+- call "%VCVARS%" %platform%
+- cd /D "%APPVEYOR_BUILD_FOLDER%"
+
+before_build:
+- git submodule update --init --recursive
+- if %MAKETOOL% == cmake cmake -G "NMake Makefiles JOM" -H. -Bbuild -DCMAKE_CXX_FLAGS="/EHsc /W3" -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX="%DEPLOY_DIR%"
+
+build_script:
+- if %MAKETOOL% == cmake cmake --build build
+- if %MAKETOOL% == qmake qmake && jom
+
+#after_build:
+#- cmake --build build --target install
+#- 7z a libqmatrixclient.zip "%DEPLOY_DIR%\"
+
+# Uncomment this to connect to the AppVeyor build worker
+#on_finish:
+# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
+
+test: off
+
+#artifacts:
+#- path: libqmatrixclient.zip
diff --git a/connection.cpp b/connection.cpp
index e46d08aa..6a3cd957 100644
--- a/connection.cpp
+++ b/connection.cpp
@@ -24,6 +24,7 @@
#include "jobs/generated/login.h"
#include "jobs/generated/logout.h"
#include "jobs/generated/receipts.h"
+#include "jobs/generated/leaving.h"
#include "jobs/sendeventjob.h"
#include "jobs/joinroomjob.h"
#include "jobs/roommessagesjob.h"
@@ -39,6 +40,7 @@
#include <QtCore/QStringBuilder>
#include <QtCore/QElapsedTimer>
#include <QtCore/QRegularExpression>
+#include <QtCore/QCoreApplication>
using namespace QMatrixClient;
@@ -60,7 +62,7 @@ class Connection::Private
// Leave state of the same room.
QHash<QPair<QString, bool>, Room*> roomMap;
QVector<QString> roomIdsToForget;
- QHash<QString, User*> userMap;
+ QMap<QString, User*> userMap;
QString userId;
SyncJob* syncJob = nullptr;
@@ -273,6 +275,7 @@ void Connection::onSyncSuccess(SyncData &&data) {
}
if ( auto* r = provideRoom(roomData.roomId, roomData.joinState) )
r->updateData(std::move(roomData));
+ QCoreApplication::instance()->processEvents();
}
}
@@ -382,6 +385,31 @@ DownloadFileJob* Connection::downloadFile(const QUrl& url,
return job;
}
+CreateRoomJob* Connection::createRoom(RoomVisibility visibility,
+ const QString& alias, const QString& name, const QString& topic,
+ const QVector<QString>& invites, const QString& presetName,
+ bool isDirect, bool guestsCanJoin,
+ const QVector<CreateRoomJob::StateEvent>& initialState,
+ const QVector<CreateRoomJob::Invite3pid>& invite3pids,
+ const QJsonObject creationContent)
+{
+ auto job = callApi<CreateRoomJob>(
+ visibility == PublishRoom ? "public" : "private", alias, name,
+ topic, invites, invite3pids, creationContent, initialState,
+ presetName, isDirect, guestsCanJoin);
+ connect(job, &BaseJob::success, this, [this,job] {
+ emit createdRoom(provideRoom(job->roomId(), JoinState::Join));
+ });
+ return job;
+}
+
+CreateRoomJob* Connection::createDirectChat(const QString& userId,
+ const QString& topic, const QString& name)
+{
+ return createRoom(UnpublishRoom, "", name, topic, {userId},
+ "trusted_private_chat", true);
+}
+
ForgetRoomJob* Connection::forgetRoom(const QString& id)
{
// To forget is hard :) First we should ensure the local user is not
@@ -434,8 +462,9 @@ User* Connection::user(const QString& userId)
{
if( d->userMap.contains(userId) )
return d->userMap.value(userId);
- auto* user = createUser(this, userId);
+ auto* user = userFactory(this, userId);
d->userMap.insert(userId, user);
+ emit newUser(user);
return user;
}
@@ -490,6 +519,11 @@ QHash< QPair<QString, bool>, Room* > Connection::roomMap() const
return roomMap;
}
+QMap<QString, User*> Connection::users() const
+{
+ return d->userMap;
+}
+
const ConnectionData* Connection::connectionData() const
{
return d->data.get();
@@ -512,7 +546,7 @@ Room* Connection::provideRoom(const QString& id, JoinState joinState)
}
else
{
- room = createRoom(this, id, joinState);
+ room = roomFactory(this, id, joinState);
if (!room)
{
qCCritical(MAIN) << "Failed to create a room" << id;
@@ -547,11 +581,11 @@ Room* Connection::provideRoom(const QString& id, JoinState joinState)
return room;
}
-Connection::room_factory_t Connection::createRoom =
+Connection::room_factory_t Connection::roomFactory =
[](Connection* c, const QString& id, JoinState joinState)
{ return new Room(c, id, joinState); };
-Connection::user_factory_t Connection::createUser =
+Connection::user_factory_t Connection::userFactory =
[](Connection* c, const QString& id) { return new User(id, c); };
QByteArray Connection::generateTxnId()
diff --git a/connection.h b/connection.h
index 79d7d658..3ec4fd9d 100644
--- a/connection.h
+++ b/connection.h
@@ -18,7 +18,7 @@
#pragma once
-#include "jobs/generated/leaving.h"
+#include "jobs/generated/create_room.h"
#include "joinstate.h"
#include <QtCore/QObject>
@@ -39,6 +39,7 @@ namespace QMatrixClient
class SyncData;
class RoomMessagesJob;
class PostReceiptJob;
+ class ForgetRoomJob;
class MediaThumbnailJob;
class JoinRoomJob;
class UploadContentJob;
@@ -51,6 +52,11 @@ namespace QMatrixClient
/** Whether or not the rooms state should be cached locally
* \sa loadState(), saveState()
*/
+ Q_PROPERTY(User* localUser READ user CONSTANT)
+ Q_PROPERTY(QString localUserId READ userId CONSTANT)
+ Q_PROPERTY(QString deviceId READ deviceId CONSTANT)
+ Q_PROPERTY(QByteArray accessToken READ accessToken CONSTANT)
+ Q_PROPERTY(QUrl homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged)
Q_PROPERTY(bool cacheState READ cacheState WRITE setCacheState NOTIFY cacheStateChanged)
public:
using room_factory_t =
@@ -58,36 +64,25 @@ namespace QMatrixClient
using user_factory_t =
std::function<User*(Connection*, const QString&)>;
+ enum RoomVisibility { PublishRoom, UnpublishRoom }; // FIXME: Should go inside CreateRoomJob
+
explicit Connection(QObject* parent = nullptr);
explicit Connection(const QUrl& server, QObject* parent = nullptr);
virtual ~Connection();
QHash<QPair<QString, bool>, Room*> roomMap() const;
-
- /** Sends /forget to the server and also deletes room locally.
- * This method is in Connection, not in Room, since it's a
- * room lifecycle operation, and Connection is an acting room manager.
- * It ensures that the local user is not a member of a room (running /leave,
- * if necessary) then issues a /forget request and if that one doesn't fail
- * deletion of the local Room object is ensured.
- * \param id - the room id to forget
- * \return - the ongoing /forget request to the server; note that the
- * success() signal of this request is connected to deleteLater()
- * of a respective room so by the moment this finishes, there might be no
- * Room object anymore.
- */
- ForgetRoomJob* forgetRoom(const QString& id);
+ QMap<QString, User*> users() const;
// FIXME: Convert Q_INVOKABLEs to Q_PROPERTIES
// (breaks back-compatibility)
- Q_INVOKABLE QUrl homeserver() const;
+ QUrl homeserver() const;
Q_INVOKABLE User* user(const QString& userId);
- Q_INVOKABLE User* user();
- Q_INVOKABLE QString userId() const;
- Q_INVOKABLE QString deviceId() const;
+ User* user();
+ QString userId() const;
+ QString deviceId() const;
/** @deprecated Use accessToken() instead. */
Q_INVOKABLE QString token() const;
- Q_INVOKABLE QByteArray accessToken() const;
+ QByteArray accessToken() const;
Q_INVOKABLE SyncJob* syncJob() const;
Q_INVOKABLE int millisToReconnect() const;
@@ -145,7 +140,7 @@ namespace QMatrixClient
template <typename T = Room>
static void setRoomType()
{
- createRoom =
+ roomFactory =
[](Connection* c, const QString& id, JoinState joinState)
{ return new T(c, id, joinState); };
}
@@ -153,7 +148,7 @@ namespace QMatrixClient
template <typename T = User>
static void setUserType()
{
- createUser =
+ userFactory =
[](Connection* c, const QString& id) { return new T(id, c); };
}
@@ -186,19 +181,50 @@ namespace QMatrixClient
int requestedHeight) const;
// QIODevice* should already be open
- virtual UploadContentJob* uploadContent(QIODevice* contentSource,
+ UploadContentJob* uploadContent(QIODevice* contentSource,
const QString& filename = {},
const QString& contentType = {}) const;
- virtual UploadContentJob* uploadFile(const QString& fileName,
- const QString& contentType = {});
- virtual GetContentJob* getContent(const QString& mediaId) const;
+ UploadContentJob* uploadFile(const QString& fileName,
+ const QString& contentType = {});
+ GetContentJob* getContent(const QString& mediaId) const;
GetContentJob* getContent(const QUrl& url) const;
// If localFilename is empty, a temporary file will be created
- virtual DownloadFileJob* downloadFile(const QUrl& url,
+ DownloadFileJob* downloadFile(const QUrl& url,
const QString& localFilename = {}) const;
+ /**
+ * \brief Create a room (generic method)
+ * This method allows to customize room entirely to your liking,
+ * providing all the attributes the original CS API provides.
+ */
+ CreateRoomJob* createRoom(RoomVisibility visibility,
+ const QString& alias, const QString& name, const QString& topic,
+ const QVector<QString>& invites, const QString& presetName = {}, bool isDirect = false,
+ bool guestsCanJoin = false,
+ const QVector<CreateRoomJob::StateEvent>& initialState = {},
+ const QVector<CreateRoomJob::Invite3pid>& invite3pids = {},
+ const QJsonObject creationContent = {});
+
+ /** Create a direct chat with a single user, optional name and topic */
+ CreateRoomJob* createDirectChat(const QString& userId,
+ const QString& topic = {}, const QString& name = {});
+
virtual JoinRoomJob* joinRoom(const QString& roomAlias);
+ /** Sends /forget to the server and also deletes room locally.
+ * This method is in Connection, not in Room, since it's a
+ * room lifecycle operation, and Connection is an acting room manager.
+ * It ensures that the local user is not a member of a room (running /leave,
+ * if necessary) then issues a /forget request and if that one doesn't fail
+ * deletion of the local Room object is ensured.
+ * \param id - the room id to forget
+ * \return - the ongoing /forget request to the server; note that the
+ * success() signal of this request is connected to deleteLater()
+ * of a respective room so by the moment this finishes, there might be no
+ * Room object anymore.
+ */
+ ForgetRoomJob* forgetRoom(const QString& id);
+
// Old API that will be abolished any time soon. DO NOT USE.
/** @deprecated Use callApi<PostMessageJob>() or Room::postMessage() instead */
@@ -237,6 +263,8 @@ namespace QMatrixClient
void syncDone();
void syncError(QString error);
+ void newUser(User* user);
+
/**
* \group Signals emitted on room transitions
*
@@ -295,6 +323,13 @@ namespace QMatrixClient
/** The room object is about to be deleted */
void aboutToDeleteRoom(Room* room);
+ /** The room has just been created by createRoom or createDirectChat
+ * This signal is not emitted in usual room state transitions,
+ * only as an outcome of room creation operations invoked by
+ * the client.
+ */
+ void createdRoom(Room* room);
+
void cacheStateChanged();
protected:
@@ -310,7 +345,7 @@ namespace QMatrixClient
* the server; in particular, does not automatically create rooms
* on the server.
* @return a pointer to a Room object with the specified id; nullptr
- * if roomId is empty if createRoom() failed to create a Room object.
+ * if roomId is empty if roomFactory() failed to create a Room object.
*/
Room* provideRoom(const QString& roomId, JoinState joinState);
@@ -340,7 +375,8 @@ namespace QMatrixClient
const QString& initialDeviceName,
const QString& deviceId = {});
- static room_factory_t createRoom;
- static user_factory_t createUser;
+ static room_factory_t roomFactory;
+ static user_factory_t userFactory;
};
} // namespace QMatrixClient
+Q_DECLARE_METATYPE(QMatrixClient::Connection*)
diff --git a/jobs/requestdata.cpp b/jobs/requestdata.cpp
index f5516c5f..5cb62221 100644
--- a/jobs/requestdata.cpp
+++ b/jobs/requestdata.cpp
@@ -8,7 +8,7 @@
using namespace QMatrixClient;
-std::unique_ptr<QIODevice> fromData(const QByteArray& data)
+auto fromData(const QByteArray& data)
{
auto source = std::make_unique<QBuffer>();
source->open(QIODevice::WriteOnly);
@@ -18,7 +18,7 @@ std::unique_ptr<QIODevice> fromData(const QByteArray& data)
}
template <typename JsonDataT>
-inline std::unique_ptr<QIODevice> fromJson(const JsonDataT& jdata)
+inline auto fromJson(const JsonDataT& jdata)
{
return fromData(QJsonDocument(jdata).toJson(QJsonDocument::Compact));
}
diff --git a/libqmatrixclient.pri b/libqmatrixclient.pri
index 9e4cb279..72637caf 100644
--- a/libqmatrixclient.pri
+++ b/libqmatrixclient.pri
@@ -1,6 +1,11 @@
QT += network
CONFIG += c++14 warn_on rtti_off
-QMAKE_CXXFLAGS_WARN_ON += -Wno-unused-parameter
+
+win32-msvc* {
+ QMAKE_CXXFLAGS_WARN_ON += -wd4100
+} else {
+ QMAKE_CXXFLAGS_WARN_ON += -Wno-unused-parameter
+}
INCLUDEPATH += $$PWD
diff --git a/room.cpp b/room.cpp
index bc7c083e..51da747e 100644
--- a/room.cpp
+++ b/room.cpp
@@ -99,7 +99,7 @@ class Room::Private
struct FileTransferPrivateInfo
{
-#if defined(_MSC_VER) && _MSC_VER < 1910
+#if (defined(_MSC_VER) && _MSC_VER < 1910) || (defined(__GNUC__) && __GNUC__ <= 4)
FileTransferPrivateInfo() = default;
FileTransferPrivateInfo(BaseJob* j, QString fileName)
: job(j), localFileInfo(fileName)
@@ -119,6 +119,9 @@ class Room::Private
if (p == 0)
p = -1;
}
+ if (p != -1)
+ qCDebug(PROFILER) << "Transfer progress:" << p << "/" << t
+ << "=" << llround(double(p) / t * 100) << "%";
progress = p; total = t;
}
};
@@ -543,6 +546,25 @@ void Room::resetHighlightCount()
emit highlightCountChanged(this);
}
+QString Room::fileNameToDownload(const QString& eventId)
+{
+ auto evtIt = findInTimeline(eventId);
+ if (evtIt != timelineEdge() &&
+ evtIt->event()->type() == EventType::RoomMessage)
+ {
+ auto* event = static_cast<const RoomMessageEvent*>(evtIt->event());
+ if (event->hasFileContent())
+ {
+ auto* fileInfo = event->content()->fileInfo();
+ return !fileInfo->originalName.isEmpty() ? fileInfo->originalName :
+ !event->plainBody().isEmpty() ? event->plainBody() :
+ QString();
+ }
+ }
+ qWarning() << "No files to download in event" << eventId;
+ return {};
+}
+
FileTransferInfo Room::fileTransferInfo(const QString& id) const
{
auto infoIt = d->fileTransfers.find(id);
@@ -561,7 +583,7 @@ FileTransferInfo Room::fileTransferInfo(const QString& id) const
total = INT_MAX;
}
-#if defined(_MSC_VER) && _MSC_VER < 1910
+#if (defined(_MSC_VER) && _MSC_VER < 1910) || (defined(__GNUC__) && __GNUC__ <= 4)
// A workaround for MSVC 2015 that fails with "error C2440: 'return':
// cannot convert from 'initializer list' to 'QMatrixClient::FileTransferInfo'"
FileTransferInfo fti;
@@ -790,8 +812,8 @@ QString Room::roomMembername(const User* u) const
// << "is not a member of the room" << id();
// }
- // In case of more than one namesake, disambiguate with user id.
- return username % " (" % u->id() % ")";
+ // In case of more than one namesake, use the full name to disambiguate
+ return u->fullName();
}
QString Room::roomMembername(const QString& userId) const
@@ -862,6 +884,17 @@ void Room::postMessage(const RoomMessageEvent& event)
connection()->callApi<SendEventJob>(id(), event);
}
+void Room::setName(const QString& newName)
+{
+ connection()->callApi<SetRoomStateJob>(id(), RoomNameEvent(newName));
+}
+
+void Room::setCanonicalAlias(const QString& newAlias)
+{
+ connection()->callApi<SetRoomStateJob>(id(),
+ RoomCanonicalAliasEvent(newAlias));
+}
+
void Room::setTopic(const QString& newTopic)
{
RoomTopicEvent evt(newTopic);
@@ -970,8 +1003,16 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename)
auto fileName = !localFilename.isEmpty() ? localFilename.toLocalFile() :
!fileInfo->originalName.isEmpty() ?
(safeTempPrefix + fileInfo->originalName) :
- !event->plainBody().isEmpty() ?
- (safeTempPrefix + event->plainBody()) : QString();
+ !event->plainBody().isEmpty() ? (safeTempPrefix + event->plainBody()) :
+ (safeTempPrefix + fileInfo->mimeType.preferredSuffix());
+ if (QSysInfo::productType() == "windows")
+ {
+ const auto& suffixes = fileInfo->mimeType.suffixes();
+ if (!suffixes.isEmpty() &&
+ std::none_of(suffixes.begin(), suffixes.end(),
+ [fileName] (const QString& s) { return fileName.endsWith(s); }))
+ fileName += '.' + fileInfo->mimeType.preferredSuffix();
+ }
auto job = connection()->downloadFile(fileInfo->url, fileName);
if (isJobRunning(job))
{
diff --git a/room.h b/room.h
index b908a763..a8d58f83 100644
--- a/room.h
+++ b/room.h
@@ -215,6 +215,7 @@ namespace QMatrixClient
Q_INVOKABLE int highlightCount() const;
Q_INVOKABLE void resetHighlightCount();
+ Q_INVOKABLE QString fileNameToDownload(const QString& eventId);
Q_INVOKABLE FileTransferInfo fileTransferInfo(const QString& id) const;
/** Pretty-prints plain text into HTML
@@ -235,6 +236,8 @@ namespace QMatrixClient
/** @deprecated If you have a custom event type, construct the event
* and pass it as a whole to postMessage() */
void postMessage(const QString& type, const QString& plainText);
+ void setName(const QString& newName);
+ void setCanonicalAlias(const QString& newAlias);
void setTopic(const QString& newTopic);
void getPreviousContent(int limit = 10);
diff --git a/user.cpp b/user.cpp
index b0890b61..c80ec883 100644
--- a/user.cpp
+++ b/user.cpp
@@ -28,6 +28,9 @@
#include <QtCore/QTimer>
#include <QtCore/QRegularExpression>
#include <QtCore/QPointer>
+#include <QtCore/QStringBuilder>
+
+#include <functional>
using namespace QMatrixClient;
@@ -65,6 +68,15 @@ QString User::id() const
return d->userId;
}
+bool User::isGuest() const
+{
+ Q_ASSERT(!d->userId.isEmpty() && d->userId.startsWith('@'));
+ auto it = std::find_if_not(d->userId.begin() + 1, d->userId.end(),
+ [] (QChar c) { return c.isDigit(); });
+ Q_ASSERT(it != d->userId.end());
+ return *it == ':';
+}
+
QString User::name() const
{
return d->name;
@@ -121,12 +133,17 @@ void User::Private::setAvatar(UploadContentJob* job, User* q)
QString User::displayname() const
{
- if( !d->name.isEmpty() )
- return d->name;
- return d->userId;
+ return d->name.isEmpty() ? d->userId : d->name;
}
-QString User::bridged() const {
+QString User::fullName() const
+{
+ return d->name.isEmpty() ? d->userId :
+ d->name % " (" % d->userId % ')';
+}
+
+QString User::bridged() const
+{
return d->bridged;
}
diff --git a/user.h b/user.h
index 8a2c53d9..37977e08 100644
--- a/user.h
+++ b/user.h
@@ -30,8 +30,10 @@ namespace QMatrixClient
{
Q_OBJECT
Q_PROPERTY(QString id READ id CONSTANT)
+ Q_PROPERTY(bool isGuest READ isGuest CONSTANT)
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
Q_PROPERTY(QString displayName READ displayname NOTIFY nameChanged STORED false)
+ Q_PROPERTY(QString fullName READ fullName NOTIFY nameChanged STORED false)
Q_PROPERTY(QString bridgeName READ bridged NOTIFY nameChanged STORED false)
Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false)
Q_PROPERTY(QUrl avatarUrl READ avatarUrl NOTIFY avatarChanged)
@@ -39,26 +41,47 @@ namespace QMatrixClient
User(QString userId, Connection* connection);
~User() override;
- /**
- * Returns the id of the user
+ /** Get unique stable user id
+ * User id is generated by the server and is not changed ever.
*/
QString id() const;
- /**
- * Returns the name chosen by the user
+ /** Get the name chosen by the user
+ * This may be empty if the user didn't choose the name or cleared
+ * it.
+ * \sa displayName
*/
QString name() const;
- /**
- * Returns the name that should be used to display the user.
+ /** Get the displayed user name
+ * This method returns the result of name() if its non-empty;
+ * otherwise it returns user id. This is convenient to show a user
+ * name outside of a room context. In a room context, user names
+ * should be disambiguated.
+ * \sa name, id, fullName Room::roomMembername
*/
QString displayname() const;
+ /** Get user name and id in one string
+ * The constructed string follows the format 'name (id)'
+ * used for users disambiguation in a room context and in other
+ * places.
+ * \sa displayName, Room::roomMembername
+ */
+ QString fullName() const;
+
/**
* Returns the name of bridge the user is connected from or empty.
*/
QString bridged() const;
+ /** Whether the user is a guest
+ * As of now, the function relies on the convention used in Synapse
+ * that guests and only guests have all-numeric IDs. This may or
+ * may not work with non-Synapse servers.
+ */
+ bool isGuest() const;
+
const Avatar& avatarObject() const;
Q_INVOKABLE QImage avatar(int dimension);
Q_INVOKABLE QImage avatar(int requestedWidth, int requestedHeight);