mirror of
				https://github.com/subsurface/subsurface.git
				synced 2025-02-19 22:16:15 +00:00 
			
		
		
		
	These do the same, since the text-codex is set to UTF-8 on startup. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
		
			
				
	
	
		
			426 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			426 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| #include "facebookconnectwidget.h"
 | |
| 
 | |
| #include <QJsonDocument>
 | |
| #include <QJsonArray>
 | |
| #include <QJsonObject>
 | |
| #include <QNetworkReply>
 | |
| #include <QNetworkRequest>
 | |
| #include <QNetworkAccessManager>
 | |
| #include <QNetworkCookieJar>
 | |
| 
 | |
| #include <QUrlQuery>
 | |
| #include <QHttpMultiPart>
 | |
| #include <QFile>
 | |
| #include <QBuffer>
 | |
| #include <QDebug>
 | |
| #include <QMessageBox>
 | |
| #include <QInputDialog>
 | |
| #include <QLoggingCategory>
 | |
| #ifdef USE_WEBENGINE
 | |
| #include <QWebEngineView>
 | |
| #else
 | |
| #include <QWebView>
 | |
| #endif
 | |
| #include "mainwindow.h"
 | |
| #include "profile-widget/profilewidget2.h"
 | |
| 
 | |
| #include "core/pref.h"
 | |
| #include "core/helpers.h"
 | |
| #include "core/subsurface-qt/SettingsObjectWrapper.h"
 | |
| 
 | |
| #include "ui_socialnetworksdialog.h"
 | |
| #include "ui_facebookconnectwidget.h"
 | |
| 
 | |
| Q_LOGGING_CATEGORY(lcFacebook, "subsurface.facebook")
 | |
| 
 | |
| FacebookManager *FacebookManager::instance()
 | |
| {
 | |
| 	static FacebookManager *self = new FacebookManager();
 | |
| 	return self;
 | |
| }
 | |
| 
 | |
| FacebookManager::FacebookManager(QObject *parent) :
 | |
| 	QObject(parent),
 | |
| 	manager(new QNetworkAccessManager(this))
 | |
| {
 | |
| 	// log only in verbose mode
 | |
| 	QLoggingCategory::setFilterRules(QStringLiteral("subsurface.facebook=%1").arg(verbose ? "true" : "false"));
 | |
| 	connect(this, &FacebookManager::albumIdReceived, this, &FacebookManager::sendDiveToAlbum);
 | |
| }
 | |
| 
 | |
| static QString graphApi = QStringLiteral("https://graph.facebook.com/v2.10/");
 | |
| 
 | |
| QUrl FacebookManager::albumListUrl()
 | |
| {
 | |
| 	return QUrl("https://graph.facebook.com/me/albums?access_token=" + QString(prefs.facebook.access_token));
 | |
| }
 | |
| 
 | |
| QUrl FacebookManager::connectUrl() {
 | |
| 	return QUrl("https://www.facebook.com/dialog/oauth?"
 | |
| 		    "client_id=427722490709000"
 | |
| 		    "&redirect_uri=http://www.facebook.com/connect/login_success.html"
 | |
| 		    "&response_type=token,granted_scopes"
 | |
| 		    "&display=popup"
 | |
| 		    "&scope=publish_actions,user_photos"
 | |
| 		    );
 | |
| }
 | |
| 
 | |
| bool FacebookManager::loggedIn() {
 | |
| 	return prefs.facebook.access_token != NULL;
 | |
| }
 | |
| 
 | |
| void FacebookManager::tryLogin(const QUrl& loginResponse)
 | |
| {
 | |
| 	qCDebug(lcFacebook) << "Current url call" << loginResponse;
 | |
| 	QString result = loginResponse.toString();
 | |
| 	if (!result.contains("access_token")) {
 | |
| 		qCDebug(lcFacebook) << "Response without access token!";
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (result.contains("denied_scopes=publish_actions") || result.contains("denied_scopes=user_photos")) {
 | |
| 		qCDebug(lcFacebook) << "user did not allow us access" << result;
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	int from = result.indexOf("access_token=") + strlen("access_token=");
 | |
| 	int to = result.indexOf("&expires_in");
 | |
| 	QString securityToken = result.mid(from, to-from);
 | |
| 
 | |
| 	auto fb = SettingsObjectWrapper::instance()->facebook;
 | |
| 	fb->setAccessToken(securityToken);
 | |
| 	qCDebug(lcFacebook) << "Got securityToken" << securityToken;
 | |
| 	requestUserId();
 | |
| }
 | |
| 
 | |
| void FacebookManager::logout()
 | |
| {
 | |
| 	auto fb = SettingsObjectWrapper::instance()->facebook;
 | |
| 	fb->setAccessToken(QString());
 | |
| 	fb->setUserId(QString());
 | |
| 	fb->setAlbumId(QString());
 | |
| 	emit justLoggedOut(true);
 | |
| }
 | |
| 
 | |
| void FacebookManager::requestAlbumId()
 | |
| {
 | |
| 	qCDebug(lcFacebook) << "Starting to request the album id" << albumListUrl();
 | |
| 	QNetworkReply *reply = manager->get(QNetworkRequest(albumListUrl()));
 | |
| 	connect(reply, &QNetworkReply::finished, this, &FacebookManager::albumListReceived);
 | |
| }
 | |
| 
 | |
| void FacebookManager::albumListReceived()
 | |
| {
 | |
| 	qCDebug(lcFacebook) << "Reply for the album id";
 | |
| 	QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
 | |
| 	QJsonDocument albumsDoc = QJsonDocument::fromJson(reply->readAll());
 | |
| 	QJsonArray albumObj = albumsDoc.object().value("data").toArray();
 | |
| 	auto fb = SettingsObjectWrapper::instance()->facebook;
 | |
| 
 | |
| 	reply->deleteLater();
 | |
| 	foreach(const QJsonValue &v, albumObj){
 | |
| 		QJsonObject obj = v.toObject();
 | |
| 		if (obj.value("name").toString() == fbInfo.albumName) {
 | |
| 			fb->setAlbumId(obj.value("id").toString());
 | |
| 			qCDebug(lcFacebook) << "Album" << fbInfo.albumName << "already exists, using id" << obj.value("id").toString();
 | |
| 			emit albumIdReceived(fb->albumId());
 | |
| 			return;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// No album with the name we requested, create a new one.
 | |
| 	createFacebookAlbum();
 | |
| }
 | |
| 
 | |
| void FacebookManager::createFacebookAlbum()
 | |
| {
 | |
| 	qCDebug(lcFacebook) << "Album with name" << fbInfo.albumName << "doesn't exists, creating it.";
 | |
| 	QUrlQuery params;
 | |
| 	params.addQueryItem("name", fbInfo.albumName );
 | |
| 	params.addQueryItem("description", "Subsurface Album");
 | |
| 	params.addQueryItem("privacy", "{'value': 'SELF'}");
 | |
| 
 | |
| 	QNetworkRequest request(albumListUrl());
 | |
| 	request.setHeader(QNetworkRequest::ContentTypeHeader, "application/octet-stream");
 | |
| 
 | |
| 	QNetworkReply *reply = manager->post(request, params.query().toUtf8());
 | |
| 	connect(reply, &QNetworkReply::finished, this, &FacebookManager::facebookAlbumCreated);
 | |
| }
 | |
| 
 | |
| void FacebookManager::facebookAlbumCreated()
 | |
| {
 | |
| 	QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
 | |
| 	QJsonDocument albumsDoc = QJsonDocument::fromJson(reply->readAll());
 | |
| 	QJsonObject album = albumsDoc.object();
 | |
| 
 | |
| 	reply->deleteLater();
 | |
| 
 | |
| 	if (album.contains("id")) {
 | |
| 		qCDebug(lcFacebook) << "Album" << fbInfo.albumName << "created successfully with id" << album.value("id").toString();
 | |
| 		auto fb = SettingsObjectWrapper::instance()->facebook;
 | |
| 		fb->setAlbumId(album.value("id").toString());
 | |
| 		emit albumIdReceived(fb->albumId());
 | |
| 		return;
 | |
| 	} else {
 | |
| 		qCDebug(lcFacebook) << "It was not possible to create the album with name" << fbInfo.albumName;
 | |
| 		qCDebug(lcFacebook).noquote() << "Reply was: " << QString(albumsDoc.toJson(QJsonDocument::Indented));
 | |
| 		// FIXME: we are lacking 'user_photos' facebook permission to create an album, 
 | |
| 		// but we are able to upload the image to Facebook (album will be named 'Subsurface Photos')
 | |
| 		qCDebug(lcFacebook) << "But we are still able to upload data. Album name will be 'Subsurface Photos'";
 | |
| 		auto fb = SettingsObjectWrapper::instance()->facebook;
 | |
| 		emit albumIdReceived(fb->albumId());
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void FacebookManager::requestUserId()
 | |
| {
 | |
| 	qCDebug(lcFacebook) << "Requesting user id";
 | |
| 	QUrl userIdRequest("https://graph.facebook.com/me?fields=id&access_token=" + QString(prefs.facebook.access_token));
 | |
| 	QNetworkReply *reply = manager->get(QNetworkRequest(userIdRequest));
 | |
| 
 | |
| 	connect(reply, &QNetworkReply::finished, this, &FacebookManager::userIdReceived);
 | |
| }
 | |
| 
 | |
| void FacebookManager::userIdReceived()
 | |
| {
 | |
| 	QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
 | |
| 	QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll());
 | |
| 	QJsonObject obj = jsonDoc.object();
 | |
| 	if (obj.keys().contains("id")) {
 | |
| 		qCDebug(lcFacebook) << "User id requested successfully:" << obj.value("id").toString();
 | |
| 		SettingsObjectWrapper::instance()->facebook->setUserId(obj.value("id").toString());
 | |
| 		emit sendMessage(tr("Facebook logged in successfully"));
 | |
| 		emit justLoggedIn(true);
 | |
| 	} else {
 | |
| 		emit sendMessage(tr("Error, unknown user id, cannot login."));
 | |
| 		qCDebug(lcFacebook) << "Error, unknown user id, cannot login.";
 | |
| 	}
 | |
| 	reply->deleteLater();
 | |
| }
 | |
| 
 | |
| QPixmap FacebookManager::grabProfilePixmap()
 | |
| {
 | |
| 	qCDebug(lcFacebook) << "Grabbing Dive Profile pixmap";
 | |
| 	ProfileWidget2 *profile = MainWindow::instance()->graphics();
 | |
| 
 | |
| 	QSize size = fbInfo.profileSize == FacebookInfo::SMALL  ? QSize(800,600) :
 | |
| 		     fbInfo.profileSize == FacebookInfo::MEDIUM ? QSize(1024,760) :
 | |
| 		     fbInfo.profileSize == FacebookInfo::BIG ? QSize(1280,1024) : QSize();
 | |
| 
 | |
| 	auto currSize = profile->size();
 | |
| 	profile->resize(size);
 | |
| 	profile->setToolTipVisibile(false);
 | |
| 	QPixmap pix = profile->grab();
 | |
| 	profile->setToolTipVisibile(true);
 | |
| 	profile->resize(currSize);
 | |
| 
 | |
| 	return pix;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* to be changed to export the currently selected dive as shown on the profile.
 | |
|  * Much much easier, and its also good to people do not select all the dives
 | |
|  * and send erroniously *all* of them to facebook. */
 | |
| void FacebookManager::sendDiveInit()
 | |
| {
 | |
| 	qCDebug(lcFacebook) << "Starting to upload the dive to facebook";
 | |
| 
 | |
| 	SocialNetworkDialog dialog(qApp->activeWindow());
 | |
| 	if (dialog.exec() != QDialog::Accepted) {
 | |
| 		qCDebug(lcFacebook) << "User cancelled.";
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	fbInfo.bodyText = dialog.text();
 | |
| 	fbInfo.profileSize = dialog.profileSize();
 | |
| 	fbInfo.profileData = grabProfilePixmap();
 | |
| 	fbInfo.albumName = dialog.album();
 | |
| 	fbInfo.albumId = QString(); // request Album Id wil handle that.
 | |
| 
 | |
| 	// will emit albumIdReceived, that's connected to sendDiveToAlbum
 | |
| 	requestAlbumId();
 | |
| }
 | |
| 
 | |
| void FacebookManager::sendDiveToAlbum(const QString& albumId)
 | |
| {
 | |
| 	qCDebug(lcFacebook) << "Starting to upload the dive to album" << fbInfo.albumName << "id" << albumId;
 | |
| 	QUrl url(graphApi + albumId + "/photos?" +
 | |
| 		 "&access_token=" + QString(prefs.facebook.access_token) +
 | |
| 		 "&source=image" +
 | |
| 		 "&message=" + fbInfo.bodyText.replace(""", "%22"));
 | |
| 
 | |
| 	QNetworkRequest request(url);
 | |
| 
 | |
| 	QString bound="margin";
 | |
| 
 | |
| 	//according to rfc 1867 we need to put this string here:
 | |
| 	QByteArray data(QString("--" + bound + "\r\n").toUtf8());
 | |
| 	data.append("Content-Disposition: form-data; name=\"action\"\r\n\r\n");
 | |
| 	data.append(graphApi + "\r\n");
 | |
| 	data.append("--" + bound + "\r\n");   //according to rfc 1867
 | |
| 
 | |
| 	//name of the input is "uploaded" in my form, next one is a file name.
 | |
| 	data.append("Content-Disposition: form-data; name=\"uploaded\"; filename=\"" + QString::number(qrand()) + ".png\"\r\n");
 | |
| 	data.append("Content-Type: image/jpeg\r\n\r\n"); //data type
 | |
| 
 | |
| 	QByteArray bytes;
 | |
| 	QBuffer buffer(&bytes);
 | |
| 	buffer.open(QIODevice::WriteOnly);
 | |
| 	fbInfo.profileData.save(&buffer, "PNG");
 | |
| 
 | |
| 	data.append(bytes);   //let's read the file
 | |
| 	data.append("\r\n");
 | |
| 	data.append("--" + bound + "--\r\n");  //closing boundary according to rfc 1867
 | |
| 
 | |
| 	request.setRawHeader(QByteArray("Content-Type"),QString("multipart/form-data; boundary=" + bound).toUtf8());
 | |
| 	request.setRawHeader(QByteArray("Content-Length"), QString::number(data.length()).toUtf8());
 | |
| 	QNetworkReply *reply = manager->post(request,data);
 | |
| 
 | |
| 	connect(reply, &QNetworkReply::finished, this, &FacebookManager::uploadFinished);
 | |
| }
 | |
| 
 | |
| void FacebookManager::uploadFinished()
 | |
| {
 | |
| 	qCDebug(lcFacebook) << "Upload finish";
 | |
| 	auto reply = qobject_cast<QNetworkReply*>(sender());
 | |
| 	QByteArray response = reply->readAll();
 | |
| 	QJsonDocument jsonDoc = QJsonDocument::fromJson(response);
 | |
| 	QJsonObject obj = jsonDoc.object();
 | |
| 
 | |
| 	reply->deleteLater();
 | |
| 
 | |
| 	if (obj.keys().contains("id")){
 | |
| 		emit sendMessage(tr("Dive uploaded successfully to Facebook"));
 | |
| 	} else {
 | |
| 		emit sendMessage(tr("Dive upload failed. Please see debug output and send to Subsurface mailing list"));
 | |
| 		qCDebug(lcFacebook) << "Dive upload failed" << response;
 | |
| 	}
 | |
| 
 | |
| 	emit sendDiveFinished();
 | |
| }
 | |
| 
 | |
| void FacebookConnectWidget::showEvent(QShowEvent *event)
 | |
| {
 | |
| 	if (FacebookManager::instance()->loggedIn()) {
 | |
| 		facebookLoggedIn();
 | |
| 	} else {
 | |
| 		facebookDisconnect();
 | |
| 	}
 | |
| 	return QDialog::showEvent(event);
 | |
| }
 | |
| 
 | |
| FacebookConnectWidget::FacebookConnectWidget(QWidget *parent) : QDialog(parent), ui(new Ui::FacebookConnectWidget) {
 | |
| 	ui->setupUi(this);
 | |
| 	FacebookManager *fb = FacebookManager::instance();
 | |
| #ifdef USE_WEBENGINE
 | |
| 	facebookWebView = new QWebEngineView(this);
 | |
| #else
 | |
| 	facebookWebView = new QWebView(this);
 | |
| #endif
 | |
| 	ui->fbWebviewContainer->layout()->addWidget(facebookWebView);
 | |
| #ifdef USE_WEBENGINE
 | |
| 	connect(facebookWebView, &QWebEngineView::urlChanged, fb, &FacebookManager::tryLogin);
 | |
| #else
 | |
| 	connect(facebookWebView, &QWebView::urlChanged, fb, &FacebookManager::tryLogin);
 | |
| #endif
 | |
| 	connect(fb, &FacebookManager::justLoggedIn, this, &FacebookConnectWidget::facebookLoggedIn);
 | |
| 	connect(fb, &FacebookManager::justLoggedOut, this, &FacebookConnectWidget::facebookDisconnect);
 | |
| }
 | |
| 
 | |
| void FacebookConnectWidget::facebookLoggedIn()
 | |
| {
 | |
| 	ui->fbWebviewContainer->hide();
 | |
| 	ui->fbWebviewContainer->setEnabled(false);
 | |
| 	ui->FBLabel->setText(tr("To disconnect Subsurface from your Facebook account, use the 'Share on' menu entry."));
 | |
| 	close();
 | |
| }
 | |
| 
 | |
| void FacebookConnectWidget::facebookDisconnect()
 | |
| {
 | |
| 	qCDebug(lcFacebook) << "Disconnecting from facebook";
 | |
| 	// remove the connect/disconnect button
 | |
| 	// and instead add the login view
 | |
| 	ui->fbWebviewContainer->show();
 | |
| 	ui->fbWebviewContainer->setEnabled(true);
 | |
| 	ui->FBLabel->setText(tr("To connect to Facebook, please log in. This enables Subsurface to publish dives to your timeline"));
 | |
| 	if (facebookWebView) {
 | |
| #ifdef USE_WEBENGINE
 | |
| 	//FIX ME
 | |
| #else
 | |
| 		facebookWebView->page()->networkAccessManager()->setCookieJar(new QNetworkCookieJar());
 | |
| #endif
 | |
| 		facebookWebView->setUrl(FacebookManager::instance()->connectUrl());
 | |
| 	}
 | |
| }
 | |
| 
 | |
| SocialNetworkDialog::SocialNetworkDialog(QWidget *parent) :
 | |
| 	QDialog(parent),
 | |
| 	ui( new Ui::SocialnetworksDialog())
 | |
| {
 | |
| 	ui->setupUi(this);
 | |
| 	ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
 | |
| 	connect(ui->date, &QCheckBox::clicked, this, &SocialNetworkDialog::selectionChanged);
 | |
| 	connect(ui->duration, &QCheckBox::clicked, this, &SocialNetworkDialog::selectionChanged);
 | |
| 	connect(ui->Buddy, &QCheckBox::clicked, this, &SocialNetworkDialog::selectionChanged);
 | |
| 	connect(ui->Divemaster, &QCheckBox::clicked, this, &SocialNetworkDialog::selectionChanged);
 | |
| 	connect(ui->Location, &QCheckBox::clicked, this, &SocialNetworkDialog::selectionChanged);
 | |
| 	connect(ui->Notes, &QCheckBox::clicked, this, &SocialNetworkDialog::selectionChanged);
 | |
| 	connect(ui->album, &QLineEdit::textChanged, this, &SocialNetworkDialog::albumChanged);
 | |
| }
 | |
| 
 | |
| FacebookInfo::Size SocialNetworkDialog::profileSize() const
 | |
| {
 | |
| 	QString currText = ui->profileSize->currentText();
 | |
| 	return currText.startsWith(tr("Small")) ? FacebookInfo::SMALL :
 | |
| 	       currText.startsWith(tr("Medium")) ?  FacebookInfo::MEDIUM :
 | |
| 	       /* currText.startsWith(tr("Big")) ? */ FacebookInfo::BIG;
 | |
| }
 | |
| 
 | |
| 
 | |
| void SocialNetworkDialog::albumChanged()
 | |
| {
 | |
| 	QAbstractButton *button = ui->buttonBox->button(QDialogButtonBox::Ok);
 | |
| 	button->setEnabled(!ui->album->text().isEmpty());
 | |
| }
 | |
| 
 | |
| void SocialNetworkDialog::selectionChanged()
 | |
| {
 | |
| 	struct dive *d = current_dive;
 | |
| 	QString fullText;
 | |
| 
 | |
| 	if (!d)
 | |
| 		return;
 | |
| 
 | |
| 	if (ui->date->isChecked()) {
 | |
| 		fullText += tr("Dive date: %1 \n").arg(get_short_dive_date_string(d->when));
 | |
| 	}
 | |
| 	if (ui->duration->isChecked()) {
 | |
| 		fullText += tr("Duration: %1 \n").arg(get_dive_duration_string(d->duration.seconds,
 | |
| 									       tr("h", "abbreviation for hours"),
 | |
| 									       tr("min", "abbreviation for minutes")));
 | |
| 	}
 | |
| 	if (ui->Location->isChecked()) {
 | |
| 		fullText += tr("Dive location: %1 \n").arg(get_dive_location(d));
 | |
| 	}
 | |
| 	if (ui->Buddy->isChecked()) {
 | |
| 		fullText += tr("Buddy: %1 \n").arg(d->buddy);
 | |
| 	}
 | |
| 	if (ui->Divemaster->isChecked()) {
 | |
| 		fullText += tr("Divemaster: %1 \n").arg(d->divemaster);
 | |
| 	}
 | |
| 	if (ui->Notes->isChecked()) {
 | |
| 		fullText += tr("\n%1").arg(d->notes);
 | |
| 	}
 | |
| 	ui->text->setPlainText(fullText);
 | |
| }
 | |
| 
 | |
| QString SocialNetworkDialog::text() const {
 | |
| 	return ui->text->toPlainText().toHtmlEscaped();
 | |
| }
 | |
| 
 | |
| QString SocialNetworkDialog::album() const {
 | |
| 	return ui->album->text().toHtmlEscaped();
 | |
| }
 | |
| 
 | |
| 
 |