MessageHandler.cpp 10.9 KB
Newer Older
1 2 3
/*
 *  Kaidan - A user-friendly XMPP client for every device!
 *
Linus Jahn's avatar
Linus Jahn committed
4 5
 *  Copyright (C) 2017-2018 Kaidan developers and contributors
 *  (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 34 35 36 37
 */

#include "MessageHandler.h"
// Std
#include <iostream>
// Qt
#include <QDateTime>
#include <QDebug>
#include <QString>
Linus Jahn's avatar
Linus Jahn committed
38 39
#include <QMimeDatabase>
#include <QMimeType>
40
// gloox
41 42 43
#include <gloox/client.h>
#include <gloox/message.h>
#include <gloox/messagesession.h>
44
#include <gloox/receipt.h>
Linus Jahn's avatar
Linus Jahn committed
45
#include <gloox/carbons.h>
Linus Jahn's avatar
Linus Jahn committed
46 47 48 49
#include "gloox-extensions/gloox-extensions.h"
#include "gloox-extensions/reference.h"
#include "gloox-extensions/sims.h"
#include "gloox-extensions/jinglefile.h"
50 51 52 53
// Kaidan
#include "MessageModel.h"
#include "Notifications.h"

54
QDateTime stringToQDateTime(std::string stamp)
55
{
56
	QString qStamp = QString::fromStdString(stamp);
57 58
	QDateTime dateTime;

59 60 61 62
	if (qStamp.contains('.'))
		dateTime = QDateTime::fromString(qStamp, Qt::ISODateWithMs);
	else
		dateTime = QDateTime::fromString(qStamp, Qt::ISODate);
63 64

	if (!dateTime.isValid())
65
		return QDateTime::currentDateTime().toUTC();
66

67 68
	// XMPP timestamps are always in UTC
	// also read it as such if 'Z' is missing in ISO timestamp
69 70 71 72
	dateTime.setTimeSpec(Qt::UTC);
	return dateTime;
}

73
MessageHandler::MessageHandler(gloox::Client *client, MessageModel *messageModel,
74 75
                               RosterModel *rosterModel, QObject *parent):
                               QObject(parent), client(client),
Linus Jahn's avatar
Linus Jahn committed
76
                               messageModel(messageModel), rosterModel(rosterModel)
77 78 79 80 81 82 83
{
}

MessageHandler::~MessageHandler()
{
}

Linus Jahn's avatar
Linus Jahn committed
84
void MessageHandler::setChatPartner(QString chatPartner)
85
{
Linus Jahn's avatar
Linus Jahn committed
86
	this->chatPartner = chatPartner;
87

88
	resetUnreadMessagesForJid(this->chatPartner);
89 90
}

Linus Jahn's avatar
Linus Jahn committed
91
void MessageHandler::handleMessage(const gloox::Message &stanza, gloox::MessageSession *session)
92
{
93 94
	bool isCarbonMessage = false;

Linus Jahn's avatar
Linus Jahn committed
95 96 97 98 99 100 101 102
	// this should contain the real message (e.g. containing the body)
	gloox::Message *message = const_cast<gloox::Message*>(&stanza);
	// if the real message is in a message carbon extract it from there
	if (stanza.hasEmbeddedStanza()) {
		// get the possible carbon extension
		const gloox::Carbons *carbon = stanza.findExtension<const gloox::Carbons>(gloox::ExtCarbons);

		// if the extension exists and contains a message, use it as the real message
103 104
		if (carbon && carbon->embeddedStanza()) {
			isCarbonMessage = true;
Linus Jahn's avatar
Linus Jahn committed
105
			message = static_cast<gloox::Message*>(carbon->embeddedStanza());
106
		}
Linus Jahn's avatar
Linus Jahn committed
107 108 109
	}

	QString body = QString::fromStdString(message->body());
110

111
	if (!body.isEmpty()) {
112
		//
Linus Jahn's avatar
Linus Jahn committed
113
		// Extract information of the message
114 115 116
		//

		// author is only the 'bare' JID: e.g. 'albert@einstein.ch'
117 118 119 120 121 122 123
		MessageModel::Message msg;
		msg.author = QString::fromStdString(message->from().bare());
		msg.authorResource = QString::fromStdString(message->from().resource());
		msg.recipient = QString::fromStdString(message->to().bare());
		msg.recipientResource = QString::fromStdString(message->to().resource());
		msg.id = QString::fromStdString(message->id());
		msg.sentByMe = (msg.author == QString::fromStdString(client->jid().bare()));
Linus Jahn's avatar
Linus Jahn committed
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
		msg.message = body;
		msg.type = MessageType::MessageText; // only text, no media

		//
		// Get media sharing (SIMS) information
		//

		const gloox::Reference *ref = message->findExtension
		                              <gloox::Reference>(gloox::EXT_REFERENCES);
		if (ref && ref->getEmbeddedSIMS()) {
			gloox::SIMS *sims = ref->getEmbeddedSIMS();
			gloox::StringList sources = sims->sources();
			for (auto &source : sources) {
				if (source.rfind("https://", 0) == 0 ||
				    source.rfind("http://", 0) == 0) {
					msg.mediaUrl = QString::fromStdString(source);
					break;
				}
			}

			gloox::Jingle::File *file = sims->file();
			if (file && file->valid()) {
				msg.message = QString::fromStdString(file->desc());
				msg.mediaSize = file->size();
				msg.mediaContentType = QString::fromStdString(file->mediaType());
				msg.mediaLastModified = stringToQDateTime(file->date()).toTime_t();
				QMimeType mimeType = QMimeDatabase().mimeTypeForName(msg.mediaContentType);
				msg.type = getMessageType(mimeType);
152 153
				for (gloox::Hash &hash : file->hashes())
					msg.mediaHashes.append(QString::fromStdString(hash.tag()->xml()));
Linus Jahn's avatar
Linus Jahn committed
154 155
			}
		}
156

Linus Jahn's avatar
Linus Jahn committed
157
		//
158
		// If it is a delayed delivery (containing a timestamp), use its timestamp
Linus Jahn's avatar
Linus Jahn committed
159 160 161 162
		//

		const gloox::DelayedDelivery *delayedDelivery = message->when();
		if (delayedDelivery)
163
			msg.timestamp = stringToQDateTime(delayedDelivery->stamp())
164
			            .toString(Qt::ISODate);
165

Linus Jahn's avatar
Linus Jahn committed
166
		// fallback: use current time from local clock
167 168
		if (msg.timestamp.isEmpty())
			msg.timestamp = QDateTime::currentDateTime().toUTC()
169
			            .toString(Qt::ISODate);
170

171
		// add the message to the database
172
		emit messageModel->addMessageRequested(msg);
173

Linus Jahn's avatar
Linus Jahn committed
174 175 176 177
		//
		// Send a new notification | TODO: Resolve nickname from JID
		//

178
		if (!msg.sentByMe && !isCarbonMessage)
Linus Jahn's avatar
Linus Jahn committed
179 180 181 182 183 184 185 186
			Notifications::sendMessageNotification(message->from().full(), body.toStdString());

		//
		// Update contact sort (lastExchanged), last message and unread message count
		//

		// the contact can differ if the message is really from a contact or just
		// a forward of another of the user's clients
187
		const QString contactJid = msg.sentByMe ? msg.recipient : msg.author;
188 189

		// update the last message for this contact
190
		emit rosterModel->setLastMessageRequested(contactJid, body);
191 192

		// update the last exchanged for this contact
193
		updateLastExchangedOfJid(contactJid);
194

Linus Jahn's avatar
Linus Jahn committed
195 196
		// Increase unread message counter
		// don't add new unread message if chat is opened or we wrote the message
197
		if (!isCarbonMessage && chatPartner != contactJid && !msg.sentByMe)
198
			newUnreadMessageForJid(contactJid);
199 200
	}

201
	// XEP-0184: Message Delivery Receipts
202 203 204 205 206 207 208 209
	// try to handle a possible delivery receipt
	handleReceiptMessage(message, isCarbonMessage);
}

void MessageHandler::handleReceiptMessage(const gloox::Message *message,
                                          bool isCarbonMessage)
{
	// check if message contains receipt
210
	gloox::Receipt *receipt = (gloox::Receipt*) message->findExtension<gloox::Receipt>(gloox::ExtReceipt);
211 212
	if (!receipt)
		return;
213

214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
	// get the type of the receipt
	gloox::Receipt::ReceiptType receiptType = receipt->rcpt();

	if (receiptType == gloox::Receipt::Request && !isCarbonMessage) {
		// carbon messages won't be accepted to not send a receipt to own msgs
		// carbon msgs from other contacts should be processed by the first client

		// send the asked confirmation, that the message has been arrived
		// new message to the author of the request
		gloox::Message receiptMessage(gloox::Message::Chat, message->from());

		// add the receipt extension containing the request's message id
		gloox::Receipt *receiptPayload = new gloox::Receipt(gloox::Receipt::Received, message->id());
		receiptMessage.addExtension(receiptPayload);

		// send the receipt message
		client->send(receiptMessage);

	} else if (receiptType == gloox::Receipt::Received) {
		// Delivery Receipt Received -> mark message as read in db
		emit messageModel->setMessageAsDeliveredRequested(
			QString::fromStdString(receipt->id()));
236 237 238
	}
}

239
void MessageHandler::sendMessage(QString toJid, QString body)
240 241 242 243 244 245 246 247
{
	const std::string id = client->getID();

	addMessageToDb(toJid, body, QString::fromStdString(id), MessageType::MessageText);
	sendOnlyMessage(toJid, body, id);
}

void MessageHandler::sendOnlyMessage(QString &toJid, QString &body, const std::string &id)
248 249
{
	// create a new message
250 251
	gloox::Message message(gloox::Message::Chat, gloox::JID(toJid.toStdString()),
	                       body.toStdString());
252
	message.setID(id);
253 254 255 256 257 258 259 260

	// XEP-0184: Message Delivery Receipts
	// request a delivery receipt from the other client
	gloox::Receipt *receiptPayload = new gloox::Receipt(gloox::Receipt::Request);
	message.addExtension(receiptPayload);

	// send the message
	client->send(message);
261 262 263
}

void MessageHandler::addMessageToDb(QString &toJid, QString &body, QString id,
264
                                     MessageType type, QString mediaLocation)
265 266
{
	// add the message to the database
267 268 269 270 271 272 273 274 275 276 277
	MessageModel::Message msg;
	msg.timestamp = QDateTime::currentDateTime().toUTC().toString(Qt::ISODate);
	msg.author = QString::fromStdString(client->jid().bare());
	msg.recipient = toJid;
	msg.message = body;
	msg.id = id;
	msg.sentByMe = true;
	msg.type = type;
	msg.mediaLocation = mediaLocation;

	emit messageModel->addMessageRequested(msg);
278 279

	// update the last message for this contact
280
	emit rosterModel->setLastMessageRequested(toJid, body);
281 282 283 284
	// update the last exchanged date
	updateLastExchangedOfJid(toJid);
}

285
void MessageHandler::updateLastExchangedOfJid(const QString &jid)
286 287
{
	QString dateTime = QDateTime::currentDateTime().toString(Qt::ISODate);
288
	emit rosterModel->setLastExchangedRequested(jid, dateTime);
289 290
}

291
void MessageHandler::newUnreadMessageForJid(const QString &jid)
292
{
293 294
	// add a new unread message to the contact
	emit rosterModel->newUnreadMessageRequested(jid);
295 296
}

297
void MessageHandler::resetUnreadMessagesForJid(const QString &jid)
298
{
299 300
	// reset the unread message count to 0
	emit rosterModel->setUnreadMessageCountRequested(jid, 0);
301
}
Linus Jahn's avatar
Linus Jahn committed
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319

MessageType MessageHandler::getMessageType(QMimeType& type)
{
	if (type.inherits("image/jpeg") || type.inherits("image/png") ||
	    type.inherits("image/gif"))
		return MessageType::MessageImage;
	else if (type.inherits("audio/flac") || type.inherits("audio/mp4") ||
	         type.inherits("audio/ogg") || type.inherits("audio/wav") ||
	         type.inherits("audio/mpeg") || type.inherits("audio/webm"))
		return MessageType::MessageAudio;
	else if (type.inherits("video/mpeg") || type.inherits("video/x-msvideo") ||
	         type.inherits("video/quicktime") || type.inherits("video/mp4") ||
	         type.inherits("video/x-matroska"))
		return MessageType::MessageVideo;
	else if (type.inherits("text/plain"))
		return MessageType::MessageDocument;
	return MessageType::MessageFile;
}