#include "xxcc.h" #include "client.h" #include "direction.h" #include "accounts.h" #include "contacts.h" #include "conversation.h" #include "message.h" #include #include #include #include #include #include #include #include #include #include #include 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(result)) { const auto &pairs = std::get(result); connectAccounts(pairs); retrieveConversations(); } else if (std::holds_alternative(result)) { const auto &error = std::get(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(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(); ui.chatinput->setFocus(); }); connect(ui.messages->model(), &QAbstractItemModel::rowsInserted, ui.messages, &QListWidget::scrollToBottom); } xxcc::~xxcc() { for (const auto c : clients) delete c; } void xxcc::connectAccounts(const QList &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(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(); 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 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(); QXmppMessage out(from, to, msg); const auto dt = QDateTime::currentDateTimeUtc(); out.setStamp(dt); auto fn = [=](const QXmpp::SendResult &&result) mutable { qDebug() << "result.index(): " << result.index(); if (std::holds_alternative(result)) { const auto &success = std::get(result); qDebug() << "acknowledged: " << success.acknowledged; addOutMessage(msg, dt); storeMessage(from, to, msg, dt, Direction::Out); ui.chatinput->clear(); } else if (std::holds_alternative(result)) { const auto &error = std::get(result); qDebug() << error.description; } }; if (ui.omemo->isChecked()) { out.setE2eeFallbackBody("[xxcc: This is an OMEMO-encrypted message]"); // 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, fn); } else selected->send(std::move(out)).then(this, fn); } 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); } }