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 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
		emit messageModel->addMessageRequested(fromJid, toJid, timestamp,
		                                       body, msgId, isSentByMe,
		                                       fromJidResource, toJidResource);
137

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

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

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

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

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

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

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

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

203
void MessageHandler::sendMessage(QString toJid, QString body)
204 205
{
	// create a new message
206 207
	gloox::Message message(gloox::Message::Chat, gloox::JID(toJid.toStdString()),
	                       body.toStdString());
208 209

	// add the message to the database
210
	const QString timestamp = QDateTime::currentDateTime().toUTC().toString(Qt::ISODate);
211
	const QString id = QString::fromStdString(message.id());
212
	const QString fromJid = QString::fromStdString(client->jid().bare());
213

214
	emit messageModel->addMessageRequested(fromJid, toJid, timestamp, body, id, true);
215 216 217 218 219 220 221 222

	// 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);
223 224

	// update the last message for this contact
225
	emit rosterModel->setLastMessageRequested(toJid, body);
226 227 228 229
	// update the last exchanged date
	updateLastExchangedOfJid(toJid);
}

230
void MessageHandler::updateLastExchangedOfJid(const QString &jid)
231 232
{
	QString dateTime = QDateTime::currentDateTime().toString(Qt::ISODate);
233
	emit rosterModel->setLastExchangedRequested(jid, dateTime);
234 235
}

236
void MessageHandler::newUnreadMessageForJid(const QString &jid)
237
{
238 239
	// add a new unread message to the contact
	emit rosterModel->newUnreadMessageRequested(jid);
240 241
}

242
void MessageHandler::resetUnreadMessagesForJid(const QString &jid)
243
{
244 245
	// reset the unread message count to 0
	emit rosterModel->setUnreadMessageCountRequested(jid, 0);
246
}