mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 03:00:46 +03:00
parent
39a8150fae
commit
4659d0efc2
17 changed files with 212 additions and 137 deletions
|
@ -144,6 +144,7 @@ set(SRC_FILES
|
|||
src/dialogs/LeaveRoom.cc
|
||||
src/dialogs/Logout.cc
|
||||
src/dialogs/ReadReceipts.cc
|
||||
src/dialogs/ReCaptcha.cpp
|
||||
|
||||
# Emoji
|
||||
src/emoji/Category.cc
|
||||
|
@ -192,7 +193,6 @@ set(SRC_FILES
|
|||
src/MainWindow.cc
|
||||
src/MatrixClient.cc
|
||||
src/QuickSwitcher.cc
|
||||
src/Register.cc
|
||||
src/RegisterPage.cc
|
||||
src/RoomInfoListItem.cc
|
||||
src/RoomList.cc
|
||||
|
@ -238,6 +238,7 @@ qt5_wrap_cpp(MOC_HEADERS
|
|||
include/dialogs/LeaveRoom.h
|
||||
include/dialogs/Logout.h
|
||||
include/dialogs/ReadReceipts.h
|
||||
include/dialogs/ReCaptcha.hpp
|
||||
|
||||
# Emoji
|
||||
include/emoji/Category.h
|
||||
|
|
|
@ -5,6 +5,7 @@ nheko
|
|||
[![Latest Release](https://img.shields.io/github/release/mujx/nheko.svg)](https://github.com/mujx/nheko/releases)
|
||||
[![Chat on Matrix](https://img.shields.io/badge/chat-on%20matrix-blue.svg)](https://matrix.to/#/#nheko:matrix.org)
|
||||
[![AUR: nheko-git](https://img.shields.io/badge/AUR-nheko--git-blue.svg)](https://aur.archlinux.org/packages/nheko-git)
|
||||
[![AUR: nheko](https://img.shields.io/badge/AUR-nheko-blue.svg)](https://aur.archlinux.org/packages/nheko)
|
||||
|
||||
The motivation behind the project is to provide a native desktop app for [Matrix] that
|
||||
feels more like a mainstream chat app ([Riot], Telegram etc) and less like an IRC client.
|
||||
|
@ -14,6 +15,7 @@ feels more like a mainstream chat app ([Riot], Telegram etc) and less like an IR
|
|||
Most of the features you would expect from a chat application are missing right now
|
||||
but we are getting close to a more feature complete client.
|
||||
Specifically there is support for:
|
||||
- User registration.
|
||||
- Creating, joining & leaving rooms.
|
||||
- Sending & receiving invites.
|
||||
- Sending & receiving files and emoji (inline widgets for images, audio and file messages).
|
||||
|
|
|
@ -23,7 +23,7 @@ ExternalProject_Add(
|
|||
MatrixStructs
|
||||
|
||||
GIT_REPOSITORY https://github.com/mujx/matrix-structs
|
||||
GIT_TAG a1beea3b115f037e26c15f22ed911341b3893411
|
||||
GIT_TAG 850100c0ac2b5a04720b2a1f09270749bf99f7dd
|
||||
|
||||
BUILD_IN_SOURCE 1
|
||||
SOURCE_DIR ${MATRIX_STRUCTS_ROOT}
|
||||
|
|
|
@ -41,7 +41,7 @@ public:
|
|||
signals:
|
||||
void backButtonClicked();
|
||||
void loggingIn();
|
||||
void errorOccured();
|
||||
void errorOccurred();
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
|
|
@ -49,6 +49,7 @@ class InviteUsers;
|
|||
class JoinRoom;
|
||||
class LeaveRoom;
|
||||
class Logout;
|
||||
class ReCaptcha;
|
||||
}
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
|
|
|
@ -54,7 +54,8 @@ public:
|
|||
void login(const QString &username, const QString &password) noexcept;
|
||||
void registerUser(const QString &username,
|
||||
const QString &password,
|
||||
const QString &server) noexcept;
|
||||
const QString &server,
|
||||
const QString &session = "") noexcept;
|
||||
void versions() noexcept;
|
||||
void fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url);
|
||||
//! Download user's avatar.
|
||||
|
@ -109,6 +110,10 @@ public slots:
|
|||
signals:
|
||||
void loginError(const QString &error);
|
||||
void registerError(const QString &error);
|
||||
void registrationFlow(const QString &user,
|
||||
const QString &pass,
|
||||
const QString &server,
|
||||
const QString &session);
|
||||
void versionError(const QString &error);
|
||||
|
||||
void loggedOut();
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Deserializable.h"
|
||||
#include <QJsonDocument>
|
||||
|
||||
class RegisterRequest
|
||||
{
|
||||
public:
|
||||
RegisterRequest();
|
||||
RegisterRequest(const QString &username, const QString &password);
|
||||
|
||||
QByteArray serialize() noexcept;
|
||||
|
||||
void setPassword(QString password) { password_ = password; };
|
||||
void setUser(QString username) { user_ = username; };
|
||||
|
||||
private:
|
||||
QString user_;
|
||||
QString password_;
|
||||
};
|
||||
|
||||
class RegisterResponse : public Deserializable
|
||||
{
|
||||
public:
|
||||
void deserialize(const QJsonDocument &data) override;
|
||||
|
||||
QString getAccessToken() { return access_token_; };
|
||||
QString getHomeServer() { return home_server_; };
|
||||
QString getUserId() { return user_id_; };
|
||||
|
||||
private:
|
||||
QString access_token_;
|
||||
QString home_server_;
|
||||
QString user_id_;
|
||||
};
|
|
@ -20,12 +20,17 @@
|
|||
#include <QLabel>
|
||||
#include <QLayout>
|
||||
#include <QSharedPointer>
|
||||
#include <memory>
|
||||
|
||||
class FlatButton;
|
||||
class MatrixClient;
|
||||
class RaisedButton;
|
||||
class TextField;
|
||||
|
||||
namespace dialogs {
|
||||
class ReCaptcha;
|
||||
}
|
||||
|
||||
class RegisterPage : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -38,6 +43,8 @@ protected:
|
|||
|
||||
signals:
|
||||
void backButtonClicked();
|
||||
void errorOccurred();
|
||||
void registering();
|
||||
|
||||
private slots:
|
||||
void onBackButtonClicked();
|
||||
|
@ -70,4 +77,6 @@ private:
|
|||
|
||||
// Matrix client API provider.
|
||||
QSharedPointer<MatrixClient> client_;
|
||||
//! ReCaptcha dialog.
|
||||
std::shared_ptr<dialogs::ReCaptcha> captchaDialog_;
|
||||
};
|
||||
|
|
28
include/dialogs/ReCaptcha.hpp
Normal file
28
include/dialogs/ReCaptcha.hpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class FlatButton;
|
||||
class RaisedButton;
|
||||
|
||||
namespace dialogs {
|
||||
|
||||
class ReCaptcha : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ReCaptcha(const QString &server, const QString &session, QWidget *parent = nullptr);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
signals:
|
||||
void closing();
|
||||
|
||||
private:
|
||||
FlatButton *openCaptchaBtn_;
|
||||
RaisedButton *confirmBtn_;
|
||||
RaisedButton *cancelBtn_;
|
||||
};
|
||||
} // dialogs
|
|
@ -101,6 +101,7 @@ Avatar {
|
|||
}
|
||||
|
||||
dialogs--Logout,
|
||||
dialogs--ReCaptcha,
|
||||
dialogs--LeaveRoom,
|
||||
dialogs--CreateRoom,
|
||||
dialogs--InviteUsers,
|
||||
|
|
|
@ -104,6 +104,7 @@ Avatar {
|
|||
}
|
||||
|
||||
dialogs--Logout,
|
||||
dialogs--ReCaptcha,
|
||||
dialogs--LeaveRoom,
|
||||
dialogs--CreateRoom,
|
||||
dialogs--InviteUsers,
|
||||
|
|
|
@ -144,7 +144,7 @@ LoginPage::LoginPage(QSharedPointer<MatrixClient> client, QWidget *parent)
|
|||
connect(password_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
|
||||
connect(serverInput_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
|
||||
connect(client_.data(), SIGNAL(loginError(QString)), this, SLOT(loginError(QString)));
|
||||
connect(client_.data(), SIGNAL(loginError(QString)), this, SIGNAL(errorOccured()));
|
||||
connect(client_.data(), SIGNAL(loginError(QString)), this, SIGNAL(errorOccurred()));
|
||||
connect(matrixid_input_, SIGNAL(editingFinished()), this, SLOT(onMatrixIdEntered()));
|
||||
connect(client_.data(), SIGNAL(versionError(QString)), this, SLOT(versionError(QString)));
|
||||
connect(client_.data(), SIGNAL(versionSuccess()), this, SLOT(versionSuccess()));
|
||||
|
|
|
@ -85,7 +85,12 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
connect(login_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
|
||||
connect(login_page_, &LoginPage::loggingIn, this, &MainWindow::showOverlayProgressBar);
|
||||
connect(
|
||||
login_page_, &LoginPage::errorOccured, this, [this]() { removeOverlayProgressBar(); });
|
||||
register_page_, &RegisterPage::registering, this, &MainWindow::showOverlayProgressBar);
|
||||
connect(
|
||||
login_page_, &LoginPage::errorOccurred, this, [this]() { removeOverlayProgressBar(); });
|
||||
connect(register_page_, &RegisterPage::errorOccurred, this, [this]() {
|
||||
removeOverlayProgressBar();
|
||||
});
|
||||
connect(register_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
|
||||
|
||||
connect(chat_page_, SIGNAL(close()), this, SLOT(showWelcomePage()));
|
||||
|
@ -120,6 +125,11 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
this,
|
||||
SLOT(showChatPage(QString, QString, QString)));
|
||||
|
||||
connect(client_.data(),
|
||||
SIGNAL(registerSuccess(QString, QString, QString)),
|
||||
this,
|
||||
SLOT(showChatPage(QString, QString, QString)));
|
||||
|
||||
QShortcut *quitShortcut = new QShortcut(QKeySequence::Quit, this);
|
||||
connect(quitShortcut, &QShortcut::activated, this, QApplication::quit);
|
||||
|
||||
|
|
|
@ -27,9 +27,10 @@
|
|||
#include <QPixmap>
|
||||
#include <QSettings>
|
||||
#include <QUrlQuery>
|
||||
#include <mtx/errors.hpp>
|
||||
|
||||
#include "Deserializable.h"
|
||||
#include "MatrixClient.h"
|
||||
#include "Register.h"
|
||||
|
||||
MatrixClient::MatrixClient(QString server, QObject *parent)
|
||||
: QNetworkAccessManager(parent)
|
||||
|
@ -193,50 +194,66 @@ MatrixClient::logout() noexcept
|
|||
}
|
||||
|
||||
void
|
||||
MatrixClient::registerUser(const QString &user, const QString &pass, const QString &server) noexcept
|
||||
MatrixClient::registerUser(const QString &user,
|
||||
const QString &pass,
|
||||
const QString &server,
|
||||
const QString &session) 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());
|
||||
QJsonObject body{{"username", user}, {"password", pass}};
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
// 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]() {
|
||||
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;
|
||||
// Try to parse a regular register response.
|
||||
try {
|
||||
mtx::responses::Register res = nlohmann::json::parse(data);
|
||||
emit registerSuccess(QString::fromStdString(res.user_id.toString()),
|
||||
QString::fromStdString(res.user_id.hostname()),
|
||||
QString::fromStdString(res.access_token));
|
||||
} catch (const std::exception &e) {
|
||||
qWarning() << "Register" << e.what();
|
||||
}
|
||||
|
||||
RegisterResponse response;
|
||||
|
||||
// Check if the server requires a registration flow.
|
||||
try {
|
||||
response.deserialize(json);
|
||||
emit registerSuccess(response.getUserId(),
|
||||
response.getHomeServer(),
|
||||
response.getAccessToken());
|
||||
} catch (DeserializationException &e) {
|
||||
qWarning() << "Register" << e.what();
|
||||
emit registerError("Received malformed response.");
|
||||
mtx::responses::RegistrationFlows res = nlohmann::json::parse(data);
|
||||
emit registrationFlow(
|
||||
user, pass, server, QString::fromStdString(res.session));
|
||||
return;
|
||||
} catch (const std::exception &) {
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
/*
|
||||
* 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 "Register.h"
|
||||
|
||||
RegisterRequest::RegisterRequest(const QString &username, const QString &password)
|
||||
: user_(username)
|
||||
, password_(password)
|
||||
{}
|
||||
|
||||
QByteArray
|
||||
RegisterRequest::serialize() noexcept
|
||||
{
|
||||
QJsonObject body{{"username", user_}, {"password", password_}};
|
||||
|
||||
return QJsonDocument(body).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
||||
void
|
||||
RegisterResponse::deserialize(const QJsonDocument &data)
|
||||
{
|
||||
if (!data.isObject())
|
||||
throw DeserializationException("Response is not a JSON object");
|
||||
|
||||
QJsonObject object = data.object();
|
||||
|
||||
if (!object.contains("access_token"))
|
||||
throw DeserializationException("Missing access_token param");
|
||||
|
||||
if (!object.contains("home_server"))
|
||||
throw DeserializationException("Missing home_server param");
|
||||
|
||||
if (!object.contains("user_id"))
|
||||
throw DeserializationException("Missing user_id param");
|
||||
|
||||
access_token_ = object.value("access_token").toString();
|
||||
home_server_ = object.value("home_server").toString();
|
||||
user_id_ = object.value("user_id").toString();
|
||||
}
|
|
@ -16,14 +16,18 @@
|
|||
*/
|
||||
|
||||
#include <QStyleOption>
|
||||
#include <QTimer>
|
||||
|
||||
#include "Config.h"
|
||||
#include "FlatButton.h"
|
||||
#include "MainWindow.h"
|
||||
#include "MatrixClient.h"
|
||||
#include "RaisedButton.h"
|
||||
#include "RegisterPage.h"
|
||||
#include "TextField.h"
|
||||
|
||||
#include "dialogs/ReCaptcha.hpp"
|
||||
|
||||
RegisterPage::RegisterPage(QSharedPointer<MatrixClient> client, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, client_(client)
|
||||
|
@ -126,6 +130,30 @@ RegisterPage::RegisterPage(QSharedPointer<MatrixClient> client, QWidget *parent)
|
|||
SIGNAL(registerError(const QString &)),
|
||||
this,
|
||||
SLOT(registerError(const QString &)));
|
||||
connect(client_.data(),
|
||||
&MatrixClient::registrationFlow,
|
||||
this,
|
||||
[this](const QString &user,
|
||||
const QString &pass,
|
||||
const QString &server,
|
||||
const QString &session) {
|
||||
emit errorOccurred();
|
||||
|
||||
if (!captchaDialog_) {
|
||||
captchaDialog_ =
|
||||
std::make_shared<dialogs::ReCaptcha>(server, session, this);
|
||||
connect(captchaDialog_.get(),
|
||||
&dialogs::ReCaptcha::closing,
|
||||
this,
|
||||
[this, user, pass, server, session]() {
|
||||
captchaDialog_->close();
|
||||
emit registering();
|
||||
client_->registerUser(user, pass, server, session);
|
||||
});
|
||||
}
|
||||
|
||||
QTimer::singleShot(1000, this, [this]() { captchaDialog_->show(); });
|
||||
});
|
||||
|
||||
setLayout(top_layout_);
|
||||
}
|
||||
|
@ -139,6 +167,7 @@ RegisterPage::onBackButtonClicked()
|
|||
void
|
||||
RegisterPage::registerError(const QString &msg)
|
||||
{
|
||||
emit errorOccurred();
|
||||
error_label_->setText(msg);
|
||||
}
|
||||
|
||||
|
@ -161,6 +190,7 @@ RegisterPage::onRegisterButtonClicked()
|
|||
QString server = server_input_->text();
|
||||
|
||||
client_->registerUser(username, password, server);
|
||||
emit registering();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
75
src/dialogs/ReCaptcha.cpp
Normal file
75
src/dialogs/ReCaptcha.cpp
Normal file
|
@ -0,0 +1,75 @@
|
|||
#include <QDesktopServices>
|
||||
#include <QLabel>
|
||||
#include <QPaintEvent>
|
||||
#include <QStyleOption>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Config.h"
|
||||
#include "FlatButton.h"
|
||||
#include "RaisedButton.h"
|
||||
#include "Theme.h"
|
||||
|
||||
#include "dialogs/ReCaptcha.hpp"
|
||||
|
||||
using namespace dialogs;
|
||||
|
||||
ReCaptcha::ReCaptcha(const QString &server, const QString &session, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
setAutoFillBackground(true);
|
||||
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->setSpacing(30);
|
||||
layout->setMargin(20);
|
||||
|
||||
auto buttonLayout = new QHBoxLayout();
|
||||
buttonLayout->setSpacing(8);
|
||||
buttonLayout->setMargin(0);
|
||||
|
||||
openCaptchaBtn_ = new FlatButton("OPEN reCAPTCHA", this);
|
||||
openCaptchaBtn_->setFontSize(conf::btn::fontSize);
|
||||
|
||||
confirmBtn_ = new RaisedButton(tr("CONFIRM"), this);
|
||||
confirmBtn_->setFontSize(conf::btn::fontSize);
|
||||
|
||||
cancelBtn_ = new RaisedButton(tr("CANCEL"), this);
|
||||
cancelBtn_->setFontSize(conf::btn::fontSize);
|
||||
|
||||
buttonLayout->addStretch(1);
|
||||
buttonLayout->addWidget(openCaptchaBtn_);
|
||||
buttonLayout->addWidget(confirmBtn_);
|
||||
buttonLayout->addWidget(cancelBtn_);
|
||||
|
||||
QFont font;
|
||||
font.setPixelSize(conf::headerFontSize);
|
||||
|
||||
auto label = new QLabel(tr("Solve the reCAPTCHA and press the confirm button"), this);
|
||||
label->setFont(font);
|
||||
|
||||
layout->addWidget(label);
|
||||
layout->addLayout(buttonLayout);
|
||||
|
||||
connect(openCaptchaBtn_, &QPushButton::clicked, [server, session, this]() {
|
||||
const auto url =
|
||||
QString(
|
||||
"https://%1/_matrix/client/r0/auth/m.login.recaptcha/fallback/web?session=%2")
|
||||
.arg(server)
|
||||
.arg(session);
|
||||
|
||||
QDesktopServices::openUrl(url);
|
||||
});
|
||||
|
||||
connect(confirmBtn_, &QPushButton::clicked, this, &dialogs::ReCaptcha::closing);
|
||||
connect(cancelBtn_, &QPushButton::clicked, this, &dialogs::ReCaptcha::close);
|
||||
}
|
||||
|
||||
void
|
||||
ReCaptcha::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QStyleOption opt;
|
||||
opt.init(this);
|
||||
QPainter p(this);
|
||||
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
||||
}
|
Loading…
Reference in a new issue