Add basic encryption support
Sorry this is a huge commit, this actually includes a ton of stuff. Text color is now readable, multiple accounts are supported alongside end-to-end encryption but no cross-signing yet :-) There's also a whole lot of other small changes, such as choosing the server you want to request a room directory from.
This commit is contained in:
parent
303001a357
commit
a825c8886d
26 changed files with 1869 additions and 570 deletions
12
CMakeLists.txt
Normal file → Executable file
12
CMakeLists.txt
Normal file → Executable file
|
@ -1,11 +1,10 @@
|
||||||
cmake_minimum_required(VERSION 2.8.12)
|
cmake_minimum_required(VERSION 2.8.12)
|
||||||
project(Trinity LANGUAGES CXX)
|
project(Trinity LANGUAGES CXX)
|
||||||
|
|
||||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
|
||||||
set(CMAKE_AUTOMOC ON)
|
set(CMAKE_AUTOMOC ON)
|
||||||
set(CMAKE_AUTORCC ON)
|
set(CMAKE_AUTORCC ON)
|
||||||
|
|
||||||
find_package(Qt5 COMPONENTS Core Quick Widgets REQUIRED)
|
find_package(Qt5 COMPONENTS Core Quick Widgets WebEngine REQUIRED)
|
||||||
|
|
||||||
add_executable(${PROJECT_NAME}
|
add_executable(${PROJECT_NAME}
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
|
@ -29,9 +28,14 @@ add_executable(${PROJECT_NAME}
|
||||||
include/roomlistsortmodel.h
|
include/roomlistsortmodel.h
|
||||||
include/emotelistmodel.h
|
include/emotelistmodel.h
|
||||||
src/emotelistmodel.cpp
|
src/emotelistmodel.cpp
|
||||||
include/emote.h)
|
include/emote.h
|
||||||
|
include/appcore.h
|
||||||
|
include/encryption.h
|
||||||
|
src/encryption.cpp)
|
||||||
|
|
||||||
target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Quick Qt5::Widgets cmark)
|
find_package(Olm REQUIRED)
|
||||||
|
|
||||||
|
target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Quick Qt5::Widgets Qt5::WebEngine Olm::Olm cmark)
|
||||||
target_include_directories(${PROJECT_NAME} PRIVATE include)
|
target_include_directories(${PROJECT_NAME} PRIVATE include)
|
||||||
|
|
||||||
install(TARGETS ${PROJECT_NAME} DESTINATION bin)
|
install(TARGETS ${PROJECT_NAME} DESTINATION bin)
|
||||||
|
|
25
include/appcore.h
Executable file
25
include/appcore.h
Executable file
|
@ -0,0 +1,25 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <vector>
|
||||||
|
#include <QQmlContext>
|
||||||
|
|
||||||
|
class MatrixCore;
|
||||||
|
|
||||||
|
class AppCore : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(QVariantList accounts READ getAccounts NOTIFY accountChange)
|
||||||
|
public:
|
||||||
|
Q_INVOKABLE void addAccount(QString profileName = "");
|
||||||
|
|
||||||
|
Q_INVOKABLE void switchAccount(QString profileName);
|
||||||
|
|
||||||
|
Q_INVOKABLE QVariantList getAccounts();
|
||||||
|
|
||||||
|
QList<MatrixCore*> accounts;
|
||||||
|
|
||||||
|
QQmlContext* context = nullptr;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void accountChange();
|
||||||
|
};
|
8
include/desktop.h
Normal file → Executable file
8
include/desktop.h
Normal file → Executable file
|
@ -11,14 +11,18 @@ public:
|
||||||
QApplication::setQuitOnLastWindowClosed(shouldHide);
|
QApplication::setQuitOnLastWindowClosed(shouldHide);
|
||||||
|
|
||||||
if(shouldHide)
|
if(shouldHide)
|
||||||
icon->hide();
|
|
||||||
else
|
|
||||||
icon->show();
|
icon->show();
|
||||||
|
else
|
||||||
|
icon->hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_INVOKABLE void showMessage(const QString title, const QString content) {
|
Q_INVOKABLE void showMessage(const QString title, const QString content) {
|
||||||
icon->showMessage(title, content);
|
icon->showMessage(title, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Q_INVOKABLE bool isTrayIconEnabled() {
|
||||||
|
return icon->isVisible();;
|
||||||
|
}
|
||||||
|
|
||||||
QSystemTrayIcon* icon;
|
QSystemTrayIcon* icon;
|
||||||
};
|
};
|
||||||
|
|
43
include/encryption.h
Executable file
43
include/encryption.h
Executable file
|
@ -0,0 +1,43 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
#include <olm/olm.h>
|
||||||
|
|
||||||
|
class Encryption {
|
||||||
|
public:
|
||||||
|
void createNewDeviceKeys();
|
||||||
|
QJsonObject generateOneTimeKeys(int number);
|
||||||
|
int getRecommendedNumberOfOneTimeKeys();
|
||||||
|
|
||||||
|
QString saveDeviceKeys();
|
||||||
|
void loadDeviceKeys(QString bytes);
|
||||||
|
|
||||||
|
OlmOutboundGroupSession* beginOutboundSession();
|
||||||
|
std::string getGroupSessionId(OlmOutboundGroupSession* session);
|
||||||
|
std::string getGroupSessionKey(OlmOutboundGroupSession* session);
|
||||||
|
OlmSession* beginOutboundOlmSession(std::string identityKey, std::string oneTimeKey);
|
||||||
|
std::string getSessionId(OlmSession* session);
|
||||||
|
std::string encrypt(OlmSession* session, std::string message);
|
||||||
|
std::string encryptGroup(OlmOutboundGroupSession* session, std::string message);
|
||||||
|
|
||||||
|
QString saveSession(OlmOutboundGroupSession* session);
|
||||||
|
OlmOutboundGroupSession* loadSession(QString bytes);
|
||||||
|
|
||||||
|
// inbound messaging
|
||||||
|
OlmSession* createInboundSession(std::string senderKey, std::string body);
|
||||||
|
std::vector<std::uint8_t> decrypt(OlmSession* session, int msgType, std::string cipherText);
|
||||||
|
|
||||||
|
OlmInboundGroupSession* beginInboundSession(std::string sessionKey);
|
||||||
|
std::vector<std::uint8_t> decrypt(OlmInboundGroupSession* session, std::string cipherText);
|
||||||
|
|
||||||
|
QString signMessage(QString message);
|
||||||
|
|
||||||
|
QJsonObject identityKey;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void initAccount();
|
||||||
|
|
||||||
|
OlmAccount* account = nullptr;
|
||||||
|
};
|
31
include/matrixcore.h
Normal file → Executable file
31
include/matrixcore.h
Normal file → Executable file
|
@ -12,16 +12,21 @@
|
||||||
#include "roomlistsortmodel.h"
|
#include "roomlistsortmodel.h"
|
||||||
#include "emote.h"
|
#include "emote.h"
|
||||||
#include "emotelistmodel.h"
|
#include "emotelistmodel.h"
|
||||||
|
#include "encryption.h"
|
||||||
|
|
||||||
|
class Network;
|
||||||
|
|
||||||
class MatrixCore : public QObject
|
class MatrixCore : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(QString profileName MEMBER profileName CONSTANT)
|
||||||
|
Q_PROPERTY(bool initialSyncComplete READ isInitialSyncComplete NOTIFY initialSyncFinished)
|
||||||
Q_PROPERTY(EventModel* eventModel READ getEventModel NOTIFY currentRoomChanged)
|
Q_PROPERTY(EventModel* eventModel READ getEventModel NOTIFY currentRoomChanged)
|
||||||
Q_PROPERTY(RoomListSortModel* roomListModel READ getRoomListModel NOTIFY roomListChanged)
|
Q_PROPERTY(RoomListSortModel* roomListModel READ getRoomListModel NOTIFY roomListChanged)
|
||||||
Q_PROPERTY(Room* currentRoom READ getCurrentRoom NOTIFY currentRoomChanged)
|
Q_PROPERTY(Room* currentRoom READ getCurrentRoom NOTIFY currentRoomChanged)
|
||||||
Q_PROPERTY(QList<Room*> rooms MEMBER rooms NOTIFY roomListChanged)
|
Q_PROPERTY(QList<Room*> rooms MEMBER rooms NOTIFY roomListChanged)
|
||||||
Q_PROPERTY(QString homeserverURL READ getHomeserverURL NOTIFY homeserverChanged)
|
Q_PROPERTY(QString homeserverURL READ getHomeserverURL NOTIFY homeserverChanged)
|
||||||
Q_PROPERTY(MemberModel* memberModel READ getMemberModel NOTIFY currentRoomChanged)
|
Q_PROPERTY(MemberListSortModel* memberModel READ getMemberModel NOTIFY currentRoomChanged)
|
||||||
Q_PROPERTY(QString displayName READ getDisplayName NOTIFY displayNameChanged)
|
Q_PROPERTY(QString displayName READ getDisplayName NOTIFY displayNameChanged)
|
||||||
Q_PROPERTY(QVariantList joinedCommunities READ getJoinedCommunitiesList NOTIFY joinedCommunitiesChanged)
|
Q_PROPERTY(QVariantList joinedCommunities READ getJoinedCommunitiesList NOTIFY joinedCommunitiesChanged)
|
||||||
Q_PROPERTY(RoomListSortModel* publicRooms READ getDirectoryListModel NOTIFY publicRoomsChanged)
|
Q_PROPERTY(RoomListSortModel* publicRooms READ getDirectoryListModel NOTIFY publicRoomsChanged)
|
||||||
|
@ -29,7 +34,10 @@ class MatrixCore : public QObject
|
||||||
Q_PROPERTY(bool markdownEnabled READ getMarkdownEnabled WRITE setMarkdownEnabled NOTIFY markdownEnabledChanged)
|
Q_PROPERTY(bool markdownEnabled READ getMarkdownEnabled WRITE setMarkdownEnabled NOTIFY markdownEnabledChanged)
|
||||||
Q_PROPERTY(EmoteListModel* localEmoteModel READ getLocalEmoteListModel NOTIFY localEmotesChanged)
|
Q_PROPERTY(EmoteListModel* localEmoteModel READ getLocalEmoteListModel NOTIFY localEmotesChanged)
|
||||||
public:
|
public:
|
||||||
MatrixCore(QObject* parent = nullptr);
|
MatrixCore(QString profileName, QObject* parent = nullptr);
|
||||||
|
|
||||||
|
Network* network = nullptr;
|
||||||
|
Encryption* encryption = nullptr;
|
||||||
|
|
||||||
// account
|
// account
|
||||||
Q_INVOKABLE void registerAccount(const QString& username, const QString& password, const QString& session = "", const QString& type = "");
|
Q_INVOKABLE void registerAccount(const QString& username, const QString& password, const QString& session = "", const QString& type = "");
|
||||||
|
@ -83,18 +91,29 @@ public:
|
||||||
|
|
||||||
Q_INVOKABLE QString getUsername() const;
|
Q_INVOKABLE QString getUsername() const;
|
||||||
|
|
||||||
Q_INVOKABLE void loadDirectory();
|
Q_INVOKABLE void loadDirectory(const QString& homeserver);
|
||||||
|
|
||||||
Q_INVOKABLE void readUpTo(Room* room, const int index);
|
Q_INVOKABLE void readUpTo(Room* room, const int index);
|
||||||
|
|
||||||
|
Q_INVOKABLE bool isInitialSyncComplete();
|
||||||
|
|
||||||
void setMarkdownEnabled(const bool enabled);
|
void setMarkdownEnabled(const bool enabled);
|
||||||
|
|
||||||
|
void sendKeyToDevice(QString roomId, QString senderCurveIdentity, QString senderEdIdentity, QString session_id, QString session_key, QString user_id, QString device_id);
|
||||||
|
|
||||||
|
OlmOutboundGroupSession* currentSession = nullptr;
|
||||||
|
QString currentSessionId, currentSessionKey;
|
||||||
|
QString deviceId;
|
||||||
|
void createOrLoadSession();
|
||||||
|
|
||||||
|
QMap<QString, OlmInboundGroupSession*> inboundSessions;
|
||||||
|
|
||||||
Room* getCurrentRoom();
|
Room* getCurrentRoom();
|
||||||
|
|
||||||
EventModel* getEventModel();
|
EventModel* getEventModel();
|
||||||
RoomListSortModel* getRoomListModel();
|
RoomListSortModel* getRoomListModel();
|
||||||
RoomListSortModel* getDirectoryListModel();
|
RoomListSortModel* getDirectoryListModel();
|
||||||
MemberModel* getMemberModel();
|
MemberListSortModel* getMemberModel();
|
||||||
EmoteListModel* getLocalEmoteListModel();
|
EmoteListModel* getLocalEmoteListModel();
|
||||||
|
|
||||||
QString getHomeserverURL() const;
|
QString getHomeserverURL() const;
|
||||||
|
@ -109,10 +128,13 @@ public:
|
||||||
RoomListModel roomListModel, directoryListModel;
|
RoomListModel roomListModel, directoryListModel;
|
||||||
RoomListSortModel roomListSortModel, directoryListSortModel;
|
RoomListSortModel roomListSortModel, directoryListSortModel;
|
||||||
MemberModel memberModel;
|
MemberModel memberModel;
|
||||||
|
MemberListSortModel memberSortModel;
|
||||||
EmoteListModel localEmoteModel;
|
EmoteListModel localEmoteModel;
|
||||||
|
|
||||||
Room* currentRoom = nullptr;
|
Room* currentRoom = nullptr;
|
||||||
|
|
||||||
|
QString profileName;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void registerAttempt(bool error, QString description);
|
void registerAttempt(bool error, QString description);
|
||||||
void registerFlow(QJsonObject data);
|
void registerFlow(QJsonObject data);
|
||||||
|
@ -132,6 +154,7 @@ signals:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void consumeEvent(const QJsonObject& event, Room& room, const bool insertFront = true);
|
void consumeEvent(const QJsonObject& event, Room& room, const bool insertFront = true);
|
||||||
|
void populateEvent(const QJsonObject& event, Event* e);
|
||||||
Community* createCommunity(const QString& id);
|
Community* createCommunity(const QString& id);
|
||||||
|
|
||||||
QString getMXCThumbnailURL(QString url);
|
QString getMXCThumbnailURL(QString url);
|
||||||
|
|
3
include/membermodel.h
Normal file → Executable file
3
include/membermodel.h
Normal file → Executable file
|
@ -11,7 +11,8 @@ public:
|
||||||
enum EventRoles {
|
enum EventRoles {
|
||||||
DisplayNameRole = Qt::UserRole + 1,
|
DisplayNameRole = Qt::UserRole + 1,
|
||||||
AvatarURLRole,
|
AvatarURLRole,
|
||||||
IdRole
|
IdRole,
|
||||||
|
SectionRole
|
||||||
};
|
};
|
||||||
|
|
||||||
int rowCount(const QModelIndex &parent) const override;
|
int rowCount(const QModelIndex &parent) const override;
|
||||||
|
|
13
include/network.h
Normal file → Executable file
13
include/network.h
Normal file → Executable file
|
@ -8,9 +8,14 @@
|
||||||
|
|
||||||
#include "requestsender.h"
|
#include "requestsender.h"
|
||||||
|
|
||||||
namespace network {
|
class Network {
|
||||||
extern QNetworkAccessManager* manager;
|
public:
|
||||||
extern QString homeserverURL, accessToken;
|
Network() {
|
||||||
|
manager = new QNetworkAccessManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkAccessManager* manager;
|
||||||
|
QString homeserverURL, accessToken;
|
||||||
|
|
||||||
template<typename Fn>
|
template<typename Fn>
|
||||||
inline void postJSON(const QString& path, const QJsonObject object, Fn&& fn) {
|
inline void postJSON(const QString& path, const QJsonObject object, Fn&& fn) {
|
||||||
|
@ -120,4 +125,4 @@ namespace network {
|
||||||
|
|
||||||
manager->get(request);
|
manager->get(request);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
2
include/requestsender.h
Normal file → Executable file
2
include/requestsender.h
Normal file → Executable file
|
@ -23,6 +23,8 @@ public:
|
||||||
|
|
||||||
void finished(QNetworkReply* reply) {
|
void finished(QNetworkReply* reply) {
|
||||||
if(reply->request().originatingObject() == this) {
|
if(reply->request().originatingObject() == this) {
|
||||||
|
//qDebug() << reply->errorString();
|
||||||
|
|
||||||
fn(reply);
|
fn(reply);
|
||||||
|
|
||||||
deleteLater();
|
deleteLater();
|
||||||
|
|
24
include/room.h
Normal file → Executable file
24
include/room.h
Normal file → Executable file
|
@ -7,6 +7,13 @@
|
||||||
|
|
||||||
#include "community.h"
|
#include "community.h"
|
||||||
|
|
||||||
|
class EncryptionInformation : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
QString cipherText;
|
||||||
|
QString sessionId;
|
||||||
|
};
|
||||||
|
|
||||||
class Event : public QObject
|
class Event : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -18,10 +25,12 @@ class Event : public QObject
|
||||||
Q_PROPERTY(QString thumbnail READ getThumbnail NOTIFY thumbnailChanged)
|
Q_PROPERTY(QString thumbnail READ getThumbnail NOTIFY thumbnailChanged)
|
||||||
Q_PROPERTY(bool sent READ getSent NOTIFY sentChanged)
|
Q_PROPERTY(bool sent READ getSent NOTIFY sentChanged)
|
||||||
Q_PROPERTY(double sentProgress READ getSentProgress NOTIFY sentProgressChanged)
|
Q_PROPERTY(double sentProgress READ getSentProgress NOTIFY sentProgressChanged)
|
||||||
Q_PROPERTY(QString eventId MEMBER eventId)
|
Q_PROPERTY(QString eventId MEMBER eventId NOTIFY msgChanged)
|
||||||
public:
|
public:
|
||||||
Event(QObject* parent = nullptr) : QObject(parent) {}
|
Event(QObject* parent = nullptr) : QObject(parent) {}
|
||||||
|
|
||||||
|
EncryptionInformation* encryptionInfo = nullptr;
|
||||||
|
|
||||||
void setSender(const QString& id) {
|
void setSender(const QString& id) {
|
||||||
sender = id;
|
sender = id;
|
||||||
emit senderChanged();
|
emit senderChanged();
|
||||||
|
@ -203,9 +212,19 @@ class Room : public QObject
|
||||||
Q_PROPERTY(QString notificationCount READ getNotificationCount NOTIFY notificationCountChanged)
|
Q_PROPERTY(QString notificationCount READ getNotificationCount NOTIFY notificationCountChanged)
|
||||||
Q_PROPERTY(bool direct READ getDirect NOTIFY directChanged)
|
Q_PROPERTY(bool direct READ getDirect NOTIFY directChanged)
|
||||||
Q_PROPERTY(int notificationLevel READ getNotificationLevel WRITE setNotificationLevel NOTIFY notificationLevelChanged)
|
Q_PROPERTY(int notificationLevel READ getNotificationLevel WRITE setNotificationLevel NOTIFY notificationLevelChanged)
|
||||||
|
Q_PROPERTY(bool encrypted READ getEncrypted NOTIFY encryptionChanged)
|
||||||
public:
|
public:
|
||||||
Room(QObject* parent = nullptr) : QObject(parent) {}
|
Room(QObject* parent = nullptr) : QObject(parent) {}
|
||||||
|
|
||||||
|
void setEncrypted() {
|
||||||
|
this->encrypted = true;
|
||||||
|
emit encryptionChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getEncrypted() const {
|
||||||
|
return encrypted;
|
||||||
|
}
|
||||||
|
|
||||||
void setId(const QString& id) {
|
void setId(const QString& id) {
|
||||||
this->id = id;
|
this->id = id;
|
||||||
emit idChanged();
|
emit idChanged();
|
||||||
|
@ -309,6 +328,7 @@ public:
|
||||||
QString prevBatch;
|
QString prevBatch;
|
||||||
|
|
||||||
QList<Member*> members;
|
QList<Member*> members;
|
||||||
|
QMap<QString, int> powerLevelList;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString id, name, topic, avatar;
|
QString id, name, topic, avatar;
|
||||||
|
@ -318,6 +338,7 @@ private:
|
||||||
unsigned int highlightCount = 0, notificationCount = 0;
|
unsigned int highlightCount = 0, notificationCount = 0;
|
||||||
bool direct = false;
|
bool direct = false;
|
||||||
int notificationLevel = 1;
|
int notificationLevel = 1;
|
||||||
|
bool encrypted = false;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void idChanged();
|
void idChanged();
|
||||||
|
@ -331,4 +352,5 @@ signals:
|
||||||
void notificationCountChanged();
|
void notificationCountChanged();
|
||||||
void directChanged();
|
void directChanged();
|
||||||
void notificationLevelChanged();
|
void notificationLevelChanged();
|
||||||
|
void encryptionChanged();
|
||||||
};
|
};
|
||||||
|
|
17
include/roomlistsortmodel.h
Normal file → Executable file
17
include/roomlistsortmodel.h
Normal file → Executable file
|
@ -19,3 +19,20 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class MemberListSortModel : public QSortFilterProxyModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const;
|
||||||
|
|
||||||
|
Q_INVOKABLE unsigned int getOriginalIndex(const unsigned int i) const {
|
||||||
|
auto const proxyIndex = index(i, 0);
|
||||||
|
auto const sourceIndex = mapToSource(proxyIndex);
|
||||||
|
|
||||||
|
if(!sourceIndex.isValid())
|
||||||
|
return 0;
|
||||||
|
else
|
||||||
|
return sourceIndex.row();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
2
qml.qrc
Normal file → Executable file
2
qml.qrc
Normal file → Executable file
|
@ -10,6 +10,8 @@
|
||||||
<file alias="RoomSettings.qml">qml/RoomSettings.qml</file>
|
<file alias="RoomSettings.qml">qml/RoomSettings.qml</file>
|
||||||
<file alias="Profile.qml">qml/Profile.qml</file>
|
<file alias="Profile.qml">qml/Profile.qml</file>
|
||||||
<file alias="Community.qml">qml/Community.qml</file>
|
<file alias="Community.qml">qml/Community.qml</file>
|
||||||
|
<file alias="ToolBarButton.qml">qml/ToolBarButton.qml</file>
|
||||||
|
<file alias="RoundedImage.qml">qml/RoundedImage.qml</file>
|
||||||
<file alias="Communities.qml">qml/Communities.qml</file>
|
<file alias="Communities.qml">qml/Communities.qml</file>
|
||||||
<file alias="Directory.qml">qml/Directory.qml</file>
|
<file alias="Directory.qml">qml/Directory.qml</file>
|
||||||
<file alias="InviteDialog.qml">qml/InviteDialog.qml</file>
|
<file alias="InviteDialog.qml">qml/InviteDialog.qml</file>
|
||||||
|
|
2
qml/BackButton.qml
Normal file → Executable file
2
qml/BackButton.qml
Normal file → Executable file
|
@ -27,7 +27,7 @@ Rectangle {
|
||||||
|
|
||||||
text: "ESC"
|
text: "ESC"
|
||||||
|
|
||||||
color: "grey"
|
color: myPalette.text
|
||||||
}
|
}
|
||||||
|
|
||||||
Shortcut {
|
Shortcut {
|
||||||
|
|
869
qml/Client.qml
Normal file → Executable file
869
qml/Client.qml
Normal file → Executable file
File diff suppressed because it is too large
Load diff
4
qml/Dialog.qml
Normal file → Executable file
4
qml/Dialog.qml
Normal file → Executable file
|
@ -22,7 +22,7 @@ Popup {
|
||||||
|
|
||||||
text: title
|
text: title
|
||||||
|
|
||||||
color: "white"
|
color: myPalette.text
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
|
@ -32,7 +32,7 @@ Popup {
|
||||||
|
|
||||||
anchors.top: titleLabel.bottom
|
anchors.top: titleLabel.bottom
|
||||||
|
|
||||||
color: "white"
|
color: myPalette.text
|
||||||
}
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
|
|
25
qml/Directory.qml
Normal file → Executable file
25
qml/Directory.qml
Normal file → Executable file
|
@ -8,9 +8,7 @@ import trinity.matrix 1.0
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: roomDirectory
|
id: roomDirectory
|
||||||
|
|
||||||
color: Qt.rgba(0.1, 0.1, 0.1, 1.0)
|
color: myPalette.window
|
||||||
|
|
||||||
Component.onCompleted: matrix.loadDirectory()
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 700
|
width: 700
|
||||||
|
@ -40,14 +38,25 @@ Rectangle {
|
||||||
font.pointSize: 25
|
font.pointSize: 25
|
||||||
font.bold: true
|
font.bold: true
|
||||||
|
|
||||||
color: "white"
|
color: myPalette.text
|
||||||
|
}
|
||||||
|
|
||||||
|
TextEdit {
|
||||||
|
id: serverEdit
|
||||||
|
|
||||||
|
anchors.top: directoryLabel.bottom
|
||||||
|
anchors.topMargin: 10
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
onEditingFinished: matrix.loadDirectory(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: parent.height - backButton.height
|
height: parent.height - backButton.height
|
||||||
|
|
||||||
anchors.top: directoryLabel.bottom
|
anchors.top: serverEdit.bottom
|
||||||
anchors.topMargin: 10
|
anchors.topMargin: 10
|
||||||
|
|
||||||
model: matrix.publicRooms
|
model: matrix.publicRooms
|
||||||
|
@ -60,7 +69,7 @@ Rectangle {
|
||||||
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
Image {
|
RoundedImage {
|
||||||
id: roomAvatar
|
id: roomAvatar
|
||||||
|
|
||||||
width: 32
|
width: 32
|
||||||
|
@ -81,7 +90,7 @@ Rectangle {
|
||||||
|
|
||||||
font.bold: true
|
font.bold: true
|
||||||
|
|
||||||
color: "white"
|
color: myPalette.text
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
|
@ -98,7 +107,7 @@ Rectangle {
|
||||||
|
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
|
|
||||||
color: "white"
|
color: myPalette.text
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
|
|
61
qml/Profile.qml
Normal file → Executable file
61
qml/Profile.qml
Normal file → Executable file
|
@ -18,7 +18,7 @@ Popup {
|
||||||
|
|
||||||
Component.onCompleted: matrix.updateMemberCommunities(member)
|
Component.onCompleted: matrix.updateMemberCommunities(member)
|
||||||
|
|
||||||
Image {
|
RoundedImage {
|
||||||
id: profileAvatar
|
id: profileAvatar
|
||||||
|
|
||||||
width: 64
|
width: 64
|
||||||
|
@ -38,7 +38,7 @@ Popup {
|
||||||
|
|
||||||
font.pointSize: 22
|
font.pointSize: 22
|
||||||
|
|
||||||
color: "white"
|
color: myPalette.text
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
|
@ -50,7 +50,40 @@ Popup {
|
||||||
|
|
||||||
text: member.id
|
text: member.id
|
||||||
|
|
||||||
color: "grey"
|
color: myPalette.dark
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: roleText
|
||||||
|
|
||||||
|
anchors.top: profileIdLabel.bottom
|
||||||
|
anchors.topMargin: 5
|
||||||
|
anchors.left: profileAvatar.right
|
||||||
|
anchors.leftMargin: 15
|
||||||
|
|
||||||
|
text: "Member Role Placeholder"
|
||||||
|
|
||||||
|
color: myPalette.dark
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: directMessageButton
|
||||||
|
|
||||||
|
anchors.verticalCenter: profileAvatar.verticalCenter
|
||||||
|
anchors.right: hamburgerButton.left
|
||||||
|
anchors.rightMargin: 10
|
||||||
|
|
||||||
|
text: "Direct message"
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: hamburgerButton
|
||||||
|
|
||||||
|
anchors.verticalCenter: profileAvatar.verticalCenter
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 15
|
||||||
|
|
||||||
|
text: "..."
|
||||||
}
|
}
|
||||||
|
|
||||||
TabBar {
|
TabBar {
|
||||||
|
@ -61,12 +94,22 @@ Popup {
|
||||||
|
|
||||||
id: profileTabs
|
id: profileTabs
|
||||||
|
|
||||||
|
TabButton {
|
||||||
|
text: "Security"
|
||||||
|
}
|
||||||
|
|
||||||
TabButton {
|
TabButton {
|
||||||
text: "Communities"
|
text: "Communities"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TabButton {
|
||||||
|
text: "Sessions"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SwipeView {
|
SwipeView {
|
||||||
|
interactive: false
|
||||||
|
|
||||||
height: parent.height - profileNameLabel.height - profileTabs.height
|
height: parent.height - profileNameLabel.height - profileTabs.height
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
|
||||||
|
@ -75,6 +118,8 @@ Popup {
|
||||||
currentIndex: profileTabs.currentIndex
|
currentIndex: profileTabs.currentIndex
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
id: communityTab
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
id: communityList
|
id: communityList
|
||||||
|
|
||||||
|
@ -90,7 +135,7 @@ Popup {
|
||||||
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
Image {
|
RoundedImage {
|
||||||
id: communityAvatar
|
id: communityAvatar
|
||||||
|
|
||||||
width: 32
|
width: 32
|
||||||
|
@ -110,7 +155,7 @@ Popup {
|
||||||
|
|
||||||
text: display.name
|
text: display.name
|
||||||
|
|
||||||
color: "white"
|
color: myPalette.text
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
|
@ -135,11 +180,15 @@ Popup {
|
||||||
|
|
||||||
text: "This member does not have any public communities."
|
text: "This member does not have any public communities."
|
||||||
|
|
||||||
color: "white"
|
color: myPalette.text
|
||||||
|
|
||||||
visible: !member.publicCommunities || member.publicCommunities.length == 0
|
visible: !member.publicCommunities || member.publicCommunities.length == 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: sessionsTab
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
41
qml/RoomSettings.qml
Normal file → Executable file
41
qml/RoomSettings.qml
Normal file → Executable file
|
@ -3,35 +3,48 @@ import QtQuick.Controls 2.3
|
||||||
import QtGraphicalEffects 1.0
|
import QtGraphicalEffects 1.0
|
||||||
import QtQuick.Shapes 1.0
|
import QtQuick.Shapes 1.0
|
||||||
|
|
||||||
Rectangle {
|
Popup {
|
||||||
id: roomSettings
|
id: roomSettings
|
||||||
|
|
||||||
color: Qt.rgba(0.1, 0.1, 0.1, 1.0)
|
width: 500
|
||||||
|
height: 256
|
||||||
|
|
||||||
|
x: parent.width / 2 - width / 2
|
||||||
|
y: parent.height / 2 - height / 2
|
||||||
|
|
||||||
|
modal: true
|
||||||
|
|
||||||
property var room
|
property var room
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 700
|
width: parent.width
|
||||||
height: parent.height
|
height: parent.height
|
||||||
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
Button {
|
|
||||||
id: backButton
|
|
||||||
|
|
||||||
text: "Back"
|
|
||||||
onClicked: stack.pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
TabBar {
|
TabBar {
|
||||||
id: bar
|
id: bar
|
||||||
|
|
||||||
anchors.top: backButton.bottom
|
TabButton {
|
||||||
|
text: "General"
|
||||||
|
}
|
||||||
|
|
||||||
TabButton {
|
TabButton {
|
||||||
text: "Overview"
|
text: "Security & Privacy"
|
||||||
|
}
|
||||||
|
|
||||||
|
TabButton {
|
||||||
|
text: "Roles & Permissions"
|
||||||
|
}
|
||||||
|
|
||||||
|
TabButton {
|
||||||
|
text: "Notifications"
|
||||||
|
}
|
||||||
|
|
||||||
|
TabButton {
|
||||||
|
text: "Advanced"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +66,7 @@ Rectangle {
|
||||||
Label {
|
Label {
|
||||||
id: nameLabel
|
id: nameLabel
|
||||||
|
|
||||||
text: "Name"
|
text: "Room Name"
|
||||||
}
|
}
|
||||||
|
|
||||||
TextField {
|
TextField {
|
||||||
|
@ -67,7 +80,7 @@ Rectangle {
|
||||||
Label {
|
Label {
|
||||||
id: topicLabel
|
id: topicLabel
|
||||||
|
|
||||||
text: "Topic"
|
text: "Room Topic"
|
||||||
|
|
||||||
anchors.top: nameField.bottom
|
anchors.top: nameField.bottom
|
||||||
}
|
}
|
||||||
|
|
36
qml/RoundedImage.qml
Executable file
36
qml/RoundedImage.qml
Executable file
|
@ -0,0 +1,36 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtGraphicalEffects 1.0
|
||||||
|
import QtQuick.Controls 2.3
|
||||||
|
|
||||||
|
Item {
|
||||||
|
property var source: String
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: image
|
||||||
|
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
|
sourceSize.width: parent.width
|
||||||
|
sourceSize.height: parent.height
|
||||||
|
|
||||||
|
source: parent.source
|
||||||
|
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: OpacityMask {
|
||||||
|
maskSource: Item {
|
||||||
|
width: image.width
|
||||||
|
height: image.height
|
||||||
|
Rectangle {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: image.width
|
||||||
|
height: image.height
|
||||||
|
radius: Math.min(width, height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
32
qml/Settings.qml
Normal file → Executable file
32
qml/Settings.qml
Normal file → Executable file
|
@ -3,11 +3,12 @@ import QtQuick.Controls 2.3
|
||||||
import QtGraphicalEffects 1.0
|
import QtGraphicalEffects 1.0
|
||||||
import QtQuick.Shapes 1.0
|
import QtQuick.Shapes 1.0
|
||||||
import QtQuick.Dialogs 1.2
|
import QtQuick.Dialogs 1.2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: settings
|
id: settings
|
||||||
|
|
||||||
color: Qt.rgba(0.1, 0.1, 0.1, 1.0)
|
color: myPalette.window
|
||||||
|
|
||||||
Component.onCompleted: matrix.updateAccountInformation()
|
Component.onCompleted: matrix.updateAccountInformation()
|
||||||
|
|
||||||
|
@ -19,11 +20,13 @@ Rectangle {
|
||||||
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
Button {
|
BackButton {
|
||||||
id: backButton
|
id: backButton
|
||||||
|
|
||||||
text: "Back"
|
anchors.top: parent.top
|
||||||
onClicked: stack.pop()
|
anchors.topMargin: 15
|
||||||
|
|
||||||
|
anchors.right: parent.right
|
||||||
}
|
}
|
||||||
|
|
||||||
TabBar {
|
TabBar {
|
||||||
|
@ -50,7 +53,7 @@ Rectangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SwipeView {
|
StackLayout {
|
||||||
id: settingsStack
|
id: settingsStack
|
||||||
|
|
||||||
anchors.top: bar.bottom
|
anchors.top: bar.bottom
|
||||||
|
@ -155,12 +158,27 @@ Rectangle {
|
||||||
id: notificationsTab
|
id: notificationsTab
|
||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
text: "Enable Desktop Notifications"
|
id: notificationsEnable
|
||||||
|
|
||||||
|
text: "Enable notifications"
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckBox {
|
||||||
|
text: "Enable notification bar icon"
|
||||||
|
|
||||||
|
anchors.top: notificationsEnable.bottom
|
||||||
|
|
||||||
|
checked: desktop.isTrayIconEnabled()
|
||||||
|
onToggled: desktop.showTrayIcon(checked)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: appearanceTab
|
id: appearanceTab
|
||||||
|
|
||||||
|
CheckBox {
|
||||||
|
text: "Show developer options"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
@ -220,7 +238,7 @@ Rectangle {
|
||||||
|
|
||||||
text: display.name
|
text: display.name
|
||||||
|
|
||||||
color: "white"
|
color: myPalette.text
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolButton {
|
ToolButton {
|
||||||
|
|
42
qml/ToolBarButton.qml
Executable file
42
qml/ToolBarButton.qml
Executable file
|
@ -0,0 +1,42 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtGraphicalEffects 1.0
|
||||||
|
import QtQuick.Controls 2.3
|
||||||
|
|
||||||
|
ToolButton {
|
||||||
|
width: 25
|
||||||
|
height: 25
|
||||||
|
|
||||||
|
signal pressed()
|
||||||
|
|
||||||
|
property var name: String
|
||||||
|
property var toolIcon: String
|
||||||
|
property bool isActivated: false
|
||||||
|
|
||||||
|
onClicked: pressed()
|
||||||
|
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: name
|
||||||
|
|
||||||
|
background: Rectangle { color: "transparent" }
|
||||||
|
contentItem: Rectangle { color: "transparent" }
|
||||||
|
|
||||||
|
visible: !matrix.currentRoom.direct
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: internalImage
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
sourceSize.width: parent.width
|
||||||
|
sourceSize.height: parent.height
|
||||||
|
|
||||||
|
source: toolIcon
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorOverlay {
|
||||||
|
anchors.fill: parent
|
||||||
|
source: internalImage
|
||||||
|
|
||||||
|
color: parent.hovered ? "white" : (isActivated ? "white" : Qt.rgba(0.8, 0.8, 0.8, 1.0))
|
||||||
|
}
|
||||||
|
}
|
59
qml/main.qml
Normal file → Executable file
59
qml/main.qml
Normal file → Executable file
|
@ -9,7 +9,9 @@ ApplicationWindow {
|
||||||
visible: true
|
visible: true
|
||||||
width: 640
|
width: 640
|
||||||
height: 480
|
height: 480
|
||||||
title: "Trinity"
|
title: "Trinity " + matrix.profileName
|
||||||
|
|
||||||
|
SystemPalette { id: myPalette; colorGroup: SystemPalette.Active }
|
||||||
|
|
||||||
property var showDialog: function(title, description, buttons) {
|
property var showDialog: function(title, description, buttons) {
|
||||||
var popup = Qt.createComponent("qrc:/Dialog.qml")
|
var popup = Qt.createComponent("qrc:/Dialog.qml")
|
||||||
|
@ -24,17 +26,60 @@ ApplicationWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if(matrix.settingsValid()) {
|
connect.onAccountChange()
|
||||||
desktop.showTrayIcon(false)
|
}
|
||||||
stack.push("qrc:/Client.qml")
|
|
||||||
} else {
|
Connections {
|
||||||
desktop.showTrayIcon(true)
|
id: connect
|
||||||
stack.push("qrc:/Login.qml")
|
|
||||||
|
target: app
|
||||||
|
|
||||||
|
function onAccountChange() {
|
||||||
|
if(matrix.settingsValid()) {
|
||||||
|
desktop.showTrayIcon(false)
|
||||||
|
stack.replace("qrc:/Client.qml")
|
||||||
|
} else {
|
||||||
|
desktop.showTrayIcon(true)
|
||||||
|
stack.replace("qrc:/Login.qml")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StackView {
|
StackView {
|
||||||
id: stack
|
id: stack
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
|
pushEnter: Transition {
|
||||||
|
PropertyAnimation {
|
||||||
|
property: "opacity"
|
||||||
|
from: 0
|
||||||
|
to:1
|
||||||
|
duration: 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pushExit: Transition {
|
||||||
|
PropertyAnimation {
|
||||||
|
property: "opacity"
|
||||||
|
from: 1
|
||||||
|
to:0
|
||||||
|
duration: 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
popEnter: Transition {
|
||||||
|
PropertyAnimation {
|
||||||
|
property: "opacity"
|
||||||
|
from: 0
|
||||||
|
to:1
|
||||||
|
duration: 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
popExit: Transition {
|
||||||
|
PropertyAnimation {
|
||||||
|
property: "opacity"
|
||||||
|
from: 1
|
||||||
|
to:0
|
||||||
|
duration: 200
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
337
src/encryption.cpp
Executable file
337
src/encryption.cpp
Executable file
|
@ -0,0 +1,337 @@
|
||||||
|
#include "encryption.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
void Encryption::createNewDeviceKeys() {
|
||||||
|
initAccount();
|
||||||
|
|
||||||
|
// create account
|
||||||
|
auto length = olm_create_account_random_length(account);
|
||||||
|
|
||||||
|
void* memory = malloc(length);
|
||||||
|
|
||||||
|
int fd = open("/dev/random", O_RDONLY);
|
||||||
|
read(fd, memory, length);
|
||||||
|
|
||||||
|
olm_create_account(account, memory, length);
|
||||||
|
qDebug() << olm_account_last_error(account);
|
||||||
|
|
||||||
|
qDebug() << "Created new device keys!";
|
||||||
|
|
||||||
|
// retrieve identity keys
|
||||||
|
length = olm_account_identity_keys_length(account);
|
||||||
|
|
||||||
|
char* identity_memory = (char*)malloc(length + 1);
|
||||||
|
identity_memory[length] = '\0';
|
||||||
|
|
||||||
|
olm_account_identity_keys(account, identity_memory, length);
|
||||||
|
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson((char*)identity_memory);
|
||||||
|
identityKey = doc.object();
|
||||||
|
|
||||||
|
qDebug() << "identity keys: " << (char*)identity_memory;
|
||||||
|
qDebug() << "identity keys (parsed): " << identityKey;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Encryption::saveDeviceKeys() {
|
||||||
|
auto length = olm_pickle_account_length(account);
|
||||||
|
char* identity_memory = (char*)malloc(length + 1);
|
||||||
|
identity_memory[length] = '\0';
|
||||||
|
|
||||||
|
auto err = olm_pickle_account(account, "secret_key", 10, identity_memory, length);
|
||||||
|
qDebug() << olm_account_last_error(account);
|
||||||
|
qDebug() << err << " = " << olm_error();
|
||||||
|
|
||||||
|
qDebug() << "Save: " << identity_memory;
|
||||||
|
|
||||||
|
return identity_memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Encryption::loadDeviceKeys(QString bytes) {
|
||||||
|
initAccount();
|
||||||
|
|
||||||
|
qDebug() << "load: " << bytes;
|
||||||
|
|
||||||
|
auto length = bytes.length();
|
||||||
|
|
||||||
|
std::string key = bytes.toStdString();
|
||||||
|
|
||||||
|
olm_unpickle_account(account, "secret_key", 10, key.data(), key.length());
|
||||||
|
qDebug() << olm_account_last_error(account);
|
||||||
|
|
||||||
|
length = olm_account_identity_keys_length(account);
|
||||||
|
char* identity_memory = (char*)malloc(length + 1);
|
||||||
|
identity_memory[length] = '\0';
|
||||||
|
|
||||||
|
olm_account_identity_keys(account, identity_memory, length);
|
||||||
|
|
||||||
|
qDebug() << identity_memory;
|
||||||
|
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(identity_memory);
|
||||||
|
qDebug() << doc;
|
||||||
|
|
||||||
|
identityKey = doc.object();
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject Encryption::generateOneTimeKeys(int number_of_keys) {
|
||||||
|
int fd = open("/dev/random", O_RDONLY);
|
||||||
|
|
||||||
|
// generate random data
|
||||||
|
auto length = olm_account_generate_one_time_keys_random_length(account, number_of_keys);
|
||||||
|
auto memory = malloc(length);
|
||||||
|
read(fd, memory, length);
|
||||||
|
|
||||||
|
olm_account_generate_one_time_keys(account, number_of_keys, memory, length);
|
||||||
|
|
||||||
|
// retrieve one time keys
|
||||||
|
length = olm_account_one_time_keys_length(account);
|
||||||
|
memory = malloc(length);
|
||||||
|
olm_account_one_time_keys(account, memory, length);
|
||||||
|
|
||||||
|
olm_account_mark_keys_as_published(account);
|
||||||
|
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson((char*)memory);
|
||||||
|
return doc.object();
|
||||||
|
}
|
||||||
|
|
||||||
|
int Encryption::getRecommendedNumberOfOneTimeKeys() {
|
||||||
|
return olm_account_max_number_of_one_time_keys(account) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Encryption::signMessage(QString message) {
|
||||||
|
auto length = olm_account_signature_length(account);
|
||||||
|
|
||||||
|
void* memory = malloc(length);
|
||||||
|
|
||||||
|
olm_account_sign(
|
||||||
|
account,
|
||||||
|
message.data(), message.length(),
|
||||||
|
memory, length
|
||||||
|
);
|
||||||
|
|
||||||
|
qDebug() << "Signed: " << (char*)memory;
|
||||||
|
|
||||||
|
return (char*)memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Encryption::initAccount() {
|
||||||
|
void* memory = malloc(olm_account_size());
|
||||||
|
account = olm_account(memory);
|
||||||
|
}
|
||||||
|
|
||||||
|
OlmOutboundGroupSession* Encryption::beginOutboundSession() {
|
||||||
|
void* outboundMemory = malloc(olm_outbound_group_session_size());
|
||||||
|
auto outboundSession = olm_outbound_group_session(outboundMemory);
|
||||||
|
|
||||||
|
auto length = olm_init_outbound_group_session_random_length(outboundSession);
|
||||||
|
|
||||||
|
void* memory = malloc(length);
|
||||||
|
|
||||||
|
int fd = open("/dev/random", O_RDONLY);
|
||||||
|
read(fd, memory, length);
|
||||||
|
|
||||||
|
olm_init_outbound_group_session(outboundSession, (uint8_t*)memory, length);
|
||||||
|
|
||||||
|
return outboundSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Encryption::getGroupSessionId(OlmOutboundGroupSession* session) {
|
||||||
|
auto encryptedLength = olm_outbound_group_session_id_length(session);
|
||||||
|
char* encryptedMemory = (char*)malloc(encryptedLength + 1);
|
||||||
|
|
||||||
|
olm_outbound_group_session_id(session, (uint8_t*)encryptedMemory, encryptedLength);
|
||||||
|
|
||||||
|
return encryptedMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Encryption::getGroupSessionKey(OlmOutboundGroupSession* session) {
|
||||||
|
auto encryptedLength = olm_outbound_group_session_key_length(session);
|
||||||
|
char* encryptedMemory = (char*)malloc(encryptedLength + 1);
|
||||||
|
|
||||||
|
olm_outbound_group_session_key(session, (uint8_t*)encryptedMemory, encryptedLength);
|
||||||
|
|
||||||
|
return encryptedMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
OlmSession* Encryption::beginOutboundOlmSession(std::string identityKey, std::string oneTimeKey) {
|
||||||
|
void* outboundMemory = malloc(olm_session_size());
|
||||||
|
auto outboundSession = olm_session(outboundMemory);
|
||||||
|
|
||||||
|
auto length = olm_create_outbound_session_random_length(outboundSession);
|
||||||
|
|
||||||
|
void* memory = malloc(length);
|
||||||
|
|
||||||
|
int fd = open("/dev/random", O_RDONLY);
|
||||||
|
read(fd, memory, length);
|
||||||
|
|
||||||
|
olm_create_outbound_session(outboundSession, account, identityKey.data(), identityKey.length(), oneTimeKey.data(), oneTimeKey.length(), (uint8_t*)memory, length);
|
||||||
|
//qDebug() << "ERR: " << olm_session_last_error(outboundSession);
|
||||||
|
|
||||||
|
return outboundSession;
|
||||||
|
|
||||||
|
/*size_t olm_create_outbound_session(
|
||||||
|
OlmSession * session,
|
||||||
|
OlmAccount * account,
|
||||||
|
void const * their_identity_key, size_t their_identity_key_length,
|
||||||
|
void const * their_one_time_key, size_t their_one_time_key_length,
|
||||||
|
void * random, size_t random_length
|
||||||
|
);*/
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Encryption::getSessionId(OlmSession* session) {
|
||||||
|
auto length = olm_session_id_length(session);
|
||||||
|
char* memory = (char*)malloc(length + 1);
|
||||||
|
memory[length] = '\0';
|
||||||
|
|
||||||
|
/** An identifier for this session. Will be the same for both ends of the
|
||||||
|
* conversation. If the id buffer is too small then olm_session_last_error()
|
||||||
|
* will be "OUTPUT_BUFFER_TOO_SMALL". */
|
||||||
|
olm_session_id(
|
||||||
|
session,
|
||||||
|
memory, length
|
||||||
|
);
|
||||||
|
|
||||||
|
return memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Encryption::encrypt(OlmSession* session, std::string message) {
|
||||||
|
auto length = olm_encrypt_random_length(session);
|
||||||
|
|
||||||
|
void* memory = malloc(length);
|
||||||
|
|
||||||
|
int fd = open("/dev/random", O_RDONLY);
|
||||||
|
read(fd, memory, length);
|
||||||
|
|
||||||
|
auto encryptedLength = olm_encrypt_message_length(session, message.length());
|
||||||
|
char* encryptedMemory = (char*)malloc(encryptedLength + 1);
|
||||||
|
|
||||||
|
olm_encrypt(session, message.data(), message.length(), memory, length, encryptedMemory, encryptedLength);
|
||||||
|
|
||||||
|
return encryptedMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Encryption::encryptGroup(OlmOutboundGroupSession* session, std::string message) {
|
||||||
|
auto encryptedLength = olm_group_encrypt_message_length(session, message.length());
|
||||||
|
char* encryptedMemory = (char*)malloc(encryptedLength + 1);
|
||||||
|
|
||||||
|
olm_group_encrypt(session, (uint8_t*)message.data(), message.length(), (uint8_t*)encryptedMemory, encryptedLength);
|
||||||
|
|
||||||
|
return encryptedMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
OlmSession* Encryption::createInboundSession(std::string senderKey, std::string body) {
|
||||||
|
void* inboundMemory = malloc(olm_session_size());
|
||||||
|
auto inboundSession = olm_session(inboundMemory);
|
||||||
|
|
||||||
|
olm_create_inbound_session_from(inboundSession, account, (void*)senderKey.c_str(), senderKey.length(), (void*)body.c_str(), body.length());
|
||||||
|
|
||||||
|
return inboundSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> Encryption::decrypt(OlmSession* session, int msgType, std::string cipherText) {
|
||||||
|
/*std::string maxPlaintextLengthBuffer = cipherText;
|
||||||
|
|
||||||
|
size_t maxPlaintextLength = olm_decrypt_max_plaintext_length(session, msgType, (void*)maxPlaintextLengthBuffer.data(), maxPlaintextLengthBuffer.length());;
|
||||||
|
qDebug() << "THE ERROR YOU ARE LOOKING FOR " << olm_session_last_error(session);
|
||||||
|
if(maxPlaintextLength == olm_error())
|
||||||
|
return "";
|
||||||
|
|
||||||
|
char* plaintext = new char[maxPlaintextLength];
|
||||||
|
//plaintext[maxPlaintextLength] = '\0';
|
||||||
|
memset(plaintext, '\0', maxPlaintextLength);
|
||||||
|
int size = olm_decrypt(session, msgType, (void*)cipherText.data(), cipherText.length(), (void*)plaintext, maxPlaintextLength);
|
||||||
|
//plaintext[size] = '\0';
|
||||||
|
|
||||||
|
plaintext[size + 1] = '\0';
|
||||||
|
|
||||||
|
return plaintext;*/
|
||||||
|
|
||||||
|
// because olm_group_decrypt_max_plaintext_length DESTROYS the buffer, why?
|
||||||
|
auto tmp_plaintext_buffer = std::vector<std::uint8_t>(cipherText.size());
|
||||||
|
std::copy(cipherText.begin(), cipherText.end(), tmp_plaintext_buffer.begin());
|
||||||
|
|
||||||
|
// create the result buffer
|
||||||
|
size_t length = olm_decrypt_max_plaintext_length(session, msgType, tmp_plaintext_buffer.data(), tmp_plaintext_buffer.size());
|
||||||
|
if(length == olm_error())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto result_buffer = std::vector<std::uint8_t>(length);
|
||||||
|
|
||||||
|
// now create another buffer for olm_group_decrypt
|
||||||
|
auto tmp_buffer = std::vector<std::uint8_t>(cipherText.size());
|
||||||
|
std::copy(cipherText.begin(), cipherText.end(), tmp_buffer.begin());
|
||||||
|
|
||||||
|
auto size = olm_decrypt(session, msgType, tmp_buffer.data(), tmp_buffer.size(), result_buffer.data(), result_buffer.size());
|
||||||
|
if(size == olm_error())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto output = std::vector<std::uint8_t>(size);
|
||||||
|
std::memcpy(output.data(), result_buffer.data(), size);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Encryption::saveSession(OlmOutboundGroupSession* session) {
|
||||||
|
auto length = olm_pickle_outbound_group_session_length(session);
|
||||||
|
char* identity_memory = (char*)malloc(length + 1);
|
||||||
|
identity_memory[length] = '\0';
|
||||||
|
|
||||||
|
auto err = olm_pickle_outbound_group_session(session, "secret_key", 10, identity_memory, length);
|
||||||
|
|
||||||
|
return identity_memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
OlmOutboundGroupSession* Encryption::loadSession(QString bytes) {
|
||||||
|
void* outboundMemory = malloc(olm_outbound_group_session_size());
|
||||||
|
auto outboundSession = olm_outbound_group_session(outboundMemory);
|
||||||
|
|
||||||
|
auto length = bytes.length();
|
||||||
|
|
||||||
|
std::string key = bytes.toStdString();
|
||||||
|
|
||||||
|
olm_unpickle_outbound_group_session(outboundSession, "secret_key", 10, (void*)key.data(), key.length());
|
||||||
|
|
||||||
|
return outboundSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
OlmInboundGroupSession* Encryption::beginInboundSession(std::string sessionKey) {
|
||||||
|
void* inboundMemory = malloc(olm_inbound_group_session_size());
|
||||||
|
auto inboundSession = olm_inbound_group_session(inboundMemory);
|
||||||
|
|
||||||
|
olm_init_inbound_group_session(inboundSession, (uint8_t*)sessionKey.data(), sessionKey.size());
|
||||||
|
|
||||||
|
return inboundSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> Encryption::decrypt(OlmInboundGroupSession* session, std::string cipherText) {
|
||||||
|
// because olm_group_decrypt_max_plaintext_length DESTROYS the buffer, why?
|
||||||
|
auto tmp_plaintext_buffer = std::vector<std::uint8_t>(cipherText.size());
|
||||||
|
std::copy(cipherText.begin(), cipherText.end(), tmp_plaintext_buffer.begin());
|
||||||
|
|
||||||
|
// create the result buffer
|
||||||
|
size_t length = olm_group_decrypt_max_plaintext_length(session, tmp_plaintext_buffer.data(), tmp_plaintext_buffer.size());
|
||||||
|
if(length == olm_error())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto result_buffer = std::vector<std::uint8_t>(length);
|
||||||
|
|
||||||
|
// now create another buffer for olm_group_decrypt
|
||||||
|
auto tmp_buffer = std::vector<std::uint8_t>(cipherText.size());
|
||||||
|
std::copy(cipherText.begin(), cipherText.end(), tmp_buffer.begin());
|
||||||
|
|
||||||
|
uint32_t msgIndex;
|
||||||
|
auto size = olm_group_decrypt(session, tmp_buffer.data(), tmp_buffer.size(), result_buffer.data(), result_buffer.size(), &msgIndex);
|
||||||
|
if(size == olm_error())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto output = std::vector<std::uint8_t>(size);
|
||||||
|
std::memcpy(output.data(), result_buffer.data(), size);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
64
src/main.cpp
Normal file → Executable file
64
src/main.cpp
Normal file → Executable file
|
@ -9,6 +9,7 @@
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QtQuick/QQuickWindow>
|
#include <QtQuick/QQuickWindow>
|
||||||
|
#include <QtWebEngine/QtWebEngine>
|
||||||
|
|
||||||
#include "eventmodel.h"
|
#include "eventmodel.h"
|
||||||
#include "membermodel.h"
|
#include "membermodel.h"
|
||||||
|
@ -19,23 +20,58 @@
|
||||||
#include "community.h"
|
#include "community.h"
|
||||||
#include "roomlistsortmodel.h"
|
#include "roomlistsortmodel.h"
|
||||||
#include "emote.h"
|
#include "emote.h"
|
||||||
|
#include "appcore.h"
|
||||||
|
|
||||||
QNetworkAccessManager* network::manager;
|
void AppCore::addAccount(QString profileName) {
|
||||||
QString network::homeserverURL, network::accessToken;
|
accounts.push_back(new MatrixCore(profileName));
|
||||||
|
|
||||||
|
context->setContextProperty("matrix", accounts.back());
|
||||||
|
|
||||||
|
emit accountChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppCore::switchAccount(QString profileName) {
|
||||||
|
qDebug() << "switching to " << profileName;
|
||||||
|
|
||||||
|
for(auto account : accounts) {
|
||||||
|
if(account->profileName == profileName) {
|
||||||
|
qDebug() << account->profileName << " = " << profileName;
|
||||||
|
context->setContextProperty("matrix", account);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit accountChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantList AppCore::getAccounts() {
|
||||||
|
QVariantList list;
|
||||||
|
for(auto account : accounts)
|
||||||
|
list.push_back(QVariant::fromValue(account));
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
QApplication app2(argc, argv);
|
|
||||||
|
|
||||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||||
QCoreApplication::setApplicationName("Trinity");
|
QCoreApplication::setApplicationName("Trinity");
|
||||||
|
QCoreApplication::setOrganizationName("redstrate");
|
||||||
|
|
||||||
|
QtWebEngine::initialize();
|
||||||
|
|
||||||
|
QApplication app2(argc, argv);
|
||||||
|
|
||||||
|
QCommandLineParser parser;
|
||||||
|
parser.addHelpOption();
|
||||||
|
|
||||||
|
QCommandLineOption profileOption("profile", "Default profile to load", "profile");
|
||||||
|
|
||||||
|
parser.addOption(profileOption);
|
||||||
|
parser.process(app2);
|
||||||
|
|
||||||
qRegisterMetaType<RequestSender>();
|
qRegisterMetaType<RequestSender>();
|
||||||
|
|
||||||
network::manager = new QNetworkAccessManager();
|
|
||||||
|
|
||||||
network::homeserverURL = "https://matrix.org";
|
|
||||||
|
|
||||||
// matrix
|
// matrix
|
||||||
|
qmlRegisterUncreatableType<AppCore>("trinity.matrix", 1, 0, "AppCore", "");
|
||||||
qmlRegisterUncreatableType<EventModel>("trinity.matrix", 1, 0, "EventModel", "");
|
qmlRegisterUncreatableType<EventModel>("trinity.matrix", 1, 0, "EventModel", "");
|
||||||
qmlRegisterUncreatableType<MatrixCore>("trinity.matrix", 1, 0, "MatrixCore", "");
|
qmlRegisterUncreatableType<MatrixCore>("trinity.matrix", 1, 0, "MatrixCore", "");
|
||||||
qmlRegisterUncreatableType<Room>("trinity.matrix", 1, 0, "Room", "");
|
qmlRegisterUncreatableType<Room>("trinity.matrix", 1, 0, "Room", "");
|
||||||
|
@ -51,7 +87,15 @@ int main(int argc, char* argv[]) {
|
||||||
QQmlApplicationEngine engine;
|
QQmlApplicationEngine engine;
|
||||||
QQmlContext* context = new QQmlContext(engine.rootContext(), &engine);
|
QQmlContext* context = new QQmlContext(engine.rootContext(), &engine);
|
||||||
|
|
||||||
MatrixCore matrix;
|
AppCore* core = new AppCore();
|
||||||
|
core->context = context;
|
||||||
|
|
||||||
|
if(parser.isSet(profileOption)) {
|
||||||
|
core->addAccount(parser.value(profileOption));
|
||||||
|
} else {
|
||||||
|
core->addAccount();
|
||||||
|
}
|
||||||
|
|
||||||
Desktop desktop;
|
Desktop desktop;
|
||||||
|
|
||||||
QSystemTrayIcon* trayIcon = new QSystemTrayIcon();
|
QSystemTrayIcon* trayIcon = new QSystemTrayIcon();
|
||||||
|
@ -59,7 +103,7 @@ int main(int argc, char* argv[]) {
|
||||||
desktop.icon = trayIcon;
|
desktop.icon = trayIcon;
|
||||||
|
|
||||||
context->setContextProperty("desktop", &desktop);
|
context->setContextProperty("desktop", &desktop);
|
||||||
context->setContextProperty("matrix", &matrix);
|
context->setContextProperty("app", core);
|
||||||
|
|
||||||
QQmlComponent component(&engine);
|
QQmlComponent component(&engine);
|
||||||
component.loadUrl(QUrl("qrc:/main.qml"));
|
component.loadUrl(QUrl("qrc:/main.qml"));
|
||||||
|
|
657
src/matrixcore.cpp
Normal file → Executable file
657
src/matrixcore.cpp
Normal file → Executable file
|
@ -10,18 +10,31 @@
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QMimeDatabase>
|
#include <QMimeDatabase>
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iterator>
|
||||||
#include "network.h"
|
#include "network.h"
|
||||||
#include "community.h"
|
#include "community.h"
|
||||||
|
|
||||||
MatrixCore::MatrixCore(QObject* parent) : QObject(parent), roomListModel(rooms), directoryListModel(publicRooms), eventModel(*this) {
|
MatrixCore::MatrixCore(QString profileName, QObject* parent) : QObject(parent), profileName(profileName), roomListModel(rooms), directoryListModel(publicRooms), eventModel(*this) {
|
||||||
|
network = new Network();
|
||||||
|
encryption = new Encryption();
|
||||||
|
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
|
settings.beginGroup(profileName);
|
||||||
homeserverURL = settings.value("homeserver", "matrix.org").toString();
|
homeserverURL = settings.value("homeserver", "matrix.org").toString();
|
||||||
userId = settings.value("userId").toString();
|
userId = settings.value("userId").toString();
|
||||||
network::homeserverURL = "https://" + homeserverURL;
|
network->homeserverURL = "https://" + homeserverURL;
|
||||||
|
deviceId = settings.value("deviceId").toString();
|
||||||
|
|
||||||
if(settings.contains("accessToken"))
|
if(settings.contains("accessToken"))
|
||||||
network::accessToken = "Bearer " + settings.value("accessToken").toString();
|
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();
|
||||||
|
|
||||||
emptyRoom.setName("Empty");
|
emptyRoom.setName("Empty");
|
||||||
emptyRoom.setTopic("There is nothing here.");
|
emptyRoom.setTopic("There is nothing here.");
|
||||||
|
@ -33,6 +46,13 @@ MatrixCore::MatrixCore(QObject* parent) : QObject(parent), roomListModel(rooms),
|
||||||
roomListSortModel.sort(0);
|
roomListSortModel.sort(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
memberSortModel.setSourceModel(&memberModel);
|
||||||
|
memberSortModel.setSortRole(RoomListModel::SectionRole);
|
||||||
|
|
||||||
|
connect(this, &MatrixCore::currentRoomChanged, [this] {
|
||||||
|
memberSortModel.sort(0);
|
||||||
|
});
|
||||||
|
|
||||||
directoryListSortModel.setSourceModel(&directoryListModel);
|
directoryListSortModel.setSourceModel(&directoryListModel);
|
||||||
|
|
||||||
updateAccountInformation();
|
updateAccountInformation();
|
||||||
|
@ -74,7 +94,7 @@ void MatrixCore::registerAccount(const QString &username, const QString &passwor
|
||||||
{"password", password}
|
{"password", password}
|
||||||
};
|
};
|
||||||
|
|
||||||
network::postJSON("/_matrix/client/r0/register?kind=user", registerObject, [this](QNetworkReply* reply) {
|
network->postJSON("/_matrix/client/r0/register?kind=user", registerObject, [this](QNetworkReply* reply) {
|
||||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||||
|
|
||||||
if(reply->error()) {
|
if(reply->error()) {
|
||||||
|
@ -103,12 +123,14 @@ void MatrixCore::registerAccount(const QString &username, const QString &passwor
|
||||||
emit registerAttempt(true, document.object()["error"].toString());
|
emit registerAttempt(true, document.object()["error"].toString());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
network::accessToken = "Bearer " + document.object()["access_token"].toString();
|
network->accessToken = "Bearer " + document.object()["access_token"].toString();
|
||||||
|
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
|
settings.beginGroup(profileName);
|
||||||
settings.setValue("accessToken", document.object()["access_token"].toString());
|
settings.setValue("accessToken", document.object()["access_token"].toString());
|
||||||
settings.setValue("userId", document.object()["user_id"].toString());
|
settings.setValue("userId", document.object()["user_id"].toString());
|
||||||
settings.setValue("deviceId", document.object()["device_id"].toString());
|
settings.setValue("deviceId", document.object()["device_id"].toString());
|
||||||
|
settings.endGroup();
|
||||||
|
|
||||||
emit registerAttempt(false, "");
|
emit registerAttempt(false, "");
|
||||||
}
|
}
|
||||||
|
@ -123,26 +145,83 @@ void MatrixCore::login(const QString& username, const QString& password) {
|
||||||
{"initial_device_display_name", "Trinity"}
|
{"initial_device_display_name", "Trinity"}
|
||||||
};
|
};
|
||||||
|
|
||||||
network::postJSON("/_matrix/client/r0/login", loginObject, [this](QNetworkReply* reply) {
|
network->postJSON("/_matrix/client/r0/login", loginObject, [this](QNetworkReply* reply) {
|
||||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||||
|
|
||||||
if(reply->error()) {
|
if(reply->error()) {
|
||||||
emit loginAttempt(true, document.object()["error"].toString());
|
emit loginAttempt(true, document.object()["error"].toString());
|
||||||
} else {
|
} else {
|
||||||
network::accessToken = "Bearer " + document.object()["access_token"].toString();
|
network->accessToken = "Bearer " + document.object()["access_token"].toString();
|
||||||
|
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
|
settings.beginGroup(profileName);
|
||||||
settings.setValue("accessToken", document.object()["access_token"].toString());
|
settings.setValue("accessToken", document.object()["access_token"].toString());
|
||||||
settings.setValue("userId", document.object()["user_id"].toString());
|
settings.setValue("userId", document.object()["user_id"].toString());
|
||||||
settings.setValue("deviceId", document.object()["device_id"].toString());
|
settings.setValue("deviceId", document.object()["device_id"].toString());
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
});
|
||||||
|
|
||||||
emit loginAttempt(false, "");
|
emit loginAttempt(false, "");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void MatrixCore::logout() {
|
void MatrixCore::logout() {
|
||||||
network::post("/_matrix/client/r0/logout");
|
network->post("/_matrix/client/r0/logout");
|
||||||
|
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
settings.remove("accessToken");
|
settings.remove("accessToken");
|
||||||
|
@ -152,7 +231,7 @@ void MatrixCore::logout() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MatrixCore::updateAccountInformation() {
|
void MatrixCore::updateAccountInformation() {
|
||||||
network::get("/_matrix/client/r0/profile/" + userId + "/displayname", [this](QNetworkReply* reply) {
|
network->get("/_matrix/client/r0/profile/" + userId + "/displayname", [this](QNetworkReply* reply) {
|
||||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||||
|
|
||||||
displayName = document.object()["displayname"].toString();
|
displayName = document.object()["displayname"].toString();
|
||||||
|
@ -167,26 +246,98 @@ void MatrixCore::setDisplayName(const QString& name) {
|
||||||
{"displayname", name}
|
{"displayname", name}
|
||||||
};
|
};
|
||||||
|
|
||||||
network::putJSON("/_matrix/client/r0/profile/" + userId + "/displayname", displayNameObject, [this, name](QNetworkReply* reply) {
|
network->putJSON("/_matrix/client/r0/profile/" + userId + "/displayname", displayNameObject, [this, name](QNetworkReply* reply) {
|
||||||
emit displayNameChanged();
|
emit displayNameChanged();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void MatrixCore::sync() {
|
void MatrixCore::sync() {
|
||||||
if(network::accessToken.isEmpty())
|
if(network->accessToken.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QString url = "/_matrix/client/r0/sync";
|
QString url = "/_matrix/client/r0/sync";
|
||||||
if(!nextBatch.isEmpty())
|
if(!nextBatch.isEmpty())
|
||||||
url += "?since=" + nextBatch;
|
url += "?since=" + nextBatch;
|
||||||
|
|
||||||
network::get(url, [this](QNetworkReply* reply) {
|
network->get(url, [this](QNetworkReply* reply) {
|
||||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||||
|
|
||||||
if(!document.object()["next_batch"].isNull())
|
if(!document.object()["next_batch"].isNull())
|
||||||
nextBatch = document.object()["next_batch"].toString();
|
nextBatch = document.object()["next_batch"].toString();
|
||||||
|
|
||||||
const auto createRoom = [this](const QString id, const QString joinState) {
|
//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) {
|
||||||
roomListModel.beginInsertRoom();
|
roomListModel.beginInsertRoom();
|
||||||
|
|
||||||
Room* room = new Room(this);
|
Room* room = new Room(this);
|
||||||
|
@ -201,45 +352,65 @@ void MatrixCore::sync() {
|
||||||
room->setNotificationLevel(1);
|
room->setNotificationLevel(1);
|
||||||
settings.endGroup();
|
settings.endGroup();
|
||||||
|
|
||||||
network::get("/_matrix/client/r0/rooms/" + id + "/state/m.room.name", [this, room](QNetworkReply* reply) {
|
if(autofill_data) {
|
||||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
network->get("/_matrix/client/r0/rooms/" + id + "/state/m.room.name", [this, room](QNetworkReply* reply) {
|
||||||
if(document.object()["errcode"].toString() == "M_GUEST_ACCESS_FORBIDDEN") {
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||||
room->setGuestDenied(true);
|
if(document.object()["errcode"].toString() == "M_GUEST_ACCESS_FORBIDDEN") {
|
||||||
return;
|
room->setGuestDenied(true);
|
||||||
} else if(document.object()["errcode"].toString() == "M_NOT_FOUND")
|
return;
|
||||||
return;
|
} else if(document.object()["errcode"].toString() == "M_NOT_FOUND")
|
||||||
|
return;
|
||||||
|
|
||||||
room->setName(document.object()["name"].toString());
|
room->setName(document.object()["name"].toString());
|
||||||
|
|
||||||
roomListModel.updateRoom(room);
|
roomListModel.updateRoom(room);
|
||||||
});
|
});
|
||||||
|
|
||||||
network::get("/_matrix/client/r0/rooms/" + id + "/state/m.room.topic", [this, room](QNetworkReply* reply) {
|
network->get("/_matrix/client/r0/rooms/" + id + "/state/m.room.topic", [this, room](QNetworkReply* reply) {
|
||||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||||
if(document.object()["errcode"].toString() == "M_GUEST_ACCESS_FORBIDDEN") {
|
if(document.object()["errcode"].toString() == "M_GUEST_ACCESS_FORBIDDEN") {
|
||||||
room->setGuestDenied(true);
|
room->setGuestDenied(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
room->setTopic(document.object()["topic"].toString());
|
room->setTopic(document.object()["topic"].toString());
|
||||||
|
|
||||||
roomListModel.updateRoom(room);
|
roomListModel.updateRoom(room);
|
||||||
});
|
});
|
||||||
|
|
||||||
network::get("/_matrix/client/r0/rooms/" + id + "/state/m.room.avatar", [this, room](QNetworkReply* reply) {
|
network->get("/_matrix/client/r0/rooms/" + id + "/state/m.room.avatar", [this, room](QNetworkReply* reply) {
|
||||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||||
if(document.object()["errcode"].toString() == "M_GUEST_ACCESS_FORBIDDEN") {
|
if(document.object()["errcode"].toString() == "M_GUEST_ACCESS_FORBIDDEN") {
|
||||||
room->setGuestDenied(true);
|
room->setGuestDenied(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(document.object().contains("url")) {
|
if(document.object().contains("url")) {
|
||||||
const QString imageId = document.object()["url"].toString().remove("mxc://");
|
const QString imageId = document.object()["url"].toString().remove("mxc://");
|
||||||
room->setAvatar(network::homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
room->setAvatar(network->homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
||||||
}
|
}
|
||||||
|
|
||||||
roomListModel.updateRoom(room);
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
rooms.push_back(room);
|
rooms.push_back(room);
|
||||||
idToRoom.insert(id, room);
|
idToRoom.insert(id, room);
|
||||||
|
@ -255,7 +426,8 @@ void MatrixCore::sync() {
|
||||||
|
|
||||||
for(const auto id : document.object()["rooms"].toObject()["invite"].toObject().keys()) {
|
for(const auto id : document.object()["rooms"].toObject()["invite"].toObject().keys()) {
|
||||||
if(!invitedRooms.count(id)) {
|
if(!invitedRooms.count(id)) {
|
||||||
Room* room = createRoom(id, "Invited");
|
Room* room = createRoom(id, "Invited", false);
|
||||||
|
room->setGuestDenied(true);
|
||||||
|
|
||||||
for(auto event : document.object()["rooms"].toObject()["invite"].toObject()[id].toObject()["invite_state"].toObject()["events"].toArray()) {
|
for(auto event : document.object()["rooms"].toObject()["invite"].toObject()[id].toObject()["invite_state"].toObject()["events"].toArray()) {
|
||||||
const QString type = event.toObject()["type"].toString();
|
const QString type = event.toObject()["type"].toString();
|
||||||
|
@ -266,7 +438,7 @@ void MatrixCore::sync() {
|
||||||
room->setName(event.toObject()["content"].toObject()["name"].toString());
|
room->setName(event.toObject()["content"].toObject()["name"].toString());
|
||||||
} else if(type == "m.room.avatar") {
|
} else if(type == "m.room.avatar") {
|
||||||
const QString imageId = event.toObject()["content"].toObject()["url"].toString().remove("mxc://");
|
const QString imageId = event.toObject()["content"].toObject()["url"].toString().remove("mxc://");
|
||||||
room->setAvatar(network::homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
room->setAvatar(network->homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
||||||
}
|
}
|
||||||
|
|
||||||
roomListModel.updateRoom(room);
|
roomListModel.updateRoom(room);
|
||||||
|
@ -393,6 +565,10 @@ void MatrixCore::sync() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MatrixCore::isInitialSyncComplete() {
|
||||||
|
return !firstSync;
|
||||||
|
}
|
||||||
|
|
||||||
void MatrixCore::sendMessage(Room* room, const QString& message) {
|
void MatrixCore::sendMessage(Room* room, const QString& message) {
|
||||||
if(message.isEmpty())
|
if(message.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
@ -437,8 +613,9 @@ void MatrixCore::sendMessage(Room* room, const QString& message) {
|
||||||
shouldSendAsMarkdown = strlen(formatted) > 8 + message.length();
|
shouldSendAsMarkdown = strlen(formatted) > 8 + message.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QJsonObject messageObject;
|
||||||
if(shouldSendAsMarkdown) {
|
if(shouldSendAsMarkdown) {
|
||||||
const QJsonObject messageObject {
|
messageObject = QJsonObject {
|
||||||
{"msgtype", "m.text"},
|
{"msgtype", "m.text"},
|
||||||
{"formatted_body", formatted},
|
{"formatted_body", formatted},
|
||||||
{"body", message},
|
{"body", message},
|
||||||
|
@ -446,15 +623,80 @@ void MatrixCore::sendMessage(Room* room, const QString& message) {
|
||||||
};
|
};
|
||||||
|
|
||||||
e->setMsg(formatted);
|
e->setMsg(formatted);
|
||||||
|
|
||||||
network::putJSON("/_matrix/client/r0/rooms/" + room->getId() + "/send/m.room.message/" + QRandomGenerator::global()->generate(), messageObject, onMessageFeedbackReceived);
|
|
||||||
} else {
|
} else {
|
||||||
const QJsonObject messageObject {
|
messageObject = QJsonObject {
|
||||||
{"msgtype", "m.text"},
|
{"msgtype", "m.text"},
|
||||||
{"body", message}
|
{"body", message}
|
||||||
};
|
};
|
||||||
|
|
||||||
network::putJSON(QString("/_matrix/client/r0/rooms/" + room->getId() + "/send/m.room.message/") + QRandomGenerator::global()->generate(), messageObject, onMessageFeedbackReceived);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -463,7 +705,7 @@ void MatrixCore::removeMessage(const QString& eventId) {
|
||||||
{"reason", ""}
|
{"reason", ""}
|
||||||
};
|
};
|
||||||
|
|
||||||
network::putJSON("/_matrix/client/r0/rooms/" + currentRoom->getId() + "/redact/" + eventId + "/" + QRandomGenerator::global()->generate(), reasonObject, [this, eventId](QNetworkReply* reply) {
|
network->putJSON("/_matrix/client/r0/rooms/" + currentRoom->getId() + "/redact/" + eventId + "/" + QString::number(QRandomGenerator::global()->generate()), reasonObject, [this, eventId](QNetworkReply* reply) {
|
||||||
auto& events = currentRoom->events;
|
auto& events = currentRoom->events;
|
||||||
for(int i = 0; i < events.size(); i++) {
|
for(int i = 0; i < events.size(); i++) {
|
||||||
if(events[i]->eventId == eventId) {
|
if(events[i]->eventId == eventId) {
|
||||||
|
@ -506,7 +748,7 @@ void MatrixCore::uploadAttachment(Room* room, const QString& path) {
|
||||||
e->setMsg(fileName);
|
e->setMsg(fileName);
|
||||||
e->setMsgType(mimeType.name().contains("image") ? "image" : "file");
|
e->setMsgType(mimeType.name().contains("image") ? "image" : "file");
|
||||||
|
|
||||||
network::postBinary("/_matrix/media/r0/upload?filename=" + f.fileName(), f.readAll(), mimeType.name(), [this, mimeType, fileName, fileSize, e](QNetworkReply* reply) {
|
network->postBinary("/_matrix/media/r0/upload?filename=" + f.fileName(), f.readAll(), mimeType.name(), [this, mimeType, fileName, fileSize, e](QNetworkReply* reply) {
|
||||||
if(!reply->error()) {
|
if(!reply->error()) {
|
||||||
e->setSent(true);
|
e->setSent(true);
|
||||||
|
|
||||||
|
@ -524,7 +766,7 @@ void MatrixCore::uploadAttachment(Room* room, const QString& path) {
|
||||||
{"info", infoObject}
|
{"info", infoObject}
|
||||||
};
|
};
|
||||||
|
|
||||||
network::putJSON("/_matrix/client/r0/rooms/" + currentRoom->getId() + "/send/m.room.message/" + QRandomGenerator::global()->generate(), imageObject);
|
network->putJSON("/_matrix/client/r0/rooms/" + currentRoom->getId() + "/send/m.room.message/" + QString::number(QRandomGenerator::global()->generate()), imageObject);
|
||||||
}
|
}
|
||||||
}, [e](qint64 sent, qint64 total) {
|
}, [e](qint64 sent, qint64 total) {
|
||||||
e->setSentProgress((double)sent / (double)total);
|
e->setSentProgress((double)sent / (double)total);
|
||||||
|
@ -540,7 +782,7 @@ void MatrixCore::startDirectChat(const QString& id) {
|
||||||
{"invite", QJsonArray{id}}
|
{"invite", QJsonArray{id}}
|
||||||
};
|
};
|
||||||
|
|
||||||
network::postJSON("/_matrix/client/r0/createRoom", roomObject, [](QNetworkReply*) {});
|
network->postJSON("/_matrix/client/r0/createRoom", roomObject, [](QNetworkReply*) {});
|
||||||
}
|
}
|
||||||
|
|
||||||
void MatrixCore::setTyping(Room* room) {
|
void MatrixCore::setTyping(Room* room) {
|
||||||
|
@ -549,11 +791,11 @@ void MatrixCore::setTyping(Room* room) {
|
||||||
{"timeout", 15000}
|
{"timeout", 15000}
|
||||||
};
|
};
|
||||||
|
|
||||||
network::putJSON("/_matrix/client/r0/rooms/" + room->getId() + "/typing/" + userId, typingObject);
|
network->putJSON("/_matrix/client/r0/rooms/" + room->getId() + "/typing/" + userId, typingObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MatrixCore::joinRoom(const QString& id) {
|
void MatrixCore::joinRoom(const QString& id) {
|
||||||
network::post("/_matrix/client/r0/rooms/" + id + "/join", [this, id](QNetworkReply* reply) {
|
network->post("/_matrix/client/r0/rooms/" + id + "/join", [this, id](QNetworkReply* reply) {
|
||||||
if(!reply->error()) {
|
if(!reply->error()) {
|
||||||
//check if its by an invite
|
//check if its by an invite
|
||||||
if(invitedRooms.contains(id)) {
|
if(invitedRooms.contains(id)) {
|
||||||
|
@ -576,7 +818,7 @@ void MatrixCore::joinRoom(const QString& id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MatrixCore::leaveRoom(const QString& id) {
|
void MatrixCore::leaveRoom(const QString& id) {
|
||||||
network::post("/_matrix/client/r0/rooms/" + id + "/leave");
|
network->post("/_matrix/client/r0/rooms/" + id + "/leave");
|
||||||
}
|
}
|
||||||
|
|
||||||
void MatrixCore::inviteToRoom(Room* room, const QString& userId) {
|
void MatrixCore::inviteToRoom(Room* room, const QString& userId) {
|
||||||
|
@ -584,14 +826,14 @@ void MatrixCore::inviteToRoom(Room* room, const QString& userId) {
|
||||||
{"user_id", userId}
|
{"user_id", userId}
|
||||||
};
|
};
|
||||||
|
|
||||||
network::postJSON("/_matrix/client/r0/rooms/" + room->getId() + "/invite", inviteObject, [](QNetworkReply*) {});
|
network->postJSON("/_matrix/client/r0/rooms/" + room->getId() + "/invite", inviteObject, [](QNetworkReply*) {});
|
||||||
}
|
}
|
||||||
|
|
||||||
void MatrixCore::updateMembers(Room* room) {
|
void MatrixCore::updateMembers(Room* room) {
|
||||||
if(!room)
|
if(!room)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
network::get("/_matrix/client/r0/rooms/" + room->getId() + "/members", [this, room](QNetworkReply* reply) {
|
network->get("/_matrix/client/r0/rooms/" + room->getId() + "/members", [this, room](QNetworkReply* reply) {
|
||||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||||
|
|
||||||
const QJsonArray& chunk = document.object()["chunk"].toArray();
|
const QJsonArray& chunk = document.object()["chunk"].toArray();
|
||||||
|
@ -620,7 +862,7 @@ void MatrixCore::updateMembers(Room* room) {
|
||||||
|
|
||||||
if(!memberJson["content"].toObject()["avatar_url"].isNull()) {
|
if(!memberJson["content"].toObject()["avatar_url"].isNull()) {
|
||||||
const QString imageId = memberJson["content"].toObject()["avatar_url"].toString().remove("mxc://");
|
const QString imageId = memberJson["content"].toObject()["avatar_url"].toString().remove("mxc://");
|
||||||
m->setAvatar(network::homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
m->setAvatar(network->homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
||||||
}
|
}
|
||||||
|
|
||||||
idToMember.insert(id, m);
|
idToMember.insert(id, m);
|
||||||
|
@ -648,7 +890,7 @@ void MatrixCore::readMessageHistory(Room* room) {
|
||||||
if(!room || room->prevBatch.isEmpty())
|
if(!room || room->prevBatch.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
network::get("/_matrix/client/r0/rooms/" + room->getId() + "/messages?from=" + room->prevBatch + "&dir=b", [this, room](QNetworkReply* reply) {
|
network->get("/_matrix/client/r0/rooms/" + room->getId() + "/messages?from=" + room->prevBatch + "&dir=b", [this, room](QNetworkReply* reply) {
|
||||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||||
|
|
||||||
room->prevBatch = document.object()["end"].toString();
|
room->prevBatch = document.object()["end"].toString();
|
||||||
|
@ -674,7 +916,7 @@ void MatrixCore::updateMemberCommunities(Member* member) {
|
||||||
{"user_ids", userIdsArray}
|
{"user_ids", userIdsArray}
|
||||||
};
|
};
|
||||||
|
|
||||||
network::postJSON("/_matrix/client/r0/publicised_groups", userIdsObject, [this, member](QNetworkReply* reply) {
|
network->postJSON("/_matrix/client/r0/publicised_groups", userIdsObject, [this, member](QNetworkReply* reply) {
|
||||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||||
|
|
||||||
for(const auto id : document.object()["users"].toObject()[member->getId()].toArray()) {
|
for(const auto id : document.object()["users"].toObject()[member->getId()].toArray()) {
|
||||||
|
@ -698,22 +940,23 @@ void MatrixCore::updateMemberCommunities(Member* member) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MatrixCore::settingsValid() {
|
bool MatrixCore::settingsValid() {
|
||||||
QSettings settings;
|
return !network->accessToken.isEmpty() && !network->homeserverURL.isEmpty();
|
||||||
return settings.contains("accessToken");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MatrixCore::setHomeserver(const QString& url) {
|
void MatrixCore::setHomeserver(const QString& url) {
|
||||||
network::homeserverURL = "https://" + url;
|
network->homeserverURL = "https://" + url;
|
||||||
|
|
||||||
network::get("/_matrix/client/versions", [this, url](QNetworkReply* reply) {
|
network->get("/_matrix/client/versions", [this, url](QNetworkReply* reply) {
|
||||||
if(!reply->error()) {
|
if(!reply->error()) {
|
||||||
homeserverURL = url;
|
homeserverURL = url;
|
||||||
|
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
|
settings.beginGroup(profileName);
|
||||||
settings.setValue("homeserver", url);
|
settings.setValue("homeserver", url);
|
||||||
|
settings.endGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
network::homeserverURL = "https://" + homeserverURL;
|
network->homeserverURL = "https://" + homeserverURL;
|
||||||
|
|
||||||
emit homeserverChanged(reply->error() == 0, reply->errorString());
|
emit homeserverChanged(reply->error() == 0, reply->errorString());
|
||||||
});
|
});
|
||||||
|
@ -798,10 +1041,10 @@ QString MatrixCore::getUsername() const {
|
||||||
return id.remove('@').split(':')[0];
|
return id.remove('@').split(':')[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
void MatrixCore::loadDirectory() {
|
void MatrixCore::loadDirectory(const QString& homeserver) {
|
||||||
const QJsonObject bodyObject;
|
const QJsonObject bodyObject;
|
||||||
|
|
||||||
network::postJSON("/_matrix/client/r0/publicRooms", bodyObject, [this](QNetworkReply* reply) {
|
network->postJSON("/_matrix/client/r0/publicRooms?server=" + homeserver, bodyObject, [this](QNetworkReply* reply) {
|
||||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||||
|
|
||||||
if(publicRooms.size() != document.object()["chunk"].toArray().size()) {
|
if(publicRooms.size() != document.object()["chunk"].toArray().size()) {
|
||||||
|
@ -820,7 +1063,7 @@ void MatrixCore::loadDirectory() {
|
||||||
|
|
||||||
if(!roomObject["avatar_url"].isNull()) {
|
if(!roomObject["avatar_url"].isNull()) {
|
||||||
const QString imageId = roomObject["avatar_url"].toString().remove("mxc://");
|
const QString imageId = roomObject["avatar_url"].toString().remove("mxc://");
|
||||||
r->setAvatar(network::homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
r->setAvatar(network->homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
||||||
}
|
}
|
||||||
|
|
||||||
r->setTopic(roomObject["topic"].toString());
|
r->setTopic(roomObject["topic"].toString());
|
||||||
|
@ -850,7 +1093,7 @@ void MatrixCore::readUpTo(Room* room, const int index) {
|
||||||
if(index < 0)
|
if(index < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
network::post("/_matrix/client/r0/rooms/" + room->getId() + "/receipt/m.read/" + room->events[index]->eventId);
|
network->post("/_matrix/client/r0/rooms/" + room->getId() + "/receipt/m.read/" + room->events[index]->eventId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MatrixCore::setMarkdownEnabled(const bool enabled) {
|
void MatrixCore::setMarkdownEnabled(const bool enabled) {
|
||||||
|
@ -878,8 +1121,8 @@ EmoteListModel* MatrixCore::getLocalEmoteListModel() {
|
||||||
return &localEmoteModel;
|
return &localEmoteModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
MemberModel* MatrixCore::getMemberModel() {
|
MemberListSortModel* MatrixCore::getMemberModel() {
|
||||||
return &memberModel;
|
return &memberSortModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MatrixCore::getHomeserverURL() const {
|
QString MatrixCore::getHomeserverURL() const {
|
||||||
|
@ -910,7 +1153,7 @@ void MatrixCore::consumeEvent(const QJsonObject& event, Room& room, const bool i
|
||||||
};
|
};
|
||||||
|
|
||||||
bool found = false;
|
bool found = false;
|
||||||
if(eventType == "m.room.message") {
|
if(eventType == "m.room.message" || eventType == "m.room.encrypted") {
|
||||||
for(size_t i = 0; i < unsentMessages.size(); i++) {
|
for(size_t i = 0; i < unsentMessages.size(); i++) {
|
||||||
if(event["sender"].toString() == userId && unsentMessages[i]->getRoom() == room.getId()) {
|
if(event["sender"].toString() == userId && unsentMessages[i]->getRoom() == room.getId()) {
|
||||||
found = true;
|
found = true;
|
||||||
|
@ -920,9 +1163,13 @@ void MatrixCore::consumeEvent(const QJsonObject& event, Room& room, const bool i
|
||||||
eventModel.updateEvent(unsentMessages[i]);
|
eventModel.updateEvent(unsentMessages[i]);
|
||||||
|
|
||||||
unsentMessages.removeAt(i);
|
unsentMessages.removeAt(i);
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(eventType == "m.room.member") {
|
}
|
||||||
|
|
||||||
|
if(eventType == "m.room.member") {
|
||||||
// avoid events tied to us
|
// avoid events tied to us
|
||||||
if(event["state_key"].toString() == userId)
|
if(event["state_key"].toString() == userId)
|
||||||
return;
|
return;
|
||||||
|
@ -935,11 +1182,75 @@ void MatrixCore::consumeEvent(const QJsonObject& event, Room& room, const bool i
|
||||||
|
|
||||||
if(!event["content"].toObject()["avatar_url"].isNull()) {
|
if(!event["content"].toObject()["avatar_url"].isNull()) {
|
||||||
const QString imageId = event["content"].toObject()["avatar_url"].toString().remove("mxc://");
|
const QString imageId = event["content"].toObject()["avatar_url"].toString().remove("mxc://");
|
||||||
room.setAvatar(network::homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
room.setAvatar(network->homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
roomListModel.updateRoom(&room);
|
roomListModel.updateRoom(&room);
|
||||||
|
} 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());
|
||||||
} else
|
} else
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -948,50 +1259,9 @@ void MatrixCore::consumeEvent(const QJsonObject& event, Room& room, const bool i
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if(!found && eventType == "m.room.message") {
|
if(!found && eventType == "m.room.message") {
|
||||||
const QString msgType = event["content"].toObject()["msgtype"].toString();
|
|
||||||
|
|
||||||
Event* e = new Event(&room);
|
Event* e = new Event(&room);
|
||||||
|
|
||||||
e->timestamp = QDateTime(QDate::currentDate(),
|
populateEvent(event, e);
|
||||||
QTime(QTime::currentTime().hour(),
|
|
||||||
QTime::currentTime().minute(),
|
|
||||||
QTime::currentTime().second(),
|
|
||||||
QTime::currentTime().msec() - 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);
|
|
||||||
|
|
||||||
addEvent(e);
|
addEvent(e);
|
||||||
|
|
||||||
|
@ -1000,11 +1270,51 @@ void MatrixCore::consumeEvent(const QJsonObject& event, Room& room, const bool i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
Community* MatrixCore::createCommunity(const QString& id) {
|
Community* MatrixCore::createCommunity(const QString& id) {
|
||||||
Community* community = new Community(this);
|
Community* community = new Community(this);
|
||||||
community->setId(id);
|
community->setId(id);
|
||||||
|
|
||||||
network::get("/_matrix/client/r0/groups/" + community->getId() + "/summary", [this, community](QNetworkReply* reply) {
|
network->get("/_matrix/client/r0/groups/" + community->getId() + "/summary", [this, community](QNetworkReply* reply) {
|
||||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||||
|
|
||||||
const QJsonObject& profile = document.object()["profile"].toObject();
|
const QJsonObject& profile = document.object()["profile"].toObject();
|
||||||
|
@ -1013,7 +1323,7 @@ Community* MatrixCore::createCommunity(const QString& id) {
|
||||||
|
|
||||||
if(!profile["avatar_url"].isNull()) {
|
if(!profile["avatar_url"].isNull()) {
|
||||||
const QString imageId = profile["avatar_url"].toString().remove("mxc://");
|
const QString imageId = profile["avatar_url"].toString().remove("mxc://");
|
||||||
community->setAvatar(network::homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
community->setAvatar(network->homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
||||||
}
|
}
|
||||||
|
|
||||||
community->setShortDescription(profile["short_description"].toString());
|
community->setShortDescription(profile["short_description"].toString());
|
||||||
|
@ -1027,12 +1337,12 @@ Community* MatrixCore::createCommunity(const QString& id) {
|
||||||
|
|
||||||
QString MatrixCore::getMXCThumbnailURL(QString url) {
|
QString MatrixCore::getMXCThumbnailURL(QString url) {
|
||||||
const QString imageId = url.remove("mxc://");
|
const QString imageId = url.remove("mxc://");
|
||||||
return network::homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale";
|
return network->homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale";
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MatrixCore::getMXCMediaURL(QString url) {
|
QString MatrixCore::getMXCMediaURL(QString url) {
|
||||||
const QString imageId = url.remove("mxc://");
|
const QString imageId = url.remove("mxc://");
|
||||||
return network::homeserverURL + "/_matrix/media/v1/download/" + imageId;
|
return network->homeserverURL + "/_matrix/media/v1/download/" + imageId;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MatrixCore::getDisplayName() const {
|
QString MatrixCore::getDisplayName() const {
|
||||||
|
@ -1054,3 +1364,102 @@ QString MatrixCore::getTypingText() const {
|
||||||
bool MatrixCore::getMarkdownEnabled() const {
|
bool MatrixCore::getMarkdownEnabled() const {
|
||||||
return markdownEnabled;
|
return markdownEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
12
src/membermodel.cpp
Normal file → Executable file
12
src/membermodel.cpp
Normal file → Executable file
|
@ -20,8 +20,17 @@ QVariant MemberModel::data(const QModelIndex &index, int role) const {
|
||||||
return room->members.at(index.row())->getDisplayName();
|
return room->members.at(index.row())->getDisplayName();
|
||||||
else if(role == AvatarURLRole)
|
else if(role == AvatarURLRole)
|
||||||
return room->members.at(index.row())->getAvatar();
|
return room->members.at(index.row())->getAvatar();
|
||||||
else
|
else if(role == IdRole)
|
||||||
return room->members.at(index.row())->getId();
|
return room->members.at(index.row())->getId();
|
||||||
|
else {
|
||||||
|
int powerLevel = room->powerLevelList[room->members.at(index.row())->getId()];
|
||||||
|
if(powerLevel == 100)
|
||||||
|
return "Admin";
|
||||||
|
else if(powerLevel == 50)
|
||||||
|
return "Moderator";
|
||||||
|
else
|
||||||
|
return "User";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QHash<int, QByteArray> MemberModel::roleNames() const {
|
QHash<int, QByteArray> MemberModel::roleNames() const {
|
||||||
|
@ -29,6 +38,7 @@ QHash<int, QByteArray> MemberModel::roleNames() const {
|
||||||
roles[DisplayNameRole] = "displayName";
|
roles[DisplayNameRole] = "displayName";
|
||||||
roles[AvatarURLRole] = "avatarURL";
|
roles[AvatarURLRole] = "avatarURL";
|
||||||
roles[IdRole] = "id";
|
roles[IdRole] = "id";
|
||||||
|
roles[SectionRole] = "section";
|
||||||
|
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
18
src/roomlistsortmodel.cpp
Normal file → Executable file
18
src/roomlistsortmodel.cpp
Normal file → Executable file
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "room.h"
|
#include "room.h"
|
||||||
#include "roomlistmodel.h"
|
#include "roomlistmodel.h"
|
||||||
|
#include "membermodel.h"
|
||||||
|
|
||||||
bool RoomListSortModel::lessThan(const QModelIndex& left, const QModelIndex& right) const {
|
bool RoomListSortModel::lessThan(const QModelIndex& left, const QModelIndex& right) const {
|
||||||
const QString sectionLeft = sourceModel()->data(left, RoomListModel::SectionRole).toString();
|
const QString sectionLeft = sourceModel()->data(left, RoomListModel::SectionRole).toString();
|
||||||
|
@ -15,3 +16,20 @@ bool RoomListSortModel::lessThan(const QModelIndex& left, const QModelIndex& rig
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MemberListSortModel::lessThan(const QModelIndex& left, const QModelIndex& right) const {
|
||||||
|
const QString sectionLeft = sourceModel()->data(left, MemberModel::SectionRole).toString();
|
||||||
|
const QString sectionRight = sourceModel()->data(right, MemberModel::SectionRole).toString();
|
||||||
|
|
||||||
|
if(sectionLeft == "Admin" && sectionRight == "Moderator")
|
||||||
|
return true;
|
||||||
|
else if(sectionLeft == "Moderator" && sectionRight == "User")
|
||||||
|
return true;
|
||||||
|
else if(sectionLeft == "Admin" && sectionRight == "User")
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if(sectionLeft == sectionRight)
|
||||||
|
return sourceModel()->data(left, MemberModel::DisplayNameRole).toString() < sourceModel()->data(right, MemberModel::DisplayNameRole).toString();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
Reference in a new issue