matrixion/src/MatrixClient.cc

1352 lines
46 KiB
C++
Raw Normal View History

2017-04-06 02:06:42 +03:00
/*
* 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>
2017-09-10 12:58:00 +03:00
#include <QFile>
#include <QImageReader>
#include <QJsonArray>
2017-04-06 02:06:42 +03:00
#include <QJsonDocument>
#include <QJsonObject>
2017-11-30 00:39:35 +03:00
#include <QMimeDatabase>
2017-04-06 02:06:42 +03:00
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QPixmap>
#include <QProcessEnvironment>
2017-04-06 02:06:42 +03:00
#include <QSettings>
#include <QUrlQuery>
2018-04-21 16:34:50 +03:00
#include <QtConcurrent>
#include <mtx/errors.hpp>
2017-04-06 02:06:42 +03:00
#include "MatrixClient.h"
MatrixClient::MatrixClient(QString server, QObject *parent)
2017-08-20 13:47:22 +03:00
: QNetworkAccessManager(parent)
2017-11-06 00:04:55 +03:00
, clientApiUrl_{"/_matrix/client/r0"}
, mediaApiUrl_{"/_matrix/media/r0"}
, serverProtocol_{"https"}
2017-04-06 02:06:42 +03:00
{
QSettings settings;
txn_id_ = settings.value("client/transaction_id", 1).toInt();
2017-04-06 02:06:42 +03:00
auto env = QProcessEnvironment::systemEnvironment();
auto allowInsecureConnections = env.value("NHEKO_ALLOW_INSECURE_CONNECTIONS", "0");
if (allowInsecureConnections == "1") {
qWarning() << "Insecure connections are allowed: SSL errors will be ignored";
connect(
this,
&QNetworkAccessManager::sslErrors,
this,
[](QNetworkReply *reply, const QList<QSslError> &) { reply->ignoreSslErrors(); });
}
setServer(server);
QJsonObject default_filter{
2018-01-11 17:34:43 +03:00
{
"room",
QJsonObject{
{"include_leave", true},
{
"account_data",
QJsonObject{
{"not_types", QJsonArray{"*"}},
},
},
},
},
{
"account_data",
QJsonObject{
{"not_types", QJsonArray{"*"}},
},
2018-01-11 17:34:43 +03:00
},
{
"presence",
QJsonObject{
{"not_types", QJsonArray{"*"}},
},
},
};
2018-01-11 17:34:43 +03:00
filter_ = settings
.value("client/sync_filter",
QJsonDocument(default_filter).toJson(QJsonDocument::Compact))
.toString();
connect(this,
&QNetworkAccessManager::networkAccessibleChanged,
this,
[this](NetworkAccessibility status) {
if (status != NetworkAccessibility::Accessible)
setNetworkAccessible(NetworkAccessibility::Accessible);
});
2017-04-06 02:06:42 +03:00
}
2017-08-20 13:47:22 +03:00
void
MatrixClient::reset() noexcept
{
next_batch_.clear();
server_.clear();
token_.clear();
txn_id_ = 0;
}
2017-08-20 13:47:22 +03:00
void
2017-10-22 22:51:50 +03:00
MatrixClient::login(const QString &username, const QString &password) noexcept
{
2017-10-22 22:51:50 +03:00
QUrl endpoint(server_);
endpoint.setPath(clientApiUrl_ + "/login");
2017-10-08 22:38:38 +03:00
2017-10-22 22:51:50 +03:00
QNetworkRequest request(endpoint);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
2017-10-08 22:38:38 +03:00
mtx::requests::Login login;
login.user = username.toStdString();
login.password = password.toStdString();
login.initial_device_display_name = "nheko";
#if defined(Q_OS_MAC)
login.initial_device_display_name = "nheko on Mac OS";
#elif defined(Q_OS_LINUX)
login.initial_device_display_name = "nheko on Linux";
#elif defined(Q_OS_WIN)
login.initial_device_display_name = "nheko on Windows";
#endif
json j = login;
auto data = QByteArray::fromStdString(j.dump());
auto reply = post(request, data);
2017-10-22 22:51:50 +03:00
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
reply->deleteLater();
2017-10-08 22:38:38 +03:00
2017-10-22 22:51:50 +03:00
int status_code =
reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
2017-10-22 22:51:50 +03:00
if (status_code == 403) {
emit loginError(tr("Wrong username or password"));
return;
}
2017-10-22 22:51:50 +03:00
if (status_code == 404) {
emit loginError(tr("Login endpoint was not found on the server"));
return;
}
2017-10-22 22:51:50 +03:00
if (status_code >= 400) {
qWarning() << "Login error: " << reply->errorString();
emit loginError(tr("An unknown error occured. Please try again."));
return;
}
if (reply->error()) {
emit loginError(reply->errorString());
return;
}
2017-10-22 22:51:50 +03:00
try {
mtx::responses::Login login =
nlohmann::json::parse(reply->readAll().data());
2017-04-06 02:06:42 +03:00
2017-10-22 22:51:50 +03:00
auto hostname = server_.host();
2017-04-06 02:06:42 +03:00
2017-10-22 22:51:50 +03:00
if (server_.port() > 0)
hostname = QString("%1:%2").arg(server_.host()).arg(server_.port());
2017-04-06 02:06:42 +03:00
2018-03-18 12:05:39 +03:00
emit loginSuccess(QString::fromStdString(login.user_id.to_string()),
hostname,
QString::fromStdString(login.access_token));
} catch (std::exception &e) {
2017-10-22 22:51:50 +03:00
qWarning() << "Malformed JSON response" << e.what();
emit loginError(tr("Malformed response. Possibly not a Matrix server"));
}
});
2017-04-06 02:06:42 +03:00
}
2017-08-20 13:47:22 +03:00
void
MatrixClient::logout() noexcept
{
QUrlQuery query;
query.addQueryItem("access_token", token_);
QUrl endpoint(server_);
2017-09-10 12:58:00 +03:00
endpoint.setPath(clientApiUrl_ + "/logout");
endpoint.setQuery(query);
QNetworkRequest request(endpoint);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QJsonObject body{};
2017-10-22 22:51:50 +03:00
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();
});
}
2017-08-20 13:47:22 +03:00
void
MatrixClient::registerUser(const QString &user,
const QString &pass,
const QString &server,
const QString &session) noexcept
2017-04-08 02:53:23 +03:00
{
setServer(server);
2017-04-08 02:53:23 +03:00
QUrl endpoint(server_);
2017-09-10 12:58:00 +03:00
endpoint.setPath(clientApiUrl_ + "/register");
2017-04-08 02:53:23 +03:00
QNetworkRequest request(QString(endpoint.toEncoded()));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
2017-04-08 02:53:23 +03:00
QJsonObject body{{"username", user}, {"password", pass}};
2017-10-22 22:51:50 +03:00
// We trying to register using the response from the recaptcha.
if (!session.isEmpty())
body = QJsonObject{
{"username", user},
{"password", pass},
{"auth", QJsonObject{{"type", "m.login.recaptcha"}, {"session", session}}}};
auto reply = post(request, QJsonDocument(body).toJson(QJsonDocument::Compact));
connect(reply, &QNetworkReply::finished, this, [this, reply, user, pass, server]() {
2017-10-22 22:51:50 +03:00
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
auto data = reply->readAll();
// Try to parse a regular register response.
try {
mtx::responses::Register res = nlohmann::json::parse(data);
2018-03-18 12:05:39 +03:00
emit registerSuccess(QString::fromStdString(res.user_id.to_string()),
QString::fromStdString(res.user_id.hostname()),
QString::fromStdString(res.access_token));
} catch (const std::exception &e) {
qWarning() << "Register" << e.what();
}
2017-04-08 02:53:23 +03:00
// Check if the server requires a registration flow.
try {
mtx::responses::RegistrationFlows res = nlohmann::json::parse(data);
emit registrationFlow(
user, pass, server, QString::fromStdString(res.session));
2017-10-22 22:51:50 +03:00
return;
} catch (const std::exception &) {
2017-10-22 22:51:50 +03:00
}
// We encountered an unknown error.
if (status == 0 || status >= 400) {
try {
mtx::errors::Error res = nlohmann::json::parse(data);
emit registerError(QString::fromStdString(res.error));
return;
} catch (const std::exception &) {
}
emit registerError(reply->errorString());
2017-10-22 22:51:50 +03:00
}
});
2017-04-08 02:53:23 +03:00
}
2017-08-20 13:47:22 +03:00
void
MatrixClient::sync() noexcept
2017-04-06 02:06:42 +03:00
{
// 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_);
2017-04-06 02:06:42 +03:00
if (next_batch_.isEmpty()) {
2017-10-01 12:11:33 +03:00
qDebug() << "Sync requires a valid next_batch token. Initial sync should "
"be performed.";
return;
}
2017-04-06 02:06:42 +03:00
query.addQueryItem("since", next_batch_);
2017-04-06 02:06:42 +03:00
QUrl endpoint(server_);
2017-09-10 12:58:00 +03:00
endpoint.setPath(clientApiUrl_ + "/sync");
endpoint.setQuery(query);
2017-04-06 02:06:42 +03:00
QNetworkRequest request(QString(endpoint.toEncoded()));
2017-04-06 02:06:42 +03:00
2017-10-22 22:51:50 +03:00
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();
2017-10-22 22:51:50 +03:00
return;
}
auto data = reply->readAll();
2017-10-22 22:51:50 +03:00
try {
mtx::responses::Sync response = nlohmann::json::parse(data);
2017-10-22 22:51:50 +03:00
emit syncCompleted(response);
} catch (std::exception &e) {
qWarning() << "Sync error: " << e.what();
2017-10-22 22:51:50 +03:00
}
});
2017-04-06 02:06:42 +03:00
}
2017-08-20 13:47:22 +03:00
void
MatrixClient::sendRoomMessage(mtx::events::MessageType ty,
int txnId,
const QString &roomid,
2017-09-10 12:58:00 +03:00
const QString &msg,
const QString &mime,
2018-02-19 23:09:21 +03:00
uint64_t media_size,
2017-09-10 12:58:00 +03:00
const QString &url) noexcept
2017-04-06 02:06:42 +03:00
{
QUrlQuery query;
query.addQueryItem("access_token", token_);
QUrl endpoint(server_);
2017-09-10 12:58:00 +03:00
endpoint.setPath(clientApiUrl_ +
QString("/rooms/%1/send/m.room.message/%2").arg(roomid).arg(txnId));
endpoint.setQuery(query);
2017-09-10 12:58:00 +03:00
QJsonObject body;
QJsonObject info = {{"size", static_cast<qint64>(media_size)}, {"mimetype", mime}};
switch (ty) {
case mtx::events::MessageType::Text:
2017-11-06 00:04:55 +03:00
body = {{"msgtype", "m.text"}, {"body", msg}};
break;
case mtx::events::MessageType::Emote:
2017-11-06 00:04:55 +03:00
body = {{"msgtype", "m.emote"}, {"body", msg}};
break;
case mtx::events::MessageType::Image:
2017-12-01 18:33:49 +03:00
body = {{"msgtype", "m.image"}, {"body", msg}, {"url", url}, {"info", info}};
break;
case mtx::events::MessageType::File:
2017-12-01 18:33:49 +03:00
body = {{"msgtype", "m.file"}, {"body", msg}, {"url", url}, {"info", info}};
break;
case mtx::events::MessageType::Audio:
2017-12-01 18:33:49 +03:00
body = {{"msgtype", "m.audio"}, {"body", msg}, {"url", url}, {"info", info}};
2017-11-30 00:39:35 +03:00
break;
case mtx::events::MessageType::Video:
body = {{"msgtype", "m.video"}, {"body", msg}, {"url", url}, {"info", info}};
break;
2017-09-10 12:58:00 +03:00
default:
qDebug() << "SendRoomMessage: Unknown message type for" << msg;
return;
}
QNetworkRequest request(QString(endpoint.toEncoded()));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
2017-10-22 22:51:50 +03:00
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);
2017-10-22 22:51:50 +03:00
return;
}
auto data = reply->readAll();
if (data.isEmpty()) {
emit messageSendFailed(roomid, txnId);
2017-10-22 22:51:50 +03:00
return;
}
2017-10-22 22:51:50 +03:00
auto json = QJsonDocument::fromJson(data);
if (!json.isObject()) {
qDebug() << "Send message response is not a JSON object";
emit messageSendFailed(roomid, txnId);
2017-10-22 22:51:50 +03:00
return;
}
auto object = json.object();
if (!object.contains("event_id")) {
qDebug() << "SendTextMessage: missing event_id from response";
emit messageSendFailed(roomid, txnId);
2017-10-22 22:51:50 +03:00
return;
}
emit messageSent(object.value("event_id").toString(), roomid, txnId);
});
2017-04-06 02:06:42 +03:00
}
2017-08-20 13:47:22 +03:00
void
MatrixClient::initialSync() noexcept
2017-04-06 02:06:42 +03:00
{
QUrlQuery query;
2017-10-05 08:47:29 +03:00
query.addQueryItem("timeout", "0");
query.addQueryItem("filter", filter_);
query.addQueryItem("access_token", token_);
2017-04-06 02:06:42 +03:00
QUrl endpoint(server_);
2017-09-10 12:58:00 +03:00
endpoint.setPath(clientApiUrl_ + "/sync");
endpoint.setQuery(query);
2017-04-06 02:06:42 +03:00
QNetworkRequest request(QString(endpoint.toEncoded()));
2017-04-06 02:06:42 +03:00
2017-10-22 22:51:50 +03:00
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(status);
2017-10-22 22:51:50 +03:00
return;
}
2018-04-21 16:34:50 +03:00
qRegisterMetaType<mtx::responses::Sync>();
QtConcurrent::run([data = reply->readAll(), this]() {
try {
emit initialSyncCompleted(nlohmann::json::parse(std::move(data)));
} catch (std::exception &e) {
qWarning() << "Initial sync error:" << e.what();
emit initialSyncFailed();
}
});
2017-10-22 22:51:50 +03:00
});
2017-04-06 02:06:42 +03:00
}
2017-08-20 13:47:22 +03:00
void
MatrixClient::versions() noexcept
2017-04-06 02:06:42 +03:00
{
QUrl endpoint(server_);
endpoint.setPath("/_matrix/client/versions");
2017-04-06 02:06:42 +03:00
QNetworkRequest request(endpoint);
2017-04-06 02:06:42 +03:00
2017-10-22 22:51:50 +03:00
auto reply = get(request);
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
reply->deleteLater();
int status_code =
reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (reply->error()) {
emit versionError(reply->errorString());
return;
}
2017-10-22 22:51:50 +03:00
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) {
2017-10-22 22:51:50 +03:00
emit versionError("Malformed response. Possibly not a Matrix server");
}
});
2017-04-06 02:06:42 +03:00
}
2017-08-20 13:47:22 +03:00
void
MatrixClient::getOwnProfile() noexcept
2017-04-06 02:06:42 +03:00
{
2017-10-01 12:11:33 +03:00
// 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();
2017-04-06 02:06:42 +03:00
QUrlQuery query;
query.addQueryItem("access_token", token_);
2017-04-06 02:06:42 +03:00
QUrl endpoint(server_);
2017-09-10 12:58:00 +03:00
endpoint.setPath(clientApiUrl_ + "/profile/" + userid);
endpoint.setQuery(query);
2017-04-06 02:06:42 +03:00
QNetworkRequest request(QString(endpoint.toEncoded()));
2017-04-06 02:06:42 +03:00
QNetworkReply *reply = get(request);
2017-10-22 22:51:50 +03:00
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) {
2017-10-22 22:51:50 +03:00
qWarning() << "Profile:" << e.what();
}
});
2017-04-06 02:06:42 +03:00
}
2018-01-09 16:07:32 +03:00
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();
2018-04-28 13:16:37 +03:00
if (!json.contains("groups")) {
qWarning() << "failed to parse own communities. 'groups' key not found";
return;
}
2018-01-21 19:25:58 +03:00
2018-04-28 13:16:37 +03:00
QList<QString> response;
for (auto group : json["groups"].toArray())
response.append(group.toString());
2018-01-21 19:25:58 +03:00
2018-04-28 13:16:37 +03:00
emit getOwnCommunitiesResponse(response);
2018-01-09 16:07:32 +03:00
});
}
2017-08-20 13:47:22 +03:00
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);
2017-12-22 01:00:48 +03:00
connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, avatar_url]() {
2017-10-22 22:51:50 +03:00
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);
2017-12-22 01:00:48 +03:00
emit roomAvatarRetrieved(roomid, pixmap, avatar_url.toString(), img);
2017-10-22 22:51:50 +03:00
});
}
2018-01-09 16:07:32 +03:00
void
MatrixClient::fetchCommunityAvatar(const QString &communityId, const QUrl &avatar_url)
{
2018-04-21 21:18:57 +03:00
if (avatar_url.isEmpty())
return;
2018-01-09 16:07:32 +03:00
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);
});
}
2018-03-25 15:59:47 +03:00
QSharedPointer<DownloadMediaProxy>
MatrixClient::fetchUserAvatar(const QUrl &avatarUrl)
{
QList<QString> url_parts = avatarUrl.toString().split("mxc://");
2018-04-21 16:34:50 +03:00
if (url_parts.size() != 2)
2018-03-25 23:05:44 +03:00
return QSharedPointer<DownloadMediaProxy>();
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);
2017-10-22 22:51:50 +03:00
auto reply = get(avatar_request);
2018-03-25 23:05:44 +03:00
auto proxy = QSharedPointer<DownloadMediaProxy>(new DownloadMediaProxy,
[](auto proxy) { proxy->deleteLater(); });
2018-03-25 15:59:47 +03:00
connect(reply, &QNetworkReply::finished, this, [reply, proxy, avatarUrl]() {
2017-10-22 22:51:50 +03:00
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
2018-03-25 15:59:47 +03:00
if (status == 0 || status >= 400) {
qWarning() << reply->errorString() << avatarUrl;
return;
}
2017-10-22 22:51:50 +03:00
auto data = reply->readAll();
2018-03-25 15:59:47 +03:00
if (data.size() == 0) {
qWarning() << "received avatar with no data:" << avatarUrl;
return;
}
2017-10-22 22:51:50 +03:00
QImage img;
img.loadFromData(data);
2018-03-25 15:59:47 +03:00
emit proxy->avatarDownloaded(img);
2017-10-22 22:51:50 +03:00
});
2018-03-25 15:59:47 +03:00
return proxy;
}
2018-03-25 15:59:47 +03:00
QSharedPointer<DownloadMediaProxy>
MatrixClient::downloadImage(const QUrl &url)
2017-04-28 14:56:45 +03:00
{
QNetworkRequest image_request(url);
2017-04-28 14:56:45 +03:00
2017-10-22 22:51:50 +03:00
auto reply = get(image_request);
2018-03-25 23:05:44 +03:00
auto proxy = QSharedPointer<DownloadMediaProxy>(new DownloadMediaProxy,
[](auto proxy) { proxy->deleteLater(); });
2018-03-11 00:31:01 +03:00
connect(reply, &QNetworkReply::finished, this, [reply, proxy]() {
2017-10-22 22:51:50 +03:00
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 proxy->imageDownloaded(pixmap);
2017-10-22 22:51:50 +03:00
});
return proxy;
2017-04-28 14:56:45 +03:00
}
2018-03-25 15:59:47 +03:00
QSharedPointer<DownloadMediaProxy>
MatrixClient::downloadFile(const QUrl &url)
{
QNetworkRequest fileRequest(url);
auto reply = get(fileRequest);
2018-03-25 23:05:44 +03:00
auto proxy = QSharedPointer<DownloadMediaProxy>(new DownloadMediaProxy,
[](auto proxy) { proxy->deleteLater(); });
2018-03-11 00:31:01 +03:00
connect(reply, &QNetworkReply::finished, this, [reply, proxy]() {
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 proxy->fileDownloaded(data);
});
return proxy;
}
2017-08-20 13:47:22 +03:00
void
2017-10-22 22:51:50 +03:00
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_);
2017-10-22 22:51:50 +03:00
endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/messages").arg(roomid));
endpoint.setQuery(query);
QNetworkRequest request(QString(endpoint.toEncoded()));
2017-10-22 22:51:50 +03:00
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) {
2017-10-22 22:51:50 +03:00
qWarning() << "Room messages from" << roomid << e.what();
return;
}
});
}
2017-09-10 12:58:00 +03:00
void
MatrixClient::uploadImage(const QString &roomid,
const QString &filename,
const QSharedPointer<QIODevice> data)
2017-09-10 12:58:00 +03:00
{
auto reply = makeUploadRequest(data);
2017-09-10 12:58:00 +03:00
2017-12-01 18:33:49 +03:00
if (reply == nullptr)
2017-09-10 12:58:00 +03:00
return;
connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, filename, data]() {
auto json = getUploadReply(reply);
if (json.isEmpty())
2017-10-22 22:51:50 +03:00
return;
auto mime = reply->request().header(QNetworkRequest::ContentTypeHeader).toString();
auto size =
reply->request().header(QNetworkRequest::ContentLengthHeader).toLongLong();
2017-10-22 22:51:50 +03:00
emit imageUploaded(
roomid, filename, json.value("content_uri").toString(), mime, size);
2017-10-22 22:51:50 +03:00
});
2017-09-10 12:58:00 +03:00
}
2017-11-30 00:39:35 +03:00
void
MatrixClient::uploadFile(const QString &roomid,
const QString &filename,
const QSharedPointer<QIODevice> data)
2017-11-30 00:39:35 +03:00
{
auto reply = makeUploadRequest(data);
2017-11-30 00:39:35 +03:00
if (reply == nullptr)
return;
2017-12-01 18:33:49 +03:00
connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, filename, data]() {
auto json = getUploadReply(reply);
if (json.isEmpty())
2017-12-01 18:33:49 +03:00
return;
auto mime = reply->request().header(QNetworkRequest::ContentTypeHeader).toString();
auto size =
reply->request().header(QNetworkRequest::ContentLengthHeader).toLongLong();
2017-12-01 18:33:49 +03:00
emit fileUploaded(
roomid, filename, json.value("content_uri").toString(), mime, size);
2017-12-01 18:33:49 +03:00
});
}
void
MatrixClient::uploadAudio(const QString &roomid,
const QString &filename,
const QSharedPointer<QIODevice> data)
2017-12-01 18:33:49 +03:00
{
auto reply = makeUploadRequest(data);
2017-11-30 00:39:35 +03:00
if (reply == nullptr)
return;
2017-11-30 00:39:35 +03:00
connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, filename, data]() {
auto json = getUploadReply(reply);
if (json.isEmpty())
2017-11-30 00:39:35 +03:00
return;
auto mime = reply->request().header(QNetworkRequest::ContentTypeHeader).toString();
auto size =
reply->request().header(QNetworkRequest::ContentLengthHeader).toLongLong();
2017-11-30 00:39:35 +03:00
emit audioUploaded(
roomid, filename, json.value("content_uri").toString(), mime, size);
});
}
2017-11-30 00:39:35 +03:00
void
MatrixClient::uploadVideo(const QString &roomid,
const QString &filename,
const QSharedPointer<QIODevice> data)
{
auto reply = makeUploadRequest(data);
2017-11-30 00:39:35 +03:00
if (reply == nullptr)
return;
2017-11-30 00:39:35 +03:00
connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, filename, data]() {
auto json = getUploadReply(reply);
if (json.isEmpty())
2017-11-30 00:39:35 +03:00
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);
2017-11-30 00:39:35 +03:00
});
}
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;
});
2017-11-30 00:39:35 +03:00
}
2017-12-01 18:33:49 +03:00
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");
2017-10-22 22:51:50 +03:00
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");
2017-10-22 22:51:50 +03:00
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);
});
}
2017-12-11 00:59:50 +03:00
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);
});
}
2017-12-12 00:00:37 +03:00
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;
2017-11-06 00:04:55 +03:00
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;
2017-11-06 00:04:55 +03:00
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));
2017-12-04 19:49:25 +03:00
connect(reply, &QNetworkReply::finished, this, [reply]() {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status == 0 || status >= 400) {
qWarning() << reply->errorString();
return;
}
});
}
2017-12-01 18:33:49 +03:00
QNetworkReply *
MatrixClient::makeUploadRequest(QSharedPointer<QIODevice> iodev)
2017-12-01 18:33:49 +03:00
{
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();
2017-12-01 18:33:49 +03:00
return nullptr;
}
QMimeDatabase db;
QMimeType mime = db.mimeTypeForData(iodev.data());
2017-12-01 18:33:49 +03:00
QNetworkRequest request(QString(endpoint.toEncoded()));
request.setHeader(QNetworkRequest::ContentTypeHeader, mime.name());
auto reply = post(request, iodev.data());
2017-12-01 18:33:49 +03:00
return reply;
}
QJsonObject
MatrixClient::getUploadReply(QNetworkReply *reply)
{
QJsonObject object;
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status == 0 || status >= 400) {
2018-02-19 01:17:54 +03:00
emit uploadFailed(status,
QString("Media upload failed - %1").arg(reply->errorString()));
return object;
}
auto res_data = reply->readAll();
2018-02-19 01:17:54 +03:00
if (res_data.isEmpty()) {
emit uploadFailed(status, "Media upload failed - Empty response");
return object;
2018-02-19 01:17:54 +03:00
}
auto json = QJsonDocument::fromJson(res_data);
if (!json.isObject()) {
2018-02-19 01:17:54 +03:00
emit uploadFailed(status, "Media upload failed - Invalid response");
return object;
}
object = json.object();
if (!object.contains("content_uri")) {
2018-02-19 01:17:54 +03:00
emit uploadFailed(status, "Media upload failed - Missing 'content_uri'");
return QJsonObject{};
}
return object;
}
2018-03-17 22:23:46 +03:00
void
MatrixClient::redactEvent(const QString &room_id, const QString &event_id)
{
QUrlQuery query;
query.addQueryItem("access_token", token_);
QUrl endpoint(server_);
endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/redact/%2/%3")
.arg(room_id)
.arg(event_id)
.arg(incrementTransactionId()));
endpoint.setQuery(query);
QNetworkRequest request(QString(endpoint.toEncoded()));
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
// TODO: no reason specified
QJsonObject body{};
auto reply = put(request, QJsonDocument(body).toJson(QJsonDocument::Compact));
connect(reply, &QNetworkReply::finished, this, [reply, this, room_id, event_id]() {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
auto data = reply->readAll();
if (status == 0 || status >= 400) {
try {
mtx::errors::Error res = nlohmann::json::parse(data);
emit redactionFailed(QString::fromStdString(res.error));
return;
} catch (const std::exception &) {
}
}
try {
mtx::responses::EventId res = nlohmann::json::parse(data);
emit redactionCompleted(room_id, event_id);
} catch (const std::exception &e) {
emit redactionFailed(QString::fromStdString(e.what()));
}
});
}