rssguard/src/librssguard/services/tt-rss/ttrssnetworkfactory.cpp
2023-09-18 13:35:13 +02:00

1205 lines
48 KiB
C++

// For license of this file, see <project-root-folder>/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 <QJsonArray>
#include <QJsonDocument>
#include <QPair>
#include <QVariant>
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<QPair<QByteArray, QByteArray>> headers;
headers << QPair<QByteArray, QByteArray>(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<QPair<QByteArray, QByteArray>> headers;
headers << QPair<QByteArray, QByteArray>(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<QPair<QByteArray, QByteArray>> headers;
headers << QPair<QByteArray, QByteArray>(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<QPair<QByteArray, QByteArray>> headers;
headers << QPair<QByteArray, QByteArray>(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<QPair<QByteArray, QByteArray>> headers;
headers << QPair<QByteArray, QByteArray>(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<QPair<QByteArray, QByteArray>> headers;
headers << QPair<QByteArray, QByteArray>(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<QPair<QByteArray, QByteArray>> headers;
headers << QPair<QByteArray, QByteArray>(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<QPair<QByteArray, QByteArray>> headers;
headers << QPair<QByteArray, QByteArray>(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<QPair<QByteArray, QByteArray>> headers;
headers << QPair<QByteArray, QByteArray>(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<QPair<QByteArray, QByteArray>> headers;
headers << QPair<QByteArray, QByteArray>(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<QPair<QByteArray, QByteArray>> headers;
headers << QPair<QByteArray, QByteArray>(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<QPair<QByteArray, QByteArray>> headers;
headers << QPair<QByteArray, QByteArray>(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<QPair<RootItem*, QJsonValue>> pairs;
pairs.reserve(items_to_process.size());
for (const QJsonValue& item : items_to_process) {
pairs.append(QPair<RootItem*, QJsonValue>(parent, item));
}
while (!pairs.isEmpty()) {
QPair<RootItem*, QJsonValue> 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<RootItem*, QJsonValue>(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<RootItem*, QJsonValue>(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<QPair<QByteArray, QByteArray>> 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<Message> TtRssGetArticleResponse::messages(ServiceRoot* root) const {
return {};
}
TtRssGetArticleResponse::~TtRssGetArticleResponse() = default;
QList<Message> TtRssGetHeadlinesResponse::messages(ServiceRoot* root) const {
QList<Message> messages;
auto active_labels = root->labelsNode() != nullptr ? root->labelsNode()->labels() : QList<Label*>();
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<qint64>(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<RootItem*> TtRssGetLabelsResponse::labels() const {
QList<RootItem*> 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;
}