bare skeleton for reddit plugin

This commit is contained in:
Martin Rotter 2021-11-10 13:00:41 +01:00
parent f8bb0ea6a6
commit 6b1a93b66c
17 changed files with 967 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

@ -34,6 +34,7 @@
<file>graphics/misc/image-placeholder.png</file>
<file>graphics/misc/inoreader.png</file>
<file>graphics/misc/nextcloud.png</file>
<file>graphics/misc/reddit.png</file>
<file>graphics/misc/reedah.png</file>
<file>graphics/misc/theoldreader.png</file>
<file>graphics/misc/tt-rss.png</file>

View File

@ -13,6 +13,7 @@
#define SERVICE_CODE_FEEDLY "feedly"
#define SERVICE_CODE_INOREADER "inoreader"
#define SERVICE_CODE_GMAIL "gmail"
#define SERVICE_CODE_REDDIT "reddit"
#define ADBLOCK_SERVER_PORT 48484
#define ADBLOCK_HOWTO "https://github.com/martinrotter/rssguard/blob/master/resources/docs/Documentation.md#adblock"
@ -132,6 +133,7 @@
#define LOGSEC_TTRSS "tt-rss: "
#define LOGSEC_GMAIL "gmail: "
#define LOGSEC_OAUTH "oauth: "
#define LOGSEC_REDDIT "reddit: "
#define MAX_ZOOM_FACTOR 5.0f
#define MIN_ZOOM_FACTOR 0.25f

View File

@ -192,6 +192,12 @@ HEADERS += core/feeddownloader.h \
services/owncloud/owncloudnetworkfactory.h \
services/owncloud/owncloudserviceentrypoint.h \
services/owncloud/owncloudserviceroot.h \
services/reddit/definitions.h \
services/reddit/gui/formeditredditaccount.h \
services/reddit/gui/redditaccountdetails.h \
services/reddit/redditentrypoint.h \
services/reddit/redditnetworkfactory.h \
services/reddit/redditserviceroot.h \
services/standard/atomparser.h \
services/standard/definitions.h \
services/standard/feedparser.h \
@ -368,6 +374,11 @@ SOURCES += core/feeddownloader.cpp \
services/owncloud/owncloudnetworkfactory.cpp \
services/owncloud/owncloudserviceentrypoint.cpp \
services/owncloud/owncloudserviceroot.cpp \
services/reddit/gui/formeditredditaccount.cpp \
services/reddit/gui/redditaccountdetails.cpp \
services/reddit/redditentrypoint.cpp \
services/reddit/redditnetworkfactory.cpp \
services/reddit/redditserviceroot.cpp \
services/standard/atomparser.cpp \
services/standard/feedparser.cpp \
services/standard/gui/formeditstandardaccount.cpp \
@ -432,6 +443,7 @@ FORMS += gui/dialogs/formabout.ui \
services/gmail/gui/gmailaccountdetails.ui \
services/greader/gui/greaderaccountdetails.ui \
services/owncloud/gui/owncloudaccountdetails.ui \
services/reddit/gui/redditaccountdetails.ui \
services/standard/gui/formstandardimportexport.ui \
services/standard/gui/standardfeeddetails.ui \
services/tt-rss/gui/ttrssaccountdetails.ui \

View File

@ -18,6 +18,7 @@
#include "services/gmail/gmailentrypoint.h"
#include "services/greader/greaderentrypoint.h"
#include "services/owncloud/owncloudserviceentrypoint.h"
#include "services/reddit/redditentrypoint.h"
#include "services/standard/standardserviceentrypoint.h"
#include "services/tt-rss/ttrssserviceentrypoint.h"
@ -60,6 +61,11 @@ QList<ServiceEntryPoint*> FeedReader::feedServices() {
m_feedServices.append(new GmailEntryPoint());
m_feedServices.append(new GreaderEntryPoint());
m_feedServices.append(new OwnCloudServiceEntryPoint());
#if defined(DEBUG)
m_feedServices.append(new RedditEntryPoint());
#endif
m_feedServices.append(new StandardServiceEntryPoint());
m_feedServices.append(new TtRssServiceEntryPoint());
}

View File

@ -0,0 +1,21 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#ifndef REDDIT_DEFINITIONS_H
#define REDDIT_DEFINITIONS_H
#define REDDIT_OAUTH_REDIRECT_URI_PORT 14499
#define REDDIT_OAUTH_AUTH_URL "https://www.reddit.com/api/v1/authorize"
#define REDDIT_OAUTH_TOKEN_URL "https://www.reddit.com/api/v1/access_token"
#define REDDIT_OAUTH_SCOPE "identity"
#define REDDIT_REG_API_URL "https://www.reddit.com/prefs/apps"
#define REDDIT_API_GET_PROFILE "https://oauth.reddit.com/api/v1/me"
#define REDDIT_DEFAULT_BATCH_SIZE 100
#define REDDIT_MAX_BATCH_SIZE 999
#define REDDIT_CONTENT_TYPE_HTTP "application/http"
#define REDDIT_CONTENT_TYPE_JSON "application/json"
#endif // REDDIT_DEFINITIONS_H

View File

@ -0,0 +1,68 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#include "services/reddit/gui/formeditredditaccount.h"
#include "gui/guiutilities.h"
#include "miscellaneous/application.h"
#include "miscellaneous/iconfactory.h"
#include "network-web/oauth2service.h"
#include "network-web/webfactory.h"
#include "services/reddit/definitions.h"
#include "services/reddit/redditserviceroot.h"
#include "services/reddit/gui/redditaccountdetails.h"
FormEditRedditAccount::FormEditRedditAccount(QWidget* parent)
: FormAccountDetails(qApp->icons()->miscIcon(QSL("reddit")), parent), m_details(new RedditAccountDetails(this)) {
insertCustomTab(m_details, tr("Server setup"), 0);
activateTab(0);
m_details->m_ui.m_txtUsername->setFocus();
connect(m_details->m_ui.m_btnTestSetup, &QPushButton::clicked, this, [this]() {
m_details->testSetup(m_proxyDetails->proxy());
});
}
void FormEditRedditAccount::apply() {
FormAccountDetails::apply();
bool using_another_acc =
m_details->m_ui.m_txtUsername->lineEdit()->text() !=account<RedditServiceRoot>()->network()->username();
// Make sure that the data copied from GUI are used for brand new login.
account<RedditServiceRoot>()->network()->oauth()->logout(false);
account<RedditServiceRoot>()->network()->oauth()->setClientId(m_details->m_ui.m_txtAppId->lineEdit()->text());
account<RedditServiceRoot>()->network()->oauth()->setClientSecret(m_details->m_ui.m_txtAppKey->lineEdit()->text());
account<RedditServiceRoot>()->network()->oauth()->setRedirectUrl(m_details->m_ui.m_txtRedirectUrl->lineEdit()->text(),
true);
account<RedditServiceRoot>()->network()->setUsername(m_details->m_ui.m_txtUsername->lineEdit()->text());
account<RedditServiceRoot>()->network()->setBatchSize(m_details->m_ui.m_spinLimitMessages->value());
account<RedditServiceRoot>()->network()->setDownloadOnlyUnreadMessages(m_details->m_ui.m_cbDownloadOnlyUnreadMessages->isChecked());
account<RedditServiceRoot>()->saveAccountDataToDatabase();
accept();
if (!m_creatingNew) {
if (using_another_acc) {
account<RedditServiceRoot>()->completelyRemoveAllData();
}
account<RedditServiceRoot>()->start(true);
}
}
void FormEditRedditAccount::loadAccountData() {
FormAccountDetails::loadAccountData();
m_details->m_oauth = account<RedditServiceRoot>()->network()->oauth();
m_details->hookNetwork();
// Setup the GUI.
m_details->m_ui.m_txtAppId->lineEdit()->setText(m_details->m_oauth->clientId());
m_details->m_ui.m_txtAppKey->lineEdit()->setText(m_details->m_oauth->clientSecret());
m_details->m_ui.m_txtRedirectUrl->lineEdit()->setText(m_details->m_oauth->redirectUrl());
m_details->m_ui.m_txtUsername->lineEdit()->setText(account<RedditServiceRoot>()->network()->username());
m_details->m_ui.m_spinLimitMessages->setValue(account<RedditServiceRoot>()->network()->batchSize());
m_details->m_ui.m_cbDownloadOnlyUnreadMessages->setChecked(account<RedditServiceRoot>()->network()->downloadOnlyUnreadMessages());
}

View File

@ -0,0 +1,29 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#ifndef FORMEDITINOREADERACCOUNT_H
#define FORMEDITINOREADERACCOUNT_H
#include "services/abstract/gui/formaccountdetails.h"
#include "services/reddit/redditnetworkfactory.h"
class RedditServiceRoot;
class RedditAccountDetails;
class FormEditRedditAccount : public FormAccountDetails {
Q_OBJECT
public:
explicit FormEditRedditAccount(QWidget* parent = nullptr);
protected slots:
virtual void apply();
protected:
virtual void loadAccountData();
private:
RedditAccountDetails* m_details;
};
#endif // FORMEDITINOREADERACCOUNT_H

View File

@ -0,0 +1,124 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#include "services/reddit/gui/redditaccountdetails.h"
#include "exceptions/applicationexception.h"
#include "gui/guiutilities.h"
#include "miscellaneous/application.h"
#include "network-web/oauth2service.h"
#include "network-web/webfactory.h"
#include "services/reddit/definitions.h"
#include "services/reddit/redditnetworkfactory.h"
RedditAccountDetails::RedditAccountDetails(QWidget* parent)
: QWidget(parent), m_oauth(nullptr), m_lastProxy({}) {
m_ui.setupUi(this);
m_ui.m_lblInfo->setHelpText(tr("You have to fill in your client ID/secret and also fill in correct redirect URL."),
true);
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Information,
tr("Not tested yet."),
tr("Not tested yet."));
m_ui.m_lblTestResult->label()->setWordWrap(true);
m_ui.m_txtUsername->lineEdit()->setPlaceholderText(tr("User-visible username"));
setTabOrder(m_ui.m_txtUsername->lineEdit(), m_ui.m_txtAppId);
setTabOrder(m_ui.m_txtAppId, m_ui.m_txtAppKey);
setTabOrder(m_ui.m_txtAppKey, m_ui.m_txtRedirectUrl);
setTabOrder(m_ui.m_txtRedirectUrl, m_ui.m_spinLimitMessages);
setTabOrder(m_ui.m_spinLimitMessages, m_ui.m_btnTestSetup);
connect(m_ui.m_txtAppId->lineEdit(), &BaseLineEdit::textChanged, this, &RedditAccountDetails::checkOAuthValue);
connect(m_ui.m_txtAppKey->lineEdit(), &BaseLineEdit::textChanged, this, &RedditAccountDetails::checkOAuthValue);
connect(m_ui.m_txtRedirectUrl->lineEdit(), &BaseLineEdit::textChanged, this, &RedditAccountDetails::checkOAuthValue);
connect(m_ui.m_txtUsername->lineEdit(), &BaseLineEdit::textChanged, this, &RedditAccountDetails::checkUsername);
connect(m_ui.m_btnRegisterApi, &QPushButton::clicked, this, &RedditAccountDetails::registerApi);
emit m_ui.m_txtUsername->lineEdit()->textChanged(m_ui.m_txtUsername->lineEdit()->text());
emit m_ui.m_txtAppId->lineEdit()->textChanged(m_ui.m_txtAppId->lineEdit()->text());
emit m_ui.m_txtAppKey->lineEdit()->textChanged(m_ui.m_txtAppKey->lineEdit()->text());
emit m_ui.m_txtRedirectUrl->lineEdit()->textChanged(m_ui.m_txtAppKey->lineEdit()->text());
hookNetwork();
}
void RedditAccountDetails::testSetup(const QNetworkProxy& custom_proxy) {
m_oauth->logout(true);
m_oauth->setClientId(m_ui.m_txtAppId->lineEdit()->text());
m_oauth->setClientSecret(m_ui.m_txtAppKey->lineEdit()->text());
m_oauth->setRedirectUrl(m_ui.m_txtRedirectUrl->lineEdit()->text(), true);
m_lastProxy = custom_proxy;
m_oauth->login();
}
void RedditAccountDetails::checkUsername(const QString& username) {
if (username.isEmpty()) {
m_ui.m_txtUsername->setStatus(WidgetWithStatus::StatusType::Error, tr("No username entered."));
}
else {
m_ui.m_txtUsername->setStatus(WidgetWithStatus::StatusType::Ok, tr("Some username entered."));
}
}
void RedditAccountDetails::onAuthFailed() {
m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Error,
tr("You did not grant access."),
tr("There was error during testing."));
}
void RedditAccountDetails::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 RedditAccountDetails::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."));
try {
RedditNetworkFactory fac;
fac.setOauth(m_oauth);
auto resp = fac.me(m_lastProxy);
m_ui.m_txtUsername->lineEdit()->setText(resp[QSL("name")].toString());
}
catch (const ApplicationException& ex) {
qCriticalNN << LOGSEC_REDDIT
<< "Failed to obtain profile with error:"
<< QUOTE_W_SPACE_DOT(ex.message());
}
}
void RedditAccountDetails::hookNetwork() {
connect(m_oauth, &OAuth2Service::tokensRetrieved, this, &RedditAccountDetails::onAuthGranted);
connect(m_oauth, &OAuth2Service::tokensRetrieveError, this, &RedditAccountDetails::onAuthError);
connect(m_oauth, &OAuth2Service::authFailed, this, &RedditAccountDetails::onAuthFailed);
}
void RedditAccountDetails::registerApi() {
qApp->web()->openUrlInExternalBrowser(QSL(REDDIT_REG_API_URL));
}
void RedditAccountDetails::checkOAuthValue(const QString& value) {
auto* line_edit = qobject_cast<LineEditWithStatus*>(sender()->parent());
if (line_edit != nullptr) {
if (value.isEmpty()) {
#if defined(REDDIT_OFFICIAL_SUPPORT)
line_edit->setStatus(WidgetWithStatus::StatusType::Ok, tr("Preconfigured client ID/secret will be used."));
#else
line_edit->setStatus(WidgetWithStatus::StatusType::Error, tr("Empty value is entered."));
#endif
}
else {
line_edit->setStatus(WidgetWithStatus::StatusType::Ok, tr("Some value is entered."));
}
}
}

View File

@ -0,0 +1,44 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#ifndef REDDITACCOUNTDETAILS_H
#define REDDITACCOUNTDETAILS_H
#include <QWidget>
#include "ui_redditaccountdetails.h"
#include <QNetworkProxy>
class OAuth2Service;
class RedditAccountDetails : public QWidget {
Q_OBJECT
friend class FormEditRedditAccount;
public:
explicit RedditAccountDetails(QWidget* parent = nullptr);
public slots:
void testSetup(const QNetworkProxy& custom_proxy);
private slots:
void registerApi();
void checkOAuthValue(const QString& value);
void checkUsername(const QString& username);
void onAuthFailed();
void onAuthError(const QString& error, const QString& detailed_description);
void onAuthGranted();
private:
void hookNetwork();
private:
Ui::RedditAccountDetails m_ui;
// Pointer to live OAuth.
OAuth2Service* m_oauth;
QNetworkProxy m_lastProxy;
};
#endif // REDDITACCOUNTDETAILS_H

View File

@ -0,0 +1,202 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RedditAccountDetails</class>
<widget class="QWidget" name="RedditAccountDetails">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>431</width>
<height>259</height>
</rect>
</property>
<layout class="QFormLayout" name="formLayout_4">
<item row="0" column="0">
<widget class="QLabel" name="m_lblUsername">
<property name="text">
<string>Username</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="LineEditWithStatus" name="m_txtUsername" native="true"/>
</item>
<item row="1" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>OAuth 2.0 settings</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="m_lblUsername_2">
<property name="text">
<string>Client ID</string>
</property>
<property name="buddy">
<cstring>m_txtAppId</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="LineEditWithStatus" name="m_txtAppId" native="true"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="m_lblUsername_3">
<property name="text">
<string>Client secret</string>
</property>
<property name="buddy">
<cstring>m_txtAppKey</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="LineEditWithStatus" name="m_txtAppKey" native="true"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="m_lblUsername_4">
<property name="text">
<string>Redirect URL</string>
</property>
<property name="buddy">
<cstring>m_txtRedirectUrl</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="LineEditWithStatus" name="m_txtRedirectUrl" native="true"/>
</item>
<item row="4" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="m_btnRegisterApi">
<property name="text">
<string>Get my credentials</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="3" column="0" colspan="2">
<widget class="HelpSpoiler" name="m_lblInfo" native="true"/>
</item>
</layout>
</widget>
</item>
<item row="3" column="0" colspan="2">
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Only download newest X articles per feed</string>
</property>
<property name="buddy">
<cstring>m_spinLimitMessages</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="MessageCountSpinBox" name="m_spinLimitMessages">
<property name="maximumSize">
<size>
<width>140</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item row="4" column="0" colspan="2">
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QPushButton" name="m_btnTestSetup">
<property name="text">
<string>&amp;Login</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="LabelWithStatus" name="m_lblTestResult" native="true">
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
</widget>
</item>
</layout>
</item>
<item row="5" column="0" colspan="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>410</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="m_cbDownloadOnlyUnreadMessages">
<property name="text">
<string>Download unread articles only</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>LineEditWithStatus</class>
<extends>QWidget</extends>
<header>lineeditwithstatus.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>LabelWithStatus</class>
<extends>QWidget</extends>
<header>labelwithstatus.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>MessageCountSpinBox</class>
<extends>QSpinBox</extends>
<header>messagecountspinbox.h</header>
</customwidget>
<customwidget>
<class>HelpSpoiler</class>
<extends>QWidget</extends>
<header>helpspoiler.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>m_btnRegisterApi</tabstop>
<tabstop>m_cbDownloadOnlyUnreadMessages</tabstop>
<tabstop>m_spinLimitMessages</tabstop>
<tabstop>m_btnTestSetup</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,45 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#include "services/reddit/redditentrypoint.h"
#include "database/databasequeries.h"
#include "definitions/definitions.h"
#include "miscellaneous/application.h"
#include "miscellaneous/iconfactory.h"
#include "services/reddit/definitions.h"
#include "services/reddit/gui/formeditredditaccount.h"
#include "services/reddit/redditserviceroot.h"
#include <QMessageBox>
ServiceRoot* RedditEntryPoint::createNewRoot() const {
FormEditRedditAccount form_acc(qApp->mainFormWidget());
return form_acc.addEditAccount<RedditServiceRoot>();
}
QList<ServiceRoot*> RedditEntryPoint::initializeSubtree() const {
QSqlDatabase database = qApp->database()->driver()->connection(QSL("RedditEntryPoint"));
return DatabaseQueries::getAccounts<RedditServiceRoot>(database, code());
}
QString RedditEntryPoint::name() const {
return QSL("Reddit");
}
QString RedditEntryPoint::code() const {
return QSL(SERVICE_CODE_REDDIT);
}
QString RedditEntryPoint::description() const {
return QObject::tr("Simplistic Reddit client.");
}
QString RedditEntryPoint::author() const {
return QSL(APP_AUTHOR);
}
QIcon RedditEntryPoint::icon() const {
return qApp->icons()->miscIcon(QSL("reddit"));
}

View File

@ -0,0 +1,19 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#ifndef REDDITENTRYPOINT_H
#define REDDITENTRYPOINT_H
#include "services/abstract/serviceentrypoint.h"
class RedditEntryPoint : public ServiceEntryPoint {
public:
virtual ServiceRoot* createNewRoot() const;
virtual QList<ServiceRoot*> initializeSubtree() const;
virtual QString name() const;
virtual QString code() const;
virtual QString description() const;
virtual QString author() const;
virtual QIcon icon() const;
};
#endif // REDDITENTRYPOINT_H

View File

@ -0,0 +1,155 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#include "services/reddit/redditnetworkfactory.h"
#include "database/databasequeries.h"
#include "definitions/definitions.h"
#include "exceptions/applicationexception.h"
#include "exceptions/networkexception.h"
#include "gui/dialogs/formmain.h"
#include "gui/tabwidget.h"
#include "miscellaneous/application.h"
#include "miscellaneous/textfactory.h"
#include "network-web/networkfactory.h"
#include "network-web/oauth2service.h"
#include "network-web/silentnetworkaccessmanager.h"
#include "network-web/webfactory.h"
#include "services/abstract/category.h"
#include "services/reddit/definitions.h"
#include "services/reddit/redditserviceroot.h"
#include <QHttpMultiPart>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QRegularExpression>
#include <QThread>
#include <QUrl>
RedditNetworkFactory::RedditNetworkFactory(QObject* parent) : QObject(parent),
m_service(nullptr), m_username(QString()), m_batchSize(REDDIT_DEFAULT_BATCH_SIZE),
m_downloadOnlyUnreadMessages(false),
m_oauth2(new OAuth2Service(QSL(REDDIT_OAUTH_AUTH_URL), QSL(REDDIT_OAUTH_TOKEN_URL),
{}, {}, QSL(REDDIT_OAUTH_SCOPE), this)) {
initializeOauth();
}
void RedditNetworkFactory::setService(RedditServiceRoot* service) {
m_service = service;
}
OAuth2Service* RedditNetworkFactory::oauth() const {
return m_oauth2;
}
QString RedditNetworkFactory::username() const {
return m_username;
}
int RedditNetworkFactory::batchSize() const {
return m_batchSize;
}
void RedditNetworkFactory::setBatchSize(int batch_size) {
m_batchSize = batch_size;
}
void RedditNetworkFactory::initializeOauth() {
m_oauth2->setUseHttpBasicAuthWithClientData(true);
m_oauth2->setRedirectUrl(QSL(OAUTH_REDIRECT_URI) +
QL1C(':') +
QString::number(REDDIT_OAUTH_REDIRECT_URI_PORT),
true);
connect(m_oauth2, &OAuth2Service::tokensRetrieveError, this, &RedditNetworkFactory::onTokensError);
connect(m_oauth2, &OAuth2Service::authFailed, this, &RedditNetworkFactory::onAuthFailed);
connect(m_oauth2, &OAuth2Service::tokensRetrieved, this, [this](QString access_token, 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()->driver()->connection(metaObject()->className());
DatabaseQueries::storeNewOauthTokens(database, refresh_token, m_service->accountId());
}
});
}
bool RedditNetworkFactory::downloadOnlyUnreadMessages() const {
return m_downloadOnlyUnreadMessages;
}
void RedditNetworkFactory::setDownloadOnlyUnreadMessages(bool download_only_unread_messages) {
m_downloadOnlyUnreadMessages = download_only_unread_messages;
}
void RedditNetworkFactory::setOauth(OAuth2Service* oauth) {
m_oauth2 = oauth;
}
void RedditNetworkFactory::setUsername(const QString& username) {
m_username = username;
}
QVariantHash RedditNetworkFactory::me(const QNetworkProxy& custom_proxy) {
QString bearer = m_oauth2->bearer().toLocal8Bit();
if (bearer.isEmpty()) {
throw ApplicationException(tr("you are not logged in"));
}
QList<QPair<QByteArray, QByteArray>> headers;
headers.append(QPair<QByteArray, QByteArray>(QSL(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(),
m_oauth2->bearer().toLocal8Bit()));
int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
QByteArray output;
auto result = NetworkFactory::performNetworkOperation(QSL(REDDIT_API_GET_PROFILE),
timeout,
{},
output,
QNetworkAccessManager::Operation::GetOperation,
headers,
false,
{},
{},
custom_proxy).first;
if (result != QNetworkReply::NetworkError::NoError) {
throw NetworkException(result, output);
}
else {
QJsonDocument doc = QJsonDocument::fromJson(output);
return doc.object().toVariantHash();
}
}
void RedditNetworkFactory::onTokensError(const QString& error, const QString& error_description) {
Q_UNUSED(error)
qApp->showGuiMessage(Notification::Event::LoginFailure, {
tr("Reddit: authentication error"),
tr("Click this to login again. Error is: '%1'").arg(error_description),
QSystemTrayIcon::MessageIcon::Critical },
{}, {
tr("Login"),
[this]() {
m_oauth2->setAccessToken(QString());
m_oauth2->setRefreshToken(QString());
m_oauth2->login();
} });
}
void RedditNetworkFactory::onAuthFailed() {
qApp->showGuiMessage(Notification::Event::LoginFailure, {
tr("Reddit: authorization denied"),
tr("Click this to login again."),
QSystemTrayIcon::MessageIcon::Critical },
{}, {
tr("Login"),
[this]() {
m_oauth2->login();
} });
}

View File

@ -0,0 +1,59 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#ifndef REDDITNETWORKFACTORY_H
#define REDDITNETWORKFACTORY_H
#include <QObject>
#include "core/message.h"
#include "3rd-party/mimesis/mimesis.hpp"
#include "services/abstract/feed.h"
#include "services/abstract/rootitem.h"
#include <QNetworkReply>
class RootItem;
class RedditServiceRoot;
class OAuth2Service;
class Downloader;
class RedditNetworkFactory : public QObject {
Q_OBJECT
public:
explicit RedditNetworkFactory(QObject* parent = nullptr);
void setService(RedditServiceRoot* service);
OAuth2Service* oauth() const;
void setOauth(OAuth2Service* oauth);
QString username() const;
void setUsername(const QString& username);
int batchSize() const;
void setBatchSize(int batch_size);
bool downloadOnlyUnreadMessages() const;
void setDownloadOnlyUnreadMessages(bool download_only_unread_messages);
// API methods.
QVariantHash me(const QNetworkProxy& custom_proxy);
private slots:
void onTokensError(const QString& error, const QString& error_description);
void onAuthFailed();
private:
void initializeOauth();
private:
RedditServiceRoot* m_service;
QString m_username;
int m_batchSize;
bool m_downloadOnlyUnreadMessages;
OAuth2Service* m_oauth2;
};
#endif // REDDITNETWORKFACTORY_H

View File

@ -0,0 +1,127 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#include "services/reddit/redditserviceroot.h"
#include "database/databasequeries.h"
#include "exceptions/feedfetchexception.h"
#include "miscellaneous/application.h"
#include "miscellaneous/iconfactory.h"
#include "network-web/oauth2service.h"
#include "services/abstract/importantnode.h"
#include "services/abstract/recyclebin.h"
#include "services/reddit/definitions.h"
#include "services/reddit/gui/formeditredditaccount.h"
#include "services/reddit/redditentrypoint.h"
#include "services/reddit/redditnetworkfactory.h"
#include <QFileDialog>
RedditServiceRoot::RedditServiceRoot(RootItem* parent)
: ServiceRoot(parent), m_network(new RedditNetworkFactory(this)) {
m_network->setService(this);
setIcon(RedditEntryPoint().icon());
}
void RedditServiceRoot::updateTitle() {
setTitle(TextFactory::extractUsernameFromEmail(m_network->username()) + QSL(" (Reddit)"));
}
RootItem* RedditServiceRoot::obtainNewTreeForSyncIn() const {
auto* root = new RootItem();
return root;
}
QVariantHash RedditServiceRoot::customDatabaseData() const {
QVariantHash data;
data[QSL("username")] = m_network->username();
data[QSL("batch_size")] = m_network->batchSize();
data[QSL("download_only_unread")] = m_network->downloadOnlyUnreadMessages();
data[QSL("client_id")] = m_network->oauth()->clientId();
data[QSL("client_secret")] = m_network->oauth()->clientSecret();
data[QSL("refresh_token")] = m_network->oauth()->refreshToken();
data[QSL("redirect_uri")] = m_network->oauth()->redirectUrl();
return data;
}
void RedditServiceRoot::setCustomDatabaseData(const QVariantHash& data) {
m_network->setUsername(data[QSL("username")].toString());
m_network->setBatchSize(data[QSL("batch_size")].toInt());
m_network->setDownloadOnlyUnreadMessages(data[QSL("download_only_unread")].toBool());
m_network->oauth()->setClientId(data[QSL("client_id")].toString());
m_network->oauth()->setClientSecret(data[QSL("client_secret")].toString());
m_network->oauth()->setRefreshToken(data[QSL("refresh_token")].toString());
m_network->oauth()->setRedirectUrl(data[QSL("redirect_uri")].toString(), true);
}
QList<Message> RedditServiceRoot::obtainNewMessages(Feed* feed,
const QHash<ServiceRoot::BagOfMessages, QStringList>& stated_messages,
const QHash<QString, QStringList>& tagged_messages) {
Q_UNUSED(stated_messages)
Q_UNUSED(tagged_messages)
Q_UNUSED(feed)
QList<Message> messages;
return messages;
}
bool RedditServiceRoot::isSyncable() const {
return true;
}
bool RedditServiceRoot::canBeEdited() const {
return true;
}
bool RedditServiceRoot::editViaGui() {
FormEditRedditAccount form_pointer(qApp->mainFormWidget());
form_pointer.addEditAccount(this);
return true;
}
bool RedditServiceRoot::supportsFeedAdding() const {
return false;
}
bool RedditServiceRoot::supportsCategoryAdding() const {
return false;
}
void RedditServiceRoot::start(bool freshly_activated) {
if (!freshly_activated) {
DatabaseQueries::loadFromDatabase<Category, Feed>(this);
loadCacheFromFile();
}
updateTitle();
/*
if (getSubTreeFeeds().isEmpty()) {
syncIn();
}
*/
m_network->oauth()->login();
}
QString RedditServiceRoot::code() const {
return RedditEntryPoint().code();
}
QString RedditServiceRoot::additionalTooltip() const {
return tr("Authentication status: %1\n"
"Login tokens expiration: %2").arg(network()->oauth()->isFullyLoggedIn()
? tr("logged-in")
: tr("NOT logged-in"),
network()->oauth()->tokensExpireIn().isValid() ?
network()->oauth()->tokensExpireIn().toString() : QSL("-"));
}
void RedditServiceRoot::saveAllCachedData(bool ignore_errors) {
Q_UNUSED(ignore_errors)
auto msg_cache = takeMessageCache();
}

View File

@ -0,0 +1,53 @@
// For license of this file, see <project-root-folder>/LICENSE.md.
#ifndef REDDITSERVICEROOT_H
#define REDDITSERVICEROOT_H
#include "services/abstract/cacheforserviceroot.h"
#include "services/abstract/serviceroot.h"
class RedditNetworkFactory;
class RedditServiceRoot : public ServiceRoot, public CacheForServiceRoot {
Q_OBJECT
public:
explicit RedditServiceRoot(RootItem* parent = nullptr);
void setNetwork(RedditNetworkFactory* network);
RedditNetworkFactory* network() const;
virtual bool isSyncable() const;
virtual bool canBeEdited() const;
virtual bool editViaGui();
virtual bool supportsFeedAdding() const;
virtual bool supportsCategoryAdding() const;
virtual void start(bool freshly_activated);
virtual QString code() const;
virtual QString additionalTooltip() const;
virtual void saveAllCachedData(bool ignore_errors);
virtual QVariantHash customDatabaseData() const;
virtual void setCustomDatabaseData(const QVariantHash& data);
virtual QList<Message> obtainNewMessages(Feed* feed,
const QHash<ServiceRoot::BagOfMessages, QStringList>& stated_messages,
const QHash<QString, QStringList>& tagged_messages);
protected:
virtual RootItem* obtainNewTreeForSyncIn() const;
private:
void updateTitle();
private:
RedditNetworkFactory* m_network;
};
inline void RedditServiceRoot::setNetwork(RedditNetworkFactory* network) {
m_network = network;
}
inline RedditNetworkFactory* RedditServiceRoot::network() const {
return m_network;
}
#endif // REDDITSERVICEROOT_H