Archived
1
Fork 0
This repository has been archived on 2025-04-12. You can view files and clone it, but cannot push or open issues or pull requests.
trinity/src/matrixcore.cpp

1057 lines
36 KiB
C++
Raw Normal View History

2021-07-21 16:08:15 -04:00
#include "matrixcore.h"
#include <cmark.h>
#include <QJsonArray>
#include <QRandomGenerator>
#include <QSettings>
#include <QPixmap>
#include <QStandardPaths>
#include <QDir>
#include <QMimeDatabase>
#include <QMimeData>
#include "network.h"
#include "community.h"
MatrixCore::MatrixCore(QObject* parent) : QObject(parent), roomListModel(rooms), directoryListModel(publicRooms), eventModel(*this) {
QSettings settings;
homeserverURL = settings.value("homeserver", "matrix.org").toString();
userId = settings.value("userId").toString();
network::homeserverURL = "https://" + homeserverURL;
if(settings.contains("accessToken"))
network::accessToken = "Bearer " + settings.value("accessToken").toString();
emptyRoom.setName("Empty");
emptyRoom.setTopic("There is nothing here.");
roomListSortModel.setSourceModel(&roomListModel);
roomListSortModel.setSortRole(RoomListModel::SectionRole);
connect(this, &MatrixCore::roomListChanged, [this] {
roomListSortModel.sort(0);
});
directoryListSortModel.setSourceModel(&directoryListModel);
updateAccountInformation();
QString appDir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
if(!QDir(appDir).exists())
QDir().mkdir(appDir);
QString emotesDir = appDir + "/emotes";
if(!QDir(emotesDir).exists())
QDir().mkdir(emotesDir);
if(QDir(emotesDir).exists()) {
for(auto emote : QDir(emotesDir).entryInfoList()) {
// TODO: add support for more than just .png emotes
if(emote.fileName().contains(".png")) {
Emote* e = new Emote();
e->name = emote.fileName().remove(".png");
e->path = emote.absoluteFilePath();
emotes.push_back(e);
}
}
}
localEmoteModel.setList(&emotes);
}
void MatrixCore::registerAccount(const QString &username, const QString &password, const QString& session, const QString& type) {
QJsonObject authObject;
if(!session.isEmpty()) {
authObject["type"] = type;
authObject["session"] = session;
}
const QJsonObject registerObject {
{"auth", authObject},
{"username", username},
{"password", password}
};
network::postJSON("/_matrix/client/r0/register?kind=user", registerObject, [this](QNetworkReply* reply) {
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
if(reply->error()) {
if(document.object().contains("flows")) {
const QString stage = document.object()["flows"].toArray()[0].toObject()["stages"].toArray()[0].toString();
if(stage == "m.login.recaptcha") {
const QJsonObject data {
{"public_key", document.object()["params"].toObject()["m.login.recaptcha"].toObject()["public_key"].toString()},
{"session", document.object()["session"].toString()},
{"type", "m.login.recaptcha"}
};
emit registerFlow(data);
} else if(stage == "m.login.dummy") {
const QJsonObject data {
{"session", document.object()["session"].toString()},
{"type", "m.login.dummy"}
};
emit registerFlow(data);
} else {
emit registerAttempt(true, "Unknown stage type " + stage);
}
} else {
emit registerAttempt(true, document.object()["error"].toString());
}
} else {
network::accessToken = "Bearer " + document.object()["access_token"].toString();
QSettings settings;
settings.setValue("accessToken", document.object()["access_token"].toString());
settings.setValue("userId", document.object()["user_id"].toString());
settings.setValue("deviceId", document.object()["device_id"].toString());
emit registerAttempt(false, "");
}
});
}
void MatrixCore::login(const QString& username, const QString& password) {
const QJsonObject loginObject {
{"type", "m.login.password"},
{"user", username},
{"password", password},
{"initial_device_display_name", "Trinity"}
};
network::postJSON("/_matrix/client/r0/login", loginObject, [this](QNetworkReply* reply) {
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
if(reply->error()) {
emit loginAttempt(true, document.object()["error"].toString());
} else {
network::accessToken = "Bearer " + document.object()["access_token"].toString();
QSettings settings;
settings.setValue("accessToken", document.object()["access_token"].toString());
settings.setValue("userId", document.object()["user_id"].toString());
settings.setValue("deviceId", document.object()["device_id"].toString());
emit loginAttempt(false, "");
}
});
}
void MatrixCore::logout() {
network::post("/_matrix/client/r0/logout");
QSettings settings;
settings.remove("accessToken");
settings.remove("deviceId");
settings.remove("userId");
settings.sync();
}
void MatrixCore::updateAccountInformation() {
network::get("/_matrix/client/r0/profile/" + userId + "/displayname", [this](QNetworkReply* reply) {
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
displayName = document.object()["displayname"].toString();
emit displayNameChanged();
});
}
void MatrixCore::setDisplayName(const QString& name) {
displayName = name;
const QJsonObject displayNameObject {
{"displayname", name}
};
network::putJSON("/_matrix/client/r0/profile/" + userId + "/displayname", displayNameObject, [this, name](QNetworkReply* reply) {
emit displayNameChanged();
});
}
void MatrixCore::sync() {
if(network::accessToken.isEmpty())
return;
QString url = "/_matrix/client/r0/sync";
if(!nextBatch.isEmpty())
url += "?since=" + nextBatch;
network::get(url, [this](QNetworkReply* reply) {
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
if(!document.object()["next_batch"].isNull())
nextBatch = document.object()["next_batch"].toString();
const auto createRoom = [this](const QString id, const QString joinState) {
roomListModel.beginInsertRoom();
Room* room = new Room(this);
room->setId(id);
room->setJoinState(joinState);
QSettings settings;
settings.beginGroup(id);
if(settings.contains("notificationLevel"))
room->setNotificationLevel(settings.value("notificationLevel").toInt(), true);
else
room->setNotificationLevel(1);
settings.endGroup();
network::get("/_matrix/client/r0/rooms/" + id + "/state/m.room.name", [this, room](QNetworkReply* reply) {
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
if(document.object()["errcode"].toString() == "M_GUEST_ACCESS_FORBIDDEN") {
room->setGuestDenied(true);
return;
} else if(document.object()["errcode"].toString() == "M_NOT_FOUND")
return;
room->setName(document.object()["name"].toString());
roomListModel.updateRoom(room);
});
network::get("/_matrix/client/r0/rooms/" + id + "/state/m.room.topic", [this, room](QNetworkReply* reply) {
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
if(document.object()["errcode"].toString() == "M_GUEST_ACCESS_FORBIDDEN") {
room->setGuestDenied(true);
return;
}
room->setTopic(document.object()["topic"].toString());
roomListModel.updateRoom(room);
});
network::get("/_matrix/client/r0/rooms/" + id + "/state/m.room.avatar", [this, room](QNetworkReply* reply) {
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
if(document.object()["errcode"].toString() == "M_GUEST_ACCESS_FORBIDDEN") {
room->setGuestDenied(true);
return;
}
if(document.object().contains("url")) {
const QString imageId = document.object()["url"].toString().remove("mxc://");
room->setAvatar(network::homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
}
roomListModel.updateRoom(room);
});
rooms.push_back(room);
idToRoom.insert(id, room);
roomListModel.endInsertRoom();
emit roomListChanged();
updateMembers(room);
return room;
};
for(const auto id : document.object()["rooms"].toObject()["invite"].toObject().keys()) {
if(!invitedRooms.count(id)) {
Room* room = createRoom(id, "Invited");
for(auto event : document.object()["rooms"].toObject()["invite"].toObject()[id].toObject()["invite_state"].toObject()["events"].toArray()) {
const QString type = event.toObject()["type"].toString();
if(type == "m.room.member")
room->setInvitedBy(event.toObject()["sender"].toString());
else if(type == "m.room.name") {
room->setName(event.toObject()["content"].toObject()["name"].toString());
} else if(type == "m.room.avatar") {
const QString imageId = event.toObject()["content"].toObject()["url"].toString().remove("mxc://");
room->setAvatar(network::homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
}
roomListModel.updateRoom(room);
}
invitedRooms.push_back(id);
}
}
for(const auto id : document.object()["rooms"].toObject()["join"].toObject().keys()) {
if(!joinedRooms.count(id)) {
createRoom(id, "Joined");
joinedRooms.push_back(id);
}
}
for(const auto id : document.object()["rooms"].toObject()["leave"].toObject().keys()) {
if(joinedRooms.count(id)) {
Room* room = resolveRoomId(id);
room->setJoinState("left");
joinedRooms.removeOne(id);
rooms.removeOne(room);
roomListModel.fullUpdate();
}
}
unsigned int i = 0;
for(const auto& room : document.object()["rooms"].toObject()["join"].toObject()) {
Room* roomState = nullptr;
for(auto& r : rooms) {
if(r->getId() == document.object()["rooms"].toObject()["join"].toObject().keys()[i])
roomState = r;
}
if(!roomState)
continue;
if(firstSync)
roomState->prevBatch = room.toObject()["timeline"].toObject()["prev_batch"].toString();
const int highlightCount = room.toObject()["unread_notifications"].toObject()["highlight_count"].toInt();
const int notificationCount = room.toObject()["unread_notifications"].toObject()["notification_count"].toInt();
if(highlightCount != roomState->getHighlightCount()) {
roomState->setNotificationCount(highlightCount);
roomListModel.updateRoom(roomState);
}
if(notificationCount != roomState->getNotificationCount()) {
roomState->setNotificationCount(notificationCount);
roomListModel.updateRoom(roomState);
}
for(const auto event : room.toObject()["ephemeral"].toObject()["events"].toArray()) {
const QString eventType = event.toObject()["type"].toString();
if(eventType == "m.typing") {
auto typing = event.toObject()["content"].toObject()["user_ids"].toArray();
QString typingText;
int trueSize = 0;
if(typing.size() < 4) {
for(int i = 0; i < typing.size(); i++) {
if(typing[i].toString() == userId)
continue;
const Member* member = resolveMemberId(typing[i].toString());
if(!member)
continue;
typingText += member->getDisplayName();
if(i != typing.size() - 1)
typingText += ", ";
trueSize++;
}
typingText += " is";
} else {
typingText = "Several people are";
}
if(trueSize != 0)
this->typingText = typingText + " typing...";
else
this->typingText.clear();
emit typingTextChanged();
}
}
for(const auto event : room.toObject()["timeline"].toObject()["events"].toArray())
consumeEvent(event.toObject(), *roomState);
i++;
}
for(const auto& id : document.object()["groups"].toObject()["join"].toObject().keys()) {
if(!joinedCommunitiesIds.count(id)) {
Community* community = nullptr;
if(!idToCommunity.count(id))
community = createCommunity(id);
else
community = idToCommunity[id];
community->setJoinState("Joined");
joinedCommunities.push_back(community);
joinedCommunitiesIds.push_back(community->getId());
emit joinedCommunitiesChanged();
}
}
if(firstSync) {
firstSync = false;
emit initialSyncFinished();
}
emit syncFinished();
});
}
void MatrixCore::sendMessage(Room* room, const QString& message) {
if(message.isEmpty())
return;
Event* e = new Event(room);
e->setSender(userId);
e->timestamp = QDateTime::currentDateTime();
e->setMsg(message);
e->setRoom(room->getId());
e->setSent(false);
e->setMsgType("text");
QString msg = e->getMsg();
for(const auto& emote : emotes) {
msg.replace(":" + emote->name + ":", "<img src='file://" + emote->path + "' width=22 height=22/>");
}
e->setMsg(msg);
eventModel.beginUpdate(0);
room->events.push_front(e);
eventModel.endUpdate();
unsentMessages.push_back(e);
const auto onMessageFeedbackReceived = [this, e](QNetworkReply* reply) {
if(!reply->error()) {
for(size_t i = 0; i < unsentMessages.size(); i++) {
if(unsentMessages[i] == e)
e->setSent(true);
}
} else {
qDebug() << reply->readAll();
}
};
bool shouldSendAsMarkdown = false;
char* formatted = nullptr;
if(markdownEnabled) {
formatted = cmark_markdown_to_html(message.toStdString().c_str(), message.length(), CMARK_OPT_DEFAULT | CMARK_OPT_HARDBREAKS);
shouldSendAsMarkdown = strlen(formatted) > 8 + message.length();
}
if(shouldSendAsMarkdown) {
const QJsonObject messageObject {
{"msgtype", "m.text"},
{"formatted_body", formatted},
{"body", message},
{"format", "org.matrix.custom.html"}
};
e->setMsg(formatted);
network::putJSON("/_matrix/client/r0/rooms/" + room->getId() + "/send/m.room.message/" + QRandomGenerator::global()->generate(), messageObject, onMessageFeedbackReceived);
} else {
const QJsonObject messageObject {
{"msgtype", "m.text"},
{"body", message}
};
network::putJSON(QString("/_matrix/client/r0/rooms/" + room->getId() + "/send/m.room.message/") + QRandomGenerator::global()->generate(), messageObject, onMessageFeedbackReceived);
}
}
void MatrixCore::removeMessage(const QString& eventId) {
const QJsonObject reasonObject {
{"reason", ""}
};
network::putJSON("/_matrix/client/r0/rooms/" + currentRoom->getId() + "/redact/" + eventId + "/" + QRandomGenerator::global()->generate(), reasonObject, [this, eventId](QNetworkReply* reply) {
auto& events = currentRoom->events;
for(int i = 0; i < events.size(); i++) {
if(events[i]->eventId == eventId) {
eventModel.beginRemoveEvent(i, 0);
events.removeAt(i);
eventModel.endRemoveEvent();
}
}
});
}
void MatrixCore::uploadAttachment(Room* room, const QString& path) {
Event* e = new Event(room);
e->setSender(userId);
e->timestamp = QDateTime::currentDateTime();
e->setRoom(room->getId());
e->setSent(false);
eventModel.beginUpdate(0);
room->events.push_front(e);
eventModel.endUpdate();
unsentMessages.push_back(e);
QMimeDatabase mimeDb;
QMimeType mimeType = mimeDb.mimeTypeForFile(path);
QString filepath = path;
filepath.remove("file://");
QFile f(filepath);
f.open(QFile::ReadOnly);
const QString fileName = QFileInfo(f.fileName()).fileName();
const qint64 fileSize = f.size();
e->setAttachment(path);
e->setThumbnail(path);
e->setMsg(fileName);
e->setMsgType(mimeType.name().contains("image") ? "image" : "file");
network::postBinary("/_matrix/media/r0/upload?filename=" + f.fileName(), f.readAll(), mimeType.name(), [this, mimeType, fileName, fileSize, e](QNetworkReply* reply) {
if(!reply->error()) {
e->setSent(true);
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
QJsonObject infoObject {
{"mimetype", mimeType.name()},
{"size", fileSize},
};
const QJsonObject imageObject {
{"msgtype", mimeType.name().contains("image") ? "m.image" : "m.file"},
{"body", fileName},
{"url", document.object()["content_uri"].toString()},
{"info", infoObject}
};
network::putJSON("/_matrix/client/r0/rooms/" + currentRoom->getId() + "/send/m.room.message/" + QRandomGenerator::global()->generate(), imageObject);
}
}, [e](qint64 sent, qint64 total) {
e->setSentProgress((double)sent / (double)total);
});
}
void MatrixCore::startDirectChat(const QString& id) {
const QJsonObject roomObject {
{"visibility", "private"},
{"creation_content", QJsonObject{{"m.federate", false}}},
{"preset", "private_chat"},
{"is_direct", true},
{"invite", QJsonArray{id}}
};
network::postJSON("/_matrix/client/r0/createRoom", roomObject, [](QNetworkReply*) {});
}
void MatrixCore::setTyping(Room* room) {
const QJsonObject typingObject {
{"typing", true},
{"timeout", 15000}
};
network::putJSON("/_matrix/client/r0/rooms/" + room->getId() + "/typing/" + userId, typingObject);
}
void MatrixCore::joinRoom(const QString& id) {
network::post("/_matrix/client/r0/rooms/" + id + "/join", [this, id](QNetworkReply* reply) {
if(!reply->error()) {
//check if its by an invite
if(invitedRooms.contains(id)) {
invitedRooms.removeOne(id);
joinedRooms.push_back(id);
for(const auto roomObject : rooms) {
if(roomObject->getId() == id) {
roomObject->setJoinState("Joined");
roomObject->setGuestDenied(false);
emit roomListChanged();
return;
}
}
}
}
});
}
void MatrixCore::leaveRoom(const QString& id) {
network::post("/_matrix/client/r0/rooms/" + id + "/leave");
}
void MatrixCore::inviteToRoom(Room* room, const QString& userId) {
const QJsonObject inviteObject {
{"user_id", userId}
};
network::postJSON("/_matrix/client/r0/rooms/" + room->getId() + "/invite", inviteObject, [](QNetworkReply*) {});
}
void MatrixCore::updateMembers(Room* room) {
if(!room)
return;
network::get("/_matrix/client/r0/rooms/" + room->getId() + "/members", [this, room](QNetworkReply* reply) {
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
const QJsonArray& chunk = document.object()["chunk"].toArray();
size_t realSize = 0;
for(const auto& member : chunk) {
if(member.toObject()["content"].toObject()["membership"].toString() == "join")
realSize++;
}
if(room->members.size() != realSize) {
room->members.clear();
room->members.reserve(realSize);
for(const auto& member : chunk) {
const QJsonObject& memberJson = member.toObject();
if(memberJson["content"].toObject()["membership"].toString() == "join") {
const QString& id = memberJson["state_key"].toString();
Member* m = nullptr;
if(!idToMember.contains(id)) {
m = new Member(this);
m->setId(id);
m->setDisplayName(memberJson["content"].toObject()["displayname"].toString());
if(!memberJson["content"].toObject()["avatar_url"].isNull()) {
const QString imageId = memberJson["content"].toObject()["avatar_url"].toString().remove("mxc://");
m->setAvatar(network::homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
}
idToMember.insert(id, m);
} else {
m = idToMember[id];
}
if(currentRoom == room) {
eventModel.updateEventsByMember(id);
memberModel.beginUpdate(0);
}
room->members.push_back(m);
if(currentRoom == room)
memberModel.endUpdate();
}
}
}
});
}
void MatrixCore::readMessageHistory(Room* room) {
if(!room || room->prevBatch.isEmpty())
return;
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());
room->prevBatch = document.object()["end"].toString();
traversingHistory = true;
for(const auto event : document.object()["chunk"].toArray())
consumeEvent(event.toObject(), *room, false);
traversingHistory = false;
});
}
void MatrixCore::updateMemberCommunities(Member* member) {
if(!member)
return;
const QJsonArray userIdsArray {
{member->getId()}
};
const QJsonObject userIdsObject {
{"user_ids", userIdsArray}
};
network::postJSON("/_matrix/client/r0/publicised_groups", userIdsObject, [this, member](QNetworkReply* reply) {
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
for(const auto id : document.object()["users"].toObject()[member->getId()].toArray()) {
bool found = false;
for(const auto community : member->getPublicCommunities()) {
if(community->getId() == id.toString())
found = true;
}
if(!found) {
Community* community = nullptr;
if(!idToCommunity.count(id.toString()))
community = createCommunity(id.toString());
else
community = idToCommunity[id.toString()];
member->addCommunity(community);
}
}
});
}
bool MatrixCore::settingsValid() {
QSettings settings;
return settings.contains("accessToken");
}
void MatrixCore::setHomeserver(const QString& url) {
network::homeserverURL = "https://" + url;
network::get("/_matrix/client/versions", [this, url](QNetworkReply* reply) {
if(!reply->error()) {
homeserverURL = url;
QSettings settings;
settings.setValue("homeserver", url);
}
network::homeserverURL = "https://" + homeserverURL;
emit homeserverChanged(reply->error() == 0, reply->errorString());
});
}
void MatrixCore::changeCurrentRoom(Room* room) {
currentRoom = room;
eventModel.setRoom(room);
eventModel.fullUpdate();
memberModel.setRoom(room);
memberModel.fullUpdate();
emit currentRoomChanged();
}
void MatrixCore::changeCurrentRoom(const unsigned int index) {
if(index < rooms.size())
changeCurrentRoom(rooms[index]);
else
changeCurrentRoom(&emptyRoom);
}
void MatrixCore::addEmote(const QString& url) {
qDebug() << "adding emote " << url;
QString newUrl = url;
newUrl.remove("file://");
QUrl file(newUrl);
QString appDir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
if(!QDir(appDir).exists())
QDir().mkdir(appDir);
QString emotesDir = appDir + "/emotes";
if(!QDir(emotesDir).exists())
QDir().mkdir(emotesDir);
QPixmap pixmap(newUrl);
pixmap = pixmap.scaled(22, 22, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
pixmap.save(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + "/emotes/" + file.fileName());
Emote* emote = new Emote();
emote->path = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + "/emotes/" + file.fileName();
emote->name = file.fileName().remove(".png");
emotes.push_back(emote);
emit localEmotesChanged();
localEmoteModel.update();
}
void MatrixCore::deleteEmote(Emote* emote) {
emotes.removeOne(emote);
QFile(emote->path).remove();
emit localEmotesChanged();
localEmoteModel.update();
}
Member* MatrixCore::resolveMemberId(const QString& id) const {
return idToMember.value(id);
}
Community* MatrixCore::resolveCommunityId(const QString &id) const {
return idToCommunity.value(id);
}
Room* MatrixCore::resolveRoomId(const QString &id) const {
return idToRoom.value(id);
}
Room* MatrixCore::getRoom(const unsigned int index) const {
return rooms[index];
}
QString MatrixCore::getUsername() const {
QString id = userId;
return id.remove('@').split(':')[0];
}
void MatrixCore::loadDirectory() {
const QJsonObject bodyObject;
network::postJSON("/_matrix/client/r0/publicRooms", bodyObject, [this](QNetworkReply* reply) {
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
if(publicRooms.size() != document.object()["chunk"].toArray().size()) {
publicRooms.clear();
publicRooms.reserve(document.object()["chunk"].toArray().size());
for(const auto room : document.object()["chunk"].toArray()) {
const QJsonObject& roomObject = room.toObject();
const QString& roomId = roomObject["room_id"].toString();
Room* r = nullptr;
if(!idToRoom.contains(roomId)) {
r = new Room(this);
r->setId(roomId);
r->setName(roomObject["name"].toString());
if(!roomObject["avatar_url"].isNull()) {
const QString imageId = roomObject["avatar_url"].toString().remove("mxc://");
r->setAvatar(network::homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
}
r->setTopic(roomObject["topic"].toString());
idToRoom.insert(roomId, r);
} else {
r = idToRoom.value(roomId);
}
directoryListModel.beginInsertRoom();
publicRooms.push_back(r);
directoryListModel.endInsertRoom();
emit publicRoomsChanged();
}
}
});
}
void MatrixCore::readUpTo(Room* room, const int index) {
if(!room)
return;
if(room->events.size() == 0)
return;
if(index < 0)
return;
network::post("/_matrix/client/r0/rooms/" + room->getId() + "/receipt/m.read/" + room->events[index]->eventId);
}
void MatrixCore::setMarkdownEnabled(const bool enabled) {
markdownEnabled = enabled;
emit markdownEnabledChanged();
}
Room* MatrixCore::getCurrentRoom() {
return currentRoom != nullptr ? currentRoom : &emptyRoom;
}
EventModel* MatrixCore::getEventModel() {
return &eventModel;
}
RoomListSortModel* MatrixCore::getRoomListModel() {
return &roomListSortModel;
}
RoomListSortModel* MatrixCore::getDirectoryListModel() {
return &directoryListSortModel;
}
EmoteListModel* MatrixCore::getLocalEmoteListModel() {
return &localEmoteModel;
}
MemberModel* MatrixCore::getMemberModel() {
return &memberModel;
}
QString MatrixCore::getHomeserverURL() const {
return homeserverURL;
}
void MatrixCore::consumeEvent(const QJsonObject& event, Room& room, const bool insertFront) {
const QString eventType = event["type"].toString();
const auto addEvent = [&room, insertFront, this](Event* object) {
if(insertFront) {
if(&room == currentRoom)
eventModel.beginUpdate(0);
room.events.push_front(object);
if(&room == currentRoom)
eventModel.endHistory();
} else {
if(&room == currentRoom)
eventModel.beginHistory(0);
room.events.push_back(object);
if(&room == currentRoom)
eventModel.endHistory();
}
};
bool found = false;
if(eventType == "m.room.message") {
for(size_t i = 0; i < unsentMessages.size(); i++) {
if(event["sender"].toString() == userId && unsentMessages[i]->getRoom() == room.getId()) {
found = true;
unsentMessages[i]->setSent(true);
if(currentRoom == &room)
eventModel.updateEvent(unsentMessages[i]);
unsentMessages.removeAt(i);
}
}
} else if(eventType == "m.room.member") {
// avoid events tied to us
if(event["state_key"].toString() == userId)
return;
if(event["content"].toObject().contains("is_direct"))
room.setDirect(event["content"].toObject()["is_direct"].toBool());
if(room.getDirect()) {
room.setName(event["content"].toObject()["displayname"].toString());
if(!event["content"].toObject()["avatar_url"].isNull()) {
const QString imageId = event["content"].toObject()["avatar_url"].toString().remove("mxc://");
room.setAvatar(network::homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
}
}
roomListModel.updateRoom(&room);
} else
return;
// don't show redacted messages
if(event["unsigned"].toObject().keys().contains("redacted_because"))
return;
if(!found && eventType == "m.room.message") {
const QString msgType = event["content"].toObject()["msgtype"].toString();
Event* e = new Event(&room);
e->timestamp = QDateTime(QDate::currentDate(),
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);
if(!firstSync && !traversingHistory)
emit message(&room, e->getSender(), e->getMsg());
}
}
Community* MatrixCore::createCommunity(const QString& id) {
Community* community = new Community(this);
community->setId(id);
network::get("/_matrix/client/r0/groups/" + community->getId() + "/summary", [this, community](QNetworkReply* reply) {
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
const QJsonObject& profile = document.object()["profile"].toObject();
community->setName(profile["name"].toString());
if(!profile["avatar_url"].isNull()) {
const QString imageId = profile["avatar_url"].toString().remove("mxc://");
community->setAvatar(network::homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
}
community->setShortDescription(profile["short_description"].toString());
community->setLongDescription(profile["long_description"].toString());
idToCommunity.insert(community->getId(), community);
});
return community;
}
QString MatrixCore::getMXCThumbnailURL(QString url) {
const QString imageId = url.remove("mxc://");
return network::homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale";
}
QString MatrixCore::getMXCMediaURL(QString url) {
const QString imageId = url.remove("mxc://");
return network::homeserverURL + "/_matrix/media/v1/download/" + imageId;
}
QString MatrixCore::getDisplayName() const {
return displayName;
}
QVariantList MatrixCore::getJoinedCommunitiesList() const {
QVariantList list;
for(const auto community : joinedCommunities)
list.push_back(QVariant::fromValue(community));
return list;
}
QString MatrixCore::getTypingText() const {
return typingText;
}
bool MatrixCore::getMarkdownEnabled() const {
return markdownEnabled;
}