// For license of this file, see /LICENSE.md. #include "services/tt-rss/ttrssnetworkfactory.h" #include "3rd-party/boolinq/boolinq.h" #include "definitions/definitions.h" #include "exceptions/feedfetchexception.h" #include "miscellaneous/application.h" #include "miscellaneous/iconfactory.h" #include "miscellaneous/settings.h" #include "miscellaneous/textfactory.h" #include "network-web/networkfactory.h" #include "services/abstract/category.h" #include "services/abstract/label.h" #include "services/abstract/labelsnode.h" #include "services/abstract/rootitem.h" #include "services/abstract/serviceroot.h" #include "services/tt-rss/definitions.h" #include "services/tt-rss/ttrssfeed.h" #include #include #include #include TtRssNetworkFactory::TtRssNetworkFactory() : m_bareUrl(QString()), m_fullUrl(QString()), m_username(QString()), m_password(QString()), m_batchSize(TTRSS_DEFAULT_MESSAGES), m_forceServerSideUpdate(false), m_intelligentSynchronization(false), m_authIsUsed(false), m_authUsername(QString()), m_authPassword(QString()), m_sessionId(QString()), m_lastError(QNetworkReply::NetworkError::NoError) {} QString TtRssNetworkFactory::url() const { return m_bareUrl; } void TtRssNetworkFactory::setUrl(const QString& url) { m_bareUrl = url; if (!m_bareUrl.endsWith(QSL("/"))) { m_bareUrl = m_bareUrl + QSL("/"); } if (!m_bareUrl.endsWith(QSL("api/"))) { m_fullUrl = m_bareUrl + QSL("api/"); } else { m_fullUrl = m_bareUrl; } } QString TtRssNetworkFactory::username() const { return m_username; } void TtRssNetworkFactory::setUsername(const QString& username) { m_username = username; } QString TtRssNetworkFactory::password() const { return m_password; } void TtRssNetworkFactory::setPassword(const QString& password) { m_password = password; } QDateTime TtRssNetworkFactory::lastLoginTime() const { return m_lastLoginTime; } QNetworkReply::NetworkError TtRssNetworkFactory::lastError() const { return m_lastError; } TtRssLoginResponse TtRssNetworkFactory::login(const QNetworkProxy& proxy) { if (!m_sessionId.isEmpty()) { qWarningNN << LOGSEC_TTRSS << "Session ID is not empty before login, logging out first."; logout(proxy); } QJsonObject json; json[QSL("op")] = QSL("login"); json[QSL("user")] = m_username; json[QSL("password")] = m_password; QByteArray result_raw; QList> headers; headers << QPair(HTTP_HEADERS_CONTENT_TYPE, TTRSS_CONTENT_TYPE_JSON); headers << NetworkFactory::generateBasicAuthHeader(NetworkFactory::NetworkAuthentication::Basic, m_authUsername, m_authPassword); NetworkResult network_reply = NetworkFactory::performNetworkOperation(m_fullUrl, qApp->settings() ->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)) .toInt(), QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact), result_raw, QNetworkAccessManager::Operation::PostOperation, headers, false, {}, {}, proxy); TtRssLoginResponse login_response(QString::fromUtf8(result_raw)); if (network_reply.m_networkError == QNetworkReply::NoError) { m_sessionId = login_response.sessionId(); m_lastLoginTime = QDateTime::currentDateTime(); } else { qWarningNN << LOGSEC_TTRSS << "Login failed with error:" << QUOTE_W_SPACE_DOT(network_reply.m_networkError); } m_lastError = network_reply.m_networkError; return login_response; } TtRssResponse TtRssNetworkFactory::logout(const QNetworkProxy& proxy) { if (!m_sessionId.isEmpty()) { QJsonObject json; json[QSL("op")] = QSL("logout"); json[QSL("sid")] = m_sessionId; QByteArray result_raw; QList> headers; headers << QPair(HTTP_HEADERS_CONTENT_TYPE, TTRSS_CONTENT_TYPE_JSON); headers << NetworkFactory::generateBasicAuthHeader(NetworkFactory::NetworkAuthentication::Basic, m_authUsername, m_authPassword); NetworkResult network_reply = NetworkFactory::performNetworkOperation(m_fullUrl, qApp->settings() ->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)) .toInt(), QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact), result_raw, QNetworkAccessManager::Operation::PostOperation, headers, false, {}, {}, proxy); m_lastError = network_reply.m_networkError; if (m_lastError == QNetworkReply::NetworkError::NoError) { m_sessionId.clear(); } else { qWarningNN << LOGSEC_TTRSS << "Logout failed with error:" << QUOTE_W_SPACE_DOT(network_reply.m_networkError); } return TtRssResponse(QString::fromUtf8(result_raw)); } else { qWarningNN << LOGSEC_TTRSS << "Cannot logout because session ID is empty."; m_lastError = QNetworkReply::NetworkError::NoError; return TtRssResponse(); } } TtRssGetLabelsResponse TtRssNetworkFactory::getLabels(const QNetworkProxy& proxy) { QJsonObject json; json[QSL("op")] = QSL("getLabels"); json[QSL("sid")] = m_sessionId; const int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); QByteArray result_raw; QList> headers; headers << QPair(HTTP_HEADERS_CONTENT_TYPE, TTRSS_CONTENT_TYPE_JSON); headers << NetworkFactory::generateBasicAuthHeader(NetworkFactory::NetworkAuthentication::Basic, m_authUsername, m_authPassword); NetworkResult network_reply = NetworkFactory::performNetworkOperation(m_fullUrl, timeout, QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact), result_raw, QNetworkAccessManager::Operation::PostOperation, headers, false, {}, {}, proxy); TtRssGetLabelsResponse result(QString::fromUtf8(result_raw)); if (result.isNotLoggedIn()) { // We are not logged in. login(proxy); json[QSL("sid")] = m_sessionId; network_reply = NetworkFactory::performNetworkOperation(m_fullUrl, timeout, QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact), result_raw, QNetworkAccessManager::Operation::PostOperation, headers, false, {}, {}, proxy); result = TtRssGetLabelsResponse(QString::fromUtf8(result_raw)); } if (network_reply.m_networkError != QNetworkReply::NoError) { qWarningNN << LOGSEC_TTRSS << "getLabels failed with error:" << QUOTE_W_SPACE_DOT(network_reply.m_networkError); } m_lastError = network_reply.m_networkError; return result; } TtRssResponse TtRssNetworkFactory::shareToPublished(const TtRssNoteToPublish& note, const QNetworkProxy& proxy) { QJsonObject json; json[QSL("op")] = QSL("shareToPublished"); json[QSL("sid")] = m_sessionId; json[QSL("title")] = note.m_title; json[QSL("url")] = note.m_url; json[QSL("content")] = note.m_content; const int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); QByteArray result_raw; QList> headers; headers << QPair(HTTP_HEADERS_CONTENT_TYPE, TTRSS_CONTENT_TYPE_JSON); headers << NetworkFactory::generateBasicAuthHeader(NetworkFactory::NetworkAuthentication::Basic, m_authUsername, m_authPassword); NetworkResult network_reply = NetworkFactory::performNetworkOperation(m_fullUrl, timeout, QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact), result_raw, QNetworkAccessManager::Operation::PostOperation, headers, false, {}, {}, proxy); TtRssResponse result(QString::fromUtf8(result_raw)); if (result.isNotLoggedIn()) { // We are not logged in. login(proxy); json[QSL("sid")] = m_sessionId; network_reply = NetworkFactory::performNetworkOperation(m_fullUrl, timeout, QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact), result_raw, QNetworkAccessManager::Operation::PostOperation, headers, false, {}, {}, proxy); result = TtRssResponse(QString::fromUtf8(result_raw)); } if (network_reply.m_networkError != QNetworkReply::NoError) { qWarningNN << LOGSEC_TTRSS << "shareToPublished failed with error:" << QUOTE_W_SPACE_DOT(network_reply.m_networkError); } m_lastError = network_reply.m_networkError; return result; } TtRssGetFeedsCategoriesResponse TtRssNetworkFactory::getFeedsCategories(const QNetworkProxy& proxy) { QJsonObject json; json[QSL("op")] = QSL("getFeedTree"); json[QSL("sid")] = m_sessionId; json[QSL("include_empty")] = true; const int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); QByteArray result_raw; QList> headers; headers << QPair(HTTP_HEADERS_CONTENT_TYPE, TTRSS_CONTENT_TYPE_JSON); headers << NetworkFactory::generateBasicAuthHeader(NetworkFactory::NetworkAuthentication::Basic, m_authUsername, m_authPassword); NetworkResult network_reply = NetworkFactory::performNetworkOperation(m_fullUrl, timeout, QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact), result_raw, QNetworkAccessManager::Operation::PostOperation, headers, false, {}, {}, proxy); TtRssGetFeedsCategoriesResponse result(QString::fromUtf8(result_raw)); if (result.isNotLoggedIn()) { // We are not logged in. login(proxy); json[QSL("sid")] = m_sessionId; network_reply = NetworkFactory::performNetworkOperation(m_fullUrl, timeout, QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact), result_raw, QNetworkAccessManager::Operation::PostOperation, headers, false, {}, {}, proxy); result = TtRssGetFeedsCategoriesResponse(QString::fromUtf8(result_raw)); } if (network_reply.m_networkError != QNetworkReply::NoError) { qWarningNN << LOGSEC_TTRSS << "getFeedTree failed with error:" << QUOTE_W_SPACE_DOT(network_reply.m_networkError); } m_lastError = network_reply.m_networkError; return result; } TtRssGetCompactHeadlinesResponse TtRssNetworkFactory::getCompactHeadlines(int feed_id, int limit, int skip, const QString& view_mode, const QNetworkProxy& proxy) { QJsonObject json; json[QSL("op")] = QSL("getCompactHeadlines"); json[QSL("sid")] = m_sessionId; json[QSL("feed_id")] = feed_id; json[QSL("limit")] = limit; // json[QSL("skip")] = skip; json[QSL("view_mode")] = view_mode; const int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); QByteArray result_raw; QList> headers; headers << QPair(HTTP_HEADERS_CONTENT_TYPE, TTRSS_CONTENT_TYPE_JSON); headers << NetworkFactory::generateBasicAuthHeader(NetworkFactory::NetworkAuthentication::Basic, m_authUsername, m_authPassword); NetworkResult network_reply = NetworkFactory::performNetworkOperation(m_fullUrl, timeout, QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact), result_raw, QNetworkAccessManager::Operation::PostOperation, headers, false, {}, {}, proxy); TtRssGetCompactHeadlinesResponse result(QString::fromUtf8(result_raw)); if (result.isUnknownMethod()) { qCriticalNN << LOGSEC_TTRSS << "'getCompactHeadlines' method is not installed."; throw FeedFetchException(Feed::Status::OtherError, QSL("'getCompactHeadlines' method is not installed on your TT-RSS instance. Install " "'api_newsplus' plugin.")); } else if (result.isNotLoggedIn()) { // We are not logged in. login(proxy); json[QSL("sid")] = m_sessionId; network_reply = NetworkFactory::performNetworkOperation(m_fullUrl, timeout, QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact), result_raw, QNetworkAccessManager::Operation::PostOperation, headers, false, {}, {}, proxy); result = TtRssGetCompactHeadlinesResponse(QString::fromUtf8(result_raw)); } if (network_reply.m_networkError != QNetworkReply::NetworkError::NoError) { qWarningNN << LOGSEC_TTRSS << "getCompactHeadlines failed with error:" << QUOTE_W_SPACE_DOT(network_reply.m_networkError); } m_lastError = network_reply.m_networkError; return result; } TtRssGetHeadlinesResponse TtRssNetworkFactory::getArticle(const QStringList& article_ids, const QNetworkProxy& proxy) { QJsonObject json; json[QSL("op")] = QSL("getArticle"); json[QSL("sid")] = m_sessionId; json[QSL("article_id")] = article_ids.join(QL1C(',')); const int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); QByteArray result_raw; QList> headers; headers << QPair(HTTP_HEADERS_CONTENT_TYPE, TTRSS_CONTENT_TYPE_JSON); headers << NetworkFactory::generateBasicAuthHeader(NetworkFactory::NetworkAuthentication::Basic, m_authUsername, m_authPassword); NetworkResult network_reply = NetworkFactory::performNetworkOperation(m_fullUrl, timeout, QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact), result_raw, QNetworkAccessManager::Operation::PostOperation, headers, false, {}, {}, proxy); TtRssGetHeadlinesResponse result(QString::fromUtf8(result_raw)); if (result.isNotLoggedIn()) { // We are not logged in. login(proxy); json[QSL("sid")] = m_sessionId; network_reply = NetworkFactory::performNetworkOperation(m_fullUrl, timeout, QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact), result_raw, QNetworkAccessManager::Operation::PostOperation, headers, false, {}, {}, proxy); result = TtRssGetHeadlinesResponse(QString::fromUtf8(result_raw)); } if (network_reply.m_networkError != QNetworkReply::NetworkError::NoError) { qWarningNN << LOGSEC_TTRSS << "getArticle failed with error:" << QUOTE_W_SPACE_DOT(network_reply.m_networkError); } m_lastError = network_reply.m_networkError; return result; } TtRssGetHeadlinesResponse TtRssNetworkFactory::getHeadlines(int feed_id, int limit, int skip, bool show_content, bool include_attachments, bool sanitize, bool unread_only, const QNetworkProxy& proxy) { QJsonObject json; json[QSL("op")] = QSL("getHeadlines"); json[QSL("sid")] = m_sessionId; json[QSL("feed_id")] = feed_id; json[QSL("force_update")] = m_forceServerSideUpdate; json[QSL("limit")] = limit; json[QSL("skip")] = skip; json[QSL("view_mode")] = unread_only ? QSL("unread") : QSL("all_articles"); json[QSL("show_content")] = show_content; json[QSL("include_attachments")] = include_attachments; json[QSL("sanitize")] = sanitize; const int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); QByteArray result_raw; QList> headers; headers << QPair(HTTP_HEADERS_CONTENT_TYPE, TTRSS_CONTENT_TYPE_JSON); headers << NetworkFactory::generateBasicAuthHeader(NetworkFactory::NetworkAuthentication::Basic, m_authUsername, m_authPassword); NetworkResult network_reply = NetworkFactory::performNetworkOperation(m_fullUrl, timeout, QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact), result_raw, QNetworkAccessManager::Operation::PostOperation, headers, false, {}, {}, proxy); TtRssGetHeadlinesResponse result(QString::fromUtf8(result_raw)); if (result.isNotLoggedIn()) { // We are not logged in. login(proxy); json[QSL("sid")] = m_sessionId; network_reply = NetworkFactory::performNetworkOperation(m_fullUrl, timeout, QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact), result_raw, QNetworkAccessManager::Operation::PostOperation, headers, false, {}, {}, proxy); result = TtRssGetHeadlinesResponse(QString::fromUtf8(result_raw)); } if (network_reply.m_networkError != QNetworkReply::NoError) { qWarningNN << LOGSEC_TTRSS << "getHeadlines failed with error:" << QUOTE_W_SPACE_DOT(network_reply.m_networkError); } m_lastError = network_reply.m_networkError; return result; } TtRssResponse TtRssNetworkFactory::setArticleLabel(const QStringList& article_ids, const QString& label_custom_id, bool assign, const QNetworkProxy& proxy) { QJsonObject json; json[QSL("op")] = QSL("setArticleLabel"); json[QSL("sid")] = m_sessionId; json[QSL("article_ids")] = article_ids.join(QSL(",")); json[QSL("label_id")] = label_custom_id.toInt(); json[QSL("assign")] = assign; const int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); QByteArray result_raw; QList> headers; headers << QPair(HTTP_HEADERS_CONTENT_TYPE, TTRSS_CONTENT_TYPE_JSON); headers << NetworkFactory::generateBasicAuthHeader(NetworkFactory::NetworkAuthentication::Basic, m_authUsername, m_authPassword); NetworkResult network_reply = NetworkFactory::performNetworkOperation(m_fullUrl, timeout, QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact), result_raw, QNetworkAccessManager::Operation::PostOperation, headers, false, {}, {}, proxy); TtRssResponse result(QString::fromUtf8(result_raw)); if (result.isNotLoggedIn()) { // We are not logged in. login(proxy); json[QSL("sid")] = m_sessionId; network_reply = NetworkFactory::performNetworkOperation(m_fullUrl, timeout, QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact), result_raw, QNetworkAccessManager::Operation::PostOperation, headers, false, {}, {}, proxy); result = TtRssResponse(QString::fromUtf8(result_raw)); } if (network_reply.m_networkError != QNetworkReply::NoError) { qWarningNN << LOGSEC_TTRSS << "setArticleLabel failed with error" << QUOTE_W_SPACE_DOT(network_reply.m_networkError); } m_lastError = network_reply.m_networkError; return result; } TtRssUpdateArticleResponse TtRssNetworkFactory::updateArticles(const QStringList& ids, UpdateArticle::OperatingField field, UpdateArticle::Mode mode, const QNetworkProxy& proxy) { QJsonObject json; json[QSL("op")] = QSL("updateArticle"); json[QSL("sid")] = m_sessionId; json[QSL("article_ids")] = ids.join(QSL(",")); json[QSL("mode")] = int(mode); json[QSL("field")] = int(field); const int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); QByteArray result_raw; QList> headers; headers << QPair(HTTP_HEADERS_CONTENT_TYPE, TTRSS_CONTENT_TYPE_JSON); headers << NetworkFactory::generateBasicAuthHeader(NetworkFactory::NetworkAuthentication::Basic, m_authUsername, m_authPassword); NetworkResult network_reply = NetworkFactory::performNetworkOperation(m_fullUrl, timeout, QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact), result_raw, QNetworkAccessManager::Operation::PostOperation, headers, false, {}, {}, proxy); TtRssUpdateArticleResponse result(QString::fromUtf8(result_raw)); if (result.isNotLoggedIn()) { // We are not logged in. login(proxy); json[QSL("sid")] = m_sessionId; network_reply = NetworkFactory::performNetworkOperation(m_fullUrl, timeout, QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact), result_raw, QNetworkAccessManager::Operation::PostOperation, headers, false, {}, {}, proxy); result = TtRssUpdateArticleResponse(QString::fromUtf8(result_raw)); } if (network_reply.m_networkError != QNetworkReply::NoError) { qWarningNN << LOGSEC_TTRSS << "updateArticle failed with error" << QUOTE_W_SPACE_DOT(network_reply.m_networkError); } m_lastError = network_reply.m_networkError; return result; } TtRssSubscribeToFeedResponse TtRssNetworkFactory::subscribeToFeed(const QString& url, int category_id, const QNetworkProxy& proxy, bool protectd, const QString& username, const QString& password) { QJsonObject json; json[QSL("op")] = QSL("subscribeToFeed"); json[QSL("sid")] = m_sessionId; json[QSL("feed_url")] = url; json[QSL("category_id")] = category_id; if (protectd) { json[QSL("login")] = username; json[QSL("password")] = password; } const int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); QByteArray result_raw; QList> headers; headers << QPair(HTTP_HEADERS_CONTENT_TYPE, TTRSS_CONTENT_TYPE_JSON); headers << NetworkFactory::generateBasicAuthHeader(NetworkFactory::NetworkAuthentication::Basic, m_authUsername, m_authPassword); NetworkResult network_reply = NetworkFactory::performNetworkOperation(m_fullUrl, timeout, QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact), result_raw, QNetworkAccessManager::Operation::PostOperation, headers, false, {}, {}, proxy); TtRssSubscribeToFeedResponse result(QString::fromUtf8(result_raw)); if (result.isNotLoggedIn()) { // We are not logged in. login(proxy); json[QSL("sid")] = m_sessionId; network_reply = NetworkFactory::performNetworkOperation(m_fullUrl, timeout, QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact), result_raw, QNetworkAccessManager::Operation::PostOperation, headers, false, {}, {}, proxy); result = TtRssSubscribeToFeedResponse(QString::fromUtf8(result_raw)); } if (network_reply.m_networkError != QNetworkReply::NoError) { qWarningNN << LOGSEC_TTRSS << "updateArticle failed with error" << QUOTE_W_SPACE_DOT(network_reply.m_networkError); } m_lastError = network_reply.m_networkError; return result; } TtRssUnsubscribeFeedResponse TtRssNetworkFactory::unsubscribeFeed(int feed_id, const QNetworkProxy& proxy) { QJsonObject json; json[QSL("op")] = QSL("unsubscribeFeed"); json[QSL("sid")] = m_sessionId; json[QSL("feed_id")] = feed_id; const int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); QByteArray result_raw; QList> headers; headers << QPair(HTTP_HEADERS_CONTENT_TYPE, TTRSS_CONTENT_TYPE_JSON); headers << NetworkFactory::generateBasicAuthHeader(NetworkFactory::NetworkAuthentication::Basic, m_authUsername, m_authPassword); NetworkResult network_reply = NetworkFactory::performNetworkOperation(m_fullUrl, timeout, QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact), result_raw, QNetworkAccessManager::Operation::PostOperation, headers, false, {}, {}, proxy); TtRssUnsubscribeFeedResponse result(QString::fromUtf8(result_raw)); if (result.isNotLoggedIn()) { // We are not logged in. login(proxy); json[QSL("sid")] = m_sessionId; network_reply = NetworkFactory::performNetworkOperation(m_fullUrl, timeout, QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact), result_raw, QNetworkAccessManager::Operation::PostOperation, headers, false, {}, {}, proxy); result = TtRssUnsubscribeFeedResponse(QString::fromUtf8(result_raw)); } if (network_reply.m_networkError != QNetworkReply::NoError) { qWarningNN << LOGSEC_TTRSS << "getFeeds failed with error" << QUOTE_W_SPACE_DOT(network_reply.m_networkError); } m_lastError = network_reply.m_networkError; return result; } int TtRssNetworkFactory::batchSize() const { return m_batchSize; } void TtRssNetworkFactory::setBatchSize(int batch_size) { m_batchSize = batch_size; } bool TtRssNetworkFactory::intelligentSynchronization() const { return m_intelligentSynchronization; } void TtRssNetworkFactory::setIntelligentSynchronization(bool intelligent_synchronization) { m_intelligentSynchronization = intelligent_synchronization; } bool TtRssNetworkFactory::downloadOnlyUnreadMessages() const { return m_downloadOnlyUnreadMessages; } void TtRssNetworkFactory::setDownloadOnlyUnreadMessages(bool download_only_unread_messages) { m_downloadOnlyUnreadMessages = download_only_unread_messages; } bool TtRssNetworkFactory::forceServerSideUpdate() const { return m_forceServerSideUpdate; } void TtRssNetworkFactory::setForceServerSideUpdate(bool force_server_side_update) { m_forceServerSideUpdate = force_server_side_update; } bool TtRssNetworkFactory::authIsUsed() const { return m_authIsUsed; } void TtRssNetworkFactory::setAuthIsUsed(bool auth_is_used) { m_authIsUsed = auth_is_used; } QString TtRssNetworkFactory::authUsername() const { return m_authUsername; } void TtRssNetworkFactory::setAuthUsername(const QString& auth_username) { m_authUsername = auth_username; } QString TtRssNetworkFactory::authPassword() const { return m_authPassword; } void TtRssNetworkFactory::setAuthPassword(const QString& auth_password) { m_authPassword = auth_password; } TtRssResponse::TtRssResponse(const QString& raw_content) { m_rawContent = QJsonDocument::fromJson(raw_content.toUtf8()).object(); } TtRssResponse::~TtRssResponse() = default; bool TtRssResponse::isLoaded() const { return !m_rawContent.isEmpty(); } int TtRssResponse::seq() const { if (!isLoaded()) { return TTRSS_CONTENT_NOT_LOADED; } else { return m_rawContent[QSL("seq")].toInt(); } } int TtRssResponse::status() const { if (!isLoaded()) { return TTRSS_CONTENT_NOT_LOADED; } else { return m_rawContent[QSL("status")].toInt(); } } bool TtRssResponse::isNotLoggedIn() const { return status() == TTRSS_API_STATUS_ERR && hasError() && error() == QSL(TTRSS_NOT_LOGGED_IN); } bool TtRssResponse::isUnknownMethod() const { return status() == TTRSS_API_STATUS_ERR && hasError() && error() == QSL(TTRSS_UNKNOWN_METHOD); } QString TtRssResponse::toString() const { return QJsonDocument(m_rawContent).toJson(QJsonDocument::JsonFormat::Compact); } TtRssLoginResponse::TtRssLoginResponse(const QString& raw_content) : TtRssResponse(raw_content) {} TtRssLoginResponse::~TtRssLoginResponse() = default; int TtRssLoginResponse::apiLevel() const { if (!isLoaded()) { return TTRSS_CONTENT_NOT_LOADED; } else { return m_rawContent[QSL("content")].toObject()[QSL("api_level")].toInt(); } } QString TtRssLoginResponse::sessionId() const { if (!isLoaded()) { return QString(); } else { return m_rawContent[QSL("content")].toObject()[QSL("session_id")].toString(); } } QString TtRssResponse::error() const { if (!isLoaded()) { return QString(); } else { return m_rawContent[QSL("content")].toObject()[QSL("error")].toString(); } } bool TtRssResponse::hasError() const { if (!isLoaded()) { return false; } else { return m_rawContent[QSL("content")].toObject().contains(QSL("error")); } } TtRssGetFeedsCategoriesResponse::TtRssGetFeedsCategoriesResponse(const QString& raw_content) : TtRssResponse(raw_content) {} TtRssGetFeedsCategoriesResponse::~TtRssGetFeedsCategoriesResponse() = default; RootItem* TtRssGetFeedsCategoriesResponse::feedsCategories(TtRssNetworkFactory* network, bool obtain_icons, const QNetworkProxy& proxy, const QString& base_address) const { auto* parent = new RootItem(); // Chop the "api/" from the end of the address. qDebugNN << LOGSEC_TTRSS << "Base address to get feed icons is" << QUOTE_W_SPACE_DOT(base_address); if (status() == TTRSS_API_STATUS_OK) { // We have data, construct object tree according to data. QJsonArray items_to_process = m_rawContent[QSL("content")].toObject()[QSL("categories")].toObject()[QSL("items")].toArray(); QVector> pairs; pairs.reserve(items_to_process.size()); for (const QJsonValue& item : items_to_process) { pairs.append(QPair(parent, item)); } while (!pairs.isEmpty()) { QPair pair = pairs.takeFirst(); RootItem* act_parent = pair.first; QJsonObject item = pair.second.toObject(); int item_id = item[QSL("bare_id")].toInt(); bool is_category = item.contains(QSL("type")) && item[QSL("type")].toString() == QSL(TTRSS_GFT_TYPE_CATEGORY); if (item_id >= 0) { if (is_category) { if (item_id == 0) { // This is "Uncategorized" category, all its feeds belong to top-level root. if (item.contains(QSL("items"))) { auto ite = item[QSL("items")].toArray(); for (const QJsonValue& child_feed : qAsConst(ite)) { pairs.append(QPair(parent, child_feed)); } } } else { auto* category = new Category(); category->setTitle(item[QSL("name")].toString()); category->setCustomId(QString::number(item_id)); act_parent->appendChild(category); if (item.contains(QSL("items"))) { auto ite = item[QSL("items")].toArray(); for (const QJsonValue& child : qAsConst(ite)) { pairs.append(QPair(category, child)); } } } } else { // We have feed. auto* feed = new TtRssFeed(); if (obtain_icons) { QString icon_path = item[QSL("icon")].type() == QJsonValue::Type::String ? item[QSL("icon")].toString() : QString(); if (!icon_path.isEmpty()) { QString full_icon_address = QUrl(base_address).resolved(icon_path).toString(); QPixmap icon; QList> headers; if (network->authIsUsed()) { headers << NetworkFactory::generateBasicAuthHeader(NetworkFactory::NetworkAuthentication::Basic, network->authUsername(), network->authPassword()); } auto res = NetworkFactory::downloadIcon({{full_icon_address, true}}, DOWNLOAD_TIMEOUT, icon, headers, proxy); if (res == QNetworkReply::NetworkError::NoError) { feed->setIcon(icon); } else { qWarningNN << LOGSEC_TTRSS << "Failed to download icon with error" << QUOTE_W_SPACE_DOT(res); } } } feed->setTitle(item[QSL("name")].toString()); feed->setCustomId(QString::number(item_id)); act_parent->appendChild(feed); } } } // Append special "published" feed to hold "notes" created by user // via "shareToPublished" method. These "notes" are not normal articles // because they do not belong to any feed. // We have feed. auto* published_feed = new TtRssFeed(); published_feed->setTitle(QSL("[SYSTEM] ") + QObject::tr("User-published articles")); published_feed->setCustomId(QString::number(0)); published_feed->setKeepOnTop(true); parent->appendChild(published_feed); } return parent; } TtRssGetHeadlinesResponse::TtRssGetHeadlinesResponse(const QString& raw_content) : TtRssResponse(raw_content) {} TtRssGetHeadlinesResponse::~TtRssGetHeadlinesResponse() = default; TtRssGetArticleResponse::TtRssGetArticleResponse(const QString& raw_content) : TtRssResponse(raw_content) {} QList TtRssGetArticleResponse::messages(ServiceRoot* root) const { return {}; } TtRssGetArticleResponse::~TtRssGetArticleResponse() = default; QList TtRssGetHeadlinesResponse::messages(ServiceRoot* root) const { QList messages; auto active_labels = root->labelsNode() != nullptr ? root->labelsNode()->labels() : QList(); auto json_msgs = m_rawContent[QSL("content")].toArray(); auto* published_lbl = boolinq::from(active_labels).firstOrDefault([](const Label* lbl) { return lbl->customNumericId() == TTRSS_PUBLISHED_LABEL_ID; }); for (const QJsonValue& item : qAsConst(json_msgs)) { QJsonObject mapped = item.toObject(); Message message; message.m_author = mapped[QSL("author")].toString(); message.m_isRead = !mapped[QSL("unread")].toBool(); message.m_isImportant = mapped[QSL("marked")].toBool(); message.m_contents = mapped[QSL("content")].toString(); message.m_rawContents = QJsonDocument(mapped).toJson(QJsonDocument::JsonFormat::Compact); if (published_lbl != nullptr && mapped[QSL("published")].toBool()) { // Article is published, set label. message.m_assignedLabels.append(published_lbl); } auto json_labels = mapped[QSL("labels")].toArray(); for (const QJsonValue& lbl_val : qAsConst(json_labels)) { QString lbl_custom_id = QString::number(lbl_val.toArray().at(0).toInt()); Label* label = boolinq::from(active_labels.begin(), active_labels.end()).firstOrDefault([lbl_custom_id](Label* lbl) { return lbl->customId() == lbl_custom_id; }); if (label != nullptr) { message.m_assignedLabels.append(label); } else { qWarningNN << LOGSEC_TTRSS << "Label with custom ID" << QUOTE_W_SPACE(lbl_custom_id) << "was not found. Maybe you need to perform sync-in to download it from server."; } } // Multiply by 1000 because Tiny Tiny RSS API does not include miliseconds in Unix // date/time number. const qint64 t = static_cast(mapped[QSL("updated")].toDouble()) * 1000; message.m_created = TextFactory::parseDateTime(t); message.m_createdFromFeed = true; message.m_customId = QString::number(mapped[QSL("id")].toInt()); message.m_feedId = mapped[QSL("feed_id")].type() == QJsonValue::Type::Double ? QString::number(mapped[QSL("feed_id")].toInt()) : mapped[QSL("feed_id")].toString(); message.m_title = mapped[QSL("title")].toString(); message.m_url = mapped[QSL("link")].toString(); if (mapped.contains(QSL("attachments"))) { // Process enclosures. auto json_att = mapped[QSL("attachments")].toArray(); for (const QJsonValue& attachment : qAsConst(json_att)) { QJsonObject mapped_attachemnt = attachment.toObject(); Enclosure enclosure; enclosure.m_mimeType = mapped_attachemnt[QSL("content_type")].toString(); enclosure.m_url = mapped_attachemnt[QSL("content_url")].toString(); message.m_enclosures.append(enclosure); } } messages.append(message); } return messages; } TtRssUpdateArticleResponse::TtRssUpdateArticleResponse(const QString& raw_content) : TtRssResponse(raw_content) {} TtRssUpdateArticleResponse::~TtRssUpdateArticleResponse() = default; QString TtRssUpdateArticleResponse::updateStatus() const { if (m_rawContent.contains(QSL("content"))) { return m_rawContent[QSL("content")].toObject()[QSL("status")].toString(); } else { return QString(); } } int TtRssUpdateArticleResponse::articlesUpdated() const { if (m_rawContent.contains(QSL("content"))) { return m_rawContent[QSL("content")].toObject()[QSL("updated")].toInt(); } else { return 0; } } TtRssSubscribeToFeedResponse::TtRssSubscribeToFeedResponse(const QString& raw_content) : TtRssResponse(raw_content) {} TtRssSubscribeToFeedResponse::~TtRssSubscribeToFeedResponse() = default; int TtRssSubscribeToFeedResponse::code() const { if (m_rawContent.contains(QSL("content"))) { return m_rawContent[QSL("content")].toObject()[QSL("status")].toObject()[QSL("code")].toInt(); } else { return STF_UNKNOWN; } } TtRssUnsubscribeFeedResponse::TtRssUnsubscribeFeedResponse(const QString& raw_content) : TtRssResponse(raw_content) {} TtRssUnsubscribeFeedResponse::~TtRssUnsubscribeFeedResponse() = default; QString TtRssUnsubscribeFeedResponse::code() const { if (m_rawContent.contains(QSL("content"))) { QJsonObject map = m_rawContent[QSL("content")].toObject(); if (map.contains(QSL("error"))) { return map[QSL("error")].toString(); } else if (map.contains(QSL("status"))) { return map[QSL("status")].toString(); } } return QString(); } TtRssGetLabelsResponse::TtRssGetLabelsResponse(const QString& raw_content) : TtRssResponse(raw_content) {} QList TtRssGetLabelsResponse::labels() const { QList labels; auto json_labels = m_rawContent[QSL("content")].toArray(); // Add "Published" label. // // NOTE: In TT-RSS there is a problem with "published" feature: // 1. If user has article in existing feed, he can mark it as "published" and in // that case, the "published" behaves more like a label. // 2. If user uses feature "shareToPublished", he essentially creates new textual // note, which is then assigned to "Published feed" but can be also assigned label from 1). // // This label solves situation 1). 2) is solved in other way (creating static system feed). QString published_caption = QSL("[SYSTEM] ") + QObject::tr("Published articles"); auto* published_lbl = new Label(published_caption, TextFactory::generateColorFromText(published_caption)); published_lbl->setKeepOnTop(true); published_lbl->setCustomId(QString::number(TTRSS_PUBLISHED_LABEL_ID)); labels.append(published_lbl); for (const QJsonValue& lbl_val : qAsConst(json_labels)) { QJsonObject lbl_obj = lbl_val.toObject(); Label* lbl = new Label(lbl_obj[QSL("caption")].toString(), QColor(lbl_obj[QSL("fg_color")].toString())); lbl->setCustomId(QString::number(lbl_obj[QSL("id")].toInt())); labels.append(lbl); } return labels; } TtRssGetCompactHeadlinesResponse::TtRssGetCompactHeadlinesResponse(const QString& raw_content) : TtRssResponse(raw_content) {} TtRssGetCompactHeadlinesResponse::~TtRssGetCompactHeadlinesResponse() = default; QStringList TtRssGetCompactHeadlinesResponse::ids() const { auto json_ids = m_rawContent[QSL("content")].toArray(); QStringList msg_ids; for (const QJsonValue& id_val : qAsConst(json_ids)) { msg_ids.append(QString::number(id_val.toObject()[QSL("id")].toInt())); } return msg_ids; }