some work on reddit
This commit is contained in:
		
							parent
							
								
									3b064d8e34
								
							
						
					
					
						commit
						0ccedad219
					
				
					 4 changed files with 240 additions and 63 deletions
				
			
		|  | @ -6,11 +6,13 @@ | |||
| #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_SCOPE "identity mysubreddits read" | ||||
| 
 | ||||
| #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_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 | ||||
|  |  | |||
|  | @ -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,14 +59,14 @@ 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) { | ||||
|   connect(m_oauth2, | ||||
|           &OAuth2Service::tokensRetrieved, | ||||
|           this, | ||||
|           [this](QString access_token, QString refresh_token, int expires_in) { | ||||
|             Q_UNUSED(expires_in) | ||||
|             Q_UNUSED(access_token) | ||||
| 
 | ||||
|  | @ -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,16 +130,178 @@ 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"), | ||||
|   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]() { | ||||
|                        {}, | ||||
|                        {tr("Login"), [this]() { | ||||
|                           m_oauth2->setAccessToken(QString()); | ||||
|                           m_oauth2->setRefreshToken(QString()); | ||||
|                           m_oauth2->login(); | ||||
|  | @ -143,13 +309,12 @@ void RedditNetworkFactory::onTokensError(const QString& error, const QString& er | |||
| } | ||||
| 
 | ||||
| void RedditNetworkFactory::onAuthFailed() { | ||||
|   qApp->showGuiMessage(Notification::Event::LoginFailure, { | ||||
|     tr("Reddit: authorization denied"), | ||||
|   qApp->showGuiMessage(Notification::Event::LoginFailure, | ||||
|                        {tr("Reddit: authorization denied"), | ||||
|                         tr("Click this to login again."), | ||||
|                         QSystemTrayIcon::MessageIcon::Critical}, | ||||
|                        {}, { | ||||
|     tr("Login"), | ||||
|     [this]() { | ||||
|                        {}, | ||||
|                        {tr("Login"), [this]() { | ||||
|                           m_oauth2->login(); | ||||
|                         }}); | ||||
| } | ||||
|  |  | |||
|  | @ -18,6 +18,8 @@ class RedditServiceRoot; | |||
| class OAuth2Service; | ||||
| class Downloader; | ||||
| 
 | ||||
| struct Subreddit {}; | ||||
| 
 | ||||
| class RedditNetworkFactory : public QObject { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
|  | @ -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,14 +107,15 @@ void RedditServiceRoot::start(bool freshly_activated) { | |||
| 
 | ||||
|   updateTitle(); | ||||
| 
 | ||||
|   /*
 | ||||
|   if (getSubTreeFeeds().isEmpty()) { | ||||
|     m_network->oauth()->login([this]() { | ||||
|       syncIn(); | ||||
|     }); | ||||
|   } | ||||
|    */ | ||||
| 
 | ||||
|   else { | ||||
|     m_network->oauth()->login(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| QString RedditServiceRoot::code() const { | ||||
|   return RedditEntryPoint().code(); | ||||
|  | @ -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…
	
	Add table
		
		Reference in a new issue