some work on reddit
This commit is contained in:
parent
3b064d8e34
commit
0ccedad219
@ -3,19 +3,21 @@
|
||||
#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_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 mysubreddits read"
|
||||
|
||||
#define REDDIT_REG_API_URL "https://www.reddit.com/prefs/apps"
|
||||
#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_API_GET_PROFILE "https://oauth.reddit.com/api/v1/me"
|
||||
#define REDDIT_API_SUBREDDITS "https://oauth.reddit.com/subreddits/mine/subscriber?limit=%1"
|
||||
#define REDDIT_API_HOT "https://oauth.reddit.com/r/%2/hot?limit=%1&%3"
|
||||
|
||||
#define REDDIT_DEFAULT_BATCH_SIZE 100
|
||||
#define REDDIT_MAX_BATCH_SIZE 999
|
||||
#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"
|
||||
#define REDDIT_CONTENT_TYPE_HTTP "application/http"
|
||||
#define REDDIT_CONTENT_TYPE_JSON "application/json"
|
||||
|
||||
#endif // REDDIT_DEFINITIONS_H
|
||||
|
@ -26,11 +26,14 @@
|
||||
#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)) {
|
||||
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();
|
||||
}
|
||||
|
||||
@ -56,23 +59,23 @@ void RedditNetworkFactory::setBatchSize(int 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);
|
||||
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)
|
||||
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());
|
||||
if (m_service != nullptr && !refresh_token.isEmpty()) {
|
||||
QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className());
|
||||
|
||||
DatabaseQueries::storeNewOauthTokens(database, refresh_token, m_service->accountId());
|
||||
}
|
||||
});
|
||||
DatabaseQueries::storeNewOauthTokens(database, refresh_token, m_service->accountId());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool RedditNetworkFactory::downloadOnlyUnreadMessages() const {
|
||||
@ -114,7 +117,8 @@ QVariantHash RedditNetworkFactory::me(const QNetworkProxy& custom_proxy) {
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
custom_proxy).m_networkError;
|
||||
custom_proxy)
|
||||
.m_networkError;
|
||||
|
||||
if (result != QNetworkReply::NetworkError::NoError) {
|
||||
throw NetworkException(result, output);
|
||||
@ -126,30 +130,191 @@ QVariantHash RedditNetworkFactory::me(const QNetworkProxy& custom_proxy) {
|
||||
}
|
||||
}
|
||||
|
||||
QList<Feed*> RedditNetworkFactory::subreddits(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();
|
||||
QString after;
|
||||
QList<Feed*> subs;
|
||||
|
||||
do {
|
||||
QByteArray output;
|
||||
QString final_url = QSL(REDDIT_API_SUBREDDITS).arg(QString::number(100));
|
||||
|
||||
if (!after.isEmpty()) {
|
||||
final_url += QSL("&after=%1").arg(after);
|
||||
}
|
||||
|
||||
auto result = NetworkFactory::performNetworkOperation(final_url,
|
||||
timeout,
|
||||
{},
|
||||
output,
|
||||
QNetworkAccessManager::Operation::GetOperation,
|
||||
headers,
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
custom_proxy)
|
||||
.m_networkError;
|
||||
|
||||
if (result != QNetworkReply::NetworkError::NoError) {
|
||||
throw NetworkException(result, output);
|
||||
}
|
||||
else {
|
||||
QJsonDocument doc = QJsonDocument::fromJson(output);
|
||||
QJsonObject root_doc = doc.object();
|
||||
|
||||
after = root_doc["data"].toObject()["after"].toString();
|
||||
|
||||
for (const QJsonValue& sub_val : root_doc["data"].toObject()["children"].toArray()) {
|
||||
const auto sub_obj = sub_val.toObject()["data"].toObject();
|
||||
|
||||
Feed* new_sub = new Feed();
|
||||
|
||||
new_sub->setCustomId(sub_obj["id"].toString());
|
||||
new_sub->setTitle(sub_obj["title"].toString());
|
||||
new_sub->setDescription(sub_obj["public_description"].toString());
|
||||
|
||||
QIcon icon;
|
||||
QString icon_url = sub_obj["community_icon"].toString();
|
||||
|
||||
if (icon_url.isEmpty()) {
|
||||
icon_url = sub_obj["icon_img"].toString();
|
||||
}
|
||||
|
||||
if (icon_url.contains(QL1S("?"))) {
|
||||
icon_url = icon_url.mid(0, icon_url.indexOf(QL1S("?")));
|
||||
}
|
||||
|
||||
if (!icon_url.isEmpty() &&
|
||||
NetworkFactory::downloadIcon({{icon_url, true}}, timeout, icon, headers, custom_proxy) ==
|
||||
QNetworkReply::NetworkError::NoError) {
|
||||
new_sub->setIcon(icon);
|
||||
}
|
||||
|
||||
subs.append(new_sub);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (!after.isEmpty());
|
||||
|
||||
// posty dle jmena redditu
|
||||
// https://oauth.reddit.com/<SUBREDDIT>/new
|
||||
//
|
||||
// komenty pro post dle id postu
|
||||
// https://oauth.reddit.com/<SUBREDDIT>/comments/<ID-POSTU>
|
||||
|
||||
return subs;
|
||||
}
|
||||
|
||||
QList<Message> RedditNetworkFactory::hot(const QString& sub_name, 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();
|
||||
QString after;
|
||||
QList<Message> msgs;
|
||||
|
||||
do {
|
||||
QByteArray output;
|
||||
QString final_url = QSL(REDDIT_API_HOT).arg(QString::number(100), sub_name, QSL("GLOBAL"));
|
||||
|
||||
if (!after.isEmpty()) {
|
||||
final_url += QSL("&after=%1").arg(after);
|
||||
}
|
||||
|
||||
auto result = NetworkFactory::performNetworkOperation(final_url,
|
||||
timeout,
|
||||
{},
|
||||
output,
|
||||
QNetworkAccessManager::Operation::GetOperation,
|
||||
headers,
|
||||
false,
|
||||
{},
|
||||
{},
|
||||
custom_proxy)
|
||||
.m_networkError;
|
||||
|
||||
if (result != QNetworkReply::NetworkError::NoError) {
|
||||
throw NetworkException(result, output);
|
||||
}
|
||||
else {
|
||||
QJsonDocument doc = QJsonDocument::fromJson(output);
|
||||
QJsonObject root_doc = doc.object();
|
||||
|
||||
after = root_doc["data"].toObject()["after"].toString();
|
||||
|
||||
for (const QJsonValue& sub_val : root_doc["data"].toObject()["children"].toArray()) {
|
||||
const auto msg_obj = sub_val.toObject()["data"].toObject();
|
||||
|
||||
Message new_msg;
|
||||
|
||||
new_msg.m_customId = msg_obj["id"].toString();
|
||||
new_msg.m_title = msg_obj["title"].toString();
|
||||
new_msg.m_author = msg_obj["author"].toString();
|
||||
new_msg.m_createdFromFeed = true;
|
||||
new_msg.m_created =
|
||||
QDateTime::fromSecsSinceEpoch(msg_obj["created_utc"].toVariant().toLongLong(), Qt::TimeSpec::UTC);
|
||||
new_msg.m_url = QSL("https://reddit.com") + msg_obj["permalink"].toString();
|
||||
new_msg.m_contents =
|
||||
msg_obj["description_html"]
|
||||
.toString(); // když prazdny, je poustnutej třeba obrazek či odkaz?, viz property "post_hint"?
|
||||
new_msg.m_rawContents = QJsonDocument(msg_obj).toJson(QJsonDocument::JsonFormat::Compact);
|
||||
|
||||
msgs.append(new_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (!after.isEmpty());
|
||||
|
||||
// posty dle jmena redditu
|
||||
// https://oauth.reddit.com/<SUBREDDIT>/new
|
||||
//
|
||||
// komenty pro post dle id postu
|
||||
// https://oauth.reddit.com/<SUBREDDIT>/comments/<ID-POSTU>
|
||||
|
||||
return msgs;
|
||||
}
|
||||
|
||||
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();
|
||||
} });
|
||||
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();
|
||||
} });
|
||||
qApp->showGuiMessage(Notification::Event::LoginFailure,
|
||||
{tr("Reddit: authorization denied"),
|
||||
tr("Click this to login again."),
|
||||
QSystemTrayIcon::MessageIcon::Critical},
|
||||
{},
|
||||
{tr("Login"), [this]() {
|
||||
m_oauth2->login();
|
||||
}});
|
||||
}
|
||||
|
@ -18,8 +18,10 @@ class RedditServiceRoot;
|
||||
class OAuth2Service;
|
||||
class Downloader;
|
||||
|
||||
struct Subreddit {};
|
||||
|
||||
class RedditNetworkFactory : public QObject {
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit RedditNetworkFactory(QObject* parent = nullptr);
|
||||
@ -40,6 +42,8 @@ class RedditNetworkFactory : public QObject {
|
||||
|
||||
// API methods.
|
||||
QVariantHash me(const QNetworkProxy& custom_proxy);
|
||||
QList<Feed*> subreddits(const QNetworkProxy& custom_proxy);
|
||||
QList<Message> hot(const QString& sub_name, const QNetworkProxy& custom_proxy);
|
||||
|
||||
private slots:
|
||||
void onTokensError(const QString& error, const QString& error_description);
|
||||
|
@ -30,6 +30,12 @@ void RedditServiceRoot::updateTitle() {
|
||||
RootItem* RedditServiceRoot::obtainNewTreeForSyncIn() const {
|
||||
auto* root = new RootItem();
|
||||
|
||||
auto feeds = m_network->subreddits(networkProxy());
|
||||
|
||||
for (auto* feed : feeds) {
|
||||
root->appendChild(feed);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@ -58,13 +64,14 @@ void RedditServiceRoot::setCustomDatabaseData(const QVariantHash& data) {
|
||||
}
|
||||
|
||||
QList<Message> RedditServiceRoot::obtainNewMessages(Feed* feed,
|
||||
const QHash<ServiceRoot::BagOfMessages, QStringList>& stated_messages,
|
||||
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;
|
||||
QList<Message> messages = m_network->hot(feed->title(), networkProxy());
|
||||
|
||||
return messages;
|
||||
}
|
||||
@ -100,13 +107,14 @@ void RedditServiceRoot::start(bool freshly_activated) {
|
||||
|
||||
updateTitle();
|
||||
|
||||
/*
|
||||
if (getSubTreeFeeds().isEmpty()) {
|
||||
syncIn();
|
||||
}
|
||||
*/
|
||||
|
||||
m_network->oauth()->login();
|
||||
if (getSubTreeFeeds().isEmpty()) {
|
||||
m_network->oauth()->login([this]() {
|
||||
syncIn();
|
||||
});
|
||||
}
|
||||
else {
|
||||
m_network->oauth()->login();
|
||||
}
|
||||
}
|
||||
|
||||
QString RedditServiceRoot::code() const {
|
||||
@ -115,11 +123,9 @@ QString RedditServiceRoot::code() const {
|
||||
|
||||
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("-"));
|
||||
"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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user