nheko/src/MatrixClient.cc
christarazi cd9d1a2ec6 Support audio, video, generic file for pasting (#220)
* Refactor widget items to use same interface

* Support audio, video, generic file for pasting

* Add utils function for human readable file sizes

* Set correct MIME type for media messages

This change also determines the size of the upload once from the
ContentLengthHeader, rather than seeking the QIODevice and asking for
its size. This prevents any future trouble in case the QIODevice is
sequential (cannot be seeked). The MIME type is also determined at
upload once, rather than using the QIODevice and the underlying data
inside.

* Allow for file urls to be used as fall-back

This fixes an issue on macOS which uses `text/uri-list` for copying
files to the clipboard.

fixes #228
2018-02-18 22:52:31 +02:00

1237 lines
41 KiB
C++

/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QDebug>
#include <QFile>
#include <QImageReader>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QMimeDatabase>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QPixmap>
#include <QSettings>
#include <QUrlQuery>
#include "Login.h"
#include "MatrixClient.h"
#include "Register.h"
MatrixClient::MatrixClient(QString server, QObject *parent)
: QNetworkAccessManager(parent)
, clientApiUrl_{"/_matrix/client/r0"}
, mediaApiUrl_{"/_matrix/media/r0"}
, server_{"https://" + server}
{
QSettings settings;
txn_id_ = settings.value("client/transaction_id", 1).toInt();
QJsonObject default_filter{
{
"room",
QJsonObject{
{"include_leave", true},
{
"account_data",
QJsonObject{
{"not_types", QJsonArray{"*"}},
},
},
},
},
{
"account_data",
QJsonObject{
{"not_types", QJsonArray{"*"}},
},
},
{
"presence",
QJsonObject{
{"not_types", QJsonArray{"*"}},
},
},
};
filter_ = settings
.value("client/sync_filter",
QJsonDocument(default_filter).toJson(QJsonDocument::Compact))
.toString();
connect(this,
&QNetworkAccessManager::networkAccessibleChanged,
this,
[=](NetworkAccessibility status) {
if (status != NetworkAccessibility::Accessible)
setNetworkAccessible(NetworkAccessibility::Accessible);
});
}
void
MatrixClient::reset() noexcept
{
next_batch_.clear();
server_.clear();
token_.clear();
txn_id_ = 0;
}
void
MatrixClient::login(const QString &username, const QString &password) noexcept
{
QUrl endpoint(server_);
endpoint.setPath(clientApiUrl_ + "/login");
QNetworkRequest request(endpoint);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
LoginRequest body(username, password);
auto reply = post(request, body.serialize());
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
reply->deleteLater();
int status_code =
reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status_code == 403) {
emit loginError(tr("Wrong username or password"));
return;
}
if (status_code == 404) {
emit loginError(tr("Login endpoint was not found on the server"));
return;
}
if (status_code >= 400) {
qWarning() << "Login error: " << reply->errorString();
emit loginError(tr("An unknown error occured. Please try again."));
return;
}
try {
mtx::responses::Login login =
nlohmann::json::parse(reply->readAll().data());
auto hostname = server_.host();
if (server_.port() > 0)
hostname = QString("%1:%2").arg(server_.host()).arg(server_.port());
emit loginSuccess(QString::fromStdString(login.user_id.toString()),
hostname,
QString::fromStdString(login.access_token));
} catch (std::exception &e) {
qWarning() << "Malformed JSON response" << e.what();
emit loginError(tr("Malformed response. Possibly not a Matrix server"));
}
});
}
void
MatrixClient::logout() noexcept
{
QUrlQuery query;
query.addQueryItem("access_token", token_);
QUrl endpoint(server_);
endpoint.setPath(clientApiUrl_ + "/logout");
endpoint.setQuery(query);
QNetworkRequest request(endpoint);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QJsonObject body{};
auto reply = post(request, QJsonDocument(body).toJson(QJsonDocument::Compact));
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status != 200) {
qWarning() << "Logout error: " << reply->errorString();
return;
}
emit loggedOut();
});
}
void
MatrixClient::registerUser(const QString &user, const QString &pass, const QString &server) noexcept
{
setServer(server);
QUrlQuery query;
query.addQueryItem("kind", "user");
QUrl endpoint(server_);
endpoint.setPath(clientApiUrl_ + "/register");
endpoint.setQuery(query);
QNetworkRequest request(QString(endpoint.toEncoded()));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
RegisterRequest body(user, pass);
auto reply = post(request, body.serialize());
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
auto data = reply->readAll();
auto json = QJsonDocument::fromJson(data);
if (status == 0 || status >= 400) {
if (json.isObject() && json.object().contains("error"))
emit registerError(json.object().value("error").toString());
else
emit registerError(reply->errorString());
return;
}
RegisterResponse response;
try {
response.deserialize(json);
emit registerSuccess(response.getUserId(),
response.getHomeServer(),
response.getAccessToken());
} catch (DeserializationException &e) {
qWarning() << "Register" << e.what();
emit registerError("Received malformed response.");
}
});
}
void
MatrixClient::sync() noexcept
{
// the filter is not uploaded yet (so it is a json with { at the beginning)
// ignore for now that the filter might be uploaded multiple times as we expect
// servers to do deduplication
if (filter_.startsWith("{")) {
uploadFilter(filter_);
}
QUrlQuery query;
query.addQueryItem("set_presence", "online");
query.addQueryItem("filter", filter_);
query.addQueryItem("timeout", "30000");
query.addQueryItem("access_token", token_);
if (next_batch_.isEmpty()) {
qDebug() << "Sync requires a valid next_batch token. Initial sync should "
"be performed.";
return;
}
query.addQueryItem("since", next_batch_);
QUrl endpoint(server_);
endpoint.setPath(clientApiUrl_ + "/sync");
endpoint.setQuery(query);
QNetworkRequest request(QString(endpoint.toEncoded()));
auto reply = get(request);
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status == 0 || status >= 400) {
qDebug() << reply->errorString();
return;
}
auto data = reply->readAll();
try {
mtx::responses::Sync response = nlohmann::json::parse(data);
emit syncCompleted(response);
} catch (std::exception &e) {
qWarning() << "Sync error: " << e.what();
}
});
}
void
MatrixClient::sendRoomMessage(mtx::events::MessageType ty,
int txnId,
const QString &roomid,
const QString &msg,
const QString &mime,
const int64_t media_size,
const QString &url) noexcept
{
QUrlQuery query;
query.addQueryItem("access_token", token_);
QUrl endpoint(server_);
endpoint.setPath(clientApiUrl_ +
QString("/rooms/%1/send/m.room.message/%2").arg(roomid).arg(txnId));
endpoint.setQuery(query);
QJsonObject body;
QJsonObject info = {{"size", static_cast<qint64>(media_size)}, {"mimetype", mime}};
switch (ty) {
case mtx::events::MessageType::Text:
body = {{"msgtype", "m.text"}, {"body", msg}};
break;
case mtx::events::MessageType::Emote:
body = {{"msgtype", "m.emote"}, {"body", msg}};
break;
case mtx::events::MessageType::Image:
body = {{"msgtype", "m.image"}, {"body", msg}, {"url", url}, {"info", info}};
break;
case mtx::events::MessageType::File:
body = {{"msgtype", "m.file"}, {"body", msg}, {"url", url}, {"info", info}};
break;
case mtx::events::MessageType::Audio:
body = {{"msgtype", "m.audio"}, {"body", msg}, {"url", url}, {"info", info}};
break;
case mtx::events::MessageType::Video:
body = {{"msgtype", "m.video"}, {"body", msg}, {"url", url}, {"info", info}};
break;
default:
qDebug() << "SendRoomMessage: Unknown message type for" << msg;
return;
}
QNetworkRequest request(QString(endpoint.toEncoded()));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
auto reply = put(request, QJsonDocument(body).toJson(QJsonDocument::Compact));
connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, txnId]() {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status == 0 || status >= 400) {
emit messageSendFailed(roomid, txnId);
return;
}
auto data = reply->readAll();
if (data.isEmpty()) {
emit messageSendFailed(roomid, txnId);
return;
}
auto json = QJsonDocument::fromJson(data);
if (!json.isObject()) {
qDebug() << "Send message response is not a JSON object";
emit messageSendFailed(roomid, txnId);
return;
}
auto object = json.object();
if (!object.contains("event_id")) {
qDebug() << "SendTextMessage: missing event_id from response";
emit messageSendFailed(roomid, txnId);
return;
}
emit messageSent(object.value("event_id").toString(), roomid, txnId);
});
}
void
MatrixClient::initialSync() noexcept
{
QUrlQuery query;
query.addQueryItem("timeout", "0");
query.addQueryItem("filter", filter_);
query.addQueryItem("access_token", token_);
QUrl endpoint(server_);
endpoint.setPath(clientApiUrl_ + "/sync");
endpoint.setQuery(query);
QNetworkRequest request(QString(endpoint.toEncoded()));
auto reply = get(request);
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status == 0 || status >= 400) {
qDebug() << "Error code received" << status;
emit initialSyncFailed();
return;
}
auto data = reply->readAll();
try {
mtx::responses::Sync response = nlohmann::json::parse(data);
emit initialSyncCompleted(response);
} catch (std::exception &e) {
qWarning() << "Initial sync error:" << e.what();
emit initialSyncFailed();
return;
}
});
}
void
MatrixClient::versions() noexcept
{
QUrl endpoint(server_);
endpoint.setPath("/_matrix/client/versions");
QNetworkRequest request(endpoint);
auto reply = get(request);
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
reply->deleteLater();
int status_code =
reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status_code == 404) {
emit versionError("Versions endpoint was not found on the server. Possibly "
"not a Matrix server");
return;
}
if (status_code >= 400) {
emit versionError("An unknown error occured. Please try again.");
return;
}
try {
mtx::responses::Versions versions =
nlohmann::json::parse(reply->readAll().data());
emit versionSuccess();
} catch (std::exception &e) {
emit versionError("Malformed response. Possibly not a Matrix server");
}
});
}
void
MatrixClient::getOwnProfile() noexcept
{
// FIXME: Remove settings from the matrix client. The class should store the
// user's matrix ID.
QSettings settings;
auto userid = settings.value("auth/user_id", "").toString();
QUrlQuery query;
query.addQueryItem("access_token", token_);
QUrl endpoint(server_);
endpoint.setPath(clientApiUrl_ + "/profile/" + userid);
endpoint.setQuery(query);
QNetworkRequest request(QString(endpoint.toEncoded()));
QNetworkReply *reply = get(request);
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status >= 400) {
qWarning() << reply->errorString();
return;
}
try {
mtx::responses::Profile profile =
nlohmann::json::parse(reply->readAll().data());
emit getOwnProfileResponse(QUrl(QString::fromStdString(profile.avatar_url)),
QString::fromStdString(profile.display_name));
} catch (std::exception &e) {
qWarning() << "Profile:" << e.what();
}
});
}
void
MatrixClient::getOwnCommunities() noexcept
{
QUrlQuery query;
query.addQueryItem("access_token", token_);
QUrl endpoint(server_);
endpoint.setPath(clientApiUrl_ + "/joined_groups");
endpoint.setQuery(query);
QNetworkRequest request(QString(endpoint.toEncoded()));
QNetworkReply *reply = get(request);
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status >= 400) {
qWarning() << reply->errorString();
return;
}
auto data = reply->readAll();
auto json = QJsonDocument::fromJson(data).object();
try {
QList<QString> response;
for (auto group : json["groups"].toArray())
response.append(group.toString());
emit getOwnCommunitiesResponse(response);
} catch (DeserializationException &e) {
qWarning() << "Own communities:" << e.what();
}
});
}
void
MatrixClient::fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url)
{
QList<QString> url_parts = avatar_url.toString().split("mxc://");
if (url_parts.size() != 2) {
qDebug() << "Invalid format for room avatar " << avatar_url.toString();
return;
}
QUrlQuery query;
query.addQueryItem("width", "512");
query.addQueryItem("height", "512");
query.addQueryItem("method", "crop");
QString media_url =
QString("%1/_matrix/media/r0/thumbnail/%2").arg(getHomeServer().toString(), url_parts[1]);
QUrl endpoint(media_url);
endpoint.setQuery(query);
QNetworkRequest avatar_request(endpoint);
QNetworkReply *reply = get(avatar_request);
connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, avatar_url]() {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status == 0 || status >= 400) {
qWarning() << reply->errorString();
return;
}
auto img = reply->readAll();
if (img.size() == 0)
return;
QPixmap pixmap;
pixmap.loadFromData(img);
emit roomAvatarRetrieved(roomid, pixmap, avatar_url.toString(), img);
});
}
void
MatrixClient::fetchCommunityAvatar(const QString &communityId, const QUrl &avatar_url)
{
QList<QString> url_parts = avatar_url.toString().split("mxc://");
if (url_parts.size() != 2) {
qDebug() << "Invalid format for community avatar " << avatar_url.toString();
return;
}
QUrlQuery query;
query.addQueryItem("width", "512");
query.addQueryItem("height", "512");
query.addQueryItem("method", "crop");
QString media_url =
QString("%1/_matrix/media/r0/thumbnail/%2").arg(getHomeServer().toString(), url_parts[1]);
QUrl endpoint(media_url);
endpoint.setQuery(query);
QNetworkRequest avatar_request(endpoint);
QNetworkReply *reply = get(avatar_request);
connect(reply, &QNetworkReply::finished, this, [this, reply, communityId]() {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status == 0 || status >= 400) {
qWarning() << reply->errorString();
return;
}
auto img = reply->readAll();
if (img.size() == 0)
return;
QPixmap pixmap;
pixmap.loadFromData(img);
emit communityAvatarRetrieved(communityId, pixmap);
});
}
void
MatrixClient::fetchCommunityProfile(const QString &communityId)
{
QUrlQuery query;
query.addQueryItem("access_token", token_);
QUrl endpoint(server_);
endpoint.setPath(clientApiUrl_ + "/groups/" + communityId + "/profile");
endpoint.setQuery(query);
QNetworkRequest request(QString(endpoint.toEncoded()));
QNetworkReply *reply = get(request);
connect(reply, &QNetworkReply::finished, this, [this, reply, communityId]() {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status >= 400) {
qWarning() << reply->errorString();
return;
}
auto data = reply->readAll();
const auto json = QJsonDocument::fromJson(data).object();
emit communityProfileRetrieved(communityId, json);
});
}
void
MatrixClient::fetchCommunityRooms(const QString &communityId)
{
QUrlQuery query;
query.addQueryItem("access_token", token_);
QUrl endpoint(server_);
endpoint.setPath(clientApiUrl_ + "/groups/" + communityId + "/rooms");
endpoint.setQuery(query);
QNetworkRequest request(QString(endpoint.toEncoded()));
QNetworkReply *reply = get(request);
connect(reply, &QNetworkReply::finished, this, [this, reply, communityId]() {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status >= 400) {
qWarning() << reply->errorString();
return;
}
auto data = reply->readAll();
const auto json = QJsonDocument::fromJson(data).object();
emit communityRoomsRetrieved(communityId, json);
});
}
void
MatrixClient::fetchUserAvatar(const QUrl &avatarUrl,
std::function<void(QImage)> onSuccess,
std::function<void(QString)> onError)
{
QList<QString> url_parts = avatarUrl.toString().split("mxc://");
if (url_parts.size() != 2) {
qDebug() << "Invalid format for user avatar " << avatarUrl.toString();
return;
}
QUrlQuery query;
query.addQueryItem("width", "128");
query.addQueryItem("height", "128");
query.addQueryItem("method", "crop");
QString media_url =
QString("%1/_matrix/media/r0/thumbnail/%2").arg(getHomeServer().toString(), url_parts[1]);
QUrl endpoint(media_url);
endpoint.setQuery(query);
QNetworkRequest avatar_request(endpoint);
auto reply = get(avatar_request);
connect(reply, &QNetworkReply::finished, this, [reply, onSuccess, onError]() {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status == 0 || status >= 400)
return onError(reply->errorString());
auto data = reply->readAll();
if (data.size() == 0)
return onError("received avatar with no data");
QImage img;
img.loadFromData(data);
onSuccess(std::move(img));
});
}
void
MatrixClient::downloadImage(const QString &event_id, const QUrl &url)
{
QNetworkRequest image_request(url);
auto reply = get(image_request);
connect(reply, &QNetworkReply::finished, this, [this, reply, event_id]() {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status == 0 || status >= 400) {
qWarning() << reply->errorString();
return;
}
auto img = reply->readAll();
if (img.size() == 0)
return;
QPixmap pixmap;
pixmap.loadFromData(img);
emit imageDownloaded(event_id, pixmap);
});
}
void
MatrixClient::downloadFile(const QString &event_id, const QUrl &url)
{
QNetworkRequest fileRequest(url);
auto reply = get(fileRequest);
connect(reply, &QNetworkReply::finished, this, [this, reply, event_id]() {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status == 0 || status >= 400) {
// TODO: Handle error
qWarning() << reply->errorString();
return;
}
auto data = reply->readAll();
if (data.size() == 0)
return;
emit fileDownloaded(event_id, data);
});
}
void
MatrixClient::messages(const QString &roomid, const QString &from_token, int limit) noexcept
{
QUrlQuery query;
query.addQueryItem("access_token", token_);
query.addQueryItem("from", from_token);
query.addQueryItem("dir", "b");
query.addQueryItem("limit", QString::number(limit));
QUrl endpoint(server_);
endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/messages").arg(roomid));
endpoint.setQuery(query);
QNetworkRequest request(QString(endpoint.toEncoded()));
auto reply = get(request);
connect(reply, &QNetworkReply::finished, this, [this, reply, roomid]() {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status == 0 || status >= 400) {
qWarning() << reply->errorString();
return;
}
try {
mtx::responses::Messages messages =
nlohmann::json::parse(reply->readAll().data());
emit messagesRetrieved(roomid, messages);
} catch (std::exception &e) {
qWarning() << "Room messages from" << roomid << e.what();
return;
}
});
}
void
MatrixClient::uploadImage(const QString &roomid,
const QString &filename,
const QSharedPointer<QIODevice> data)
{
auto reply = makeUploadRequest(data);
if (reply == nullptr)
return;
connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, filename, data]() {
auto json = getUploadReply(reply);
if (json.isEmpty())
return;
auto mime = reply->request().header(QNetworkRequest::ContentTypeHeader).toString();
auto size =
reply->request().header(QNetworkRequest::ContentLengthHeader).toLongLong();
emit imageUploaded(
roomid, filename, json.value("content_uri").toString(), mime, size);
});
}
void
MatrixClient::uploadFile(const QString &roomid,
const QString &filename,
const QSharedPointer<QIODevice> data)
{
auto reply = makeUploadRequest(data);
if (reply == nullptr)
return;
connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, filename, data]() {
auto json = getUploadReply(reply);
if (json.isEmpty())
return;
auto mime = reply->request().header(QNetworkRequest::ContentTypeHeader).toString();
auto size =
reply->request().header(QNetworkRequest::ContentLengthHeader).toLongLong();
emit fileUploaded(
roomid, filename, json.value("content_uri").toString(), mime, size);
});
}
void
MatrixClient::uploadAudio(const QString &roomid,
const QString &filename,
const QSharedPointer<QIODevice> data)
{
auto reply = makeUploadRequest(data);
if (reply == nullptr)
return;
connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, filename, data]() {
auto json = getUploadReply(reply);
if (json.isEmpty())
return;
auto mime = reply->request().header(QNetworkRequest::ContentTypeHeader).toString();
auto size =
reply->request().header(QNetworkRequest::ContentLengthHeader).toLongLong();
emit audioUploaded(
roomid, filename, json.value("content_uri").toString(), mime, size);
});
}
void
MatrixClient::uploadVideo(const QString &roomid,
const QString &filename,
const QSharedPointer<QIODevice> data)
{
auto reply = makeUploadRequest(data);
if (reply == nullptr)
return;
connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, filename, data]() {
auto json = getUploadReply(reply);
if (json.isEmpty())
return;
auto mime = reply->request().header(QNetworkRequest::ContentTypeHeader).toString();
auto size =
reply->request().header(QNetworkRequest::ContentLengthHeader).toLongLong();
emit videoUploaded(
roomid, filename, json.value("content_uri").toString(), mime, size);
});
}
void
MatrixClient::uploadFilter(const QString &filter) noexcept
{
// validate that filter is a Json-String
QJsonDocument doc = QJsonDocument::fromJson(filter.toUtf8());
if (doc.isNull() || !doc.isObject()) {
qWarning() << "Input which should be uploaded as filter is no JsonObject";
return;
}
QSettings settings;
auto userid = settings.value("auth/user_id", "").toString();
QUrlQuery query;
query.addQueryItem("access_token", token_);
QUrl endpoint(server_);
endpoint.setPath(clientApiUrl_ + QString("/user/%1/filter").arg(userid));
endpoint.setQuery(query);
QNetworkRequest request(endpoint);
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
auto reply = post(request, doc.toJson(QJsonDocument::Compact));
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status == 0 || status >= 400) {
qWarning() << reply->errorString() << "42";
return;
}
auto data = reply->readAll();
auto response = QJsonDocument::fromJson(data);
auto filter_id = response.object()["filter_id"].toString();
qDebug() << "Filter with ID" << filter_id << "created.";
QSettings settings;
settings.setValue("client/sync_filter", filter_id);
settings.sync();
// set the filter_ var so following syncs will use it
filter_ = filter_id;
});
}
void
MatrixClient::joinRoom(const QString &roomIdOrAlias)
{
QUrlQuery query;
query.addQueryItem("access_token", token_);
QUrl endpoint(server_);
endpoint.setPath(clientApiUrl_ + QString("/join/%1").arg(roomIdOrAlias));
endpoint.setQuery(query);
QNetworkRequest request(endpoint);
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
auto reply = post(request, "{}");
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status == 0 || status >= 400) {
auto data = reply->readAll();
auto response = QJsonDocument::fromJson(data);
auto json = response.object();
if (json.contains("error"))
emit joinFailed(json["error"].toString());
else
qDebug() << reply->errorString();
return;
}
auto data = reply->readAll();
auto response = QJsonDocument::fromJson(data);
auto room_id = response.object()["room_id"].toString();
emit joinedRoom(room_id);
});
}
void
MatrixClient::leaveRoom(const QString &roomId)
{
QUrlQuery query;
query.addQueryItem("access_token", token_);
QUrl endpoint(server_);
endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/leave").arg(roomId));
endpoint.setQuery(query);
QNetworkRequest request(endpoint);
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
auto reply = post(request, "{}");
connect(reply, &QNetworkReply::finished, this, [this, reply, roomId]() {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status == 0 || status >= 400) {
qWarning() << reply->errorString();
return;
}
emit leftRoom(roomId);
});
}
void
MatrixClient::inviteUser(const QString &roomId, const QString &user)
{
QUrlQuery query;
query.addQueryItem("access_token", token_);
QUrl endpoint(server_);
endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/invite").arg(roomId));
endpoint.setQuery(query);
QNetworkRequest request(endpoint);
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
QJsonObject body{{"user_id", user}};
auto reply = post(request, QJsonDocument(body).toJson(QJsonDocument::Compact));
connect(reply, &QNetworkReply::finished, this, [this, reply, roomId, user]() {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status == 0 || status >= 400) {
// TODO: Handle failure.
qWarning() << reply->errorString();
return;
}
emit invitedUser(roomId, user);
});
}
void
MatrixClient::createRoom(const mtx::requests::CreateRoom &create_room_request)
{
QUrlQuery query;
query.addQueryItem("access_token", token_);
QUrl endpoint(server_);
endpoint.setPath(clientApiUrl_ + QString("/createRoom"));
endpoint.setQuery(query);
QNetworkRequest request(endpoint);
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
nlohmann::json body = create_room_request;
auto reply = post(request, QString::fromStdString(body.dump()).toUtf8());
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status == 0 || status >= 400) {
auto data = reply->readAll();
auto response = QJsonDocument::fromJson(data);
auto json = response.object();
if (json.contains("error"))
emit roomCreationFailed(json["error"].toString());
else
qDebug() << reply->errorString();
return;
}
auto data = reply->readAll();
auto response = QJsonDocument::fromJson(data);
auto room_id = response.object()["room_id"].toString();
emit roomCreated(room_id);
});
}
void
MatrixClient::sendTypingNotification(const QString &roomid, int timeoutInMillis)
{
QSettings settings;
QString user_id = settings.value("auth/user_id").toString();
QUrlQuery query;
query.addQueryItem("access_token", token_);
QUrl endpoint(server_);
endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/typing/%2").arg(roomid).arg(user_id));
endpoint.setQuery(query);
QString msgType("");
QJsonObject body;
body = {{"typing", true}, {"timeout", timeoutInMillis}};
QNetworkRequest request(QString(endpoint.toEncoded()));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
put(request, QJsonDocument(body).toJson(QJsonDocument::Compact));
}
void
MatrixClient::removeTypingNotification(const QString &roomid)
{
QSettings settings;
QString user_id = settings.value("auth/user_id").toString();
QUrlQuery query;
query.addQueryItem("access_token", token_);
QUrl endpoint(server_);
endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/typing/%2").arg(roomid).arg(user_id));
endpoint.setQuery(query);
QString msgType("");
QJsonObject body;
body = {{"typing", false}};
QNetworkRequest request(QString(endpoint.toEncoded()));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
put(request, QJsonDocument(body).toJson(QJsonDocument::Compact));
}
void
MatrixClient::readEvent(const QString &room_id, const QString &event_id)
{
QUrlQuery query;
query.addQueryItem("access_token", token_);
QUrl endpoint(server_);
endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/read_markers").arg(room_id));
endpoint.setQuery(query);
QNetworkRequest request(QString(endpoint.toEncoded()));
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
QJsonObject body({{"m.fully_read", event_id}, {"m.read", event_id}});
auto reply = post(request, QJsonDocument(body).toJson(QJsonDocument::Compact));
connect(reply, &QNetworkReply::finished, this, [reply]() {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status == 0 || status >= 400) {
qWarning() << reply->errorString();
return;
}
});
}
QNetworkReply *
MatrixClient::makeUploadRequest(QSharedPointer<QIODevice> iodev)
{
QUrlQuery query;
query.addQueryItem("access_token", token_);
QUrl endpoint(server_);
endpoint.setPath(mediaApiUrl_ + "/upload");
endpoint.setQuery(query);
if (!iodev->open(QIODevice::ReadOnly)) {
qWarning() << "Error while reading device:" << iodev->errorString();
return nullptr;
}
QMimeDatabase db;
QMimeType mime = db.mimeTypeForData(iodev.data());
QNetworkRequest request(QString(endpoint.toEncoded()));
request.setHeader(QNetworkRequest::ContentTypeHeader, mime.name());
auto reply = post(request, iodev.data());
return reply;
}
QJsonObject
MatrixClient::getUploadReply(QNetworkReply *reply)
{
QJsonObject object;
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status == 0 || status >= 400) {
emit syncFailed(reply->errorString());
return object;
}
auto res_data = reply->readAll();
if (res_data.isEmpty())
return object;
auto json = QJsonDocument::fromJson(res_data);
if (!json.isObject()) {
qDebug() << "Media upload: Response is not a json object.";
return object;
}
object = json.object();
if (!object.contains("content_uri")) {
qDebug() << "Media upload: Missing content_uri key";
qDebug() << object;
return QJsonObject{};
}
return object;
}