From b63b82528fe0135f55e96f9bb6e1f528676c1e32 Mon Sep 17 00:00:00 2001 From: Gurkengewuerz Date: Sat, 22 Feb 2020 14:17:14 +0100 Subject: [PATCH] added Google Drive API --- src/src.pro | 5 + .../default/gdrivesettingsdialog.cpp | 177 ++++++++++++++++++ .../default/gdrivesettingsdialog.hpp | 29 +++ src/uploaders/default/gdrivesettingsdialog.ui | 174 +++++++++++++++++ src/uploaders/default/gdriveuploader.cpp | 161 ++++++++++++++++ src/uploaders/default/gdriveuploader.hpp | 27 +++ src/uploaders/uploadersingleton.cpp | 2 + 7 files changed, 575 insertions(+) create mode 100644 src/uploaders/default/gdrivesettingsdialog.cpp create mode 100644 src/uploaders/default/gdrivesettingsdialog.hpp create mode 100644 src/uploaders/default/gdrivesettingsdialog.ui create mode 100644 src/uploaders/default/gdriveuploader.cpp create mode 100644 src/uploaders/default/gdriveuploader.hpp diff --git a/src/src.pro b/src/src.pro index d0227e5..7c07cb5 100644 --- a/src/src.pro +++ b/src/src.pro @@ -34,6 +34,7 @@ SOURCES += main.cpp\ screenshotter.cpp \ utils.cpp \ uploaders/default/imguruploader.cpp \ + uploaders/default/gdriveuploader.cpp \ io/ioutils.cpp \ settings.cpp \ uploaders/default/clipboarduploader.cpp \ @@ -65,6 +66,7 @@ SOURCES += main.cpp\ hotkeyinputdialog.cpp \ cropeditor/drawing/arrowitem.cpp \ uploaders/default/imgursettingsdialog.cpp \ + uploaders/default/gdrivesettingsdialog.cpp \ filenamevalidator.cpp \ logs/requestlogging.cpp \ monospacetextdialog.cpp \ @@ -85,6 +87,7 @@ HEADERS += mainwindow.hpp \ screenshotter.hpp \ utils.hpp \ uploaders/default/imguruploader.hpp \ + uploaders/default/gdriveuploader.hpp \ io/ioutils.hpp \ settings.hpp \ uploaders/default/clipboarduploader.hpp \ @@ -118,6 +121,7 @@ HEADERS += mainwindow.hpp \ hotkeyinputdialog.hpp \ cropeditor/drawing/arrowitem.hpp \ uploaders/default/imgursettingsdialog.hpp \ + uploaders/default/gdrivesettingsdialog.hpp \ filenamevalidator.hpp \ logs/requestlogging.hpp \ monospacetextdialog.hpp \ @@ -189,6 +193,7 @@ FORMS += mainwindow.ui \ aboutbox.ui \ hotkeyinputdialog.ui \ uploaders/default/imgursettingsdialog.ui \ + uploaders/default/gdrivesettingsdialog.ui \ monospacetextdialog.ui \ screenoverlay/screenoverlaysettings.ui diff --git a/src/uploaders/default/gdrivesettingsdialog.cpp b/src/uploaders/default/gdrivesettingsdialog.cpp new file mode 100644 index 0000000..1330928 --- /dev/null +++ b/src/uploaders/default/gdrivesettingsdialog.cpp @@ -0,0 +1,177 @@ +#include "gdrivesettingsdialog.hpp" +#include "ui_gdrivesettingsdialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +GDriveSettingsDialog::GDriveSettingsDialog(QWidget *parent) : QDialog(parent), ui(new Ui::GDriveSettingsDialog) { + ui->setupUi(this); + connect(this, &GDriveSettingsDialog::accepted, this, &GDriveSettingsDialog::deleteLater); + ui->clientId->setText(settings::settings().value("google/cid").toString()); + ui->clientSecret->setText(settings::settings().value("google/csecret").toString()); + ui->folderId->setText(settings::settings().value("google/folder").toString().toUtf8()); + + ui->isPublic->setChecked(settings::settings().value("google/public").toBool()); +} + +GDriveSettingsDialog::~GDriveSettingsDialog() { + delete ui; +} + +void GDriveSettingsDialog::on_addApp_clicked() { + QDesktopServices::openUrl( + QUrl(QString("https://accounts.google.com/o/oauth2/auth?client_id=%1&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive").arg(ui->clientId->text()))); +} + +void GDriveSettingsDialog::on_isPublic_clicked(bool checked) { + settings::settings().setValue("google/public", checked); +} + +void GDriveSettingsDialog::on_folderId_textChanged(QString arg1) { + settings::settings().setValue("google/folder", arg1); +} + +bool GDriveSettingsDialog::checkAuthorization () { + if (ui->folderId->text().isEmpty()) return false; + settings::settings().setValue("google/folder", ui->folderId->text()); + + if (settings::settings().contains("google/expire") // + && settings::settings().contains("google/refresh") // + && settings::settings().contains("google/access")) { + QDateTime expireTime = settings::settings().value("google/expire").toDateTime(); + if (QDateTime::currentDateTimeUtc() > expireTime) { + // Token expired + ui->status->setText(tr("Token expired")); + } else { + ui->buttonBox->setEnabled(false); + ui->testButton->setEnabled(false); + ioutils::getData(QUrl(QString("https://www.googleapis.com/drive/v3/files?q='%1'+in+parents&fields=files(md5Checksum,+originalFilename)").arg(ui->folderId->text())), + QList>({ QPair("Authorization", settings::settings().value("google/access").toString().prepend("Bearer ")) }), + [&](QByteArray a, QNetworkReply *r) { + + QVariant statusCode = r->attribute(QNetworkRequest::HttpStatusCodeAttribute); + if (!statusCode.isValid()) return; + + ui->testButton->setEnabled(true); + + int status = statusCode.toInt(); + if (status != 200 && status != 400) + { + QString reason = r->attribute( QNetworkRequest::HttpReasonPhraseAttribute ).toString(); + qDebug() << reason; + + ui->buttonBox->setEnabled(true); + ui->status->setText(reason); + return; + } + + QJsonDocument response = QJsonDocument::fromJson(a); + if (!response.isObject()) { + ui->buttonBox->setEnabled(true); + ui->status->setText("Invalid JSON"); + return; + } + QJsonObject res = response.object(); + + if (res.contains("error")) { + ui->buttonBox->setEnabled(true); + + ui->status->setText(res.value("error_description").toString().toUtf8()); + ui->status->setStyleSheet("* { color: red; }"); + + return; + } + + ui->buttonBox->setEnabled(true); + ui->testButton->hide(); + + ui->status->setText(tr("It works!")); + ui->status->setStyleSheet("* { color: green; }"); + }); + } + } else { + // No Token set + ui->status->setText(tr("Login with Google needed")); + } +} + +void GDriveSettingsDialog::on_testButton_clicked() { + GDriveSettingsDialog::checkAuthorization(); +} + +void GDriveSettingsDialog::on_authorize_clicked() { + if (ui->pin->text().isEmpty() || ui->folderId->text().isEmpty()) return; + ui->buttonBox->setEnabled(false); + QJsonObject object; + object.insert("client_id", ui->clientId->text()); + object.insert("client_secret", ui->clientSecret->text()); + object.insert("grant_type", "authorization_code"); + object.insert("code", ui->pin->text()); + object.insert("redirect_uri", "urn:ietf:wg:oauth:2.0:oob"); + settings::settings().setValue("google/cid", ui->clientId->text()); + settings::settings().setValue("google/csecret", ui->clientSecret->text()); + settings::settings().setValue("google/folder", ui->folderId->text()); + settings::settings().setValue("google/public", ui->isPublic->isChecked()); + + ioutils::postJson(QUrl("https://accounts.google.com/o/oauth2/token"), + QList>({ QPair("Content-Type", "applicaton/json") }), + QJsonDocument::fromVariant(object.toVariantMap()).toJson(), + [&](QJsonDocument response, QByteArray, QNetworkReply *r) { + QVariant statusCode = r->attribute(QNetworkRequest::HttpStatusCodeAttribute); + if (!statusCode.isValid()) return; + + int status = statusCode.toInt(); + if (status != 200 && status != 400) + { + QString reason = r->attribute( QNetworkRequest::HttpReasonPhraseAttribute ).toString(); + qDebug() << reason; + + ui->buttonBox->setEnabled(true); + return; + } + + + if (!response.isObject()) { + ui->buttonBox->setEnabled(true); + return; + } + QJsonObject res = response.object(); + + if (res.contains("error")) { + ui->buttonBox->setEnabled(true); + + ui->status->setText(res.value("error_description").toString().toUtf8()); + ui->status->setStyleSheet("* { color: red; }"); + + return; + } + + if (res.value("success").toBool()) { + ui->buttonBox->setEnabled(true); + return; + } + + settings::settings().setValue("google/expire", + QDateTime::currentDateTimeUtc().addSecs(res["expires_in"].toInt())); + settings::settings().setValue("google/refresh", res["refresh_token"].toString().toUtf8()); + settings::settings().setValue("google/access", res["access_token"].toString().toUtf8()); + ui->status->setText(tr("It works!")); + ui->status->setStyleSheet("* { color: green; }"); + + ui->authorize->setEnabled(false); + ui->addApp->setEnabled(false); + ui->clientSecret->setEnabled(false); + ui->clientId->setEnabled(false); + + ui->buttonBox->setEnabled(true); + + ui->testButton->hide(); + }); +} diff --git a/src/uploaders/default/gdrivesettingsdialog.hpp b/src/uploaders/default/gdrivesettingsdialog.hpp new file mode 100644 index 0000000..e894bc7 --- /dev/null +++ b/src/uploaders/default/gdrivesettingsdialog.hpp @@ -0,0 +1,29 @@ +#ifndef GDRIVESETTINGSDIALOG_HPP +#define GDRIVESETTINGSDIALOG_HPP + +#include + +namespace Ui { + class GDriveSettingsDialog; +} + +class GDriveSettingsDialog : public QDialog { + Q_OBJECT + +public: + explicit GDriveSettingsDialog(QWidget *parent = 0); + ~GDriveSettingsDialog(); + +private slots: + void on_addApp_clicked(); + void on_authorize_clicked(); + void on_testButton_clicked(); + bool checkAuthorization(); + void on_isPublic_clicked(bool); + void on_folderId_textChanged(QString); + +private: + Ui::GDriveSettingsDialog *ui; +}; + +#endif // GRDIVESETTINGSDIALOG_HPP diff --git a/src/uploaders/default/gdrivesettingsdialog.ui b/src/uploaders/default/gdrivesettingsdialog.ui new file mode 100644 index 0000000..03164b8 --- /dev/null +++ b/src/uploaders/default/gdrivesettingsdialog.ui @@ -0,0 +1,174 @@ + + + GDriveSettingsDialog + + + + 0 + 0 + 287 + 389 + + + + GDrive auth + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + OAuth2 + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Noto Sans'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Authentication needed</span></p></body></html> + + + + + + + Login with Google + + + + + + + <html><head/><body><p>Enter verification code</p></body></html> + + + + + + + Authorization code + + + + + + + Authorize + + + + + + + * { color: red; } + + + Not working + + + + + + + Client ID/Client Secret + + + + + + + Client Secret + + + + + + + Client ID + + + + + + + Upload Public + + + + + + + Folder ID + + + + + + + Check Authorization + + + + + + + + + + Is Public + + + + + + + + + + + + buttonBox + accepted() + GDriveSettingsDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + GDriveSettingsDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/uploaders/default/gdriveuploader.cpp b/src/uploaders/default/gdriveuploader.cpp new file mode 100644 index 0000000..6b00575 --- /dev/null +++ b/src/uploaders/default/gdriveuploader.cpp @@ -0,0 +1,161 @@ +#include "gdriveuploader.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct SegfaultWorkaround { // I'm a scrub for doing this + SegfaultWorkaround(QByteArray a, GDriveUploader *u, QString m, bool isPublic, ScreenshotFile sf) : byteArray(), dis(u), mime(m) { + a.swap(byteArray); + QJsonObject object; + object.insert("client_id", settings::settings().value("google/cid").toString()); + object.insert("client_secret", settings::settings().value("google/csecret").toString()); + object.insert("grant_type", "refresh_token"); + object.insert("refresh_token", settings::settings().value("google/refresh").toString()); + + ioutils::postJson( + QUrl("https://accounts.google.com/o/oauth2/token"), + QList>({ QPair("Content-Type", "applicaton/json") }), + QJsonDocument::fromVariant(object.toVariantMap()).toJson(), [&](QJsonDocument response, QByteArray, QNetworkReply *r) { + qDebug() << response; + if (r->error() != QNetworkReply::NoError || !response.isObject()) { + notifications::notify(QObject::tr("KShare Google Uploader"), + QString(QObject::tr("Failed upload! Error in refresh token"))); + notifications::playSound(notifications::Sound::ERROR); + return; + } + + QJsonObject res = response.object(); + QString token = res["access_token"].toString(); + settings::settings().setValue("google/expire", QDateTime::currentDateTimeUtc().addSecs(res["expires_in"].toInt())); + settings::settings().setValue("google/refresh", res["refresh_token"].toString()); + settings::settings().setValue("google/access", token); + + dis->handleSend(token.prepend("Bearer "), isPublic, mime, byteArray, sf); + QScopedPointer(this); + }); + } + +private: + QByteArray byteArray; + GDriveUploader *dis; + QString mime; + bool isPublic; + ScreenshotFile sf; +}; // I feel terrible for making this. I am sorry, reader + +void GDriveUploader::doUpload(QByteArray byteArray, QString format, ScreenshotFile sf) { + if (byteArray.size() > 1e+7) { + notifications::notify(tr("KShare imgur Uploader"), tr("Failed upload! Image too big")); + return; + } + QString mime; + if (formats::normalFormatFromName(format) != formats::Normal::None) + mime = formats::normalFormatMIME(formats::normalFormatFromName(format)); + else + mime = formats::recordingFormatMIME(formats::recordingFormatFromName(format)); + + if (settings::settings().contains("google/expire") // + && settings::settings().contains("google/refresh") // + && settings::settings().contains("google/access")) { + QDateTime expireTime = settings::settings().value("google/expire").toDateTime(); + bool isPublic = settings::settings().value("google/public", false).toBool(); + if (QDateTime::currentDateTimeUtc() > expireTime) { + qDebug() << "Need to refresh Google Access Token"; + new SegfaultWorkaround(byteArray, this, mime, isPublic, sf); + } else handleSend("Bearer " + settings::settings().value("google/access", "").toString().toUtf8(), isPublic, mime, byteArray, sf); + } +} + +void GDriveUploader::showSettings() { + (new GDriveSettingsDialog())->show(); +} + +void GDriveUploader::setPermission(QString fileID, QString auth, QString role, QString type, bool allowFileDiscovery) { + QJsonObject object; + object.insert("role", role); + object.insert("type", type); + object.insert("allowFileDiscovery", QString(allowFileDiscovery)); + QJsonDocument doc(object); + + ioutils::postData(QUrl(QString("https://www.googleapis.com/drive/v3/files/%1/permissions").arg(fileID)), + QList>({ QPair("Content-Type", "application/json"), QPair("Authorization", auth) }), + QString(doc.toJson(QJsonDocument::Compact)).toUtf8(), + [&](QByteArray , QNetworkReply *) { + + }); +} + +void GDriveUploader::handleSend(QString auth, bool isPublic, QString mime, QByteArray byteArray, ScreenshotFile sf) { + QHttpMultiPart *multipart = new QHttpMultiPart(QHttpMultiPart::RelatedType); + + QHttpPart metaPart; + metaPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json")); + metaPart.setHeader( + QNetworkRequest::ContentDispositionHeader, + QVariant("form-data; name=\"metadata\"") + ); + QString data("{name: '"+sf.getFilename()+"', parents: ['"+settings::settings().value("google/folder").toString().toUtf8()+"']}"); + metaPart.setBody(data.toUtf8()); + multipart->append(metaPart); + + QHttpPart imagePart; + imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(mime.toUtf8())); + imagePart.setHeader( + QNetworkRequest::ContentDispositionHeader, + QVariant("form-data; name=\"file\"; filename=\""+sf.getFilename()+"\"") + ); + + imagePart.setBody(byteArray); + multipart->append(imagePart); + + ioutils::postMultipartData(QUrl("https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id,webViewLink,webContentLink"), + QList>() << QPair("Authorization", auth), + multipart, [byteArray, this, mime, sf, auth, isPublic](QByteArray res, QNetworkReply *r) { + qDebug() << QString(res).toUtf8(); + if (r->error() == QNetworkReply::ContentAccessDenied) { + (new GDriveSettingsDialog())->show(); + return; + } + + QString result = QString(res).toUtf8(); + if (!result.isEmpty()) { + QJsonDocument doc = QJsonDocument::fromJson(result.toUtf8()); + if(!doc.isObject()) { + notifications::notify(tr("KShare Google Uploader "), + QString(tr("Failed upload! Invalid JSON"))); + notifications::playSound(notifications::Sound::ERROR); + return; + } + + if(isPublic) { + setPermission(doc.object()["id"].toString(), auth); + } + result = doc.object()["webViewLink"].toString(); + ioutils::addLogEntry(r, res, result, sf); + + notifications::notify(tr("KShare Google Uploader"), tr("Uploaded to Google!")); + notifications::playSound(notifications::Sound::SUCCESS); + utils::toClipboard(result); + } else { + + ioutils::addLogEntry(r, res, result, sf); + notifications::notify(tr("KShare Google Uploader "), + QString(tr("Failed upload! Google said: HTTP %1: %2")) + .arg(r->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()) + .arg(r->errorString())); + notifications::playSound(notifications::Sound::ERROR); + } + }); +} diff --git a/src/uploaders/default/gdriveuploader.hpp b/src/uploaders/default/gdriveuploader.hpp new file mode 100644 index 0000000..e7cb64a --- /dev/null +++ b/src/uploaders/default/gdriveuploader.hpp @@ -0,0 +1,27 @@ +#ifndef GDRIVEUPLOADER_HPP +#define GRIVEUPLOADER_HPP + +#include "../uploader.hpp" +#include +#include + +class GDriveUploader : public Uploader { + Q_DECLARE_TR_FUNCTIONS(GDriveUploader) + friend struct SegfaultWorkaround; + +public: + QString name() override { + return "Google Drive"; + } + QString description() override { + return "drive.google.com uploader"; + } + void doUpload(QByteArray byteArray, QString, ScreenshotFile sf) override; + void showSettings() override; + void setPermission(QString, QString, QString = "reader", QString = "anyone", bool = false); + +private: + void handleSend(QString auth, bool isPublic, QString mime, QByteArray byteArray, ScreenshotFile sf); +}; + +#endif // GDRIVEUPLOADER_HPP diff --git a/src/uploaders/uploadersingleton.cpp b/src/uploaders/uploadersingleton.cpp index c3548ee..a9c3eb4 100644 --- a/src/uploaders/uploadersingleton.cpp +++ b/src/uploaders/uploadersingleton.cpp @@ -2,6 +2,7 @@ #include "customuploader.hpp" #include "default/clipboarduploader.hpp" #include "default/imguruploader.hpp" +#include "default/gdriveuploader.hpp" #include #include #include @@ -33,6 +34,7 @@ UploaderSingleton::UploaderSingleton() : QObject() { // UPLOADERS registerUploader(new ImgurUploader); + registerUploader(new GDriveUploader); registerUploader(new ClipboardUploader); // ---------