Automatically populate the contacts widget via [YellWOWPages](yellow.wownero.com/). One may still add custom contacts - they will get saved normally like before.

Requires https://git.wownero.com/wowlet/wowlet-backend/pulls/17

![https://i.imgur.com/EYa1BBf.png](https://i.imgur.com/EYa1BBf.png)

Adds some new CLI arguments

- `backend-host`
- `backend-port`
- `backend-tls`

They refer to wowlet-backend
This commit is contained in:
dsc 2022-03-14 18:55:27 +02:00 committed by Sander
parent 65ceab6323
commit 14d9793193
12 changed files with 159 additions and 36 deletions

View file

@ -100,8 +100,21 @@ AppContext::AppContext(QCommandLineParser *cmdargs) {
connect(this, &AppContext::nodeSourceChanged, this->nodes, &Nodes::onNodeSourceChanged);
connect(this, &AppContext::setCustomNodes, this->nodes, &Nodes::setCustomNodes);
// Tor & socks proxy
this->ws = new WSClient(this, wsUrl);
// init backend URLs
if(cmdargs->isSet("backend-host"))
this->backendHost = cmdargs->value("backend-host");
if(cmdargs->isSet("backend-host"))
this->backendPort = cmdargs->value("backend-port").toUInt();
if(cmdargs->isSet("backend-tls"))
this->backendTLS = true;
backendWSUrl = this->backendTLS ? "wss://" : "ws://";
backendWSUrl += QString("%1:%2").arg(this->backendHost).arg(this->backendPort);
backendHTTPUrl = this->backendTLS ? "https://" : "http://";
backendHTTPUrl += QString("%1:%2").arg(this->backendHost).arg(this->backendPort);
// init websocket client
this->ws = new WSClient(this);
connect(this->ws, &WSClient::WSMessage, this, &AppContext::onWSMessage);
connect(this->ws, &WSClient::connectionEstablished, this, &AppContext::wsConnected);
connect(this->ws, &WSClient::closed, this, &AppContext::wsDisconnected);
@ -177,7 +190,8 @@ void AppContext::initTor() {
this->tor = new Tor(this, this);
this->tor->start();
if (!isWhonix && wsUrl.contains(".onion")) {
if (!isWhonix && backendHost.contains(".onion")) {
qDebug() << "'backend-host' did not contain '.onion' - running without Tor proxy.";
this->networkProxy = new QNetworkProxy(QNetworkProxy::Socks5Proxy, Tor::torHost, Tor::torPort);
this->network->setProxy(*networkProxy);
this->ws->webSocket.setProxy(*networkProxy);
@ -421,7 +435,10 @@ void AppContext::onWSMessage(const QJsonObject &msg) {
if(changed)
emit blockHeightWSUpdated(this->heights);
}
else if(cmd == "yellwow") {
this->yellowPagesData = msg.value("data").toArray();
emit yellowUpdated();
}
else if(cmd == "rpc_nodes") {
this->onWSNodes(msg.value("data").toArray());
}

View file

@ -66,7 +66,13 @@ public:
QString defaultWalletDir;
QString defaultWalletDirRoot;
QString tmpTxDescription;
QString wsUrl = "6wku2m4zrv6j666crlo7lzofv6ud6enzllyhou3ijeigpukymi37caad.onion";
// https://git.wownero.com/wowlet/wowlet-backend/
QString backendHost = "6wku2m4zrv6j666crlo7lzofv6ud6enzllyhou3ijeigpukymi37caad.onion";
unsigned int backendPort = 80;
bool backendTLS = false;
QString backendWSUrl;
QString backendHTTPUrl;
QString walletPath;
QString walletPassword = "";
@ -106,6 +112,7 @@ public:
static QMap<QString, QString> txDescriptionCache;
static QMap<QString, QString> txCache;
static TxFiatHistory *txFiatHistory;
QJsonArray yellowPagesData;
QJsonObject versionPending;
// libwalletqt
@ -205,6 +212,7 @@ signals:
void nodesUpdated(QList<QSharedPointer<WowletNode>> &nodes);
void ccsUpdated(QList<QSharedPointer<CCSEntry>> &entries);
void suchWowUpdated(const QJsonArray &such_data);
void yellowUpdated();
void nodeSourceChanged(NodeSource nodeSource);
void XMRigDownloads(const QJsonObject &data);
void pinLookupReceived(QString address, QString pin);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 148 KiB

View file

@ -32,17 +32,22 @@ ContactsWidget::ContactsWidget(QWidget *parent) :
this->newContact();
});
// row context menu
m_rowMenu = new QMenu(ui->contacts);
m_rowMenu->addAction(QIcon(":/assets/images/copy.png"), "Copy address", this, &ContactsWidget::copyAddress);
m_rowMenu->addAction(QIcon(":/assets/images/copy.png"), "Copy name", this, &ContactsWidget::copyName);
m_rowMenu->addAction("Pay to", this, &ContactsWidget::payTo);
m_rowMenu->addAction("Delete", this, &ContactsWidget::deleteContact);
connect(ui->contacts, &QTreeView::customContextMenuRequested, [=](const QPoint & point){
QModelIndex index = ui->contacts->indexAt(point);
if (index.isValid()) {
auto username = index.model()->data(index.siblingAtColumn(AddressBookModel::Description), Qt::UserRole).toString();
m_rowMenu = new QMenu(ui->contacts);
if(username.contains("(YellWOWPages)"))
m_rowMenu->addAction(QIcon(":/assets/images/network.png"), "Visit user's YellWOWPage", this, &ContactsWidget::visitYellowPage);
m_rowMenu->addAction(QIcon(":/assets/images/copy.png"), "Copy address", this, &ContactsWidget::copyAddress);
m_rowMenu->addAction(QIcon(":/assets/images/copy.png"), "Copy name", this, &ContactsWidget::copyName);
m_rowMenu->addAction("Pay to", this, &ContactsWidget::payTo);
m_rowMenu->addAction("Delete", this, &ContactsWidget::deleteContact);
m_rowMenu->exec(ui->contacts->viewport()->mapToGlobal(point));
m_rowMenu->deleteLater();
}
else {
m_contextMenu->exec(ui->contacts->viewport()->mapToGlobal(point));
@ -52,6 +57,68 @@ ContactsWidget::ContactsWidget(QWidget *parent) :
connect(ui->search, &QLineEdit::textChanged, this, &ContactsWidget::setSearchFilter);
}
QMap<QString, QString> ContactsWidget::data() {
auto rtn = QMap<QString, QString>();
for (int i = 0; i < m_ctx->currentWallet->addressBook()->count(); i++) {
m_ctx->currentWallet->addressBook()->getRow(i, [&rtn](const AddressBookInfo &entry) {
rtn[entry.description()] = entry.address();
});
}
return rtn;
}
unsigned int ContactsWidget::rowIndex(const QString &name) {
// name -> row index lookup
int result = -1;
for (int i = 0; i < m_ctx->currentWallet->addressBook()->count(); i++) {
m_ctx->currentWallet->addressBook()->getRow(i, [i, name, &result](const AddressBookInfo &entry) {
if(entry.description() == name) result = i;
return;
});
if(result != -1)
return result;
}
return result;
}
void ContactsWidget::loadYellowPages() {
if (m_ctx->currentWallet == nullptr || m_ctx->yellowPagesData.empty())
return;
auto contacts = this->data();
for (auto item: m_ctx->yellowPagesData) {
auto obj = item.toObject();
const auto username = QString("%1 (YellWOWPages)").arg(obj.value("username").toString());
const auto address = obj.value("address").toString();
if(contacts.contains(username)) {
if(contacts[username] == address) continue;
// update the address
auto idx = this->rowIndex(username);
if(idx == -1) continue;
m_model->deleteRow((int)idx);
}
bool addressValid = WalletManager::addressValid(address, m_ctx->currentWallet->nettype());
if (!addressValid) {
continue;
}
m_ctx->currentWallet->addressBook()->addRow(address, "", username);
}
}
void ContactsWidget::visitYellowPage() {
auto index = ui->contacts->currentIndex();
auto username = index.model()->data(
index.siblingAtColumn(AddressBookModel::Description),
Qt::UserRole).toString();
username = username.replace(" (YellWOWPages)", "").trimmed();
Utils::externalLinkWarning(this, QString("https://yellow.wownero.com/user/%1").arg(username));
}
void ContactsWidget::copyAddress() {
QModelIndex index = ui->contacts->currentIndex();
ModelUtils::copyColumn(&index, AddressBookModel::Address);

View file

@ -22,6 +22,7 @@ class ContactsWidget : public QWidget
public:
explicit ContactsWidget(QWidget *parent = nullptr);
void setModel(AddressBookModel * model);
QMap<QString, QString> data();
~ContactsWidget() override;
public slots:
@ -30,9 +31,11 @@ public slots:
void payTo();
void newContact(QString address = "", QString name = "");
void deleteContact();
void visitYellowPage();
void setShowFullAddresses(bool show);
void setSearchFilter(const QString &filter);
void resetModel();
void loadYellowPages();
signals:
void fillAddress(QString &address);
@ -50,6 +53,8 @@ private:
QMenu *m_headerMenu;
AddressBookModel * m_model;
AddressBookProxyModel * m_proxyModel;
unsigned int rowIndex(const QString &name);
};
#endif // CONTACTSWIDGET_H

View file

@ -50,6 +50,7 @@ void DebugInfoDialog::updateInfo() {
ui->label_synchronized->setText(m_ctx->currentWallet->synchronized() ? "True" : "False");
auto node = m_ctx->nodes->connection();
ui->label_websocketURL->setText(m_ctx->backendWSUrl);
ui->label_remoteNode->setText(node.full);
ui->label_walletStatus->setText(this->statusToString(m_ctx->currentWallet->connectionStatus()));
ui->label_torStatus->setText(torStatus);
@ -101,6 +102,7 @@ void DebugInfoDialog::copyToClipboad() {
text += QString("Remote node: %1 \n").arg(ui->label_remoteNode->text());
text += QString("Wallet status: %1 \n").arg(ui->label_walletStatus->text());
text += QString("Tor status: %1 \n").arg(ui->label_torStatus->text());
text += QString("Websocket URL: %1 \n").arg(ui->label_websocketURL->text());
text += QString("Websocket status: %1 \n").arg(ui->label_websocketStatus->text());
text += QString("Network type: %1 \n").arg(ui->label_netType->text());

View file

@ -183,14 +183,14 @@
</property>
</widget>
</item>
<item row="12" column="0">
<item row="13" column="0">
<widget class="QLabel" name="label_19">
<property name="text">
<string>Websocket status:</string>
</property>
</widget>
</item>
<item row="12" column="1">
<item row="13" column="1">
<widget class="QLabel" name="label_websocketStatus">
<property name="text">
<string>TextLabel</string>
@ -200,21 +200,21 @@
</property>
</widget>
</item>
<item row="13" column="1">
<item row="14" column="1">
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="14" column="0">
<item row="15" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Network type:</string>
</property>
</widget>
</item>
<item row="14" column="1">
<item row="15" column="1">
<widget class="QLabel" name="label_netType">
<property name="text">
<string>TextLabel</string>
@ -224,14 +224,14 @@
</property>
</widget>
</item>
<item row="15" column="0">
<item row="16" column="0">
<widget class="QLabel" name="label_23">
<property name="text">
<string>Seed type:</string>
</property>
</widget>
</item>
<item row="15" column="1">
<item row="16" column="1">
<widget class="QLabel" name="label_seedType">
<property name="text">
<string>TextLabel</string>
@ -241,14 +241,14 @@
</property>
</widget>
</item>
<item row="16" column="0">
<item row="17" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>View only:</string>
</property>
</widget>
</item>
<item row="16" column="1">
<item row="17" column="1">
<widget class="QLabel" name="label_viewOnly">
<property name="text">
<string>TextLabel</string>
@ -258,14 +258,14 @@
</property>
</widget>
</item>
<item row="20" column="0">
<item row="21" column="0">
<widget class="QLabel" name="label_24">
<property name="text">
<string>Timestamp:</string>
</property>
</widget>
</item>
<item row="20" column="1">
<item row="21" column="1">
<widget class="QLabel" name="label_timestamp">
<property name="text">
<string>TextLabel</string>
@ -275,14 +275,14 @@
</property>
</widget>
</item>
<item row="19" column="0">
<item row="20" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Operating system:</string>
</property>
</widget>
</item>
<item row="19" column="1">
<item row="20" column="1">
<widget class="QLabel" name="label_OS">
<property name="text">
<string>TextLabel</string>
@ -292,7 +292,7 @@
</property>
</widget>
</item>
<item row="18" column="1">
<item row="19" column="1">
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -316,20 +316,34 @@
</property>
</widget>
</item>
<item row="17" column="1">
<item row="18" column="1">
<widget class="QLabel" name="label_primaryOnly">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="17" column="0">
<item row="18" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Primary only:</string>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Websocket URL:</string>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="QLabel" name="label_websocketURL">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
</layout>
</item>
<item>

View file

@ -107,6 +107,15 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
QCommandLineOption androidDebugOption(QStringList() << "android-debug", "Start the Android interface without actually running on Android - for debugging purposes. Requires -DANDROID_DEBUG=ON CMake definition.");
parser.addOption(androidDebugOption);
QCommandLineOption backendHostOption(QStringList() << "backend-host", "specify your own `wowlet-backend` host", "backend-host");
parser.addOption(backendHostOption);
QCommandLineOption backendPortOption(QStringList() << "backend-port", "specify your own `wowlet-backend` port", "backend-port");
parser.addOption(backendPortOption);
QCommandLineOption backendTLS(QStringList() << "backend-tls", "`wowlet-backend` is running via TLS? 'wss://' and 'https://' will be used.", "backend-tls");
parser.addOption(backendTLS);
auto parsed = parser.parse(argv_);
if(!parsed) {
qCritical() << parser.errorText();

View file

@ -303,6 +303,7 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
// Contacts
connect(ui->contactWidget, &ContactsWidget::fillAddress, ui->sendWidget, &SendWidget::fillAddress);
connect(m_ctx, &AppContext::yellowUpdated, ui->contactWidget, &ContactsWidget::loadYellowPages);
// Open alias
connect(ui->sendWidget, &SendWidget::resolveOpenAlias, m_ctx, &AppContext::onOpenAliasResolve);
@ -632,6 +633,7 @@ void MainWindow::onWalletOpened(Wallet *wallet) {
// contacts widget
ui->contactWidget->setModel(m_ctx->currentWallet->addressBookModel());
ui->contactWidget->loadYellowPages();
// coins page
m_ctx->currentWallet->coins()->refresh(m_ctx->currentWallet->currentSubaddressAccount());

View file

@ -8,18 +8,18 @@
#include "wsclient.h"
#include "appcontext.h"
WSClient::WSClient(AppContext *ctx, const QString &url, QObject *parent) :
WSClient::WSClient(AppContext *ctx, QObject *parent) :
QObject(parent),
m_ctx(ctx) {
// this class connects to `https://git.wownero.com/wowlet/wowlet-backend/`
connect(&this->webSocket, &QWebSocket::binaryMessageReceived, this, &WSClient::onbinaryMessageReceived);
connect(&this->webSocket, &QWebSocket::connected, this, &WSClient::onConnected);
connect(&this->webSocket, &QWebSocket::disconnected, this, &WSClient::closed);
connect(&this->webSocket, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error), this, &WSClient::onError);
m_tor = url.contains(".onion");
this->url = QString("ws://%1/ws").arg(url);
m_tor = m_ctx->backendHost.contains(".onion");
// Keep websocket connection alive
// keep-alive
connect(&m_pingTimer, &QTimer::timeout, [this]{
if (this->webSocket.state() == QAbstractSocket::ConnectedState)
this->webSocket.ping();
@ -40,7 +40,7 @@ void WSClient::start() {
qDebug() << "WebSocket connect:" << url.url();
#endif
if((m_tor && this->m_ctx->tor->torConnected) || !m_tor)
this->webSocket.open(QUrl(this->url));
this->webSocket.open(QString("%1/ws").arg(m_ctx->backendWSUrl));
if(!this->m_connectionTimer.isActive()) {
connect(&this->m_connectionTimer, &QTimer::timeout, this, &WSClient::checkConnection);

View file

@ -14,11 +14,10 @@ class WSClient : public QObject
Q_OBJECT
public:
explicit WSClient(AppContext *ctx, const QString &url, QObject *parent = nullptr);
explicit WSClient(AppContext *ctx, QObject *parent = nullptr);
void start();
void sendMsg(const QByteArray &data);
QWebSocket webSocket;
QString url;
signals:
void closed();

View file

@ -19,7 +19,7 @@ SuchWowPost::SuchWowPost(AppContext *ctx, QObject *parent) :
m_ctx(ctx) {
m_networkImg = new UtilsNetworking(m_ctx->network, this);
m_networkThumb = new UtilsNetworking(m_ctx->network, this);
m_weburl = QString("http://%1/suchwow").arg(this->m_ctx->wsUrl);
m_weburl = QString("%1/suchwow").arg(this->m_ctx->backendHTTPUrl);
}
void SuchWowPost::onThumbReceived(QByteArray data) {