From 0101b616518ad2b9e60f5aa3d6ac1136ef46e35c Mon Sep 17 00:00:00 2001 From: ArsenArsen Date: Mon, 24 Apr 2017 23:14:01 +0200 Subject: [PATCH] Custom uploaders --- KShare.pro | 6 +- io/ioutils.cpp | 41 +++- io/ioutils.hpp | 12 +- uploaders/customuploader.cpp | 344 ++++++++++++++++++++++++++++ uploaders/customuploader.hpp | 43 ++++ uploaders/default/imguruploader.cpp | 5 +- uploaders/uploadersingleton.cpp | 26 ++- 7 files changed, 450 insertions(+), 27 deletions(-) create mode 100644 uploaders/customuploader.cpp create mode 100644 uploaders/customuploader.hpp diff --git a/KShare.pro b/KShare.pro index c36fed8..c51045a 100644 --- a/KShare.pro +++ b/KShare.pro @@ -35,7 +35,8 @@ SOURCES += main.cpp\ io/ioutils.cpp \ settings.cpp \ uploaders/default/clipboarduploader.cpp \ - formatter.cpp + formatter.cpp \ + uploaders/customuploader.cpp HEADERS += mainwindow.hpp \ cropeditor/cropeditor.hpp \ @@ -49,7 +50,8 @@ HEADERS += mainwindow.hpp \ io/ioutils.hpp \ settings.hpp \ uploaders/default/clipboarduploader.hpp \ - formatter.hpp + formatter.hpp \ + uploaders/customuploader.hpp FORMS += mainwindow.ui diff --git a/io/ioutils.cpp b/io/ioutils.cpp index d0dae2b..074f953 100644 --- a/io/ioutils.cpp +++ b/io/ioutils.cpp @@ -9,7 +9,7 @@ namespace ioutils QNetworkAccessManager networkManager; } -void ioutils::getJson(QUrl target, QList> headers, std::function callback) +void ioutils::getJson(QUrl target, QList> headers, std::function callback) { QNetworkRequest req(target); for (auto header : headers) @@ -18,7 +18,7 @@ void ioutils::getJson(QUrl target, QList> headers, std:: } QNetworkReply *reply = networkManager.get(req); QObject::connect(reply, &QNetworkReply::finished, [reply, callback] { - callback(QJsonDocument::fromJson(reply->readAll())); + callback(QJsonDocument::fromJson(reply->readAll()), reply); reply->deleteLater(); }); } @@ -26,7 +26,7 @@ void ioutils::getJson(QUrl target, QList> headers, std:: void ioutils::postJson(QUrl target, QList> headers, QByteArray body, - std::function callback) + std::function callback) { QNetworkRequest req(target); for (auto header : headers) @@ -35,7 +35,38 @@ void ioutils::postJson(QUrl target, } QNetworkReply *reply = networkManager.post(req, body); QObject::connect(reply, &QNetworkReply::finished, [reply, callback] { - callback(QJsonDocument::fromJson(reply->readAll())); - reply->deleteLater(); + callback(QJsonDocument::fromJson(reply->readAll()), reply); + delete reply; + }); +} + +void ioutils::getData(QUrl target, QList> headers, std::function callback) +{ + QNetworkRequest req(target); + for (auto header : headers) + { + req.setRawHeader(header.first.toUtf8(), header.second.toUtf8()); + } + QNetworkReply *reply = networkManager.get(req); + QObject::connect(reply, &QNetworkReply::finished, [reply, callback] { + callback(reply->readAll(), reply); + delete reply; + }); +} + +void ioutils::postData(QUrl target, + QList> headers, + QByteArray body, + std::function callback) +{ + QNetworkRequest req(target); + for (auto header : headers) + { + req.setRawHeader(header.first.toUtf8(), header.second.toUtf8()); + } + QNetworkReply *reply = networkManager.post(req, body); + QObject::connect(reply, &QNetworkReply::finished, [reply, callback] { + callback(reply->readAll(), reply); + delete reply; }); } diff --git a/io/ioutils.hpp b/io/ioutils.hpp index bd4c3fc..842c9a8 100644 --- a/io/ioutils.hpp +++ b/io/ioutils.hpp @@ -10,14 +10,10 @@ namespace ioutils { extern QNetworkAccessManager networkManager; -void getJson(QUrl target, QList> headers, std::function callback); -void postJson(QUrl target, - QList> headers, - QByteArray body, - std::function callback); -// If I need more I will add -// Maybe when people start with plugins and custom uploaders -// Wait, that's a secret +void getJson(QUrl target, QList> headers, std::function callback); +void postJson(QUrl target, QList> headers, QByteArray body, std::function callback); +void getData(QUrl target, QList> headers, std::function callback); +void postData(QUrl target, QList> headers, QByteArray body, std::function callback); } #endif // IOUTILS_HPP diff --git a/uploaders/customuploader.cpp b/uploaders/customuploader.cpp new file mode 100644 index 0000000..52a97f6 --- /dev/null +++ b/uploaders/customuploader.cpp @@ -0,0 +1,344 @@ +#include "customuploader.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +using std::runtime_error; + +CustomUploader::CustomUploader(QString absFilePath) +{ + types.insert("PNG", "image/png"); // Is a list of supported formats, too + types.insert("GIF", "image/gif"); + types.insert("JPG", "image/jpeg"); + types.insert("JPEG", "image/jpeg"); + types.insert("WEBM", "video/webm"); + types.insert("WEBM", "video/mp4"); + // Let's go + QFile file(absFilePath); + file.open(QIODevice::ReadOnly | QIODevice::Text); + QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); + if (!doc.isObject()) + { + throw runtime_error(QString("Invalid file: ").append(absFilePath).toStdString()); + } + QJsonObject obj = doc.object(); + if (!obj["name"].isString()) + throw runtime_error((QString("Invalid file: ").append(absFilePath) + ": name is not a string").toStdString()); + else + uName = obj["name"].toString(); + if (!obj.contains("desc")) + { + if (!obj["desc"].isString()) + throw runtime_error((QString("Invalid file: ").append(absFilePath) + ": desc not a string, or nonexisting").toStdString()); + else + desc = obj["desc"].toString(); + } + else + desc = absFilePath; + QJsonValue m = obj["method"]; + if (!m.isUndefined() && !m.isNull()) + { + if (!m.isString()) + throw runtime_error((QString("Invalid file: ").append(absFilePath) + ": method not string").toStdString()); + QString toCheck = m.toString().toLower(); + if (toCheck == "post") + method = HttpMethod::POST; + else + throw runtime_error((QString("Invalid file: ").append(absFilePath) + ": Bad method").toStdString()); + } + QJsonValue url = obj["target"]; + if (!url.isString()) + { + throw runtime_error((QString("Invalid file: ").append(absFilePath) + ": target missing").toStdString()); + } + QUrl target(url.toString()); + if (!target.isValid()) + throw runtime_error((QString("Invalid file: ").append(absFilePath) + ": target not URL").toStdString()); + this->target = target; + QJsonValue formatValue = obj["format"]; + if (!formatValue.isUndefined() && !formatValue.isNull()) + { + if (formatValue.isString()) + { + QString formatString = formatValue.toString().toLower(); + if (formatString == "x-www-form-urlencoded") + format = RequestFormat::X_WWW_FORM_URLENCODED; + else if (formatString == "json") + format = RequestFormat::JSON; + else if (formatString == "plain") + format = RequestFormat::PLAIN; + else + throw runtime_error((QString("Invalid file: ").append(absFilePath) + ": format invalid").toStdString()); + } + } + else + throw runtime_error((QString("Invalid file: ").append(absFilePath) + ": format provided but not string").toStdString()); + QJsonValue imageValue = obj["imageformat"]; + if (!imageValue.isString()) + { + throw runtime_error((QString("Invalid file: ").append(absFilePath) + ": imageformat invalid/missing").toStdString()); + } + QString imageFormat = imageValue.toString(); + if (imageFormat == "base64" || QRegExp("base64\\([^+]+\\+[^+]+)").exactMatch(imageFormat) + || QRegExp("[^+]+\\+[^+]+").exactMatch(imageFormat)) + { + this->iFormat = imageFormat; + } + else + throw runtime_error((QString("Invalid file: ").append(absFilePath) + ": imageformat bad").toStdString()); + QJsonValue bodyValue = obj["body"]; + if (format != RequestFormat::PLAIN) + { + if (bodyValue.isUndefined()) + throw runtime_error((QString("Invalid file: ").append(absFilePath) + ": body unset").toStdString()); + if (bodyValue.isObject()) + body = bodyValue; + else + throw runtime_error((QString("Invalid file: ").append(absFilePath) + ": body must be object").toStdString()); + } + else + { + if (bodyValue.isString()) + { + body = bodyValue; + } + else + throw runtime_error((QString("Invalid file: ").append(absFilePath) + ": body must be string (due to PLAIN)").toStdString()); + } + QJsonValue headerVal = obj["headers"]; + if (!(headerVal.isUndefined() || headerVal.isNull())) + { + if (!headerVal.isObject()) + throw runtime_error((QString("Invalid file: ").append(absFilePath) + ": headers must be object").toStdString()); + headers = headerVal.toObject(); + } + else + headers = QJsonObject(); + QJsonValue returnPsVal = obj["return"]; + if (returnPsVal.isString()) + { + returnPathspec = returnPsVal.toString(); + } + else + throw runtime_error((QString("Invalid file: ").append(absFilePath) + ": return invalid").toStdString()); +} + +QString CustomUploader::name() +{ + return uName; +} + +QString CustomUploader::description() +{ + return desc; +} + +QString getCType(RequestFormat format, QString plainType) +{ + switch (format) + { + case RequestFormat::X_WWW_FORM_URLENCODED: + return "application/x-www-form-urlencoded"; + case RequestFormat::JSON: + return "application/json"; + case RequestFormat::PLAIN: + return plainType; + } + return plainType; +} + +QList> getHeaders(QJsonObject h, QString imageFormat, QMap types, RequestFormat format) +{ + QList> headers; + for (QString s : h.keys()) + { + if (s.toLower() == "content-type") continue; + QJsonValue v = h[s]; + if (!v.isString()) + headers << QPair(s, QJsonDocument::fromVariant(v.toVariant()).toJson()); + else + headers << QPair(s, v.toString()); + } + headers << QPair("Content-Type", getCType(format, types.value(imageFormat))); + return headers; +} + +QByteArray imageBytes(QPixmap *pixmap, QString format) +{ + QByteArray returnVal; + QBuffer buff(&returnVal); + buff.open(QIODevice::WriteOnly); + pixmap->save(&buff, format.toUpper().toLocal8Bit().constData()); + return returnVal; +} + +QString CustomUploader::getFormatString(bool animated) +{ + if (iFormat == "base64") + return animated ? "GIF" : "PNG"; + else if (QRegExp("[^+]+\\+[^+]+").exactMatch(iFormat)) + return iFormat.split('+')[(int)animated]; + else if (QRegExp("base64\\([^+]+\\+[^+]+)").exactMatch(iFormat)) + return iFormat.mid(7, iFormat.length() - 8).split('+')[(int)animated]; + return ""; +} + +QJsonObject recurseAndReplace(QJsonObject &body, QByteArray &data, QString contentType) +{ + QJsonObject o; + for (QString s : body.keys()) + { + QJsonValue v = body[s]; + if (v.isObject()) + { + QJsonObject vo = v.toObject(); + o.insert(s, recurseAndReplace(vo, data, contentType)); + } + else if (v.isString()) + { + QString str = v.toString(); + if (str.startsWith("/") && str.endsWith("/")) + { + o.insert(s, str.replace("%image", data).replace("%contenttype", contentType)); + } + } + } + return o; +} + +QString parsePathspec(QJsonDocument &response, QString &pathspec) +{ + if (!pathspec.startsWith(".")) + { + // Does not point to anything + return ""; + } + else + { + if (!response.isObject()) return ""; + QStringList fields = pathspec.right(pathspec.length() - 1).split('.', QString::SkipEmptyParts); + QJsonObject o = response.object(); + if (pathspec == ".") + { + return QString::fromUtf8(response.toJson()); + } + QJsonValue val = o[fields.at(0)]; + if (val.isUndefined() || val.isNull()) + return ""; + else if (val.isString()) + return val.toString(); + else if (!val.isObject()) + return QString::fromUtf8(QJsonDocument::fromVariant(val.toVariant()).toJson()); + for (int i = 1; i < fields.size(); i++) + { + if (val.isUndefined() || val.isNull()) + return ""; + else if (val.isString()) + return val.toString(); + else if (!val.isObject()) + return QString::fromUtf8(QJsonDocument::fromVariant(val.toVariant()).toJson()); + else + val = val.toObject()[fields.at(i)]; + } + if (val.isUndefined() || val.isNull()) + return ""; + else if (val.isString()) + return val.toString(); + else if (!val.isObject()) + return QString::fromUtf8(QJsonDocument::fromVariant(val.toVariant()).toJson()); + } + return ""; +} + +void CustomUploader::doUpload(QPixmap *pixmap) +{ + auto h = getHeaders(headers, getFormatString(false), types, this->format); + QString format = getFormatString(false); // Soon:tm: + QByteArray data; + QByteArray imgData = imageBytes(pixmap, format); + if (iFormat == "base64" || QRegExp("base64\\([^+]\\+[^+]\\)").exactMatch(iFormat)) imgData = imgData.toBase64(); + switch (this->format) + { + case RequestFormat::PLAIN: + { + data = imgData; + } + break; + case RequestFormat::JSON: + { + if (body.isString()) + { + QStringList split = body.toString().replace("%contenttype", types.value(format)).split("%imagedata"); + for (int i = 0; i < split.size(); i++) + { + data.append(split[i]); + if (i < split.size() - 1) data.append(imgData); + } + } + else + { + QJsonObject vo = body.toObject(); + data = QJsonDocument::fromVariant(recurseAndReplace(vo, imgData, types.value(format)).toVariantMap()).toJson(); + } + } + break; + case RequestFormat::X_WWW_FORM_URLENCODED: + { + QJsonObject body = this->body.toObject(); + for (QString key : body.keys()) + { + QJsonValue val = body[key]; + if (val.isString()) + { + QString str = val.toString(); + QByteArray strB; + if (str.startsWith("/") && str.endsWith("/")) + { + str = str.mid(1, str.length() - 2); + QStringList split = str.replace("%contenttype", types.value(format)).split("%imagedata"); + for (int i = 0; i < split.size(); i++) + { + strB.append(split[i]); + if (i < split.size() - 1) strB.append(imgData); + } + } + data.append(QUrl::toPercentEncoding(key)).append('=').append(strB); + } + else + { + if (!data.isEmpty()) data.append('&'); + data.append(QUrl::toPercentEncoding(key)) + .append('=') + .append(QUrl::toPercentEncoding(QJsonDocument::fromVariant(body[key].toVariant()).toJson())); + } + } + } + break; + } + switch (method) + { + case HttpMethod::POST: + if (returnPathspec == "|") + { + ioutils::postData(target, h, data, [&](QByteArray result, QNetworkReply *) { + QApplication::clipboard()->setText(QString::fromUtf8(result)); + }); + } + else + { + ioutils::postJson(target, h, data, [&](QJsonDocument result, QNetworkReply *) { + if (result.isObject()) + { + QApplication::clipboard()->setText(parsePathspec(result, returnPathspec)); + } + }); + } + break; + } +} diff --git a/uploaders/customuploader.hpp b/uploaders/customuploader.hpp new file mode 100644 index 0000000..e85c985 --- /dev/null +++ b/uploaders/customuploader.hpp @@ -0,0 +1,43 @@ +#ifndef CUSTOMUPLOADER_HPP +#define CUSTOMUPLOADER_HPP + +#include "uploader.hpp" +#include +#include +#include + +enum class HttpMethod +{ + POST +}; + +enum class RequestFormat +{ + X_WWW_FORM_URLENCODED, + JSON, + PLAIN +}; + +class CustomUploader : public Uploader +{ + public: + CustomUploader(QString absFilePath); + QString name(); + QString description(); + void doUpload(QPixmap *pixmap); + QString getFormatString(bool animated); + QMap types; + + private: + QString desc; + QString uName; + RequestFormat format = RequestFormat::JSON; + HttpMethod method = HttpMethod::POST; + QUrl target; + QJsonValue body; + QJsonObject headers; + QString returnPathspec; + QString iFormat; +}; + +#endif // CUSTOMUPLOADER_HPP diff --git a/uploaders/default/imguruploader.cpp b/uploaders/default/imguruploader.cpp index 34131ba..03b3664 100644 --- a/uploaders/default/imguruploader.cpp +++ b/uploaders/default/imguruploader.cpp @@ -13,10 +13,9 @@ void ImgurUploader::doUpload(QPixmap *pixmap) pixmap->save(&buffer, "PNG"); ioutils::postJson(QUrl("https://api.imgur.com/3/image"), QList>() - << QPair("Content-Type", - "application/x-www-form-urlencoded") + << QPair("Content-Type", "application/x-www-form-urlencoded") << QPair("Authorization", "Client-ID 8a98f183fc895da"), - byteArray, [](QJsonDocument res) { + byteArray, [](QJsonDocument res, QNetworkReply *) { screenshotutil::toClipboard(res.object()["data"].toObject()["link"].toString()); }); } diff --git a/uploaders/uploadersingleton.cpp b/uploaders/uploadersingleton.cpp index 040b05a..d76eaa2 100644 --- a/uploaders/uploadersingleton.cpp +++ b/uploaders/uploadersingleton.cpp @@ -1,6 +1,8 @@ #include "uploadersingleton.hpp" +#include "customuploader.hpp" #include "default/clipboarduploader.hpp" #include "default/imguruploader.hpp" +#include #include #include #include @@ -8,15 +10,21 @@ UploaderSingleton::UploaderSingleton() { - // QDir - // configDir(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation)); - // configDir.mkpath("KShare/uploaders"); - // configDir.cd("KShare/uploaders"); - // configDir.setNameFilters({"*.uploader"}); - // for (QString file : configDir.entryList()) { - // registerUploader(new CustomUploader(file)); - // } - // TODO + QDir configDir(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation)); + configDir.mkpath("KShare/uploaders"); + configDir.cd("KShare/uploaders"); + configDir.setNameFilters({ "*.uploader" }); + for (QString file : configDir.entryList()) + { + try + { + registerUploader(new CustomUploader(configDir.absoluteFilePath(file))); + } + catch (std::runtime_error e) + { + qWarning() << e.what(); // u wot m8 + } + } // UPLOADERS registerUploader(new ImgurUploader);