2021-07-21 16:08:15 -04:00
|
|
|
#include "matrixcore.h"
|
|
|
|
|
|
|
|
#include <cmark.h>
|
|
|
|
|
|
|
|
#include <QJsonArray>
|
|
|
|
#include <QRandomGenerator>
|
|
|
|
#include <QSettings>
|
|
|
|
#include <QPixmap>
|
|
|
|
#include <QStandardPaths>
|
|
|
|
#include <QDir>
|
|
|
|
#include <QMimeDatabase>
|
|
|
|
#include <QMimeData>
|
2022-03-01 16:20:32 -05:00
|
|
|
#include <fstream>
|
|
|
|
#include <iterator>
|
2021-07-21 16:08:15 -04:00
|
|
|
#include "network.h"
|
|
|
|
#include "community.h"
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
MatrixCore::MatrixCore(QString profileName, QObject* parent) : QObject(parent), profileName(profileName), roomListModel(rooms), directoryListModel(publicRooms), eventModel(*this) {
|
|
|
|
network = new Network();
|
|
|
|
encryption = new Encryption();
|
|
|
|
|
2021-07-21 16:08:15 -04:00
|
|
|
QSettings settings;
|
2022-03-01 16:20:32 -05:00
|
|
|
settings.beginGroup(profileName);
|
2021-07-21 16:08:15 -04:00
|
|
|
homeserverURL = settings.value("homeserver", "matrix.org").toString();
|
|
|
|
userId = settings.value("userId").toString();
|
2022-03-01 16:20:32 -05:00
|
|
|
network->homeserverURL = "https://" + homeserverURL;
|
|
|
|
deviceId = settings.value("deviceId").toString();
|
2021-07-21 16:08:15 -04:00
|
|
|
|
|
|
|
if(settings.contains("accessToken"))
|
2022-03-01 16:20:32 -05:00
|
|
|
network->accessToken = "Bearer " + settings.value("accessToken").toString();
|
|
|
|
|
|
|
|
if(settings.contains("accountPickle")) {
|
|
|
|
encryption->loadDeviceKeys(settings.value("accountPickle").toString());
|
|
|
|
qDebug() << "testing identity key: " << encryption->identityKey;
|
|
|
|
}
|
|
|
|
|
|
|
|
settings.endGroup();
|
2021-07-21 16:08:15 -04:00
|
|
|
|
|
|
|
emptyRoom.setName("Empty");
|
|
|
|
emptyRoom.setTopic("There is nothing here.");
|
|
|
|
|
|
|
|
roomListSortModel.setSourceModel(&roomListModel);
|
|
|
|
roomListSortModel.setSortRole(RoomListModel::SectionRole);
|
|
|
|
|
|
|
|
connect(this, &MatrixCore::roomListChanged, [this] {
|
|
|
|
roomListSortModel.sort(0);
|
|
|
|
});
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
memberSortModel.setSourceModel(&memberModel);
|
|
|
|
memberSortModel.setSortRole(RoomListModel::SectionRole);
|
|
|
|
|
|
|
|
connect(this, &MatrixCore::currentRoomChanged, [this] {
|
|
|
|
memberSortModel.sort(0);
|
|
|
|
});
|
|
|
|
|
2021-07-21 16:08:15 -04:00
|
|
|
directoryListSortModel.setSourceModel(&directoryListModel);
|
|
|
|
|
|
|
|
updateAccountInformation();
|
|
|
|
|
|
|
|
QString appDir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
|
|
|
|
if(!QDir(appDir).exists())
|
|
|
|
QDir().mkdir(appDir);
|
|
|
|
|
|
|
|
QString emotesDir = appDir + "/emotes";
|
|
|
|
if(!QDir(emotesDir).exists())
|
|
|
|
QDir().mkdir(emotesDir);
|
|
|
|
|
|
|
|
if(QDir(emotesDir).exists()) {
|
|
|
|
for(auto emote : QDir(emotesDir).entryInfoList()) {
|
|
|
|
// TODO: add support for more than just .png emotes
|
|
|
|
if(emote.fileName().contains(".png")) {
|
|
|
|
Emote* e = new Emote();
|
|
|
|
e->name = emote.fileName().remove(".png");
|
|
|
|
e->path = emote.absoluteFilePath();
|
|
|
|
|
|
|
|
emotes.push_back(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
localEmoteModel.setList(&emotes);
|
|
|
|
}
|
|
|
|
|
|
|
|
void MatrixCore::registerAccount(const QString &username, const QString &password, const QString& session, const QString& type) {
|
|
|
|
QJsonObject authObject;
|
|
|
|
if(!session.isEmpty()) {
|
|
|
|
authObject["type"] = type;
|
|
|
|
authObject["session"] = session;
|
|
|
|
}
|
|
|
|
|
|
|
|
const QJsonObject registerObject {
|
|
|
|
{"auth", authObject},
|
|
|
|
{"username", username},
|
|
|
|
{"password", password}
|
|
|
|
};
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
network->postJSON("/_matrix/client/r0/register?kind=user", registerObject, [this](QNetworkReply* reply) {
|
2021-07-21 16:08:15 -04:00
|
|
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
|
|
|
|
|
|
|
if(reply->error()) {
|
|
|
|
if(document.object().contains("flows")) {
|
|
|
|
const QString stage = document.object()["flows"].toArray()[0].toObject()["stages"].toArray()[0].toString();
|
|
|
|
|
|
|
|
if(stage == "m.login.recaptcha") {
|
|
|
|
const QJsonObject data {
|
|
|
|
{"public_key", document.object()["params"].toObject()["m.login.recaptcha"].toObject()["public_key"].toString()},
|
|
|
|
{"session", document.object()["session"].toString()},
|
|
|
|
{"type", "m.login.recaptcha"}
|
|
|
|
};
|
|
|
|
|
|
|
|
emit registerFlow(data);
|
|
|
|
} else if(stage == "m.login.dummy") {
|
|
|
|
const QJsonObject data {
|
|
|
|
{"session", document.object()["session"].toString()},
|
|
|
|
{"type", "m.login.dummy"}
|
|
|
|
};
|
|
|
|
|
|
|
|
emit registerFlow(data);
|
|
|
|
} else {
|
|
|
|
emit registerAttempt(true, "Unknown stage type " + stage);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
emit registerAttempt(true, document.object()["error"].toString());
|
|
|
|
}
|
|
|
|
} else {
|
2022-03-01 16:20:32 -05:00
|
|
|
network->accessToken = "Bearer " + document.object()["access_token"].toString();
|
2021-07-21 16:08:15 -04:00
|
|
|
|
|
|
|
QSettings settings;
|
2022-03-01 16:20:32 -05:00
|
|
|
settings.beginGroup(profileName);
|
2021-07-21 16:08:15 -04:00
|
|
|
settings.setValue("accessToken", document.object()["access_token"].toString());
|
|
|
|
settings.setValue("userId", document.object()["user_id"].toString());
|
|
|
|
settings.setValue("deviceId", document.object()["device_id"].toString());
|
2022-03-01 16:20:32 -05:00
|
|
|
settings.endGroup();
|
2021-07-21 16:08:15 -04:00
|
|
|
|
|
|
|
emit registerAttempt(false, "");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void MatrixCore::login(const QString& username, const QString& password) {
|
|
|
|
const QJsonObject loginObject {
|
|
|
|
{"type", "m.login.password"},
|
|
|
|
{"user", username},
|
|
|
|
{"password", password},
|
|
|
|
{"initial_device_display_name", "Trinity"}
|
|
|
|
};
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
network->postJSON("/_matrix/client/r0/login", loginObject, [this](QNetworkReply* reply) {
|
2021-07-21 16:08:15 -04:00
|
|
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
|
|
|
|
|
|
|
if(reply->error()) {
|
|
|
|
emit loginAttempt(true, document.object()["error"].toString());
|
|
|
|
} else {
|
2022-03-01 16:20:32 -05:00
|
|
|
network->accessToken = "Bearer " + document.object()["access_token"].toString();
|
2021-07-21 16:08:15 -04:00
|
|
|
|
|
|
|
QSettings settings;
|
2022-03-01 16:20:32 -05:00
|
|
|
settings.beginGroup(profileName);
|
2021-07-21 16:08:15 -04:00
|
|
|
settings.setValue("accessToken", document.object()["access_token"].toString());
|
|
|
|
settings.setValue("userId", document.object()["user_id"].toString());
|
|
|
|
settings.setValue("deviceId", document.object()["device_id"].toString());
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
// upload keys
|
|
|
|
encryption->createNewDeviceKeys();
|
|
|
|
|
|
|
|
QJsonObject keysObject {
|
|
|
|
{"curve25519:" + document.object()["device_id"].toString(), encryption->identityKey["curve25519"]},
|
|
|
|
{"ed25519:" + document.object()["device_id"].toString(), encryption->identityKey["ed25519"]}
|
|
|
|
};
|
|
|
|
|
|
|
|
QJsonObject deviceKeysObject {
|
|
|
|
{"user_id", document.object()["user_id"].toString()},
|
|
|
|
{"device_id", document.object()["device_id"].toString()},
|
|
|
|
{"algorithms", QJsonArray({"m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"})},
|
|
|
|
{"keys", keysObject}
|
|
|
|
};
|
|
|
|
|
|
|
|
QJsonObject signature {
|
|
|
|
{"ed25519:" + document.object()["device_id"].toString(), encryption->signMessage(QJsonDocument(deviceKeysObject).toJson(QJsonDocument::Compact)) }};
|
|
|
|
|
|
|
|
deviceKeysObject["signatures"] = QJsonObject {
|
|
|
|
{document.object()["user_id"].toString(), signature}};
|
|
|
|
|
|
|
|
QJsonObject oneTimeKeyObject;
|
|
|
|
|
|
|
|
auto one_time_keys = encryption->generateOneTimeKeys(encryption->getRecommendedNumberOfOneTimeKeys());
|
|
|
|
|
|
|
|
for(auto key : one_time_keys["curve25519"].toObject().keys()) {
|
|
|
|
QJsonObject keyObject {
|
|
|
|
{"key", one_time_keys["curve25519"].toObject()[key]}
|
|
|
|
};
|
|
|
|
|
|
|
|
QJsonObject signature {
|
|
|
|
{"ed25519:" + document.object()["device_id"].toString(), encryption->signMessage(QJsonDocument(keyObject).toJson(QJsonDocument::Compact)) }};
|
|
|
|
|
|
|
|
keyObject["signatures"] = QJsonObject {
|
|
|
|
{document.object()["user_id"].toString(), signature}};
|
|
|
|
|
|
|
|
oneTimeKeyObject["signed_curve25519:" + key] = keyObject;
|
|
|
|
}
|
|
|
|
|
|
|
|
QJsonObject masterKeysObject {
|
|
|
|
{"device_keys", deviceKeysObject},
|
|
|
|
{"one_time_keys", oneTimeKeyObject}
|
|
|
|
};
|
|
|
|
|
|
|
|
//qDebug() << masterKeysObject;
|
|
|
|
|
|
|
|
auto keys = encryption->saveDeviceKeys();
|
|
|
|
|
|
|
|
settings.setValue("accountPickle", keys);
|
|
|
|
settings.endGroup();
|
|
|
|
|
|
|
|
network->postJSON("/_matrix/client/r0/keys/upload", masterKeysObject, [this](QNetworkReply* reply) {
|
|
|
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
|
|
|
qDebug() << "KEY UPLOAD RESULT: " << document;
|
|
|
|
});
|
|
|
|
|
2021-07-21 16:08:15 -04:00
|
|
|
emit loginAttempt(false, "");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void MatrixCore::logout() {
|
2022-03-01 16:20:32 -05:00
|
|
|
network->post("/_matrix/client/r0/logout");
|
2021-07-21 16:08:15 -04:00
|
|
|
|
|
|
|
QSettings settings;
|
|
|
|
settings.remove("accessToken");
|
|
|
|
settings.remove("deviceId");
|
|
|
|
settings.remove("userId");
|
|
|
|
settings.sync();
|
|
|
|
}
|
|
|
|
|
|
|
|
void MatrixCore::updateAccountInformation() {
|
2022-03-01 16:20:32 -05:00
|
|
|
network->get("/_matrix/client/r0/profile/" + userId + "/displayname", [this](QNetworkReply* reply) {
|
2021-07-21 16:08:15 -04:00
|
|
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
|
|
|
|
|
|
|
displayName = document.object()["displayname"].toString();
|
|
|
|
emit displayNameChanged();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void MatrixCore::setDisplayName(const QString& name) {
|
|
|
|
displayName = name;
|
|
|
|
|
|
|
|
const QJsonObject displayNameObject {
|
|
|
|
{"displayname", name}
|
|
|
|
};
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
network->putJSON("/_matrix/client/r0/profile/" + userId + "/displayname", displayNameObject, [this, name](QNetworkReply* reply) {
|
2021-07-21 16:08:15 -04:00
|
|
|
emit displayNameChanged();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void MatrixCore::sync() {
|
2022-03-01 16:20:32 -05:00
|
|
|
if(network->accessToken.isEmpty())
|
2021-07-21 16:08:15 -04:00
|
|
|
return;
|
|
|
|
|
|
|
|
QString url = "/_matrix/client/r0/sync";
|
|
|
|
if(!nextBatch.isEmpty())
|
|
|
|
url += "?since=" + nextBatch;
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
network->get(url, [this](QNetworkReply* reply) {
|
2021-07-21 16:08:15 -04:00
|
|
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
|
|
|
|
|
|
|
if(!document.object()["next_batch"].isNull())
|
|
|
|
nextBatch = document.object()["next_batch"].toString();
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
//qDebug() << document.object()["to_device"];
|
|
|
|
//qDebug() << document.object()["device_one_time_keys_count"];
|
|
|
|
|
|
|
|
for(auto event : document.object()["to_device"].toObject()["events"].toArray()) {
|
|
|
|
if(event.toObject()["type"] == "m.room_key_request" && event.toObject()["content"].toObject()["action"] == "request") {
|
|
|
|
auto sender = event.toObject()["sender"].toString();
|
|
|
|
auto device_id = event.toObject()["content"].toObject()["requesting_device_id"].toString();
|
|
|
|
auto room_id = event.toObject()["content"].toObject()["body"].toObject()["room_id"].toString();
|
|
|
|
|
|
|
|
QJsonObject queryObject {
|
|
|
|
{"timeout", 10000},
|
|
|
|
{"device_keys", QJsonObject{
|
|
|
|
{sender, QJsonArray({device_id})}
|
|
|
|
}},
|
|
|
|
{"token", "string"},
|
|
|
|
};
|
|
|
|
|
|
|
|
network->postJSON("/_matrix/client/r0/keys/query", queryObject, [this, event, sender, device_id, room_id](QNetworkReply* reply) {
|
|
|
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
|
|
|
|
|
|
|
auto senderCurveKey = document.object()["device_keys"].toObject()[sender].toObject()[device_id].toObject()["keys"].toObject()["curve25519:" + device_id].toString();
|
|
|
|
auto senderEdKey = document.object()["device_keys"].toObject()[sender].toObject()[device_id].toObject()["keys"].toObject()["ed25519:" + device_id].toString();
|
|
|
|
|
|
|
|
qDebug() << "sending keys to " << device_id;
|
|
|
|
|
|
|
|
createOrLoadSession();
|
|
|
|
|
|
|
|
sendKeyToDevice(room_id, senderCurveKey, senderEdKey, currentSessionId, currentSessionKey, sender, device_id);
|
|
|
|
});
|
|
|
|
} else if(event.toObject()["type"] == "m.room_key") {
|
|
|
|
qDebug() << "we recieved a new key from a user in the room :-)";
|
|
|
|
} else if(event.toObject()["type"] == "m.forwarded_room_key") {
|
|
|
|
qDebug() << "we recieved a new key from a user in the room :-)";
|
|
|
|
} else if(event.toObject()["type"] == "m.room.encrypted") {
|
|
|
|
auto curveKey = event.toObject()["content"].toObject()["ciphertext"].toObject().keys()[0];
|
|
|
|
auto senderKey = event.toObject()["content"].toObject()["sender_key"].toString();
|
|
|
|
int type = event.toObject()["content"].toObject()["ciphertext"].toObject()[curveKey].toObject()["type"].toInt();
|
|
|
|
auto body = event.toObject()["content"].toObject()["ciphertext"].toObject()[curveKey].toObject()["body"].toString();
|
|
|
|
|
|
|
|
// create a new inbound session
|
|
|
|
auto session = encryption->createInboundSession(senderKey.toStdString(), body.toStdString());
|
|
|
|
|
|
|
|
auto decryptedMsg = encryption->decrypt(session, type, body.toStdString());
|
|
|
|
|
|
|
|
const QJsonDocument document = QJsonDocument::fromJson(QByteArray(reinterpret_cast<const char*>(decryptedMsg.data()), decryptedMsg.size()));
|
|
|
|
auto id = document.object()["content"].toObject()["session_id"].toString();
|
|
|
|
|
|
|
|
qDebug() << "NEW KEY " << id << " = " << document;
|
|
|
|
|
|
|
|
// create new inbound session, append to list
|
|
|
|
auto sess = encryption->beginInboundSession(document.object()["content"].toObject()["session_key"].toString().toStdString());
|
|
|
|
inboundSessions[id] = sess;
|
|
|
|
|
|
|
|
// if we recieved a new key, let's see if we can decrypt some old messages!
|
|
|
|
for(auto room : rooms) {
|
|
|
|
if(room->getId() == document.object()["content"].toObject()["room_id"].toString()) {
|
|
|
|
for(auto event : room->events) {
|
|
|
|
if(event->encryptionInfo != nullptr && event->encryptionInfo->sessionId == id) {
|
|
|
|
auto msg = encryption->decrypt(sess, event->encryptionInfo->cipherText.toStdString());
|
|
|
|
|
|
|
|
const QJsonDocument document = QJsonDocument::fromJson(QByteArray(reinterpret_cast<const char*>(msg.data()), msg.size()));
|
|
|
|
|
|
|
|
populateEvent(document.object(), event);
|
|
|
|
|
|
|
|
emit event->msgChanged();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto createRoom = [this](const QString id, const QString joinState, bool autofill_data = true) {
|
2021-07-21 16:08:15 -04:00
|
|
|
roomListModel.beginInsertRoom();
|
|
|
|
|
|
|
|
Room* room = new Room(this);
|
|
|
|
room->setId(id);
|
|
|
|
room->setJoinState(joinState);
|
|
|
|
|
|
|
|
QSettings settings;
|
|
|
|
settings.beginGroup(id);
|
|
|
|
if(settings.contains("notificationLevel"))
|
|
|
|
room->setNotificationLevel(settings.value("notificationLevel").toInt(), true);
|
|
|
|
else
|
|
|
|
room->setNotificationLevel(1);
|
|
|
|
settings.endGroup();
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
if(autofill_data) {
|
|
|
|
network->get("/_matrix/client/r0/rooms/" + id + "/state/m.room.name", [this, room](QNetworkReply* reply) {
|
|
|
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
|
|
|
if(document.object()["errcode"].toString() == "M_GUEST_ACCESS_FORBIDDEN") {
|
|
|
|
room->setGuestDenied(true);
|
|
|
|
return;
|
|
|
|
} else if(document.object()["errcode"].toString() == "M_NOT_FOUND")
|
|
|
|
return;
|
2021-07-21 16:08:15 -04:00
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
room->setName(document.object()["name"].toString());
|
2021-07-21 16:08:15 -04:00
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
roomListModel.updateRoom(room);
|
|
|
|
});
|
2021-07-21 16:08:15 -04:00
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
network->get("/_matrix/client/r0/rooms/" + id + "/state/m.room.topic", [this, room](QNetworkReply* reply) {
|
|
|
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
|
|
|
if(document.object()["errcode"].toString() == "M_GUEST_ACCESS_FORBIDDEN") {
|
|
|
|
room->setGuestDenied(true);
|
|
|
|
return;
|
|
|
|
}
|
2021-07-21 16:08:15 -04:00
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
room->setTopic(document.object()["topic"].toString());
|
2021-07-21 16:08:15 -04:00
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
roomListModel.updateRoom(room);
|
|
|
|
});
|
2021-07-21 16:08:15 -04:00
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
network->get("/_matrix/client/r0/rooms/" + id + "/state/m.room.avatar", [this, room](QNetworkReply* reply) {
|
|
|
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
|
|
|
if(document.object()["errcode"].toString() == "M_GUEST_ACCESS_FORBIDDEN") {
|
|
|
|
room->setGuestDenied(true);
|
|
|
|
return;
|
|
|
|
}
|
2021-07-21 16:08:15 -04:00
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
if(document.object().contains("url")) {
|
|
|
|
const QString imageId = document.object()["url"].toString().remove("mxc://");
|
|
|
|
room->setAvatar(network->homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
|
|
|
}
|
2021-07-21 16:08:15 -04:00
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
roomListModel.updateRoom(room);
|
|
|
|
});
|
|
|
|
|
|
|
|
network->get("/_matrix/client/r0/rooms/" + id + "/state/m.room.power_levels", [this, room](QNetworkReply* reply) {
|
|
|
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
|
|
|
|
|
|
|
for(auto user : document.object()["users"].toObject().keys()) {
|
|
|
|
room->powerLevelList[user] = document.object()["users"].toObject()[user].toInt();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
network->get("/_matrix/client/r0/rooms/" + id + "/state/m.room.encryption", [this, room](QNetworkReply* reply) {
|
|
|
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
|
|
|
|
|
|
|
if(document.object().contains("algorithm")) {
|
|
|
|
room->setEncrypted();
|
|
|
|
}
|
|
|
|
|
|
|
|
//qDebug() << document;
|
|
|
|
});
|
|
|
|
}
|
2021-07-21 16:08:15 -04:00
|
|
|
|
|
|
|
rooms.push_back(room);
|
|
|
|
idToRoom.insert(id, room);
|
|
|
|
|
|
|
|
roomListModel.endInsertRoom();
|
|
|
|
|
|
|
|
emit roomListChanged();
|
|
|
|
|
|
|
|
updateMembers(room);
|
|
|
|
|
|
|
|
return room;
|
|
|
|
};
|
|
|
|
|
|
|
|
for(const auto id : document.object()["rooms"].toObject()["invite"].toObject().keys()) {
|
|
|
|
if(!invitedRooms.count(id)) {
|
2022-03-01 16:20:32 -05:00
|
|
|
Room* room = createRoom(id, "Invited", false);
|
|
|
|
room->setGuestDenied(true);
|
2021-07-21 16:08:15 -04:00
|
|
|
|
|
|
|
for(auto event : document.object()["rooms"].toObject()["invite"].toObject()[id].toObject()["invite_state"].toObject()["events"].toArray()) {
|
|
|
|
const QString type = event.toObject()["type"].toString();
|
|
|
|
|
|
|
|
if(type == "m.room.member")
|
|
|
|
room->setInvitedBy(event.toObject()["sender"].toString());
|
|
|
|
else if(type == "m.room.name") {
|
|
|
|
room->setName(event.toObject()["content"].toObject()["name"].toString());
|
|
|
|
} else if(type == "m.room.avatar") {
|
|
|
|
const QString imageId = event.toObject()["content"].toObject()["url"].toString().remove("mxc://");
|
2022-03-01 16:20:32 -05:00
|
|
|
room->setAvatar(network->homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
2021-07-21 16:08:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
roomListModel.updateRoom(room);
|
|
|
|
}
|
|
|
|
|
|
|
|
invitedRooms.push_back(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for(const auto id : document.object()["rooms"].toObject()["join"].toObject().keys()) {
|
|
|
|
if(!joinedRooms.count(id)) {
|
|
|
|
createRoom(id, "Joined");
|
|
|
|
joinedRooms.push_back(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for(const auto id : document.object()["rooms"].toObject()["leave"].toObject().keys()) {
|
|
|
|
|
|
|
|
if(joinedRooms.count(id)) {
|
|
|
|
Room* room = resolveRoomId(id);
|
|
|
|
room->setJoinState("left");
|
|
|
|
|
|
|
|
joinedRooms.removeOne(id);
|
|
|
|
rooms.removeOne(room);
|
|
|
|
|
|
|
|
roomListModel.fullUpdate();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int i = 0;
|
|
|
|
for(const auto& room : document.object()["rooms"].toObject()["join"].toObject()) {
|
|
|
|
Room* roomState = nullptr;
|
|
|
|
for(auto& r : rooms) {
|
|
|
|
if(r->getId() == document.object()["rooms"].toObject()["join"].toObject().keys()[i])
|
|
|
|
roomState = r;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!roomState)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if(firstSync)
|
|
|
|
roomState->prevBatch = room.toObject()["timeline"].toObject()["prev_batch"].toString();
|
|
|
|
|
|
|
|
const int highlightCount = room.toObject()["unread_notifications"].toObject()["highlight_count"].toInt();
|
|
|
|
const int notificationCount = room.toObject()["unread_notifications"].toObject()["notification_count"].toInt();
|
|
|
|
|
|
|
|
if(highlightCount != roomState->getHighlightCount()) {
|
|
|
|
roomState->setNotificationCount(highlightCount);
|
|
|
|
roomListModel.updateRoom(roomState);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(notificationCount != roomState->getNotificationCount()) {
|
|
|
|
roomState->setNotificationCount(notificationCount);
|
|
|
|
roomListModel.updateRoom(roomState);
|
|
|
|
}
|
|
|
|
|
|
|
|
for(const auto event : room.toObject()["ephemeral"].toObject()["events"].toArray()) {
|
|
|
|
const QString eventType = event.toObject()["type"].toString();
|
|
|
|
|
|
|
|
if(eventType == "m.typing") {
|
|
|
|
auto typing = event.toObject()["content"].toObject()["user_ids"].toArray();
|
|
|
|
|
|
|
|
QString typingText;
|
|
|
|
int trueSize = 0;
|
|
|
|
if(typing.size() < 4) {
|
|
|
|
for(int i = 0; i < typing.size(); i++) {
|
|
|
|
if(typing[i].toString() == userId)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const Member* member = resolveMemberId(typing[i].toString());
|
|
|
|
if(!member)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
typingText += member->getDisplayName();
|
|
|
|
if(i != typing.size() - 1)
|
|
|
|
typingText += ", ";
|
|
|
|
|
|
|
|
trueSize++;
|
|
|
|
}
|
|
|
|
|
|
|
|
typingText += " is";
|
|
|
|
} else {
|
|
|
|
typingText = "Several people are";
|
|
|
|
}
|
|
|
|
|
|
|
|
if(trueSize != 0)
|
|
|
|
this->typingText = typingText + " typing...";
|
|
|
|
else
|
|
|
|
this->typingText.clear();
|
|
|
|
|
|
|
|
emit typingTextChanged();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for(const auto event : room.toObject()["timeline"].toObject()["events"].toArray())
|
|
|
|
consumeEvent(event.toObject(), *roomState);
|
|
|
|
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
for(const auto& id : document.object()["groups"].toObject()["join"].toObject().keys()) {
|
|
|
|
if(!joinedCommunitiesIds.count(id)) {
|
|
|
|
Community* community = nullptr;
|
|
|
|
if(!idToCommunity.count(id))
|
|
|
|
community = createCommunity(id);
|
|
|
|
else
|
|
|
|
community = idToCommunity[id];
|
|
|
|
|
|
|
|
community->setJoinState("Joined");
|
|
|
|
|
|
|
|
joinedCommunities.push_back(community);
|
|
|
|
joinedCommunitiesIds.push_back(community->getId());
|
|
|
|
|
|
|
|
emit joinedCommunitiesChanged();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(firstSync) {
|
|
|
|
firstSync = false;
|
|
|
|
emit initialSyncFinished();
|
|
|
|
}
|
|
|
|
|
|
|
|
emit syncFinished();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
bool MatrixCore::isInitialSyncComplete() {
|
|
|
|
return !firstSync;
|
|
|
|
}
|
|
|
|
|
2021-07-21 16:08:15 -04:00
|
|
|
void MatrixCore::sendMessage(Room* room, const QString& message) {
|
|
|
|
if(message.isEmpty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
Event* e = new Event(room);
|
|
|
|
e->setSender(userId);
|
|
|
|
e->timestamp = QDateTime::currentDateTime();
|
|
|
|
e->setMsg(message);
|
|
|
|
e->setRoom(room->getId());
|
|
|
|
e->setSent(false);
|
|
|
|
e->setMsgType("text");
|
|
|
|
|
|
|
|
QString msg = e->getMsg();
|
|
|
|
for(const auto& emote : emotes) {
|
|
|
|
msg.replace(":" + emote->name + ":", "<img src='file://" + emote->path + "' width=22 height=22/>");
|
|
|
|
}
|
|
|
|
e->setMsg(msg);
|
|
|
|
|
|
|
|
eventModel.beginUpdate(0);
|
|
|
|
room->events.push_front(e);
|
|
|
|
eventModel.endUpdate();
|
|
|
|
|
|
|
|
unsentMessages.push_back(e);
|
|
|
|
|
|
|
|
const auto onMessageFeedbackReceived = [this, e](QNetworkReply* reply) {
|
|
|
|
if(!reply->error()) {
|
|
|
|
for(size_t i = 0; i < unsentMessages.size(); i++) {
|
|
|
|
if(unsentMessages[i] == e)
|
|
|
|
e->setSent(true);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
qDebug() << reply->readAll();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
bool shouldSendAsMarkdown = false;
|
|
|
|
char* formatted = nullptr;
|
|
|
|
|
|
|
|
if(markdownEnabled) {
|
|
|
|
formatted = cmark_markdown_to_html(message.toStdString().c_str(), message.length(), CMARK_OPT_DEFAULT | CMARK_OPT_HARDBREAKS);
|
|
|
|
|
|
|
|
shouldSendAsMarkdown = strlen(formatted) > 8 + message.length();
|
|
|
|
}
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
QJsonObject messageObject;
|
2021-07-21 16:08:15 -04:00
|
|
|
if(shouldSendAsMarkdown) {
|
2022-03-01 16:20:32 -05:00
|
|
|
messageObject = QJsonObject {
|
2021-07-21 16:08:15 -04:00
|
|
|
{"msgtype", "m.text"},
|
|
|
|
{"formatted_body", formatted},
|
|
|
|
{"body", message},
|
|
|
|
{"format", "org.matrix.custom.html"}
|
|
|
|
};
|
|
|
|
|
|
|
|
e->setMsg(formatted);
|
|
|
|
} else {
|
2022-03-01 16:20:32 -05:00
|
|
|
messageObject = QJsonObject {
|
2021-07-21 16:08:15 -04:00
|
|
|
{"msgtype", "m.text"},
|
|
|
|
{"body", message}
|
|
|
|
};
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
e->setMsg(message);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(room->getEncrypted()) {
|
|
|
|
// create megolm session
|
|
|
|
createOrLoadSession();
|
|
|
|
|
|
|
|
/*QJsonObject deviceKeys;
|
|
|
|
|
|
|
|
// get device list for each user
|
|
|
|
for(auto member : room->members) {
|
|
|
|
deviceKeys.insert(member->getId(), QJsonArray());
|
|
|
|
}
|
|
|
|
|
|
|
|
QJsonObject queryObject {
|
|
|
|
{"timeout", 10000},
|
|
|
|
{"device_keys", deviceKeys},
|
|
|
|
{"token", "string"},
|
|
|
|
};
|
|
|
|
|
|
|
|
network->postJSON("/_matrix/client/r0/keys/query", queryObject, [this, room](QNetworkReply* reply) {
|
|
|
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
|
|
|
// for each user, and for each of their devices, start a new olm session
|
|
|
|
for(auto user_id : document.object()["device_keys"].toObject().keys()) {
|
|
|
|
for(auto device_id : document.object()["device_keys"].toObject()[user_id].toObject().keys()) {
|
|
|
|
QJsonObject claimObject {
|
|
|
|
{"timeout", 10000},
|
|
|
|
{"one_time_keys", QJsonObject {
|
|
|
|
{user_id, QJsonObject {
|
|
|
|
{device_id, "signed_curve25519"}
|
|
|
|
}}
|
|
|
|
}}
|
|
|
|
};
|
|
|
|
|
|
|
|
std::string identityKey = document.object()["device_keys"].toObject()[user_id].toObject()[device_id].toObject()["keys"].toObject()[QString("curve25519:") + device_id].toString().toStdString();
|
|
|
|
|
|
|
|
std::string edIdentityKey = document.object()["device_keys"].toObject()[user_id].toObject()[device_id].toObject()["keys"].toObject()[QString("ed25519:") + device_id].toString().toStdString();
|
|
|
|
|
|
|
|
sendKeyToDevice(room->getId(), identityKey.c_str(), edIdentityKey.c_str(), currentSessionId, currentSessionKey, user_id, device_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});*/
|
|
|
|
|
|
|
|
QJsonObject trueObject {
|
|
|
|
{"room_id", room->getId()},
|
|
|
|
{"type", "m.room.message"},
|
|
|
|
{"content", messageObject}
|
|
|
|
/*{"keys", QJsonObject {
|
|
|
|
{"ed25519", encryption->identityKey["ed25519"]}
|
|
|
|
}},
|
|
|
|
{"sender", userId},
|
|
|
|
{"sender_device", deviceId}*/
|
|
|
|
};
|
|
|
|
|
|
|
|
// construct the m.room.encrypted event
|
|
|
|
const QJsonObject roomEncryptedObject {
|
|
|
|
{"algorithm", "m.megolm.v1.aes-sha2"},
|
|
|
|
{"ciphertext", encryption->encryptGroup(currentSession, QString(QJsonDocument(trueObject).toJson(QJsonDocument::Compact)).toStdString()).c_str()},
|
|
|
|
{"sender_key", encryption->identityKey["curve25519"]},
|
|
|
|
{"session_id", currentSessionId},
|
|
|
|
{"device_id", deviceId}
|
|
|
|
};
|
|
|
|
|
|
|
|
network->putJSON("/_matrix/client/r0/rooms/" + room->getId() + "/send/m.room.encrypted/" + QRandomGenerator::global()->generate(), roomEncryptedObject, [](QNetworkReply* reply) {
|
|
|
|
//qDebug() << "reply from room send: " << reply->readAll();
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
network->putJSON(QString("/_matrix/client/r0/rooms/" + room->getId() + "/send/m.room.message/") + QString::number(QRandomGenerator::global()->generate()), messageObject, onMessageFeedbackReceived);
|
2021-07-21 16:08:15 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void MatrixCore::removeMessage(const QString& eventId) {
|
|
|
|
const QJsonObject reasonObject {
|
|
|
|
{"reason", ""}
|
|
|
|
};
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
network->putJSON("/_matrix/client/r0/rooms/" + currentRoom->getId() + "/redact/" + eventId + "/" + QString::number(QRandomGenerator::global()->generate()), reasonObject, [this, eventId](QNetworkReply* reply) {
|
2021-07-21 16:08:15 -04:00
|
|
|
auto& events = currentRoom->events;
|
|
|
|
for(int i = 0; i < events.size(); i++) {
|
|
|
|
if(events[i]->eventId == eventId) {
|
|
|
|
eventModel.beginRemoveEvent(i, 0);
|
|
|
|
|
|
|
|
events.removeAt(i);
|
|
|
|
|
|
|
|
eventModel.endRemoveEvent();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void MatrixCore::uploadAttachment(Room* room, const QString& path) {
|
|
|
|
Event* e = new Event(room);
|
|
|
|
e->setSender(userId);
|
|
|
|
e->timestamp = QDateTime::currentDateTime();
|
|
|
|
e->setRoom(room->getId());
|
|
|
|
e->setSent(false);
|
|
|
|
|
|
|
|
eventModel.beginUpdate(0);
|
|
|
|
room->events.push_front(e);
|
|
|
|
eventModel.endUpdate();
|
|
|
|
|
|
|
|
unsentMessages.push_back(e);
|
|
|
|
|
|
|
|
QMimeDatabase mimeDb;
|
|
|
|
QMimeType mimeType = mimeDb.mimeTypeForFile(path);
|
|
|
|
|
|
|
|
QString filepath = path;
|
|
|
|
filepath.remove("file://");
|
|
|
|
QFile f(filepath);
|
|
|
|
f.open(QFile::ReadOnly);
|
|
|
|
|
|
|
|
const QString fileName = QFileInfo(f.fileName()).fileName();
|
|
|
|
const qint64 fileSize = f.size();
|
|
|
|
|
|
|
|
e->setAttachment(path);
|
|
|
|
e->setThumbnail(path);
|
|
|
|
e->setMsg(fileName);
|
|
|
|
e->setMsgType(mimeType.name().contains("image") ? "image" : "file");
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
network->postBinary("/_matrix/media/r0/upload?filename=" + f.fileName(), f.readAll(), mimeType.name(), [this, mimeType, fileName, fileSize, e](QNetworkReply* reply) {
|
2021-07-21 16:08:15 -04:00
|
|
|
if(!reply->error()) {
|
|
|
|
e->setSent(true);
|
|
|
|
|
|
|
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
|
|
|
|
|
|
|
QJsonObject infoObject {
|
|
|
|
{"mimetype", mimeType.name()},
|
|
|
|
{"size", fileSize},
|
|
|
|
};
|
|
|
|
|
|
|
|
const QJsonObject imageObject {
|
|
|
|
{"msgtype", mimeType.name().contains("image") ? "m.image" : "m.file"},
|
|
|
|
{"body", fileName},
|
|
|
|
{"url", document.object()["content_uri"].toString()},
|
|
|
|
{"info", infoObject}
|
|
|
|
};
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
network->putJSON("/_matrix/client/r0/rooms/" + currentRoom->getId() + "/send/m.room.message/" + QString::number(QRandomGenerator::global()->generate()), imageObject);
|
2021-07-21 16:08:15 -04:00
|
|
|
}
|
|
|
|
}, [e](qint64 sent, qint64 total) {
|
|
|
|
e->setSentProgress((double)sent / (double)total);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void MatrixCore::startDirectChat(const QString& id) {
|
|
|
|
const QJsonObject roomObject {
|
|
|
|
{"visibility", "private"},
|
|
|
|
{"creation_content", QJsonObject{{"m.federate", false}}},
|
|
|
|
{"preset", "private_chat"},
|
|
|
|
{"is_direct", true},
|
|
|
|
{"invite", QJsonArray{id}}
|
|
|
|
};
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
network->postJSON("/_matrix/client/r0/createRoom", roomObject, [](QNetworkReply*) {});
|
2021-07-21 16:08:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void MatrixCore::setTyping(Room* room) {
|
|
|
|
const QJsonObject typingObject {
|
|
|
|
{"typing", true},
|
|
|
|
{"timeout", 15000}
|
|
|
|
};
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
network->putJSON("/_matrix/client/r0/rooms/" + room->getId() + "/typing/" + userId, typingObject);
|
2021-07-21 16:08:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void MatrixCore::joinRoom(const QString& id) {
|
2022-03-01 16:20:32 -05:00
|
|
|
network->post("/_matrix/client/r0/rooms/" + id + "/join", [this, id](QNetworkReply* reply) {
|
2021-07-21 16:08:15 -04:00
|
|
|
if(!reply->error()) {
|
|
|
|
//check if its by an invite
|
|
|
|
if(invitedRooms.contains(id)) {
|
|
|
|
invitedRooms.removeOne(id);
|
|
|
|
joinedRooms.push_back(id);
|
|
|
|
|
|
|
|
for(const auto roomObject : rooms) {
|
|
|
|
if(roomObject->getId() == id) {
|
|
|
|
roomObject->setJoinState("Joined");
|
|
|
|
roomObject->setGuestDenied(false);
|
|
|
|
|
|
|
|
emit roomListChanged();
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void MatrixCore::leaveRoom(const QString& id) {
|
2022-03-01 16:20:32 -05:00
|
|
|
network->post("/_matrix/client/r0/rooms/" + id + "/leave");
|
2021-07-21 16:08:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void MatrixCore::inviteToRoom(Room* room, const QString& userId) {
|
|
|
|
const QJsonObject inviteObject {
|
|
|
|
{"user_id", userId}
|
|
|
|
};
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
network->postJSON("/_matrix/client/r0/rooms/" + room->getId() + "/invite", inviteObject, [](QNetworkReply*) {});
|
2021-07-21 16:08:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void MatrixCore::updateMembers(Room* room) {
|
|
|
|
if(!room)
|
|
|
|
return;
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
network->get("/_matrix/client/r0/rooms/" + room->getId() + "/members", [this, room](QNetworkReply* reply) {
|
2021-07-21 16:08:15 -04:00
|
|
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
|
|
|
|
|
|
|
const QJsonArray& chunk = document.object()["chunk"].toArray();
|
|
|
|
|
|
|
|
size_t realSize = 0;
|
|
|
|
for(const auto& member : chunk) {
|
|
|
|
if(member.toObject()["content"].toObject()["membership"].toString() == "join")
|
|
|
|
realSize++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(room->members.size() != realSize) {
|
|
|
|
room->members.clear();
|
|
|
|
room->members.reserve(realSize);
|
|
|
|
|
|
|
|
for(const auto& member : chunk) {
|
|
|
|
const QJsonObject& memberJson = member.toObject();
|
|
|
|
|
|
|
|
if(memberJson["content"].toObject()["membership"].toString() == "join") {
|
|
|
|
const QString& id = memberJson["state_key"].toString();
|
|
|
|
|
|
|
|
Member* m = nullptr;
|
|
|
|
if(!idToMember.contains(id)) {
|
|
|
|
m = new Member(this);
|
|
|
|
m->setId(id);
|
|
|
|
m->setDisplayName(memberJson["content"].toObject()["displayname"].toString());
|
|
|
|
|
|
|
|
if(!memberJson["content"].toObject()["avatar_url"].isNull()) {
|
|
|
|
const QString imageId = memberJson["content"].toObject()["avatar_url"].toString().remove("mxc://");
|
2022-03-01 16:20:32 -05:00
|
|
|
m->setAvatar(network->homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
2021-07-21 16:08:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
idToMember.insert(id, m);
|
|
|
|
} else {
|
|
|
|
m = idToMember[id];
|
|
|
|
}
|
|
|
|
|
|
|
|
if(currentRoom == room) {
|
|
|
|
eventModel.updateEventsByMember(id);
|
|
|
|
|
|
|
|
memberModel.beginUpdate(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
room->members.push_back(m);
|
|
|
|
|
|
|
|
if(currentRoom == room)
|
|
|
|
memberModel.endUpdate();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void MatrixCore::readMessageHistory(Room* room) {
|
|
|
|
if(!room || room->prevBatch.isEmpty())
|
|
|
|
return;
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
network->get("/_matrix/client/r0/rooms/" + room->getId() + "/messages?from=" + room->prevBatch + "&dir=b", [this, room](QNetworkReply* reply) {
|
2021-07-21 16:08:15 -04:00
|
|
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
|
|
|
|
|
|
|
room->prevBatch = document.object()["end"].toString();
|
|
|
|
|
|
|
|
traversingHistory = true;
|
|
|
|
|
|
|
|
for(const auto event : document.object()["chunk"].toArray())
|
|
|
|
consumeEvent(event.toObject(), *room, false);
|
|
|
|
|
|
|
|
traversingHistory = false;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void MatrixCore::updateMemberCommunities(Member* member) {
|
|
|
|
if(!member)
|
|
|
|
return;
|
|
|
|
|
|
|
|
const QJsonArray userIdsArray {
|
|
|
|
{member->getId()}
|
|
|
|
};
|
|
|
|
|
|
|
|
const QJsonObject userIdsObject {
|
|
|
|
{"user_ids", userIdsArray}
|
|
|
|
};
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
network->postJSON("/_matrix/client/r0/publicised_groups", userIdsObject, [this, member](QNetworkReply* reply) {
|
2021-07-21 16:08:15 -04:00
|
|
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
|
|
|
|
|
|
|
for(const auto id : document.object()["users"].toObject()[member->getId()].toArray()) {
|
|
|
|
bool found = false;
|
|
|
|
for(const auto community : member->getPublicCommunities()) {
|
|
|
|
if(community->getId() == id.toString())
|
|
|
|
found = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!found) {
|
|
|
|
Community* community = nullptr;
|
|
|
|
if(!idToCommunity.count(id.toString()))
|
|
|
|
community = createCommunity(id.toString());
|
|
|
|
else
|
|
|
|
community = idToCommunity[id.toString()];
|
|
|
|
|
|
|
|
member->addCommunity(community);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MatrixCore::settingsValid() {
|
2022-03-01 16:20:32 -05:00
|
|
|
return !network->accessToken.isEmpty() && !network->homeserverURL.isEmpty();
|
2021-07-21 16:08:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void MatrixCore::setHomeserver(const QString& url) {
|
2022-03-01 16:20:32 -05:00
|
|
|
network->homeserverURL = "https://" + url;
|
2021-07-21 16:08:15 -04:00
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
network->get("/_matrix/client/versions", [this, url](QNetworkReply* reply) {
|
2021-07-21 16:08:15 -04:00
|
|
|
if(!reply->error()) {
|
|
|
|
homeserverURL = url;
|
|
|
|
|
|
|
|
QSettings settings;
|
2022-03-01 16:20:32 -05:00
|
|
|
settings.beginGroup(profileName);
|
2021-07-21 16:08:15 -04:00
|
|
|
settings.setValue("homeserver", url);
|
2022-03-01 16:20:32 -05:00
|
|
|
settings.endGroup();
|
2021-07-21 16:08:15 -04:00
|
|
|
}
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
network->homeserverURL = "https://" + homeserverURL;
|
2021-07-21 16:08:15 -04:00
|
|
|
|
|
|
|
emit homeserverChanged(reply->error() == 0, reply->errorString());
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void MatrixCore::changeCurrentRoom(Room* room) {
|
|
|
|
currentRoom = room;
|
|
|
|
|
|
|
|
eventModel.setRoom(room);
|
|
|
|
eventModel.fullUpdate();
|
|
|
|
|
|
|
|
memberModel.setRoom(room);
|
|
|
|
memberModel.fullUpdate();
|
|
|
|
|
|
|
|
emit currentRoomChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void MatrixCore::changeCurrentRoom(const unsigned int index) {
|
|
|
|
if(index < rooms.size())
|
|
|
|
changeCurrentRoom(rooms[index]);
|
|
|
|
else
|
|
|
|
changeCurrentRoom(&emptyRoom);
|
|
|
|
}
|
|
|
|
|
|
|
|
void MatrixCore::addEmote(const QString& url) {
|
|
|
|
qDebug() << "adding emote " << url;
|
|
|
|
|
|
|
|
QString newUrl = url;
|
|
|
|
newUrl.remove("file://");
|
|
|
|
|
|
|
|
QUrl file(newUrl);
|
|
|
|
|
|
|
|
QString appDir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
|
|
|
|
if(!QDir(appDir).exists())
|
|
|
|
QDir().mkdir(appDir);
|
|
|
|
|
|
|
|
QString emotesDir = appDir + "/emotes";
|
|
|
|
if(!QDir(emotesDir).exists())
|
|
|
|
QDir().mkdir(emotesDir);
|
|
|
|
|
|
|
|
QPixmap pixmap(newUrl);
|
|
|
|
pixmap = pixmap.scaled(22, 22, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
|
|
|
pixmap.save(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + "/emotes/" + file.fileName());
|
|
|
|
|
|
|
|
Emote* emote = new Emote();
|
|
|
|
emote->path = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + "/emotes/" + file.fileName();
|
|
|
|
emote->name = file.fileName().remove(".png");
|
|
|
|
|
|
|
|
emotes.push_back(emote);
|
|
|
|
|
|
|
|
emit localEmotesChanged();
|
|
|
|
localEmoteModel.update();
|
|
|
|
}
|
|
|
|
|
|
|
|
void MatrixCore::deleteEmote(Emote* emote) {
|
|
|
|
emotes.removeOne(emote);
|
|
|
|
|
|
|
|
QFile(emote->path).remove();
|
|
|
|
|
|
|
|
emit localEmotesChanged();
|
|
|
|
localEmoteModel.update();
|
|
|
|
}
|
|
|
|
|
|
|
|
Member* MatrixCore::resolveMemberId(const QString& id) const {
|
|
|
|
return idToMember.value(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
Community* MatrixCore::resolveCommunityId(const QString &id) const {
|
|
|
|
return idToCommunity.value(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
Room* MatrixCore::resolveRoomId(const QString &id) const {
|
|
|
|
return idToRoom.value(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
Room* MatrixCore::getRoom(const unsigned int index) const {
|
|
|
|
return rooms[index];
|
|
|
|
}
|
|
|
|
|
|
|
|
QString MatrixCore::getUsername() const {
|
|
|
|
QString id = userId;
|
|
|
|
return id.remove('@').split(':')[0];
|
|
|
|
}
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
void MatrixCore::loadDirectory(const QString& homeserver) {
|
2021-07-21 16:08:15 -04:00
|
|
|
const QJsonObject bodyObject;
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
network->postJSON("/_matrix/client/r0/publicRooms?server=" + homeserver, bodyObject, [this](QNetworkReply* reply) {
|
2021-07-21 16:08:15 -04:00
|
|
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
|
|
|
|
|
|
|
if(publicRooms.size() != document.object()["chunk"].toArray().size()) {
|
|
|
|
publicRooms.clear();
|
|
|
|
publicRooms.reserve(document.object()["chunk"].toArray().size());
|
|
|
|
|
|
|
|
for(const auto room : document.object()["chunk"].toArray()) {
|
|
|
|
const QJsonObject& roomObject = room.toObject();
|
|
|
|
const QString& roomId = roomObject["room_id"].toString();
|
|
|
|
|
|
|
|
Room* r = nullptr;
|
|
|
|
if(!idToRoom.contains(roomId)) {
|
|
|
|
r = new Room(this);
|
|
|
|
r->setId(roomId);
|
|
|
|
r->setName(roomObject["name"].toString());
|
|
|
|
|
|
|
|
if(!roomObject["avatar_url"].isNull()) {
|
|
|
|
const QString imageId = roomObject["avatar_url"].toString().remove("mxc://");
|
2022-03-01 16:20:32 -05:00
|
|
|
r->setAvatar(network->homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
2021-07-21 16:08:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
r->setTopic(roomObject["topic"].toString());
|
|
|
|
|
|
|
|
idToRoom.insert(roomId, r);
|
|
|
|
} else {
|
|
|
|
r = idToRoom.value(roomId);
|
|
|
|
}
|
|
|
|
|
|
|
|
directoryListModel.beginInsertRoom();
|
|
|
|
publicRooms.push_back(r);
|
|
|
|
directoryListModel.endInsertRoom();
|
|
|
|
|
|
|
|
emit publicRoomsChanged();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void MatrixCore::readUpTo(Room* room, const int index) {
|
|
|
|
if(!room)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if(room->events.size() == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if(index < 0)
|
|
|
|
return;
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
network->post("/_matrix/client/r0/rooms/" + room->getId() + "/receipt/m.read/" + room->events[index]->eventId);
|
2021-07-21 16:08:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void MatrixCore::setMarkdownEnabled(const bool enabled) {
|
|
|
|
markdownEnabled = enabled;
|
|
|
|
emit markdownEnabledChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
Room* MatrixCore::getCurrentRoom() {
|
|
|
|
return currentRoom != nullptr ? currentRoom : &emptyRoom;
|
|
|
|
}
|
|
|
|
|
|
|
|
EventModel* MatrixCore::getEventModel() {
|
|
|
|
return &eventModel;
|
|
|
|
}
|
|
|
|
|
|
|
|
RoomListSortModel* MatrixCore::getRoomListModel() {
|
|
|
|
return &roomListSortModel;
|
|
|
|
}
|
|
|
|
|
|
|
|
RoomListSortModel* MatrixCore::getDirectoryListModel() {
|
|
|
|
return &directoryListSortModel;
|
|
|
|
}
|
|
|
|
|
|
|
|
EmoteListModel* MatrixCore::getLocalEmoteListModel() {
|
|
|
|
return &localEmoteModel;
|
|
|
|
}
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
MemberListSortModel* MatrixCore::getMemberModel() {
|
|
|
|
return &memberSortModel;
|
2021-07-21 16:08:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
QString MatrixCore::getHomeserverURL() const {
|
|
|
|
return homeserverURL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MatrixCore::consumeEvent(const QJsonObject& event, Room& room, const bool insertFront) {
|
|
|
|
const QString eventType = event["type"].toString();
|
|
|
|
|
|
|
|
const auto addEvent = [&room, insertFront, this](Event* object) {
|
|
|
|
if(insertFront) {
|
|
|
|
if(&room == currentRoom)
|
|
|
|
eventModel.beginUpdate(0);
|
|
|
|
|
|
|
|
room.events.push_front(object);
|
|
|
|
|
|
|
|
if(&room == currentRoom)
|
|
|
|
eventModel.endHistory();
|
|
|
|
} else {
|
|
|
|
if(&room == currentRoom)
|
|
|
|
eventModel.beginHistory(0);
|
|
|
|
|
|
|
|
room.events.push_back(object);
|
|
|
|
|
|
|
|
if(&room == currentRoom)
|
|
|
|
eventModel.endHistory();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
bool found = false;
|
2022-03-01 16:20:32 -05:00
|
|
|
if(eventType == "m.room.message" || eventType == "m.room.encrypted") {
|
2021-07-21 16:08:15 -04:00
|
|
|
for(size_t i = 0; i < unsentMessages.size(); i++) {
|
|
|
|
if(event["sender"].toString() == userId && unsentMessages[i]->getRoom() == room.getId()) {
|
|
|
|
found = true;
|
|
|
|
unsentMessages[i]->setSent(true);
|
|
|
|
|
|
|
|
if(currentRoom == &room)
|
|
|
|
eventModel.updateEvent(unsentMessages[i]);
|
|
|
|
|
|
|
|
unsentMessages.removeAt(i);
|
2022-03-01 16:20:32 -05:00
|
|
|
|
|
|
|
return;
|
2021-07-21 16:08:15 -04:00
|
|
|
}
|
|
|
|
}
|
2022-03-01 16:20:32 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if(eventType == "m.room.member") {
|
2021-07-21 16:08:15 -04:00
|
|
|
// avoid events tied to us
|
|
|
|
if(event["state_key"].toString() == userId)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if(event["content"].toObject().contains("is_direct"))
|
|
|
|
room.setDirect(event["content"].toObject()["is_direct"].toBool());
|
|
|
|
|
|
|
|
if(room.getDirect()) {
|
|
|
|
room.setName(event["content"].toObject()["displayname"].toString());
|
|
|
|
|
|
|
|
if(!event["content"].toObject()["avatar_url"].isNull()) {
|
|
|
|
const QString imageId = event["content"].toObject()["avatar_url"].toString().remove("mxc://");
|
2022-03-01 16:20:32 -05:00
|
|
|
room.setAvatar(network->homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
2021-07-21 16:08:15 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
roomListModel.updateRoom(&room);
|
2022-03-01 16:20:32 -05:00
|
|
|
} else if(eventType == "m.room.encrypted") {
|
|
|
|
Event* e = new Event(&room);
|
|
|
|
|
|
|
|
if(event["content"].toObject()["device_id"] != deviceId) {
|
|
|
|
EncryptionInformation* info = new EncryptionInformation();
|
|
|
|
e->encryptionInfo = info;
|
|
|
|
info->cipherText = event["content"].toObject()["ciphertext"].toString();
|
|
|
|
info->sessionId = event["content"].toObject()["session_id"].toString();
|
|
|
|
|
|
|
|
if(!inboundSessions.contains(event["content"].toObject()["session_id"].toString())) {
|
|
|
|
//qDebug() << "new encrypted event " << event;
|
|
|
|
e->setMsg("** ERR: we can't decrypt this yet, we do not have the key.");
|
|
|
|
|
|
|
|
qDebug() << "the failed id: " << event["content"].toObject()["session_id"].toString();
|
|
|
|
qDebug() << inboundSessions;
|
|
|
|
|
|
|
|
// let's send a room key request event
|
|
|
|
// construct m.room.key event
|
|
|
|
const QJsonObject roomKeyObject {
|
|
|
|
{"action", "request"},
|
|
|
|
{"body", QJsonObject {
|
|
|
|
{"algorithm", event["content"].toObject()["algorithm"]},
|
|
|
|
{"room_id", room.getId()},
|
|
|
|
{"sender_key", event["content"].toObject()["sender_key"]},
|
|
|
|
{"session_id", event["content"].toObject()["session_id"]}
|
|
|
|
}},
|
|
|
|
{"requesting_device_id", deviceId},
|
|
|
|
{"request_id", QString("lololol") + QString::number(QRandomGenerator::global()->generate())}
|
|
|
|
};
|
|
|
|
|
|
|
|
const QJsonObject sendToDeviceObject {
|
|
|
|
{"messages", QJsonObject {
|
|
|
|
{ event["sender"].toString(), QJsonObject {
|
|
|
|
{ event["content"].toObject()["device_id"].toString(), roomKeyObject}
|
|
|
|
}}
|
|
|
|
}}
|
|
|
|
};
|
|
|
|
|
|
|
|
//qDebug() << QJsonDocument(sendToDeviceObject).toJson(QJsonDocument::Compact);
|
|
|
|
|
|
|
|
network->putJSON(QString("/_matrix/client/r0/sendToDevice/m.room_key_request/") + QString::number(QRandomGenerator::global()->generate()), sendToDeviceObject, [](QNetworkReply* reply) {
|
|
|
|
//qDebug() << "REPLY FROM KEY REQUEST SEND: " << reply->readAll();
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
auto session = inboundSessions[event["content"].toObject()["session_id"].toString()];
|
|
|
|
auto msg = encryption->decrypt(session, event["content"].toObject()["ciphertext"].toString().toStdString());
|
|
|
|
|
|
|
|
const QJsonDocument document = QJsonDocument::fromJson(QByteArray(reinterpret_cast<const char*>(msg.data()), msg.size()));
|
|
|
|
|
|
|
|
populateEvent(document.object(), e);
|
|
|
|
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
e->setMsg("** ERR: messages sent from the same device are not supported yet.");
|
|
|
|
}
|
|
|
|
|
|
|
|
e->setSender("placeholder");
|
|
|
|
e->eventId = event["event_id"].toString();
|
|
|
|
e->setMsgType("text");
|
|
|
|
|
|
|
|
addEvent(e);
|
|
|
|
|
|
|
|
if(!firstSync && !traversingHistory)
|
|
|
|
emit message(&room, e->getSender(), e->getMsg());
|
2021-07-21 16:08:15 -04:00
|
|
|
} else
|
|
|
|
return;
|
|
|
|
|
|
|
|
// don't show redacted messages
|
|
|
|
if(event["unsigned"].toObject().keys().contains("redacted_because"))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if(!found && eventType == "m.room.message") {
|
|
|
|
Event* e = new Event(&room);
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
populateEvent(event, e);
|
2021-07-21 16:08:15 -04:00
|
|
|
|
|
|
|
addEvent(e);
|
|
|
|
|
|
|
|
if(!firstSync && !traversingHistory)
|
|
|
|
emit message(&room, e->getSender(), e->getMsg());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
void MatrixCore::populateEvent(const QJsonObject& event, Event* e) {
|
|
|
|
const QString msgType = event["content"].toObject()["msgtype"].toString();
|
|
|
|
|
|
|
|
e->timestamp = QDateTime::currentDateTime().addMSecs(-event["unsigned"].toObject()["age"].toInt());
|
|
|
|
e->setSender(event["sender"].toString());
|
|
|
|
e->eventId = event["event_id"].toString();
|
|
|
|
|
|
|
|
if(msgType == "m.text" && !event["content"].toObject().contains("formatted_body")) {
|
|
|
|
e->setMsgType("text");
|
|
|
|
e->setMsg(event["content"].toObject()["body"].toString());
|
|
|
|
} else if(msgType == "m.text" && event["content"].toObject().contains("formatted_body")) {
|
|
|
|
e->setMsgType("text");
|
|
|
|
e->setMsg(event["content"].toObject()["formatted_body"].toString());
|
|
|
|
} else if(msgType == "m.image") {
|
|
|
|
e->setMsgType("image");
|
|
|
|
e->setAttachment(getMXCMediaURL(event["content"].toObject()["url"].toString()));
|
|
|
|
e->setAttachmentSize(event["content"].toObject()["info"].toObject()["size"].toInt());
|
|
|
|
|
|
|
|
if(event["content"].toObject()["info"].toObject().contains("thumbnail_url"))
|
|
|
|
e->setThumbnail(getMXCThumbnailURL(event["content"].toObject()["info"].toObject()["thumbnail_url"].toString()));
|
|
|
|
else
|
|
|
|
e->setThumbnail(getMXCMediaURL(event["content"].toObject()["url"].toString()));
|
|
|
|
|
|
|
|
e->setMsg(event["content"].toObject()["body"].toString());
|
|
|
|
} else if(msgType == "m.file") {
|
|
|
|
e->setMsgType("file");
|
|
|
|
e->setAttachment(getMXCMediaURL(event["content"].toObject()["url"].toString()));
|
|
|
|
e->setAttachmentSize(event["content"].toObject()["info"].toObject()["size"].toInt());
|
|
|
|
e->setMsg(event["content"].toObject()["body"].toString());
|
|
|
|
} else
|
|
|
|
e->setMsg(event["content"].toObject()["body"].toString());
|
|
|
|
|
|
|
|
QString msg = e->getMsg();
|
|
|
|
for(const auto& emote : emotes) {
|
|
|
|
msg.replace(":" + emote->name + ":", "<img src='file://" + emote->path + "' width=22 height=22/>");
|
|
|
|
}
|
|
|
|
|
|
|
|
e->setMsg(msg);
|
|
|
|
}
|
|
|
|
|
2021-07-21 16:08:15 -04:00
|
|
|
Community* MatrixCore::createCommunity(const QString& id) {
|
|
|
|
Community* community = new Community(this);
|
|
|
|
community->setId(id);
|
|
|
|
|
2022-03-01 16:20:32 -05:00
|
|
|
network->get("/_matrix/client/r0/groups/" + community->getId() + "/summary", [this, community](QNetworkReply* reply) {
|
2021-07-21 16:08:15 -04:00
|
|
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
|
|
|
|
|
|
|
const QJsonObject& profile = document.object()["profile"].toObject();
|
|
|
|
|
|
|
|
community->setName(profile["name"].toString());
|
|
|
|
|
|
|
|
if(!profile["avatar_url"].isNull()) {
|
|
|
|
const QString imageId = profile["avatar_url"].toString().remove("mxc://");
|
2022-03-01 16:20:32 -05:00
|
|
|
community->setAvatar(network->homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
2021-07-21 16:08:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
community->setShortDescription(profile["short_description"].toString());
|
|
|
|
community->setLongDescription(profile["long_description"].toString());
|
|
|
|
|
|
|
|
idToCommunity.insert(community->getId(), community);
|
|
|
|
});
|
|
|
|
|
|
|
|
return community;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString MatrixCore::getMXCThumbnailURL(QString url) {
|
|
|
|
const QString imageId = url.remove("mxc://");
|
2022-03-01 16:20:32 -05:00
|
|
|
return network->homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale";
|
2021-07-21 16:08:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
QString MatrixCore::getMXCMediaURL(QString url) {
|
|
|
|
const QString imageId = url.remove("mxc://");
|
2022-03-01 16:20:32 -05:00
|
|
|
return network->homeserverURL + "/_matrix/media/v1/download/" + imageId;
|
2021-07-21 16:08:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
QString MatrixCore::getDisplayName() const {
|
|
|
|
return displayName;
|
|
|
|
}
|
|
|
|
|
|
|
|
QVariantList MatrixCore::getJoinedCommunitiesList() const {
|
|
|
|
QVariantList list;
|
|
|
|
for(const auto community : joinedCommunities)
|
|
|
|
list.push_back(QVariant::fromValue(community));
|
|
|
|
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString MatrixCore::getTypingText() const {
|
|
|
|
return typingText;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MatrixCore::getMarkdownEnabled() const {
|
|
|
|
return markdownEnabled;
|
|
|
|
}
|
2022-03-01 16:20:32 -05:00
|
|
|
|
|
|
|
void MatrixCore::sendKeyToDevice(QString roomId, QString senderCurveIdentity, QString senderEdIdentity, QString session_id, QString session_key, QString user_id, QString device_id)
|
|
|
|
{
|
|
|
|
// why we would we send ourselves a key?
|
|
|
|
if(device_id == deviceId)
|
|
|
|
return;
|
|
|
|
|
|
|
|
QJsonObject claimObject {
|
|
|
|
{"timeout", 10000},
|
|
|
|
{"one_time_keys", QJsonObject {
|
|
|
|
{user_id, QJsonObject {
|
|
|
|
{device_id, "signed_curve25519"}
|
|
|
|
}}
|
|
|
|
}}
|
|
|
|
};
|
|
|
|
|
|
|
|
network->postJSON("/_matrix/client/r0/keys/claim", claimObject, [this, device_id, user_id, senderCurveIdentity, senderEdIdentity, roomId, session_id, session_key](QNetworkReply* reply) {
|
|
|
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
|
|
|
|
|
|
|
std::string identityKey = senderCurveIdentity.toStdString();
|
|
|
|
|
|
|
|
// we couldnt claim a key, skip
|
|
|
|
if(document.object()["one_time_keys"].toObject()[user_id].toObject()[device_id].toObject().keys().empty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
std::string oneTimeKey = document.object()["one_time_keys"].toObject()[user_id].toObject()[device_id].toObject()[document.object()["one_time_keys"].toObject()[user_id].toObject()[device_id].toObject().keys()[0]].toObject()["key"].toString().toStdString();
|
|
|
|
|
|
|
|
auto session = encryption->beginOutboundOlmSession(identityKey, oneTimeKey);
|
|
|
|
|
|
|
|
// construct m.room.key event
|
|
|
|
const QJsonObject roomKeyObject {
|
|
|
|
{"content", QJsonObject {
|
|
|
|
{"algorithm", "m.megolm.v1.aes-sha2"},
|
|
|
|
{"room_id", roomId},
|
|
|
|
{"session_id", session_id},
|
|
|
|
{"session_key", session_key}
|
|
|
|
}},
|
|
|
|
{"type", "m.room_key"},
|
|
|
|
{"keys", QJsonObject {
|
|
|
|
{"ed25519", encryption->identityKey["ed25519"]}
|
|
|
|
}},
|
|
|
|
{"sender", userId},
|
|
|
|
{"sender_device", deviceId},
|
|
|
|
{"recipient", user_id},
|
|
|
|
{"recipient_keys", QJsonObject {
|
|
|
|
{"ed25519", senderEdIdentity}
|
|
|
|
}}
|
|
|
|
};
|
|
|
|
|
|
|
|
// construct the m.room.encrypted event
|
|
|
|
const QJsonObject roomEncryptedObject {
|
|
|
|
{"algorithm", "m.olm.v1.curve25519-aes-sha2"},
|
|
|
|
{"ciphertext", QJsonObject {
|
|
|
|
{identityKey.c_str(), QJsonObject {
|
|
|
|
{"body", encryption->encrypt(session, QString(QJsonDocument(roomKeyObject).toJson(QJsonDocument::Compact)).toStdString()).c_str()},
|
|
|
|
{"type", (int)olm_encrypt_message_type(session)}
|
|
|
|
}}}},
|
|
|
|
{"sender_key", encryption->identityKey["curve25519"]},
|
|
|
|
};
|
|
|
|
|
|
|
|
const QJsonObject sendToDeviceObject {
|
|
|
|
{"messages", QJsonObject {
|
|
|
|
{ user_id, QJsonObject {
|
|
|
|
{ device_id, roomEncryptedObject}
|
|
|
|
}}
|
|
|
|
}}
|
|
|
|
};
|
|
|
|
|
|
|
|
network->putJSON(QString("/_matrix/client/r0/sendToDevice/m.room.encrypted/") + QString::number(QRandomGenerator::global()->generate()), sendToDeviceObject, [](QNetworkReply* reply) {
|
|
|
|
//qDebug() << "REPLY FROM KEY SEND: " << reply->readAll();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void MatrixCore::createOrLoadSession() {
|
|
|
|
if(currentSession != nullptr)
|
|
|
|
return;
|
|
|
|
|
|
|
|
QSettings settings;
|
|
|
|
settings.beginGroup(profileName);
|
|
|
|
|
|
|
|
if(settings.contains("sessionPickle")) {
|
|
|
|
currentSession = encryption->loadSession(settings.value("sessionPickle").toString());
|
|
|
|
currentSessionId = encryption->getGroupSessionId(currentSession).c_str();
|
|
|
|
currentSessionKey = encryption->getGroupSessionKey(currentSession).c_str();
|
|
|
|
} else {
|
|
|
|
currentSession = encryption->beginOutboundSession();
|
|
|
|
|
|
|
|
auto session_id = encryption->getGroupSessionId(currentSession);
|
|
|
|
auto session_key = encryption->getGroupSessionKey(currentSession);
|
|
|
|
|
|
|
|
//qDebug () << "CREATING NEW SESSION WITH ID " << session_id.c_str();
|
|
|
|
|
|
|
|
currentSessionId = session_id.c_str();
|
|
|
|
currentSessionKey = session_key.c_str();
|
|
|
|
}
|
|
|
|
|
|
|
|
settings.endGroup();
|
|
|
|
}
|