File indexing completed on 2024-12-01 04:33:08

0001 /**
0002  * SPDX-FileCopyrightText: 2013 Albert Vaca <albertvaka@gmail.com>
0003  * SPDX-FileCopyrightText: 2018 Simon Redman <simon@ergotech.com>
0004  *
0005  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006  */
0007 
0008 #include "smsplugin.h"
0009 
0010 #include <KLocalizedString>
0011 #include <KPluginFactory>
0012 
0013 #include <QDBusConnection>
0014 #include <QDebug>
0015 #include <QFile>
0016 #include <QFileInfo>
0017 #include <QMimeDatabase>
0018 #include <QProcess>
0019 #include <QTextCodec>
0020 
0021 #include <core/daemon.h>
0022 #include <core/device.h>
0023 #include <core/filetransferjob.h>
0024 
0025 #include "plugin_sms_debug.h"
0026 
0027 K_PLUGIN_CLASS_WITH_JSON(SmsPlugin, "kdeconnect_sms.json")
0028 
0029 SmsPlugin::SmsPlugin(QObject *parent, const QVariantList &args)
0030     : KdeConnectPlugin(parent, args)
0031     , m_telepathyInterface(QStringLiteral("org.freedesktop.Telepathy.ConnectionManager.kdeconnect"), QStringLiteral("/kdeconnect"))
0032     , m_conversationInterface(new ConversationsDbusInterface(this))
0033 {
0034     m_codec = QTextCodec::codecForName(CODEC_NAME);
0035 }
0036 
0037 SmsPlugin::~SmsPlugin()
0038 {
0039     // m_conversationInterface is self-deleting, see ~ConversationsDbusInterface for more information
0040 }
0041 
0042 void SmsPlugin::receivePacket(const NetworkPacket &np)
0043 {
0044     if (np.type() == PACKET_TYPE_SMS_MESSAGES) {
0045         handleBatchMessages(np);
0046     }
0047 
0048     if (np.type() == PACKET_TYPE_SMS_ATTACHMENT_FILE && np.hasPayload()) {
0049         handleSmsAttachmentFile(np);
0050     }
0051 }
0052 
0053 void SmsPlugin::sendSms(const QVariantList &addresses, const QString &textMessage, const QVariantList &attachmentUrls, const qint64 subID)
0054 {
0055     QVariantList addressMapList;
0056     for (const QVariant &address : addresses) {
0057         QVariantMap addressMap({{QStringLiteral("address"), qdbus_cast<ConversationAddress>(address).address()}});
0058         addressMapList.append(addressMap);
0059     }
0060 
0061     QVariantMap packetMap({{QStringLiteral("version"), SMS_REQUEST_PACKET_VERSION}, {QStringLiteral("addresses"), addressMapList}});
0062 
0063     // If there is any text message add it to the network packet
0064     if (textMessage != QStringLiteral("")) {
0065         packetMap[QStringLiteral("messageBody")] = textMessage;
0066     }
0067 
0068     if (subID != -1) {
0069         packetMap[QStringLiteral("subID")] = subID;
0070     }
0071 
0072     QVariantList attachmentMapList;
0073     for (const QVariant &attachmentUrl : attachmentUrls) {
0074         const Attachment attachment = createAttachmentFromUrl(attachmentUrl.toString());
0075         QVariantMap attachmentMap({{QStringLiteral("fileName"), attachment.uniqueIdentifier()},
0076                                    {QStringLiteral("base64EncodedFile"), attachment.base64EncodedFile()},
0077                                    {QStringLiteral("mimeType"), attachment.mimeType()}});
0078         attachmentMapList.append(attachmentMap);
0079     }
0080 
0081     // If there is any attachment add it to the network packet
0082     if (!attachmentMapList.isEmpty()) {
0083         packetMap[QStringLiteral("attachments")] = attachmentMapList;
0084     }
0085 
0086     NetworkPacket np(PACKET_TYPE_SMS_REQUEST, packetMap);
0087     qCDebug(KDECONNECT_PLUGIN_SMS) << "Dispatching SMS send request to remote";
0088     sendPacket(np);
0089 }
0090 
0091 void SmsPlugin::requestAllConversations()
0092 {
0093     NetworkPacket np(PACKET_TYPE_SMS_REQUEST_CONVERSATIONS);
0094 
0095     sendPacket(np);
0096 }
0097 
0098 void SmsPlugin::requestConversation(const qint64 conversationID, const qint64 rangeStartTimestamp, const qint64 numberToRequest) const
0099 {
0100     NetworkPacket np(PACKET_TYPE_SMS_REQUEST_CONVERSATION);
0101     np.set(QStringLiteral("threadID"), conversationID);
0102     np.set(QStringLiteral("rangeStartTimestamp"), rangeStartTimestamp);
0103     np.set(QStringLiteral("numberToRequest"), numberToRequest);
0104 
0105     sendPacket(np);
0106 }
0107 
0108 void SmsPlugin::requestAttachment(const qint64 &partID, const QString &uniqueIdentifier)
0109 {
0110     const QVariantMap packetMap({{QStringLiteral("part_id"), partID}, {QStringLiteral("unique_identifier"), uniqueIdentifier}});
0111 
0112     NetworkPacket np(PACKET_TYPE_SMS_REQUEST_ATTACHMENT, packetMap);
0113 
0114     sendPacket(np);
0115 }
0116 
0117 void SmsPlugin::forwardToTelepathy(const ConversationMessage &message)
0118 {
0119     // If we don't have a valid Telepathy interface, bail out
0120     if (!(m_telepathyInterface.isValid()))
0121         return;
0122 
0123     qCDebug(KDECONNECT_PLUGIN_SMS) << "Passing a text message to the telepathy interface";
0124     connect(&m_telepathyInterface, SIGNAL(messageReceived(QString, QString)), this, SLOT(sendSms(QString, QString)), Qt::UniqueConnection);
0125     const QString messageBody = message.body();
0126     const QString contactName; // TODO: When telepathy support is improved, look up the contact with KPeople
0127     const QString phoneNumber = message.addresses()[0].address();
0128     m_telepathyInterface.call(QDBus::NoBlock, QStringLiteral("sendMessage"), phoneNumber, contactName, messageBody);
0129 }
0130 
0131 bool SmsPlugin::handleBatchMessages(const NetworkPacket &np)
0132 {
0133     const auto messages = np.get<QVariantList>(QStringLiteral("messages"));
0134     QList<ConversationMessage> messagesList;
0135     messagesList.reserve(messages.count());
0136 
0137     for (const QVariant &body : messages) {
0138         ConversationMessage message(body.toMap());
0139         if (message.containsTextBody()) {
0140             forwardToTelepathy(message);
0141         }
0142         messagesList.append(message);
0143     }
0144 
0145     m_conversationInterface->addMessages(messagesList);
0146 
0147     return true;
0148 }
0149 
0150 bool SmsPlugin::handleSmsAttachmentFile(const NetworkPacket &np)
0151 {
0152     const QString fileName = np.get<QString>(QStringLiteral("filename"));
0153 
0154     QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
0155     cacheDir.append(QStringLiteral("/") + device()->name() + QStringLiteral("/"));
0156     QDir attachmentsCacheDir(cacheDir);
0157 
0158     if (!attachmentsCacheDir.exists()) {
0159         qDebug() << attachmentsCacheDir.absolutePath() << " directory doesn't exist.";
0160         return false;
0161     }
0162 
0163     QUrl fileUrl = QUrl::fromLocalFile(attachmentsCacheDir.absolutePath());
0164     fileUrl = fileUrl.adjusted(QUrl::StripTrailingSlash);
0165     fileUrl.setPath(fileUrl.path() + QStringLiteral("/") + fileName, QUrl::DecodedMode);
0166 
0167     FileTransferJob *job = np.createPayloadTransferJob(fileUrl);
0168     connect(job, &FileTransferJob::result, this, [this, fileName](KJob *job) -> void {
0169         FileTransferJob *ftjob = qobject_cast<FileTransferJob *>(job);
0170         if (ftjob && !job->error()) {
0171             // Notify SMS app about the newly downloaded attachment
0172             m_conversationInterface->attachmentDownloaded(ftjob->destination().path(), fileName);
0173         } else {
0174             qCDebug(KDECONNECT_PLUGIN_SMS) << ftjob->errorString() << (ftjob ? ftjob->destination() : QUrl());
0175         }
0176     });
0177     job->start();
0178 
0179     return true;
0180 }
0181 
0182 void SmsPlugin::getAttachment(const qint64 &partID, const QString &uniqueIdentifier)
0183 {
0184     QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
0185     cacheDir.append(QStringLiteral("/") + device()->name() + QStringLiteral("/"));
0186     QDir fileDirectory(cacheDir);
0187 
0188     bool fileFound = false;
0189     if (fileDirectory.exists()) {
0190         // Search for the attachment file locally before sending request to remote device
0191         fileFound = fileDirectory.exists(uniqueIdentifier);
0192     } else {
0193         bool ret = fileDirectory.mkpath(QStringLiteral("."));
0194         if (!ret) {
0195             qWarning() << "couldn't create directorty " << fileDirectory.absolutePath();
0196         }
0197     }
0198 
0199     if (!fileFound) {
0200         // If the file is not present in the local dir request the remote device for the file
0201         requestAttachment(partID, uniqueIdentifier);
0202     } else {
0203         const QString fileDestination = fileDirectory.absoluteFilePath(uniqueIdentifier);
0204         m_conversationInterface->attachmentDownloaded(fileDestination, uniqueIdentifier);
0205     }
0206 }
0207 
0208 Attachment SmsPlugin::createAttachmentFromUrl(const QString &url)
0209 {
0210     QFile file(url);
0211     file.open(QIODevice::ReadOnly);
0212 
0213     if (!file.exists()) {
0214         return Attachment();
0215     }
0216 
0217     QFileInfo fileInfo(file);
0218     QString fileName(fileInfo.fileName());
0219 
0220     QByteArray byteArray = file.readAll().toBase64();
0221     file.close();
0222 
0223     QString base64EncodedFile = m_codec->toUnicode(byteArray);
0224 
0225     QMimeDatabase mimeDatabase;
0226     QString mimeType = mimeDatabase.mimeTypeForFile(url).name();
0227 
0228     Attachment attachment(-1, mimeType, base64EncodedFile, fileName);
0229     return attachment;
0230 }
0231 
0232 QString SmsPlugin::dbusPath() const
0233 {
0234     return QLatin1String("/modules/kdeconnect/devices/%1/sms").arg(device()->id());
0235 }
0236 
0237 void SmsPlugin::launchApp()
0238 {
0239     QProcess::startDetached(QLatin1String("kdeconnect-sms"), {QStringLiteral("--device"), device()->id()});
0240 }
0241 
0242 #include "moc_smsplugin.cpp"
0243 #include "smsplugin.moc"