311 lines
7.9 KiB
C++
311 lines
7.9 KiB
C++
#include "xxcc.h"
|
|
#include "client.h"
|
|
#include "direction.h"
|
|
#include "accounts.h"
|
|
#include "contacts.h"
|
|
#include "conversation.h"
|
|
#include "message.h"
|
|
#include <QXmppMessage.h>
|
|
#include <QXmppOmemoElement_p.h>
|
|
#include <QXmppRosterManager.h>
|
|
#include <QXmppUtils.h>
|
|
#include <QKeyEvent>
|
|
#include <QPushButton>
|
|
#include <QScroller>
|
|
#include <iostream>
|
|
#include <stdexcept>
|
|
#include <utility>
|
|
#include <variant>
|
|
|
|
xxcc::xxcc(QWidget *const parent) :
|
|
QWidget(parent),
|
|
selected(nullptr)
|
|
{
|
|
ui.setupUi(this);
|
|
QScroller::grabGesture(ui.conversations_list, QScroller::TouchGesture);
|
|
QScroller::grabGesture(ui.messages, QScroller::TouchGesture);
|
|
|
|
creds.load().then(this,
|
|
[=] (Credentials::PairListResult &&result)
|
|
{
|
|
if (std::holds_alternative<Credentials::PairList>(result))
|
|
{
|
|
const auto &pairs = std::get<Credentials::PairList>(result);
|
|
|
|
connectAccounts(pairs);
|
|
retrieveConversations();
|
|
}
|
|
else if (std::holds_alternative<Credentials::Error>(result))
|
|
{
|
|
const auto &error = std::get<Credentials::Error>(result);
|
|
|
|
std::cerr << qPrintable(error.description) << std::endl;
|
|
}
|
|
});
|
|
|
|
|
|
connect(ui.accounts, &QPushButton::released, this,
|
|
[this]
|
|
{
|
|
Accounts a(clients, this);
|
|
|
|
a.connect(&a, &Accounts::new_account, this, &xxcc::addAccount);
|
|
a.connect(&a, &Accounts::new_account, this,
|
|
[&] (Client *c)
|
|
{
|
|
creds.store(c);
|
|
});
|
|
|
|
a.exec();
|
|
});
|
|
|
|
connect(ui.contacts, &QPushButton::released, this,
|
|
[this]
|
|
{
|
|
Contacts c(clients, this);
|
|
|
|
connect(&c, &Contacts::startChat, this, &xxcc::startChat);
|
|
|
|
for (const auto cl : clients)
|
|
{
|
|
const auto db = &cl->database();
|
|
|
|
c.connect(db, &JidDb::addedToRoster, &c,
|
|
[&c, db] (const QString jid)
|
|
{
|
|
c.add(db->jid, jid);
|
|
});
|
|
}
|
|
|
|
c.exec();
|
|
});
|
|
|
|
connect(ui.send, &QPushButton::released, this, &xxcc::send);
|
|
connect(ui.back, &QPushButton::released, this,
|
|
[this]
|
|
{
|
|
ui.sw->setCurrentIndex(Tab::Conversations);
|
|
ui.messages->clear();
|
|
selected = nullptr;
|
|
});
|
|
|
|
connect(ui.conversations_list, &QListWidget::itemActivated, this,
|
|
[this] (QListWidgetItem *const it)
|
|
{
|
|
const auto conv = static_cast<const Conversation *>(it);
|
|
|
|
for (const auto c : clients)
|
|
if (c->jidBare() == conv->from)
|
|
{
|
|
selected = c;
|
|
break;
|
|
}
|
|
|
|
if (selected)
|
|
{
|
|
const auto &db = selected->database();
|
|
static const auto n_messages = 20;
|
|
const auto messages = db.messages(conv->to, n_messages);
|
|
|
|
for (auto it = messages.rbegin(); it != messages.rend(); it++)
|
|
new Message(it->body, it->dt, it->direction, ui.messages);
|
|
}
|
|
|
|
ui.sw->setCurrentIndex(Tab::Chat);
|
|
ui.jid->setText(conv->to);
|
|
ui.messages->scrollToBottom();
|
|
});
|
|
|
|
connect(ui.messages->model(), &QAbstractItemModel::rowsInserted,
|
|
ui.messages, &QListWidget::scrollToBottom);
|
|
}
|
|
|
|
xxcc::~xxcc()
|
|
{
|
|
for (const auto c : clients)
|
|
delete c;
|
|
}
|
|
|
|
void xxcc::connectAccounts(const QList<Credentials::Pair> &pairs)
|
|
{
|
|
for (const auto &p : pairs)
|
|
{
|
|
QXmppConfiguration cfg;
|
|
|
|
cfg.setStreamSecurityMode(QXmppConfiguration::TLSRequired);
|
|
cfg.setJid(p.first);
|
|
cfg.setPassword(p.second);
|
|
cfg.setAutoReconnectionEnabled(true);
|
|
|
|
const auto client = new Client(p.first);
|
|
|
|
addAccount(client);
|
|
client->connectToServer(cfg);
|
|
}
|
|
}
|
|
|
|
void xxcc::startChat(const QString from, const QString to)
|
|
{
|
|
bool found = false;
|
|
|
|
for (int i = 0; i < ui.conversations_list->count(); i++)
|
|
{
|
|
const auto it =
|
|
static_cast<const Conversation *>(ui.conversations_list->item(i));
|
|
|
|
if (it->from == from && it->to == to)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
new Conversation(from, to, ui.conversations_list);
|
|
|
|
for (const auto c : clients)
|
|
if (c->jidBare() == from)
|
|
{
|
|
selected = c;
|
|
break;
|
|
}
|
|
|
|
ui.sw->setCurrentIndex(Tab::Chat);
|
|
ui.jid->setText(to);
|
|
ui.messages->scrollToBottom();
|
|
}
|
|
|
|
void xxcc::addInMessage(const QXmppMessage &msg)
|
|
{
|
|
new Message(msg.body(), msg.stamp().toLocalTime(), Direction::In,
|
|
ui.messages);
|
|
}
|
|
|
|
void xxcc::addOutMessage(const QString &msg, const QDateTime &dt)
|
|
{
|
|
new Message(msg, dt.toLocalTime(), Direction::Out,
|
|
ui.messages);
|
|
}
|
|
|
|
void xxcc::addAccount(Client *const c)
|
|
{
|
|
c->configuration().setAutoReconnectionEnabled(true);
|
|
clients.append(c);
|
|
|
|
c->connect(c, &Client::messageReceived, this,
|
|
[this] (QXmppMessage msg)
|
|
{
|
|
if (msg.body().isEmpty())
|
|
return;
|
|
else if (msg.stamp().isNull())
|
|
msg.setStamp(QDateTime::currentDateTimeUtc());
|
|
|
|
storeMessage(msg, Direction::In);
|
|
|
|
if (selected)
|
|
addInMessage(msg);
|
|
});
|
|
|
|
const auto roster = c->findExtension<QXmppRosterManager>();
|
|
|
|
if (roster)
|
|
roster->connect(roster, &QXmppRosterManager::rosterReceived, c,
|
|
[this, c, roster]
|
|
{
|
|
c->database().addToRoster(roster->getRosterBareJids());
|
|
});
|
|
else
|
|
throw std::runtime_error("Expected non-null QXmppRosterManager");
|
|
}
|
|
|
|
#include <QDebug>
|
|
|
|
void xxcc::send(void)
|
|
{
|
|
if (!selected)
|
|
throw std::runtime_error("Expected non-null selected client");
|
|
|
|
const auto from = selected->jidBare(),
|
|
to = ui.jid->text(), msg = ui.chatinput->toPlainText();
|
|
const bool enc = ui.omemo->isChecked();
|
|
QXmppMessage out(from, to, msg);
|
|
const auto dt = QDateTime::currentDateTimeUtc();
|
|
|
|
out.setE2eeFallbackBody("[xxcc: This is an OMEMO-encrypted message]");
|
|
out.setStamp(dt);
|
|
// TODO: QXmpp forces OMEMO 2 (XEP-0384 version >= 0.8.0).
|
|
// This breaks compatibility with Dino and Gajim, who still use 0.1.0.
|
|
// out.setEncryptionMethod(QXmpp::Omemo0);
|
|
|
|
selected->sendSensitive(std::move(out)).then(this,
|
|
[=](const QXmpp::SendResult &&result) mutable
|
|
{
|
|
qDebug() << "result.index(): " << result.index();
|
|
|
|
if (std::holds_alternative<QXmpp::SendSuccess>(result))
|
|
{
|
|
const auto &success = std::get<QXmpp::SendSuccess>(result);
|
|
qDebug() << "acknowledged: " << success.acknowledged;
|
|
addOutMessage(msg, dt);
|
|
storeMessage(from, to, msg, dt, Direction::Out);
|
|
ui.chatinput->clear();
|
|
}
|
|
else if (std::holds_alternative<QXmppError>(result))
|
|
{
|
|
const auto &error = std::get<QXmppError>(result);
|
|
qDebug() << error.description;
|
|
}
|
|
});
|
|
}
|
|
|
|
void xxcc::storeMessage(const QString &from, const QString &to,
|
|
const QString &msg, const QDateTime &dt, const Direction dir) const
|
|
{
|
|
QString jid, contact;
|
|
|
|
switch (dir)
|
|
{
|
|
case Direction::In:
|
|
jid = QXmppUtils::jidToBareJid(to);
|
|
contact = QXmppUtils::jidToBareJid(from);
|
|
break;
|
|
|
|
case Direction::Out:
|
|
jid = from;
|
|
contact = to;
|
|
break;
|
|
}
|
|
|
|
for (const auto c : clients)
|
|
{
|
|
if (c->jidBare() == jid)
|
|
{
|
|
const auto &db = c->database();
|
|
JidDb::Message m;
|
|
|
|
m.body = msg;
|
|
m.dt = dt;
|
|
m.direction = dir;
|
|
m.contact = contact;
|
|
db.store(m);
|
|
}
|
|
}
|
|
}
|
|
|
|
void xxcc::storeMessage(const QXmppMessage &msg, const Direction dir) const
|
|
{
|
|
storeMessage(msg.from(), msg.to(), msg.body(), msg.stamp(), dir);
|
|
}
|
|
|
|
void xxcc::retrieveConversations()
|
|
{
|
|
for (const auto c : clients)
|
|
{
|
|
const auto &db = c->database();
|
|
|
|
for (const auto &conv : db.conversations())
|
|
new Conversation(db.jid, conv.to,
|
|
ui.conversations_list, conv.last_msg, conv.dt);
|
|
}
|
|
}
|