MessageHandler.cpp 8.54 KB
Newer Older
1 2 3
/*
 *  Kaidan - A user-friendly XMPP client for every device!
 *
Linus Jahn's avatar
Linus Jahn committed
4
 *  Copyright (C) 2016-2019 Kaidan developers and contributors
Linus Jahn's avatar
Linus Jahn committed
5
 *  (see the LICENSE file for a full list of copyright authors)
6 7 8 9 10 11
 *
 *  Kaidan 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.
 *
Linus Jahn's avatar
Linus Jahn committed
12 13 14 15 16 17 18 19 20 21
 *  In addition, as a special exception, the author of Kaidan gives
 *  permission to link the code of its release with the OpenSSL
 *  project's "OpenSSL" library (or with modified versions of it that
 *  use the same license as the "OpenSSL" library), and distribute the
 *  linked executables. You must obey the GNU General Public License in
 *  all respects for all of the code used other than "OpenSSL". If you
 *  modify this file, you may extend this exception to your version of
 *  the file, but you are not obligated to do so.  If you do not wish to
 *  do so, delete this exception statement from your version.
 *
22 23 24 25 26 27
 *  Kaidan 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
Linus Jahn's avatar
Linus Jahn committed
28
 *  along with Kaidan.  If not, see <http://www.gnu.org/licenses/>.
29 30 31 32 33
 */

#include "MessageHandler.h"
// Qt
#include <QDateTime>
34
#include <QMimeDatabase>
35
#include <QString>
36
#include <QUrl>
Linus Jahn's avatar
Linus Jahn committed
37 38 39 40
// QXmpp
#include <QXmppClient.h>
#include <QXmppUtils.h>
#include <QXmppRosterManager.h>
41 42 43 44
#include <QXmppDiscoveryManager.h>
#if QXMPP_VERSION >= 0x000904
#include <QXmppCarbonManager.h>
#endif
45
// Kaidan
Linus Jahn's avatar
Linus Jahn committed
46
#include "Kaidan.h"
47 48 49
#include "MessageModel.h"
#include "Notifications.h"

Linus Jahn's avatar
Linus Jahn committed
50 51 52
MessageHandler::MessageHandler(Kaidan *kaidan, QXmppClient *client, MessageModel *model,
                               QObject *parent)
	: QObject(parent), kaidan(kaidan), client(client), model(model)
53
{
Linus Jahn's avatar
Linus Jahn committed
54 55
	connect(client, &QXmppClient::messageReceived, this, &MessageHandler::handleMessage);
	connect(kaidan, &Kaidan::sendMessage, this, &MessageHandler::sendMessage);
56
	connect(kaidan, &Kaidan::correctMessage, this, &MessageHandler::correctMessage);
Linus Jahn's avatar
Linus Jahn committed
57 58 59 60 61 62

	client->addExtension(&receiptManager);
	connect(&receiptManager, &QXmppMessageReceiptManager::messageDelivered,
		[=] (const QString&, const QString &id) {
		emit model->setMessageAsDeliveredRequested(id);
	});
63 64 65 66 67 68 69 70 71 72 73 74 75

#if QXMPP_VERSION >= 0x000904 // QXmppCarbonManager was added in v0.9.4
	carbonManager = new QXmppCarbonManager();
	client->addExtension(carbonManager);

	// messages sent to our account (forwarded from another client)
	connect(carbonManager, &QXmppCarbonManager::messageReceived,
	        client, &QXmppClient::messageReceived);
	// messages sent from our account (but another client)
	connect(carbonManager, &QXmppCarbonManager::messageSent,
	        client, &QXmppClient::messageReceived);

	// carbons discovery
76
	auto *discoManager = client->findExtension<QXmppDiscoveryManager>();
77 78 79 80 81 82 83 84 85 86 87 88 89
	if (!discoManager)
		return;

	connect(discoManager, &QXmppDiscoveryManager::infoReceived,
	        this, &MessageHandler::handleDiscoInfo);
#endif
}

MessageHandler::~MessageHandler()
{
#if QXMPP_VERSION >= 0x000904
	delete carbonManager;
#endif
90 91
}

Linus Jahn's avatar
Linus Jahn committed
92
void MessageHandler::handleMessage(const QXmppMessage &msg)
93
{
94 95
	bool isCarbonMessage = false;

Linus Jahn's avatar
Linus Jahn committed
96
	if (msg.body().isEmpty())
97
		return;
98

Linus Jahn's avatar
Linus Jahn committed
99 100 101 102 103 104
	MessageModel::Message entry;
	entry.author = QXmppUtils::jidToBareJid(msg.from());
	entry.recipient = QXmppUtils::jidToBareJid(msg.to());
	entry.id = msg.id();
	entry.sentByMe = (entry.author == client->configuration().jidBare());
	entry.message = msg.body();
105 106
	entry.type = MessageType::MessageText; // default to text message without media

107
	// check if message contains a link and also check out of band url
108
	QList<QString> bodyWords = msg.body().split(" ");
109 110 111
#if QXMPP_VERSION >= QT_VERSION_CHECK(1, 0, 0)
	bodyWords.prepend(msg.outOfBandUrl());
#endif
112
	for (const QString &word : bodyWords) {
113
		if (!word.startsWith("https://") || !word.startsWith("http://"))
114 115 116 117 118 119 120 121 122 123 124
			continue;

		// check message type by file name in link
		// This is hacky, but needed without SIMS or an additional HTTP request.
		// Also, this can be useful when a user manually posts an HTTP url.
		QUrl url(word);
		QList<QMimeType> mediaTypes = QMimeDatabase().mimeTypesForFileName(url.fileName());
		for (const QMimeType &type : mediaTypes) {
			MessageType mType = MessageModel::messageTypeFromMimeType(type);
			if (mType == MessageType::MessageImage ||
			    mType == MessageType::MessageAudio ||
125 126 127
			    mType == MessageType::MessageVideo ||
			    mType == MessageType::MessageDocument ||
			    mType == MessageType::MessageFile) {
128 129 130 131 132 133 134 135
				entry.type = mType;
				entry.mediaContentType = type.name();
				entry.mediaUrl = url.toEncoded();
				break;
			}
		}
		break; // we can only handle one link
	}
136

Linus Jahn's avatar
Linus Jahn committed
137
	// get possible delay (timestamp)
138 139 140
	entry.timestamp = (msg.stamp().isNull() || !msg.stamp().isValid())
	                  ? QDateTime::currentDateTimeUtc().toString(Qt::ISODate)
	                  : msg.stamp().toUTC().toString(Qt::ISODate);
141

Linus Jahn's avatar
Linus Jahn committed
142
	// save the message to the database
143 144 145 146 147 148 149 150 151 152
#if QXMPP_VERSION >= QT_VERSION_CHECK(1, 0, 0)
	// in case of message correction, replace old message
	if (msg.replaceId().isEmpty()) {
		emit model->addMessageRequested(entry);
	} else {
		entry.edited = true;
		emit model->updateMessageRequested(msg.replaceId(), entry);
	}
#else
	// no message correction with old QXmpp
Linus Jahn's avatar
Linus Jahn committed
153
	emit model->addMessageRequested(entry);
154
#endif
155

Linus Jahn's avatar
Linus Jahn committed
156 157 158 159 160 161 162 163 164 165 166 167 168
	// Send a message notification
	//
	// The contact can differ if the message is really from a contact or just
	// a forward of another of the user's clients.
	QString contactJid = entry.sentByMe ? entry.recipient : entry.author;
	// resolve user-defined name of this JID
	QString contactName = client->rosterManager().getRosterEntry(contactJid).name();
	if (contactName.isEmpty())
		contactName = contactJid;

	if (!entry.sentByMe && !isCarbonMessage)
		Notifications::sendMessageNotification(contactName.toStdString(),
		                                       msg.body().toStdString());
169 170
}

171
void MessageHandler::sendMessage(QString toJid, QString body)
172
{
Linus Jahn's avatar
Linus Jahn committed
173 174 175 176 177 178 179 180
	// TODO: Add offline message cache and send when connnected again
	if (client->state() != QXmppClient::ConnectedState) {
		emit kaidan->passiveNotificationRequested(
			tr("Could not send message, as a result of not being connected.")
		);
		qWarning() << "[client] [MessageHandler] Could not send message, as a result of "
		              "not being connected.";
		return;
Linus Jahn's avatar
Linus Jahn committed
181
	}
182

183
	MessageModel::Message msg;
Linus Jahn's avatar
Linus Jahn committed
184
	msg.author = client->configuration().jidBare();
185
	msg.recipient = toJid;
Linus Jahn's avatar
Linus Jahn committed
186
	msg.id = QXmppUtils::generateStanzaHash(48);
187
	msg.sentByMe = true;
Linus Jahn's avatar
Linus Jahn committed
188 189 190
	msg.message = body;
	msg.type = MessageType::MessageText; // text message without media
	msg.timestamp = QDateTime::currentDateTime().toUTC().toString(Qt::ISODate);
191

Linus Jahn's avatar
Linus Jahn committed
192
	emit model->addMessageRequested(msg);
193

Linus Jahn's avatar
Linus Jahn committed
194 195 196
	QXmppMessage m(msg.author, msg.recipient, body);
	m.setId(msg.id);
	m.setReceiptRequested(true);
197

198
	if (client->sendPacket(m))
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
		emit model->setMessageAsSentRequested(msg.id);
	// TODO: handle error
}

void MessageHandler::correctMessage(QString toJid, QString msgId, QString body)
{
	// TODO: load old message from model and put everything into the new message
	//       instead of only the new body

	// TODO: Add offline message cache and send when connnected again
	if (client->state() != QXmppClient::ConnectedState) {
		emit kaidan->passiveNotificationRequested(
			tr("Could not correct message, as a result of not being connected.")
		);
		qWarning() << "[client] [MessageHandler] Could not correct message, as a result of "
		              "not being connected.";
		return;
	}

	MessageModel::Message msg;
	msg.author = client->configuration().jidBare();
	msg.recipient = toJid;
	msg.id = QXmppUtils::generateStanzaHash(48);
	msg.sentByMe = true;
	msg.message = body;
	msg.type = MessageType::MessageText; // text message without media
	msg.edited = true;

	emit model->updateMessageRequested(msgId, msg);

	QXmppMessage m(msg.author, msg.recipient, body);
	m.setId(msg.id);
	m.setReceiptRequested(true);
#if QXMPP_VERSION >= QT_VERSION_CHECK(1, 0, 0)
	m.setReplaceId(msgId);
#endif

236
	if (client->sendPacket(m))
237 238
		emit model->setMessageAsSentRequested(msg.id);
	// TODO: handle error
239
}
Linus Jahn's avatar
Linus Jahn committed
240

241 242 243 244 245 246 247 248 249 250
void MessageHandler::handleDiscoInfo(const QXmppDiscoveryIq &info)
{
#if QXMPP_VERSION >= 0x000904
	if (info.from() != client->configuration().domain())
		return;
	// enable carbons, if feature found
	if (info.features().contains(QString("urn:xmpp:carbons:2")))
		carbonManager->setCarbonsEnabled(true);
#endif
}