xxcc/xxcc.cpp

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);
}
}