diff --git a/pri/vars.pri b/pri/vars.pri index 2855f7c46..309d4f3ba 100644 --- a/pri/vars.pri +++ b/pri/vars.pri @@ -34,3 +34,17 @@ isEmpty(USE_WEBENGINE) { ##message($$MSG_PREFIX: WebEngine component is probably NOT installed, disabling it.) } } + +isEmpty(FEEDLY_CLIENT_ID)|isEmpty(FEEDLY_CLIENT_SECRET) { + FEEDLY_OFFICIAL_SUPPORT = false + + message($$MSG_PREFIX: Feedly client ID/secret variables are not set. Disabling official support.) +} +else { + FEEDLY_OFFICIAL_SUPPORT = true + DEFINES *= FEEDLY_OFFICIAL_SUPPORT + DEFINES *= FEEDLY_CLIENT_ID='"\\\"$$FEEDLY_CLIENT_ID\\\""' + DEFINES *= FEEDLY_CLIENT_SECRET='"\\\"$$FEEDLY_CLIENT_SECRET\\\""' + + message($$MSG_PREFIX: Enabling official Feedly support.) +} diff --git a/resources/desktop/com.github.rssguard.appdata.xml b/resources/desktop/com.github.rssguard.appdata.xml index 937f5e74d..5b0599196 100644 --- a/resources/desktop/com.github.rssguard.appdata.xml +++ b/resources/desktop/com.github.rssguard.appdata.xml @@ -30,7 +30,7 @@ https://martinrotter.github.io/donate/ - + none diff --git a/resources/graphics/misc/feedly.png b/resources/graphics/misc/feedly.png new file mode 100755 index 000000000..4c40aca34 Binary files /dev/null and b/resources/graphics/misc/feedly.png differ diff --git a/resources/rssguard.qrc b/resources/rssguard.qrc index d2fe9c3a0..22722de10 100755 --- a/resources/rssguard.qrc +++ b/resources/rssguard.qrc @@ -17,6 +17,7 @@ graphics/misc/adblock.png graphics/misc/adblock-disabled.png graphics/misc/bazqux.png + graphics/misc/feedly.png graphics/misc/freshrss.png graphics/misc/gmail.png graphics/misc/google.png diff --git a/resources/scripts/7za b/resources/scripts/7za index 9c10723bf..47f412575 160000 --- a/resources/scripts/7za +++ b/resources/scripts/7za @@ -1 +1 @@ -Subproject commit 9c10723bfbaf6cb85107d6ee16e0324e9e487749 +Subproject commit 47f4125753452eff8800dbd6600c5a05540b15d9 diff --git a/src/librssguard/definitions/definitions.h b/src/librssguard/definitions/definitions.h index 6cb52d1d1..a03f9b229 100755 --- a/src/librssguard/definitions/definitions.h +++ b/src/librssguard/definitions/definitions.h @@ -13,6 +13,7 @@ #define SERVICE_CODE_TT_RSS "tt-rss" #define SERVICE_CODE_OWNCLOUD "owncloud" #define SERVICE_CODE_GREADER "greader" +#define SERVICE_CODE_FEEDLY "feedly" #define SERVICE_CODE_INOREADER "inoreader" #define SERVICE_CODE_GMAIL "gmail" diff --git a/src/librssguard/librssguard.pro b/src/librssguard/librssguard.pro index 5c6359180..e69b18a50 100644 --- a/src/librssguard/librssguard.pro +++ b/src/librssguard/librssguard.pro @@ -143,6 +143,13 @@ HEADERS += core/feeddownloader.h \ services/abstract/rootitem.h \ services/abstract/serviceentrypoint.h \ services/abstract/serviceroot.h \ + services/feedly/definitions.h \ + services/feedly/feedlyentrypoint.h \ + services/feedly/feedlyfeed.h \ + services/feedly/feedlynetwork.h \ + services/feedly/feedlyserviceroot.h \ + services/feedly/gui/feedlyaccountdetails.h \ + services/feedly/gui/formeditfeedlyaccount.h \ services/gmail/definitions.h \ services/gmail/gmailentrypoint.h \ services/gmail/gmailfeed.h \ @@ -312,6 +319,12 @@ SOURCES += core/feeddownloader.cpp \ services/abstract/recyclebin.cpp \ services/abstract/rootitem.cpp \ services/abstract/serviceroot.cpp \ + services/feedly/feedlyentrypoint.cpp \ + services/feedly/feedlyfeed.cpp \ + services/feedly/feedlynetwork.cpp \ + services/feedly/feedlyserviceroot.cpp \ + services/feedly/gui/feedlyaccountdetails.cpp \ + services/feedly/gui/formeditfeedlyaccount.cpp \ services/gmail/gmailentrypoint.cpp \ services/gmail/gmailfeed.cpp \ services/gmail/gmailserviceroot.cpp \ @@ -398,6 +411,7 @@ FORMS += gui/dialogs/formabout.ui \ services/abstract/gui/formaccountdetails.ui \ services/abstract/gui/formfeeddetails.ui \ services/abstract/gui/authenticationdetails.ui \ + services/feedly/gui/feedlyaccountdetails.ui \ services/gmail/gui/gmailaccountdetails.ui \ services/greader/gui/greaderaccountdetails.ui \ services/inoreader/gui/inoreaderaccountdetails.ui \ diff --git a/src/librssguard/miscellaneous/feedreader.cpp b/src/librssguard/miscellaneous/feedreader.cpp index 4e184eb58..6e969b6d5 100644 --- a/src/librssguard/miscellaneous/feedreader.cpp +++ b/src/librssguard/miscellaneous/feedreader.cpp @@ -14,6 +14,7 @@ #include "miscellaneous/mutex.h" #include "services/abstract/cacheforserviceroot.h" #include "services/abstract/serviceroot.h" +#include "services/feedly/feedlyentrypoint.h" #include "services/gmail/gmailentrypoint.h" #include "services/greader/greaderentrypoint.h" #include "services/inoreader/inoreaderentrypoint.h" @@ -55,6 +56,7 @@ FeedReader::~FeedReader() { QList FeedReader::feedServices() { if (m_feedServices.isEmpty()) { // NOTE: All installed services create their entry points here. + m_feedServices.append(new FeedlyEntryPoint()); m_feedServices.append(new GmailEntryPoint()); m_feedServices.append(new GreaderEntryPoint()); m_feedServices.append(new InoreaderEntryPoint()); diff --git a/src/librssguard/network-web/oauthhttphandler.cpp b/src/librssguard/network-web/oauthhttphandler.cpp index 686777147..1810ae131 100644 --- a/src/librssguard/network-web/oauthhttphandler.cpp +++ b/src/librssguard/network-web/oauthhttphandler.cpp @@ -10,7 +10,8 @@ #include #include -OAuthHttpHandler::OAuthHttpHandler(const QString& success_text, QObject* parent) : QObject(parent), m_successText(success_text) { +OAuthHttpHandler::OAuthHttpHandler(const QString& success_text, QObject* parent) + : QObject(parent), m_successText(success_text) { connect(&m_httpServer, &QTcpServer::newConnection, this, &OAuthHttpHandler::clientConnected); setListenAddressPort(QString(OAUTH_REDIRECT_URI) + QL1C(':') + QString::number(OAUTH_REDIRECT_URI_PORT)); } diff --git a/src/librssguard/services/feedly/definitions.h b/src/librssguard/services/feedly/definitions.h new file mode 100755 index 000000000..5d9cca062 --- /dev/null +++ b/src/librssguard/services/feedly/definitions.h @@ -0,0 +1,14 @@ +#ifndef FEEDLY_DEFINITIONS_H +#define FEEDLY_DEFINITIONS_H + +#define FEEDLY_UNLIMITED_BATCH_SIZE -1 +#define FEEDLY_GENERATE_DAT "https://feedly.com/v3/auth/dev" + +#define FEEDLY_API_REDIRECT_URI_PORT 8080 +#define FEEDLY_API_SCOPE "https://cloud.feedly.com/subscriptions" + +#define FEEDLY_API_URL_BASE "https://cloud.feedly.com/v3/" +#define FEEDLY_API_URL_AUTH "auth/auth" +#define FEEDLY_API_URL_TOKEN "auth/token" + +#endif // FEEDLY_DEFINITIONS_H diff --git a/src/librssguard/services/feedly/feedlyentrypoint.cpp b/src/librssguard/services/feedly/feedlyentrypoint.cpp new file mode 100755 index 000000000..95ff211eb --- /dev/null +++ b/src/librssguard/services/feedly/feedlyentrypoint.cpp @@ -0,0 +1,47 @@ +// For license of this file, see /LICENSE.md. + +#include "services/feedly/feedlyentrypoint.h" + +#include "definitions/definitions.h" +#include "miscellaneous/application.h" +#include "miscellaneous/databasequeries.h" +#include "miscellaneous/iconfactory.h" +#include "services/feedly/definitions.h" +#include "services/feedly/feedlyserviceroot.h" +#include "services/feedly/gui/formeditfeedlyaccount.h" + +ServiceRoot* FeedlyEntryPoint::createNewRoot() const { + FormEditFeedlyAccount form_acc(qApp->mainFormWidget()); + + return form_acc.addEditAccount(); +} + +QList FeedlyEntryPoint::initializeSubtree() const { + QSqlDatabase database = qApp->database()->connection(QSL("FeedlyEntryPoint")); + + return {}; + + //return DatabaseQueries::getGreaderAccounts(database); +} + +QString FeedlyEntryPoint::name() const { + return QSL("Feedly (WIP)"); +} + +QString FeedlyEntryPoint::code() const { + return SERVICE_CODE_FEEDLY; +} + +QString FeedlyEntryPoint::description() const { + return QObject::tr("Keep up with the topics and trends you care about, without the overwhelm. " + "Feedly is a secure space where you can privately organize and research the " + "topics and trends that matter to you."); +} + +QString FeedlyEntryPoint::author() const { + return APP_AUTHOR; +} + +QIcon FeedlyEntryPoint::icon() const { + return qApp->icons()->miscIcon(QSL("feedly")); +} diff --git a/src/librssguard/services/feedly/feedlyentrypoint.h b/src/librssguard/services/feedly/feedlyentrypoint.h new file mode 100755 index 000000000..392b43774 --- /dev/null +++ b/src/librssguard/services/feedly/feedlyentrypoint.h @@ -0,0 +1,19 @@ +// For license of this file, see /LICENSE.md. + +#ifndef FEEDLYENTRYPOINT_H +#define FEEDLYENTRYPOINT_H + +#include "services/abstract/serviceentrypoint.h" + +class FeedlyEntryPoint : public ServiceEntryPoint { + public: + virtual ServiceRoot* createNewRoot() const; + virtual QList initializeSubtree() const; + virtual QString name() const; + virtual QString code() const; + virtual QString description() const; + virtual QString author() const; + virtual QIcon icon() const; +}; + +#endif // FEEDLYENTRYPOINT_H diff --git a/src/librssguard/services/feedly/feedlyfeed.cpp b/src/librssguard/services/feedly/feedlyfeed.cpp new file mode 100755 index 000000000..1e2fd3d4e --- /dev/null +++ b/src/librssguard/services/feedly/feedlyfeed.cpp @@ -0,0 +1,35 @@ +// For license of this file, see /LICENSE.md. + +#include "services/feedly/feedlyfeed.h" + +#include "miscellaneous/application.h" +#include "miscellaneous/iconfactory.h" +#include "services/feedly/feedlynetwork.h" +#include "services/feedly/feedlyserviceroot.h" + +FeedlyFeed::FeedlyFeed(RootItem* parent) : Feed(parent) {} + +FeedlyFeed::FeedlyFeed(const QSqlRecord& record) : Feed(record) {} + +FeedlyServiceRoot* FeedlyFeed::serviceRoot() const { + return qobject_cast(getParentServiceRoot()); +} + +QList FeedlyFeed::obtainNewMessages(bool* error_during_obtaining) { + return {}; + + /* + Feed::Status error = Feed::Status::Normal; + QList messages = serviceRoot()->network()->streamContents(getParentServiceRoot(), + customId(), + error, + getParentServiceRoot()->networkProxy()); + + setStatus(error); + + if (error == Feed::Status::NetworkError || error == Feed::Status::AuthError) { + * error_during_obtaining = true; + } + + return messages;*/ +} diff --git a/src/librssguard/services/feedly/feedlyfeed.h b/src/librssguard/services/feedly/feedlyfeed.h new file mode 100755 index 000000000..aaf756295 --- /dev/null +++ b/src/librssguard/services/feedly/feedlyfeed.h @@ -0,0 +1,19 @@ +// For license of this file, see /LICENSE.md. + +#ifndef FEEDLYFEED_H +#define FEEDLYFEED_H + +#include "services/abstract/feed.h" + +class FeedlyServiceRoot; + +class FeedlyFeed : public Feed { + public: + explicit FeedlyFeed(RootItem* parent = nullptr); + explicit FeedlyFeed(const QSqlRecord& record); + + FeedlyServiceRoot* serviceRoot() const; + QList obtainNewMessages(bool* error_during_obtaining); +}; + +#endif // FEEDLYFEED_H diff --git a/src/librssguard/services/feedly/feedlynetwork.cpp b/src/librssguard/services/feedly/feedlynetwork.cpp new file mode 100755 index 000000000..d6b587228 --- /dev/null +++ b/src/librssguard/services/feedly/feedlynetwork.cpp @@ -0,0 +1,123 @@ +// For license of this file, see /LICENSE.md. + +#include "services/feedly/feedlynetwork.h" + +#include "3rd-party/boolinq/boolinq.h" +#include "miscellaneous/application.h" +#include "network-web/networkfactory.h" + +#include "network-web/webfactory.h" +#include "services/abstract/category.h" +#include "services/abstract/label.h" +#include "services/abstract/labelsnode.h" +#include "services/feedly/definitions.h" +#include "services/feedly/feedlyfeed.h" +#include "services/feedly/feedlyserviceroot.h" + +#if defined (FEEDLY_OFFICIAL_SUPPORT) +#include "network-web/oauth2service.h" +#endif + +#include +#include +#include + +FeedlyNetwork::FeedlyNetwork(QObject* parent) + : QObject(parent), m_service(nullptr), +#if defined (FEEDLY_OFFICIAL_SUPPORT) + m_oauth(new OAuth2Service(QSL(FEEDLY_API_URL_BASE) + FEEDLY_API_URL_AUTH, + QSL(FEEDLY_API_URL_BASE) + FEEDLY_API_URL_TOKEN, + "dontknow", + "dontknow", + FEEDLY_API_SCOPE, this)), +#endif + m_username(QString()), + m_developerAccessToken(QString()), m_batchSize(FEEDLY_UNLIMITED_BATCH_SIZE) { + +#if defined (FEEDLY_OFFICIAL_SUPPORT) + m_oauth->setRedirectUrl(QString(OAUTH_REDIRECT_URI) + QL1C(':') + QString::number(FEEDLY_API_REDIRECT_URI_PORT)); + + connect(m_oauth, &OAuth2Service::tokensRetrieveError, this, &FeedlyNetwork::onTokensError); + connect(m_oauth, &OAuth2Service::authFailed, this, &FeedlyNetwork::onAuthFailed); + connect(m_oauth, &OAuth2Service::tokensReceived, this, &FeedlyNetwork::onTokensReceived); +#endif +} + +QString FeedlyNetwork::username() const { + return m_username; +} + +void FeedlyNetwork::setUsername(const QString& username) { + m_username = username; +} + +QString FeedlyNetwork::developerAccessToken() const { + return m_developerAccessToken; +} + +void FeedlyNetwork::setDeveloperAccessToken(const QString& dev_acc_token) { + m_developerAccessToken = dev_acc_token; +} + +int FeedlyNetwork::batchSize() const { + return m_batchSize; +} + +void FeedlyNetwork::setBatchSize(int batch_size) { + m_batchSize = batch_size; +} + +#if defined (FEEDLY_OFFICIAL_SUPPORT) + +void FeedlyNetwork::onTokensError(const QString& error, const QString& error_description) { + Q_UNUSED(error) + + qApp->showGuiMessage(tr("Feedly: authentication error"), + tr("Click this to login again. Error is: '%1'").arg(error_description), + QSystemTrayIcon::MessageIcon::Critical, + nullptr, false, + [this]() { + m_oauth->setAccessToken({}); + m_oauth->setRefreshToken({}); + m_oauth->login(); + }); +} + +void FeedlyNetwork::onAuthFailed() { + qApp->showGuiMessage(tr("Feedly: authorization denied"), + tr("Click this to login again."), + QSystemTrayIcon::MessageIcon::Critical, + nullptr, false, + [this]() { + m_oauth->login(); + }); +} + +void FeedlyNetwork::onTokensReceived(const QString& access_token, const QString& refresh_token, int expires_in) { + Q_UNUSED(expires_in) + Q_UNUSED(access_token) + + if (m_service != nullptr && !refresh_token.isEmpty()) { + QSqlDatabase database = qApp->database()->connection(metaObject()->className()); + + //DatabaseQueries::storeNewInoreaderTokens(database, refresh_token, m_service->accountId()); + + qApp->showGuiMessage(tr("Logged in successfully"), + tr("Your login to Feedly was authorized."), + QSystemTrayIcon::MessageIcon::Information); + } +} + +OAuth2Service* FeedlyNetwork::oauth() const { + return m_oauth; +} + +void FeedlyNetwork::setOauth(OAuth2Service* oauth) { + m_oauth = oauth; +} + +#endif + +void FeedlyNetwork::setService(FeedlyServiceRoot* service) { + m_service = service; +} diff --git a/src/librssguard/services/feedly/feedlynetwork.h b/src/librssguard/services/feedly/feedlynetwork.h new file mode 100755 index 000000000..635fd24a6 --- /dev/null +++ b/src/librssguard/services/feedly/feedlynetwork.h @@ -0,0 +1,56 @@ +// For license of this file, see /LICENSE.md. + +#ifndef FEEDLYNETWORK_H +#define FEEDLYNETWORK_H + +#include + +#include "network-web/networkfactory.h" +#include "services/abstract/feed.h" + +#if defined (FEEDLY_OFFICIAL_SUPPORT) +class OAuth2Service; +#endif + +class FeedlyServiceRoot; + +class FeedlyNetwork : public QObject { + Q_OBJECT + + public: + explicit FeedlyNetwork(QObject* parent = nullptr); + + QString username() const; + void setUsername(const QString& username); + + QString developerAccessToken() const; + void setDeveloperAccessToken(const QString& dev_acc_token); + + int batchSize() const; + void setBatchSize(int batch_size); + + void setService(FeedlyServiceRoot* service); + +#if defined (FEEDLY_OFFICIAL_SUPPORT) + OAuth2Service* oauth() const; + void setOauth(OAuth2Service* oauth); + + private slots: + void onTokensError(const QString& error, const QString& error_description); + void onAuthFailed(); + void onTokensReceived(const QString& access_token, const QString& refresh_token, int expires_in); +#endif + + private: + FeedlyServiceRoot* m_service; + +#if defined (FEEDLY_OFFICIAL_SUPPORT) + OAuth2Service* m_oauth; +#endif + + QString m_username; + QString m_developerAccessToken; + int m_batchSize; +}; + +#endif // FEEDLYNETWORK_H diff --git a/src/librssguard/services/feedly/feedlyserviceroot.cpp b/src/librssguard/services/feedly/feedlyserviceroot.cpp new file mode 100755 index 000000000..d72b8795e --- /dev/null +++ b/src/librssguard/services/feedly/feedlyserviceroot.cpp @@ -0,0 +1,111 @@ +// For license of this file, see /LICENSE.md. + +#include "services/feedly/feedlyserviceroot.h" + +#include "definitions/definitions.h" +#include "miscellaneous/application.h" +#include "miscellaneous/databasequeries.h" +#include "miscellaneous/iconfactory.h" +#include "miscellaneous/mutex.h" +#include "miscellaneous/textfactory.h" +#include "services/abstract/importantnode.h" +#include "services/abstract/recyclebin.h" +#include "services/feedly/feedlyentrypoint.h" +#include "services/feedly/feedlyfeed.h" +#include "services/feedly/feedlynetwork.h" +#include "services/feedly/gui/formeditfeedlyaccount.h" + +FeedlyServiceRoot::FeedlyServiceRoot(RootItem* parent) + : ServiceRoot(parent), m_network(new FeedlyNetwork(this)) { + setIcon(FeedlyEntryPoint().icon()); + m_network->setService(this); +} + +bool FeedlyServiceRoot::isSyncable() const { + return true; +} + +bool FeedlyServiceRoot::canBeEdited() const { + return true; +} + +bool FeedlyServiceRoot::canBeDeleted() const { + return true; +} + +bool FeedlyServiceRoot::editViaGui() { + FormEditFeedlyAccount form_pointer(qApp->mainFormWidget()); + + form_pointer.addEditAccount(this); + return true; +} + +bool FeedlyServiceRoot::deleteViaGui() { + QSqlDatabase database = qApp->database()->connection(metaObject()->className()); + + if (DatabaseQueries::deleteGreaderAccount(database, accountId())) { + return ServiceRoot::deleteViaGui(); + } + else { + return false; + } +} + +void FeedlyServiceRoot::start(bool freshly_activated) { + Q_UNUSED(freshly_activated) + loadFromDatabase(); + loadCacheFromFile(); + + if (childCount() <= 3) { + syncIn(); + } +} + +QString FeedlyServiceRoot::code() const { + return FeedlyServiceRoot().code(); +} + +void FeedlyServiceRoot::saveAllCachedData(bool ignore_errors) { + auto msg_cache = takeMessageCache(); +} + +void FeedlyServiceRoot::updateTitle() { + setTitle(QString("%1 (Feedly)").arg(TextFactory::extractUsernameFromEmail(m_network->username()))); +} + +void FeedlyServiceRoot::saveAccountDataToDatabase(bool creating_new) { + QSqlDatabase database = qApp->database()->connection(metaObject()->className()); + + /* + if (!creating_new) { + if (DatabaseQueries::overwriteGreaderAccount(database, m_network->username(), + m_network->password(), m_network->service(), + m_network->baseUrl(), m_network->batchSize(), + accountId())) { + updateTitle(); + itemChanged(QList() << this); + } + } + else { + if (DatabaseQueries::createGreaderAccount(database, accountId(), m_network->username(), + m_network->password(), m_network->service(), + m_network->baseUrl(), m_network->batchSize())) { + updateTitle(); + } + }*/ +} + +RootItem* FeedlyServiceRoot::obtainNewTreeForSyncIn() const { + return nullptr; + + //return m_network->categoriesFeedsLabelsTree(true, networkProxy()); +} + +void FeedlyServiceRoot::loadFromDatabase() { + QSqlDatabase database = qApp->database()->connection(metaObject()->className()); + Assignment categories = DatabaseQueries::getCategories(database, accountId()); + Assignment feeds = DatabaseQueries::getFeeds(database, qApp->feedReader()->messageFilters(), accountId()); + auto labels = DatabaseQueries::getLabels(database, accountId()); + + performInitialAssembly(categories, feeds, labels); +} diff --git a/src/librssguard/services/feedly/feedlyserviceroot.h b/src/librssguard/services/feedly/feedlyserviceroot.h new file mode 100755 index 000000000..789628799 --- /dev/null +++ b/src/librssguard/services/feedly/feedlyserviceroot.h @@ -0,0 +1,45 @@ +// For license of this file, see /LICENSE.md. + +#ifndef FEEDLYSERVICEROOT_H +#define FEEDLYSERVICEROOT_H + +#include "services/abstract/cacheforserviceroot.h" +#include "services/abstract/serviceroot.h" + +class FeedlyNetwork; + +class FeedlyServiceRoot : public ServiceRoot, public CacheForServiceRoot { + Q_OBJECT + + public: + explicit FeedlyServiceRoot(RootItem* parent = nullptr); + + virtual bool isSyncable() const; + virtual bool canBeEdited() const; + virtual bool canBeDeleted() const; + virtual bool editViaGui(); + virtual bool deleteViaGui(); + virtual void start(bool freshly_activated); + virtual QString code() const; + virtual void saveAllCachedData(bool ignore_errors); + + FeedlyNetwork* network() const; + + void updateTitle(); + void saveAccountDataToDatabase(bool creating_new); + + protected: + virtual RootItem* obtainNewTreeForSyncIn() const; + + private: + void loadFromDatabase(); + + private: + FeedlyNetwork* m_network; +}; + +inline FeedlyNetwork* FeedlyServiceRoot::network() const { + return m_network; +} + +#endif // FEEDLYSERVICEROOT_H diff --git a/src/librssguard/services/feedly/gui/feedlyaccountdetails.cpp b/src/librssguard/services/feedly/gui/feedlyaccountdetails.cpp new file mode 100755 index 000000000..99bc426a1 --- /dev/null +++ b/src/librssguard/services/feedly/gui/feedlyaccountdetails.cpp @@ -0,0 +1,153 @@ +// For license of this file, see /LICENSE.md. + +#include "services/feedly/gui/feedlyaccountdetails.h" + +#include "definitions/definitions.h" +#include "gui/guiutilities.h" +#include "miscellaneous/application.h" +#include "miscellaneous/systemfactory.h" +#include "network-web/webfactory.h" +#include "services/feedly/definitions.h" +#include "services/feedly/feedlynetwork.h" + +#if defined (FEEDLY_OFFICIAL_SUPPORT) +#include "network-web/oauth2service.h" +#endif + +FeedlyAccountDetails::FeedlyAccountDetails(QWidget* parent) : QWidget(parent) { +#if defined (FEEDLY_OFFICIAL_SUPPORT) + m_oauth = new OAuth2Service(QSL(FEEDLY_API_URL_BASE) + FEEDLY_API_URL_AUTH, + QSL(FEEDLY_API_URL_BASE) + FEEDLY_API_URL_TOKEN, + "dontknow", + "dontknow", + FEEDLY_API_SCOPE, this), +#endif + + m_ui.setupUi(this); + + m_ui.m_lblTestResult->label()->setWordWrap(true); + m_ui.m_txtUsername->lineEdit()->setPlaceholderText(tr("Username for your account")); + m_ui.m_txtDeveloperAccessToken->lineEdit()->setPlaceholderText(tr("Developer access token")); + m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Information, + tr("No test done yet."), + tr("Here, results of connection test are shown.")); + +#if defined (FEEDLY_OFFICIAL_SUPPORT) + m_ui.m_lblInfo->setText(tr("Your %1 build has official Feedly support. You do not have to use \"developer acess " + "token\". You can therefore leave corresponding field empty.").arg(APP_NAME)); +#else + m_ui.m_lblInfo->setText(tr("Your %1 does not offer official Feedly support, thus you must " + "authorize via special authorization code called \"developer access token\". " + "These tokens are usually valid only for 1 month and allow only 250 API calls " + "each day.").arg(APP_NAME)); +#endif + + connect(m_ui.m_spinLimitMessages, static_cast(&QSpinBox::valueChanged), this, [=](int value) { + if (value <= 0) { + m_ui.m_spinLimitMessages->setSuffix(QSL(" ") + tr("= unlimited")); + } + else { + m_ui.m_spinLimitMessages->setSuffix(QSL(" ") + tr("messages")); + } + }); + + GuiUtilities::setLabelAsNotice(*m_ui.m_lblInfo, true); + + connect(m_ui.m_btnGetToken, &QPushButton::clicked, this, &FeedlyAccountDetails::getDeveloperAccessToken); + connect(m_ui.m_txtUsername->lineEdit(), &BaseLineEdit::textChanged, this, &FeedlyAccountDetails::onUsernameChanged); + connect(m_ui.m_txtDeveloperAccessToken->lineEdit(), &BaseLineEdit::textChanged, + this, &FeedlyAccountDetails::onDeveloperAccessTokenChanged); + + setTabOrder(m_ui.m_txtUsername->lineEdit(), m_ui.m_btnGetToken); + setTabOrder(m_ui.m_btnGetToken, m_ui.m_txtDeveloperAccessToken->lineEdit()); + setTabOrder(m_ui.m_txtDeveloperAccessToken->lineEdit(), m_ui.m_spinLimitMessages); + setTabOrder(m_ui.m_spinLimitMessages, m_ui.m_btnTestSetup); + + onDeveloperAccessTokenChanged(); + onUsernameChanged(); + +#if defined (FEEDLY_OFFICIAL_SUPPORT) + hookNetwork(); +#endif +} + +void FeedlyAccountDetails::getDeveloperAccessToken() { + qApp->web()->openUrlInExternalBrowser(FEEDLY_GENERATE_DAT); +} + +#if defined (FEEDLY_OFFICIAL_SUPPORT) + +void FeedlyAccountDetails::hookNetwork() { + connect(m_oauth, &OAuth2Service::tokensReceived, this, &FeedlyAccountDetails::onAuthGranted); + connect(m_oauth, &OAuth2Service::tokensRetrieveError, this, &FeedlyAccountDetails::onAuthError); + connect(m_oauth, &OAuth2Service::authFailed, this, &FeedlyAccountDetails::onAuthFailed); +} + +void FeedlyAccountDetails::onAuthFailed() { + m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Error, + tr("You did not grant access."), + tr("There was error during testing.")); +} + +void FeedlyAccountDetails::onAuthError(const QString& error, const QString& detailed_description) { + Q_UNUSED(error) + + m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Error, + tr("There is error. %1").arg(detailed_description), + tr("There was error during testing.")); +} + +void FeedlyAccountDetails::onAuthGranted() { + m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Ok, + tr("Tested successfully. You may be prompted to login once more."), + tr("Your access was approved.")); +} + +#endif + +void FeedlyAccountDetails::performTest(const QNetworkProxy& custom_proxy) { +#if defined (FEEDLY_OFFICIAL_SUPPORT) + m_oauth->logout(); + + if (m_oauth->login()) { + m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Ok, + tr("You are already logged in."), + tr("Access granted.")); + } +#else + FeedlyNetwork factory; + + factory.setUsername(m_ui.m_txtUsername->lineEdit()->text()); + factory.setDeveloperAccessToken(m_ui.m_txtDeveloperAccessToken->lineEdit()->text()); + + // TODO: todo +#endif +} + +void FeedlyAccountDetails::onUsernameChanged() { + const QString username = m_ui.m_txtUsername->lineEdit()->text(); + + if (username.isEmpty()) { + m_ui.m_txtUsername->setStatus(WidgetWithStatus::StatusType::Error, tr("Username cannot be empty.")); + } + else { + m_ui.m_txtUsername->setStatus(WidgetWithStatus::StatusType::Ok, tr("Username is okay.")); + } +} + +void FeedlyAccountDetails::onDeveloperAccessTokenChanged() { + const QString token = m_ui.m_txtDeveloperAccessToken->lineEdit()->text(); + + if (token.isEmpty()) { +#if defined (FEEDLY_OFFICIAL_SUPPORT) + WidgetWithStatus::StatusType stat = WidgetWithStatus::StatusType::Ok; +#else + WidgetWithStatus::StatusType stat = WidgetWithStatus::StatusType::Error; +#endif + + m_ui.m_txtDeveloperAccessToken->setStatus(stat, tr("Access token is empty.")); + } + else { + m_ui.m_txtDeveloperAccessToken->setStatus(WidgetWithStatus::StatusType::Ok, tr("Access token is okay.")); + } +} diff --git a/src/librssguard/services/feedly/gui/feedlyaccountdetails.h b/src/librssguard/services/feedly/gui/feedlyaccountdetails.h new file mode 100755 index 000000000..faa6e7c42 --- /dev/null +++ b/src/librssguard/services/feedly/gui/feedlyaccountdetails.h @@ -0,0 +1,50 @@ +// For license of this file, see /LICENSE.md. + +#ifndef FEEDLYACCOUNTDETAILS_H +#define FEEDLYACCOUNTDETAILS_H + +#include + +#include "ui_feedlyaccountdetails.h" + +#include "services/feedly/feedlyserviceroot.h" + +#include + +#if defined (FEEDLY_OFFICIAL_SUPPORT) +class OAuth2Service; +#endif + +class FeedlyAccountDetails : public QWidget { + Q_OBJECT + + friend class FormEditFeedlyAccount; + + public: + explicit FeedlyAccountDetails(QWidget* parent = nullptr); + + private slots: + void getDeveloperAccessToken(); + void performTest(const QNetworkProxy& custom_proxy); + void onUsernameChanged(); + void onDeveloperAccessTokenChanged(); + +#if defined (FEEDLY_OFFICIAL_SUPPORT) + private slots: + void onAuthFailed(); + void onAuthError(const QString& error, const QString& detailed_description); + void onAuthGranted(); + + private: + void hookNetwork(); +#endif + + private: + Ui::FeedlyAccountDetails m_ui; + +#if defined (FEEDLY_OFFICIAL_SUPPORT) + OAuth2Service* m_oauth; +#endif +}; + +#endif // FEEDLYACCOUNTDETAILS_H diff --git a/src/librssguard/services/feedly/gui/feedlyaccountdetails.ui b/src/librssguard/services/feedly/gui/feedlyaccountdetails.ui new file mode 100755 index 000000000..04fb07ef4 --- /dev/null +++ b/src/librssguard/services/feedly/gui/feedlyaccountdetails.ui @@ -0,0 +1,131 @@ + + + FeedlyAccountDetails + + + + 0 + 0 + 421 + 235 + + + + + + + Username + + + + + + + + + + Developer access token + + + + + + + + + Get token + + + + + + + + + + + + Qt::AlignCenter + + + true + + + + + + + + + + 140 + 16777215 + + + + message(s) + + + + + + + Only download newest X messages per feed + + + m_spinLimitMessages + + + + + + + + + Qt::Vertical + + + + 400 + 86 + + + + + + + + + + &Login + + + + + + + Qt::RightToLeft + + + + + + + + + + LineEditWithStatus + QWidget +
lineeditwithstatus.h
+ 1 +
+ + LabelWithStatus + QWidget +
labelwithstatus.h
+ 1 +
+
+ + +
diff --git a/src/librssguard/services/feedly/gui/formeditfeedlyaccount.cpp b/src/librssguard/services/feedly/gui/formeditfeedlyaccount.cpp new file mode 100755 index 000000000..05e17cc6d --- /dev/null +++ b/src/librssguard/services/feedly/gui/formeditfeedlyaccount.cpp @@ -0,0 +1,66 @@ +// For license of this file, see /LICENSE.md. + +#include "services/feedly/gui/formeditfeedlyaccount.h" + +#include "gui/guiutilities.h" +#include "miscellaneous/iconfactory.h" +#include "network-web/networkfactory.h" +#include "services/feedly/definitions.h" +#include "services/feedly/feedlynetwork.h" +#include "services/feedly/feedlyserviceroot.h" +#include "services/feedly/gui/feedlyaccountdetails.h" + +#if defined (FEEDLY_OFFICIAL_SUPPORT) +#include "network-web/oauth2service.h" +#endif + +FormEditFeedlyAccount::FormEditFeedlyAccount(QWidget* parent) + : FormAccountDetails(qApp->icons()->miscIcon(QSL("google")), parent), m_details(new FeedlyAccountDetails(this)) { + insertCustomTab(m_details, tr("Service setup"), 0); + activateTab(0); + + connect(m_details->m_ui.m_btnTestSetup, &QPushButton::clicked, this, &FormEditFeedlyAccount::performTest); + m_details->m_ui.m_txtUsername->setFocus(); +} + +void FormEditFeedlyAccount::apply() { + bool editing_account = !applyInternal(); + +#if defined (FEEDLY_OFFICIAL_SUPPORT) + // We copy credentials from testing OAuth to live OAuth. + account()->network()->oauth()->setAccessToken(m_details->m_oauth->accessToken()); + account()->network()->oauth()->setRefreshToken(m_details->m_oauth->refreshToken()); + account()->network()->oauth()->setTokensExpireIn(m_details->m_oauth->tokensExpireIn()); +#endif + + account()->network()->setUsername(m_details->m_ui.m_txtUsername->lineEdit()->text()); + account()->network()->setBatchSize(m_details->m_ui.m_spinLimitMessages->value()); + account()->network()->setDeveloperAccessToken(m_details->m_ui.m_txtDeveloperAccessToken->lineEdit()->text()); + + account()->saveAccountDataToDatabase(!editing_account); + accept(); + + if (editing_account) { + account()->completelyRemoveAllData(); + account()->syncIn(); + } +} + +void FormEditFeedlyAccount::setEditableAccount(ServiceRoot* editable_account) { + FormAccountDetails::setEditableAccount(editable_account); + + FeedlyServiceRoot* existing_root = account(); + +#if defined (FEEDLY_OFFICIAL_SUPPORT) + m_details->m_oauth = account()->network()->oauth(); + m_details->hookNetwork(); +#endif + + m_details->m_ui.m_txtUsername->lineEdit()->setText(existing_root->network()->username()); + m_details->m_ui.m_txtDeveloperAccessToken->lineEdit()->setText(existing_root->network()->developerAccessToken()); + m_details->m_ui.m_spinLimitMessages->setValue(existing_root->network()->batchSize()); +} + +void FormEditFeedlyAccount::performTest() { + m_details->performTest(m_proxyDetails->proxy()); +} diff --git a/src/librssguard/services/feedly/gui/formeditfeedlyaccount.h b/src/librssguard/services/feedly/gui/formeditfeedlyaccount.h new file mode 100755 index 000000000..4d86a091f --- /dev/null +++ b/src/librssguard/services/feedly/gui/formeditfeedlyaccount.h @@ -0,0 +1,30 @@ +// For license of this file, see /LICENSE.md. + +#ifndef FORMEDITFEEDLYACCOUNT_H +#define FORMEDITFEEDLYACCOUNT_H + +#include "services/abstract/gui/formaccountdetails.h" + +class FeedlyAccountDetails; +class FeedlyServiceRoot; + +class FormEditFeedlyAccount : public FormAccountDetails { + Q_OBJECT + + public: + explicit FormEditFeedlyAccount(QWidget* parent = nullptr); + + protected slots: + virtual void apply(); + + protected: + virtual void setEditableAccount(ServiceRoot* editable_account); + + private slots: + void performTest(); + + private: + FeedlyAccountDetails* m_details; +}; + +#endif // FORMEDITFEEDLYACCOUNT_H diff --git a/src/librssguard/services/greader/greaderentrypoint.cpp b/src/librssguard/services/greader/greaderentrypoint.cpp index a9871ad7a..496313af6 100755 --- a/src/librssguard/services/greader/greaderentrypoint.cpp +++ b/src/librssguard/services/greader/greaderentrypoint.cpp @@ -32,7 +32,7 @@ QString GreaderEntryPoint::code() const { QString GreaderEntryPoint::description() const { return QObject::tr("Google Reader API is used by many online RSS readers. This is here to support") + - QSL(" FreshRSS, Bazqux, TheOldReader."); + QSL(" FreshRSS, Bazqux, TheOldReader, Reedah, ..."); } QString GreaderEntryPoint::author() const {