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