File indexing completed on 2024-06-16 04:29:30

0001 /*
0002     SnoreNotify is a Notification Framework based on Qt
0003     Copyright (C) 2015  Hannah von Reth <vonreth@kde.org>
0004 
0005     SnoreNotify is free software: you can redistribute it and/or modify
0006     it under the terms of the GNU Lesser General Public License as published by
0007     the Free Software Foundation, either version 3 of the License, or
0008     (at your option) any later version.
0009 
0010     SnoreNotify is distributed in the hope that it will be useful,
0011     but WITHOUT ANY WARRANTY; without even the implied warranty of
0012     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013     GNU Lesser General Public License for more details.
0014 
0015     You should have received a copy of the GNU Lesser General Public License
0016     along with SnoreNotify.  If not, see <http://www.gnu.org/licenses/>.
0017 */
0018 
0019 #include "pushoverclient.h"
0020 #include "pushover_frontend.h"
0021 #include "../../secondary_backends/pushover_backend/pushoverconstants.h"
0022 
0023 
0024 #include "libsnore/snore.h"
0025 #include "libsnore/notification/notification_p.h"
0026 
0027 #include <QNetworkReply>
0028 #include <QJsonDocument>
0029 #include <QJsonObject>
0030 #include <QJsonArray>
0031 #include <QtWebSockets>
0032 
0033 // TODO: use qtkeychain to encrypt credentials?
0034 // TODO: massive refactoring ...
0035 
0036 using namespace Snore;
0037 
0038 PushoverClient::PushoverClient(PushoverFrontend *frontend):
0039     m_frontend(frontend)
0040 {
0041     connect(this, &PushoverClient::loggedInChanged, [this](PushoverClient::LoginState state) {
0042         m_loggedIn = state;
0043     });
0044     connect(this, &PushoverClient::error, [this](const QString & error) {
0045         qCWarning(SNORE) << error;
0046         m_errorMessage = error;
0047     });
0048 }
0049 
0050 void PushoverClient::login(const QString &email, const QString &password, const QString &deviceName)
0051 {
0052     m_frontend->setSettingsValue(PushoverConstants::DeviceName, deviceName);
0053 
0054     QNetworkRequest request(QUrl(QStringLiteral("https://api.pushover.net/1/users/login.json")));
0055 
0056     request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(QLatin1String("application/x-www-form-urlencoded")));
0057     QNetworkReply *reply = m_manager.post(request, (QLatin1String("email=") + email + QLatin1String("&password=") + password).toUtf8().constData());
0058 
0059     connect(reply, &QNetworkReply::finished, [reply, deviceName, this]() {
0060         qCDebug(SNORE) << reply->error();
0061         QByteArray input = reply->readAll();
0062         reply->close();
0063         reply->deleteLater();
0064 
0065         QJsonObject message = QJsonDocument::fromJson(input).object();
0066 
0067         if (message.value(QStringLiteral("status")).toInt() == 1) {
0068             registerDevice(message.value(QStringLiteral("secret")).toString(), deviceName);
0069         } else {
0070             emit error(tr("Failed to login. Please check your credentials."));
0071             emit loggedInChanged(Error);
0072         }
0073     });
0074 }
0075 
0076 void PushoverClient::logOut()
0077 {
0078     m_frontend->setSettingsValue(PushoverConstants::Secret, QString());
0079     m_frontend->setSettingsValue(PushoverConstants::DeviceID, QString());
0080     m_socket->close();
0081     m_socket->deleteLater();
0082     emit loggedInChanged(LoggedOut);
0083 }
0084 
0085 PushoverClient::LoginState PushoverClient::isLoggedIn() const
0086 {
0087     return m_loggedIn;
0088 }
0089 
0090 QString PushoverClient::errorMessage()
0091 {
0092     return m_errorMessage;
0093 }
0094 
0095 QString PushoverClient::secret()
0096 {
0097     return m_frontend->settingsValue(PushoverConstants::Secret).toString();
0098 }
0099 
0100 QString PushoverClient::device()
0101 {
0102     return m_frontend->settingsValue(PushoverConstants::DeviceID).toString();
0103 }
0104 
0105 void PushoverClient::connectToService()
0106 {
0107     if (secret().isEmpty() || device().isEmpty()) {
0108         qCWarning(SNORE) << "not logged in";
0109         return;
0110     }
0111     qCDebug(SNORE) << "Connecting ton service";
0112     m_socket = new QWebSocket(QString(), QWebSocketProtocol::VersionLatest, this);
0113 
0114     connect(m_socket.data(), &QWebSocket::binaryMessageReceived, [&](const QByteArray & msg) {
0115         char c = msg.at(0);
0116         switch (c) {
0117         case '#':
0118             qCDebug(SNORE) << "still alive";
0119             break;
0120         case '!':
0121             getMessages();
0122             break;
0123         case 'R':
0124             qCDebug(SNORE) << "need to reconnect";
0125             m_socket->close();
0126             m_socket->deleteLater();
0127             connectToService();
0128             break;
0129         case 'E':
0130             qCWarning(SNORE) << "Connection Error";
0131             emit error(tr("Please login to %1 and reenable your device.").arg(QStringLiteral("https://pushover.net")));
0132             emit loggedInChanged(Error);
0133             m_socket->close();
0134             m_socket->deleteLater();
0135             break;
0136         default:
0137             qCWarning(SNORE) << "unknown message received" << msg;
0138         }
0139     });
0140     connect(m_socket.data(), &QWebSocket::disconnected, [this]() {
0141         qCWarning(SNORE) << "disconnected";
0142         //TODO: use new style connect once we depend on qt 5.4
0143         QTimer::singleShot(500, this, SLOT(connectToService()));
0144     });
0145     connect(m_socket.data(), static_cast<void (QWebSocket::*)(QAbstractSocket::SocketError)>(&QWebSocket::error), [&](QAbstractSocket::SocketError error) {
0146         qCWarning(SNORE) << error << m_socket->errorString();
0147         emit loggedInChanged(Error);
0148     });
0149     connect(m_socket.data(), &QWebSocket::connected, [&]() {
0150         qCDebug(SNORE) << "connecting";
0151         m_socket->sendBinaryMessage((QLatin1String("login:") + device() + QLatin1Char(':') + secret() + QLatin1Char('\n')).toUtf8().constData());
0152         emit loggedInChanged(Error);
0153         getMessages();
0154     });
0155     m_socket->open(QUrl::fromEncoded("wss://client.pushover.net/push"));
0156 }
0157 
0158 void PushoverClient::disconnectService()
0159 {
0160     if (m_socket) {
0161         m_socket->close();
0162         m_socket->deleteLater();
0163     }
0164 }
0165 
0166 void PushoverClient::registerDevice(const QString &secret, const QString &deviceName)
0167 {
0168     QNetworkRequest request(QUrl(QStringLiteral("https://api.pushover.net/1/devices.json")));
0169 
0170     request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(QLatin1String("application/x-www-form-urlencoded")));
0171     QNetworkReply *reply = m_manager.post(request, (QLatin1String("os=O&secret=") + secret + QLatin1String("&name=") + deviceName).toUtf8().constData());
0172 
0173     connect(reply, &QNetworkReply::finished, [reply, secret, this]() {
0174         qCDebug(SNORE) << reply->error();
0175         QByteArray input = reply->readAll();
0176         reply->close();
0177         reply->deleteLater();
0178         QJsonObject message = QJsonDocument::fromJson(input).object();
0179         if (message.value(QStringLiteral("status")).toInt() == 1) {
0180             m_frontend->setSettingsValue(PushoverConstants::Secret, secret);
0181             m_frontend->setSettingsValue(PushoverConstants::DeviceID, message.value(QStringLiteral("id")).toString());
0182             connectToService();
0183         } else {
0184             qCWarning(SNORE) << "An error occured" << input;
0185             emit loggedInChanged(Error);
0186             QStringList errorMessages;
0187             QJsonObject errors = message.value(QStringLiteral("errors")).toObject();
0188             foreach(const auto & errorKey, errors.keys()) {
0189                 auto array = errors.value(errorKey).toArray();
0190                 errorMessages.reserve(array.size());
0191                 foreach(const auto & error, array) {
0192                     errorMessages << errorKey + QLatin1Char(' ') + error.toString();
0193                 }
0194             }
0195             emit error(tr("Failed to login due to errors: %1").arg(errorMessages.join(QStringLiteral(", "))));
0196         }
0197 
0198     });
0199 
0200 }
0201 
0202 void PushoverClient::getMessages()
0203 {
0204     QNetworkRequest request(QUrl::fromEncoded((QLatin1String("https://api.pushover.net/1/messages.json?"
0205                             "secret=") + secret() + QLatin1String("&device_id=") + device()).toUtf8().constData()));
0206     QNetworkReply *reply =  m_manager.get(request);
0207 
0208     connect(reply, &QNetworkReply::finished, [reply, this]() {
0209         qCDebug(SNORE) << reply->error();
0210         QByteArray input = reply->readAll();
0211         reply->close();
0212         reply->deleteLater();
0213 
0214         qCDebug(SNORE) << input;
0215 
0216         QJsonObject message = QJsonDocument::fromJson(input).object();
0217 
0218         int latestID = -1;
0219         if (message.value(QStringLiteral("status")).toInt() == 1) {
0220             QJsonArray notifications = message.value(QStringLiteral("messages")).toArray();
0221             foreach(const QJsonValue & v, notifications) {
0222                 QJsonObject notification = v.toObject();
0223 
0224                 latestID = qMax(latestID, notification.value(QStringLiteral("id")).toInt());
0225 
0226                 QString appName = notification.value(QStringLiteral("app")).toString();
0227                 Application app = SnoreCore::instance().aplications().value(appName);
0228 
0229                 if (!app.isValid()) {
0230                     Icon icon(Icon::fromWebUrl(QUrl::fromEncoded((QLatin1String("https://api.pushover.net/icons/") +
0231                                                notification.value(QStringLiteral("icon")).toString() +
0232                                                QLatin1String(".png")).toUtf8().constData())));
0233                     app = Application(appName, icon);
0234                     SnoreCore::instance().registerApplication(app);
0235                 }
0236 
0237                 Notification n(app, app.defaultAlert(), notification.value(QStringLiteral("title")).toString(),
0238                                notification.value(QStringLiteral("message")).toString(),
0239                                app.icon(), Notification::defaultTimeout(),
0240                                static_cast<Notification::Prioritys>(notification.value(QStringLiteral("priority")).toInt()));
0241                 if (n.priority() == Notification::Emergency) {
0242                     n.hints().setValue("receipt", notification.value(QStringLiteral("receipt")).toString());
0243                     n.hints().setValue("acked", notification.value(QStringLiteral("acked")).toInt());
0244                 }
0245                 if (notification.value(QStringLiteral("html")).toInt() == 1) {
0246                     n.hints().setValue("use-markup", true) ;
0247                 }
0248                 n.data()->setSource(m_frontend);
0249                 SnoreCore::instance().broadcastNotification(n);
0250             }
0251             if (latestID != -1) {
0252                 deleteMessages(latestID);
0253             }
0254         } else {
0255             qCWarning(SNORE) << "An error occured" << input;
0256         }
0257 
0258     });
0259 
0260 }
0261 
0262 void PushoverClient::deleteMessages(int latestMessageId)
0263 {
0264 
0265     QNetworkRequest request(QUrl::fromEncoded((QLatin1String("https://api.pushover.net/1/devices/") +
0266                             device() + QLatin1String("/update_highest_message.json")).toUtf8().constData()));
0267 
0268     request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(QLatin1String("application/x-www-form-urlencoded")));
0269     QNetworkReply *reply = m_manager.post(request, (QLatin1String("secret=") + secret() + QLatin1String("&message=") + QString::number(latestMessageId)).toUtf8().constData());
0270 
0271     connect(reply, &QNetworkReply::finished, [reply]() {
0272         qCDebug(SNORE) << reply->error();
0273         qCDebug(SNORE) << reply->readAll();
0274         reply->close();
0275         reply->deleteLater();
0276     });
0277 }
0278 
0279 void PushoverClient::acknowledgeNotification(Notification notification)
0280 {
0281     if (notification.constHints().value("acked").toInt() == 1) {
0282         return;
0283     }
0284     qCDebug(SNORE) << notification.constHints().value("acked").toInt();
0285     QString receipt = notification.constHints().value("receipt").toString();
0286 
0287     QNetworkRequest request(QUrl::fromEncoded((QLatin1String("https://api.pushover.net/1/receipts/") +
0288                             receipt + QLatin1String("/acknowledge.json")).toUtf8().constData()));
0289     qCWarning(SNORE) << request.url();
0290     request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(QLatin1String("application/x-www-form-urlencoded")));
0291     QNetworkReply *reply = m_manager.post(request, (QLatin1String("secret=") + secret()).toUtf8().constData());
0292 
0293     connect(reply, &QNetworkReply::finished, [reply]() {
0294         qCDebug(SNORE) << reply->error();
0295         qCDebug(SNORE) << reply->readAll();
0296         //TODO:parse reply
0297         reply->close();
0298         reply->deleteLater();
0299     });
0300 }
0301