MessageHandler.cpp 8.85 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 38
 */

#include "MessageHandler.h"
// Std
#include <iostream>
// Qt
#include <QDateTime>
#include <QDebug>
#include <QString>
// gloox
39 40 41
#include <gloox/client.h>
#include <gloox/message.h>
#include <gloox/messagesession.h>
42
#include <gloox/receipt.h>
Linus Jahn's avatar
Linus Jahn committed
43
#include <gloox/carbons.h>
44 45 46 47
// Kaidan
#include "MessageModel.h"
#include "Notifications.h"

48
QDateTime stringToQDateTime(std::string stamp)
49
{
50
	QString qStamp = QString::fromStdString(stamp);
51 52
	QDateTime dateTime;

53 54 55 56
	if (qStamp.contains('.'))
		dateTime = QDateTime::fromString(qStamp, Qt::ISODateWithMs);
	else
		dateTime = QDateTime::fromString(qStamp, Qt::ISODate);
57 58

	if (!dateTime.isValid())
59
		return QDateTime::currentDateTime().toUTC();
60

61 62
	// XMPP timestamps are always in UTC
	// also read it as such if 'Z' is missing in ISO timestamp
63 64 65 66
	dateTime.setTimeSpec(Qt::UTC);
	return dateTime;
}

67
MessageHandler::MessageHandler(gloox::Client *client, MessageModel *messageModel,
68 69
                               RosterModel *rosterModel, QObject *parent):
                               QObject(parent), client(client),
Linus Jahn's avatar
Linus Jahn committed
70
                               messageModel(messageModel), rosterModel(rosterModel)
71 72 73 74 75 76 77
{
}

MessageHandler::~MessageHandler()
{
}

Linus Jahn's avatar
Linus Jahn committed
78
void MessageHandler::setChatPartner(QString chatPartner)
79
{
Linus Jahn's avatar
Linus Jahn committed
80
	this->chatPartner = chatPartner;
81

82
	resetUnreadMessagesForJid(this->chatPartner);
83 84
}

Linus Jahn's avatar
Linus Jahn committed
85
void MessageHandler::handleMessage(const gloox::Message &stanza, gloox::MessageSession *session)
86
{
87 88
	bool isCarbonMessage = false;

Linus Jahn's avatar
Linus Jahn committed
89 90 91 92 93 94 95 96
	// 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
97 98
		if (carbon && carbon->embeddedStanza()) {
			isCarbonMessage = true;
Linus Jahn's avatar
Linus Jahn committed
99
			message = static_cast<gloox::Message*>(carbon->embeddedStanza());
100
		}
Linus Jahn's avatar
Linus Jahn committed
101 102 103
	}

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

105
	if (!body.isEmpty()) {
106
		//
Linus Jahn's avatar
Linus Jahn committed
107
		// Extract information of the message
108 109 110
		//

		// author is only the 'bare' JID: e.g. 'albert@einstein.ch'
Linus Jahn's avatar
Linus Jahn committed
111 112 113 114 115 116
		const QString fromJid = QString::fromStdString(message->from().bare());
		const QString fromJidResource = QString::fromStdString(message->from().resource());
		const QString toJid = QString::fromStdString(message->to().bare());
		const QString toJidResource = QString::fromStdString(message->to().resource());
		const QString msgId = QString::fromStdString(message->id());
		const bool isSentByMe = fromJid == QString::fromStdString(client->jid().bare());
117 118
		QString timestamp;

Linus Jahn's avatar
Linus Jahn committed
119
		//
120
		// If it is a delayed delivery (containing a timestamp), use its timestamp
Linus Jahn's avatar
Linus Jahn committed
121 122 123 124
		//

		const gloox::DelayedDelivery *delayedDelivery = message->when();
		if (delayedDelivery)
125
			timestamp = stringToQDateTime(delayedDelivery->stamp())
126
			            .toString(Qt::ISODate);
127

Linus Jahn's avatar
Linus Jahn committed
128 129
		// fallback: use current time from local clock
		if (timestamp.isEmpty())
130 131
			timestamp = QDateTime::currentDateTime().toUTC()
			            .toString(Qt::ISODate);
132

133
		// add the message to the database
134 135 136 137
		emit messageModel->addMessageRequested(
			fromJid, toJid, timestamp, body, msgId, isSentByMe,
			MessageType::MessageText, fromJidResource, toJidResource
		);
138

Linus Jahn's avatar
Linus Jahn committed
139 140 141 142
		//
		// Send a new notification | TODO: Resolve nickname from JID
		//

143
		if (!isSentByMe && !isCarbonMessage)
Linus Jahn's avatar
Linus Jahn committed
144 145 146 147 148 149 150 151
			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
152
		const QString contactJid = isSentByMe ? toJid : fromJid;
153 154

		// update the last message for this contact
155
		emit rosterModel->setLastMessageRequested(contactJid, body);
156 157

		// update the last exchanged for this contact
158
		updateLastExchangedOfJid(contactJid);
159

Linus Jahn's avatar
Linus Jahn committed
160 161
		// Increase unread message counter
		// don't add new unread message if chat is opened or we wrote the message
162
		if (!isCarbonMessage && chatPartner != contactJid && !isSentByMe)
163
			newUnreadMessageForJid(contactJid);
164 165
	}

166
	// XEP-0184: Message Delivery Receipts
167 168 169 170 171 172 173 174
	// try to handle a possible delivery receipt
	handleReceiptMessage(message, isCarbonMessage);
}

void MessageHandler::handleReceiptMessage(const gloox::Message *message,
                                          bool isCarbonMessage)
{
	// check if message contains receipt
175
	gloox::Receipt *receipt = (gloox::Receipt*) message->findExtension<gloox::Receipt>(gloox::ExtReceipt);
176 177
	if (!receipt)
		return;
178

179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
	// 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()));
201 202 203
	}
}

204
void MessageHandler::sendMessage(QString toJid, QString body)
205 206 207 208 209 210 211 212
{
	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)
213 214
{
	// create a new message
215 216
	gloox::Message message(gloox::Message::Chat, gloox::JID(toJid.toStdString()),
	                       body.toStdString());
217
	message.setID(id);
218 219 220 221 222 223 224 225

	// 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);
226 227 228 229 230 231 232 233 234 235 236 237
}

void MessageHandler::addMessageToDb(QString &toJid, QString &body, QString id,
                                    MessageType type)
{
	// add the message to the database
	const QString timestamp = QDateTime::currentDateTime().toUTC().toString(Qt::ISODate);
	const QString fromJid = QString::fromStdString(client->jid().bare());

	emit messageModel->addMessageRequested(
		fromJid, toJid, timestamp, body, id, true, type
	);
238 239

	// update the last message for this contact
240
	emit rosterModel->setLastMessageRequested(toJid, body);
241 242 243 244
	// update the last exchanged date
	updateLastExchangedOfJid(toJid);
}

245
void MessageHandler::updateLastExchangedOfJid(const QString &jid)
246 247
{
	QString dateTime = QDateTime::currentDateTime().toString(Qt::ISODate);
248
	emit rosterModel->setLastExchangedRequested(jid, dateTime);
249 250
}

251
void MessageHandler::newUnreadMessageForJid(const QString &jid)
252
{
253 254
	// add a new unread message to the contact
	emit rosterModel->newUnreadMessageRequested(jid);
255 256
}

257
void MessageHandler::resetUnreadMessagesForJid(const QString &jid)
258
{
259 260
	// reset the unread message count to 0
	emit rosterModel->setUnreadMessageCountRequested(jid, 0);
261
}