1 // For license of this file, see <project-root-folder>/LICENSE.md.
2 
3 #include "services/tt-rss/ttrssnetworkfactory.h"
4 
5 #include "3rd-party/boolinq/boolinq.h"
6 #include "definitions/definitions.h"
7 #include "miscellaneous/application.h"
8 #include "miscellaneous/iconfactory.h"
9 #include "miscellaneous/textfactory.h"
10 #include "network-web/networkfactory.h"
11 #include "services/abstract/category.h"
12 #include "services/abstract/label.h"
13 #include "services/abstract/labelsnode.h"
14 #include "services/abstract/rootitem.h"
15 #include "services/abstract/serviceroot.h"
16 #include "services/tt-rss/definitions.h"
17 #include "services/tt-rss/ttrssfeed.h"
18 
19 #include <QJsonArray>
20 #include <QJsonDocument>
21 #include <QPair>
22 #include <QVariant>
23 
TtRssNetworkFactory()24 TtRssNetworkFactory::TtRssNetworkFactory()
25   : m_bareUrl(QString()), m_fullUrl(QString()), m_username(QString()), m_password(QString()), m_batchSize(TTRSS_DEFAULT_MESSAGES),
26   m_forceServerSideUpdate(false), m_authIsUsed(false), m_authUsername(QString()), m_authPassword(QString()), m_sessionId(QString()),
27   m_lastError(QNetworkReply::NoError) {}
28 
url() const29 QString TtRssNetworkFactory::url() const {
30   return m_bareUrl;
31 }
32 
setUrl(const QString & url)33 void TtRssNetworkFactory::setUrl(const QString& url) {
34   m_bareUrl = url;
35 
36   if (!m_bareUrl.endsWith(QSL("/"))) {
37     m_bareUrl = m_bareUrl + QSL("/");
38   }
39 
40   if (!m_bareUrl.endsWith(QSL("api/"))) {
41     m_fullUrl = m_bareUrl + QSL("api/");
42   }
43   else {
44     m_fullUrl = m_bareUrl;
45   }
46 }
47 
username() const48 QString TtRssNetworkFactory::username() const {
49   return m_username;
50 }
51 
setUsername(const QString & username)52 void TtRssNetworkFactory::setUsername(const QString& username) {
53   m_username = username;
54 }
55 
password() const56 QString TtRssNetworkFactory::password() const {
57   return m_password;
58 }
59 
setPassword(const QString & password)60 void TtRssNetworkFactory::setPassword(const QString& password) {
61   m_password = password;
62 }
63 
lastLoginTime() const64 QDateTime TtRssNetworkFactory::lastLoginTime() const {
65   return m_lastLoginTime;
66 }
67 
lastError() const68 QNetworkReply::NetworkError TtRssNetworkFactory::lastError() const {
69   return m_lastError;
70 }
71 
login(const QNetworkProxy & proxy)72 TtRssLoginResponse TtRssNetworkFactory::login(const QNetworkProxy& proxy) {
73   if (!m_sessionId.isEmpty()) {
74     qWarningNN << LOGSEC_TTRSS
75                << "Session ID is not empty before login, logging out first.";
76     logout(proxy);
77   }
78 
79   QJsonObject json;
80 
81   json[QSL("op")] = QSL("login");
82   json[QSL("user")] = m_username;
83   json[QSL("password")] = m_password;
84 
85   QByteArray result_raw;
86   QList<QPair<QByteArray, QByteArray>> headers;
87 
88   headers << QPair<QByteArray, QByteArray>(HTTP_HEADERS_CONTENT_TYPE, TTRSS_CONTENT_TYPE_JSON);
89   headers << NetworkFactory::generateBasicAuthHeader(m_authUsername, m_authPassword);
90 
91   NetworkResult network_reply = NetworkFactory::performNetworkOperation(m_fullUrl,
92                                                                         qApp->settings()->value(GROUP(Feeds),
93                                                                                                 SETTING(
94                                                                                                   Feeds::UpdateTimeout)).toInt(),
95                                                                         QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact),
96                                                                         result_raw,
97                                                                         QNetworkAccessManager::Operation::PostOperation,
98                                                                         headers,
99                                                                         false,
100                                                                         {},
101                                                                         {},
102                                                                         proxy);
103   TtRssLoginResponse login_response(QString::fromUtf8(result_raw));
104 
105   if (network_reply.first == QNetworkReply::NoError) {
106     m_sessionId = login_response.sessionId();
107     m_lastLoginTime = QDateTime::currentDateTime();
108   }
109   else {
110     qWarningNN << LOGSEC_TTRSS
111                << "Login failed with error:"
112                << QUOTE_W_SPACE_DOT(network_reply.first);
113   }
114 
115   m_lastError = network_reply.first;
116   return login_response;
117 }
118 
logout(const QNetworkProxy & proxy)119 TtRssResponse TtRssNetworkFactory::logout(const QNetworkProxy& proxy) {
120   if (!m_sessionId.isEmpty()) {
121     QJsonObject json;
122 
123     json[QSL("op")] = QSL("logout");
124     json[QSL("sid")] = m_sessionId;
125     QByteArray result_raw;
126     QList<QPair<QByteArray, QByteArray>> headers;
127 
128     headers << QPair<QByteArray, QByteArray>(HTTP_HEADERS_CONTENT_TYPE, TTRSS_CONTENT_TYPE_JSON);
129     headers << NetworkFactory::generateBasicAuthHeader(m_authUsername, m_authPassword);
130 
131     NetworkResult network_reply = NetworkFactory::performNetworkOperation(m_fullUrl,
132                                                                           qApp->settings()->value(GROUP(Feeds),
133                                                                                                   SETTING(
134                                                                                                     Feeds::UpdateTimeout)).toInt(),
135                                                                           QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact),
136                                                                           result_raw,
137                                                                           QNetworkAccessManager::Operation::PostOperation,
138                                                                           headers,
139                                                                           false,
140                                                                           {},
141                                                                           {},
142                                                                           proxy);
143 
144     m_lastError = network_reply.first;
145 
146     if (m_lastError == QNetworkReply::NoError) {
147       m_sessionId.clear();
148     }
149     else {
150       qWarningNN << LOGSEC_TTRSS
151                  << "Logout failed with error:"
152                  << QUOTE_W_SPACE_DOT(network_reply.first);
153     }
154 
155     return TtRssResponse(QString::fromUtf8(result_raw));
156   }
157   else {
158     qWarningNN << LOGSEC_TTRSS
159                << "Cannot logout because session ID is empty.";
160     m_lastError = QNetworkReply::NetworkError::NoError;
161     return TtRssResponse();
162   }
163 }
164 
getLabels(const QNetworkProxy & proxy)165 TtRssGetLabelsResponse TtRssNetworkFactory::getLabels(const QNetworkProxy& proxy) {
166   QJsonObject json;
167 
168   json[QSL("op")] = QSL("getLabels");
169   json[QSL("sid")] = m_sessionId;
170 
171   const int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
172   QByteArray result_raw;
173   QList<QPair<QByteArray, QByteArray>> headers;
174 
175   headers << QPair<QByteArray, QByteArray>(HTTP_HEADERS_CONTENT_TYPE, TTRSS_CONTENT_TYPE_JSON);
176   headers << NetworkFactory::generateBasicAuthHeader(m_authUsername, m_authPassword);
177 
178   NetworkResult network_reply = NetworkFactory::performNetworkOperation(m_fullUrl, timeout,
179                                                                         QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact),
180                                                                         result_raw,
181                                                                         QNetworkAccessManager::Operation::PostOperation,
182                                                                         headers);
183   TtRssGetLabelsResponse result(QString::fromUtf8(result_raw));
184 
185   if (result.isNotLoggedIn()) {
186     // We are not logged in.
187     login(proxy);
188     json[QSL("sid")] = m_sessionId;
189     network_reply = NetworkFactory::performNetworkOperation(m_fullUrl,
190                                                             timeout,
191                                                             QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact),
192                                                             result_raw,
193                                                             QNetworkAccessManager::Operation::PostOperation,
194                                                             headers,
195                                                             false,
196                                                             {},
197                                                             {},
198                                                             proxy);
199     result = TtRssGetLabelsResponse(QString::fromUtf8(result_raw));
200   }
201 
202   if (network_reply.first != QNetworkReply::NoError) {
203     qWarningNN << LOGSEC_TTRSS
204                << "getLabels failed with error:"
205                << QUOTE_W_SPACE_DOT(network_reply.first);
206   }
207 
208   m_lastError = network_reply.first;
209   return result;
210 }
211 
getFeedsCategories(const QNetworkProxy & proxy)212 TtRssGetFeedsCategoriesResponse TtRssNetworkFactory::getFeedsCategories(const QNetworkProxy& proxy) {
213   QJsonObject json;
214 
215   json[QSL("op")] = QSL("getFeedTree");
216   json[QSL("sid")] = m_sessionId;
217   json[QSL("include_empty")] = true;
218   const int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
219   QByteArray result_raw;
220   QList<QPair<QByteArray, QByteArray>> headers;
221 
222   headers << QPair<QByteArray, QByteArray>(HTTP_HEADERS_CONTENT_TYPE, TTRSS_CONTENT_TYPE_JSON);
223   headers << NetworkFactory::generateBasicAuthHeader(m_authUsername, m_authPassword);
224 
225   NetworkResult network_reply = NetworkFactory::performNetworkOperation(m_fullUrl, timeout,
226                                                                         QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact),
227                                                                         result_raw,
228                                                                         QNetworkAccessManager::Operation::PostOperation,
229                                                                         headers,
230                                                                         false,
231                                                                         {},
232                                                                         {},
233                                                                         proxy);
234   TtRssGetFeedsCategoriesResponse result(QString::fromUtf8(result_raw));
235 
236   if (result.isNotLoggedIn()) {
237     // We are not logged in.
238     login(proxy);
239     json[QSL("sid")] = m_sessionId;
240     network_reply = NetworkFactory::performNetworkOperation(m_fullUrl,
241                                                             timeout,
242                                                             QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact),
243                                                             result_raw,
244                                                             QNetworkAccessManager::Operation::PostOperation,
245                                                             headers,
246                                                             false,
247                                                             {},
248                                                             {},
249                                                             proxy);
250     result = TtRssGetFeedsCategoriesResponse(QString::fromUtf8(result_raw));
251   }
252 
253   if (network_reply.first != QNetworkReply::NoError) {
254     qWarningNN << LOGSEC_TTRSS
255                << "getFeedTree failed with error:"
256                << QUOTE_W_SPACE_DOT(network_reply.first);
257   }
258 
259   m_lastError = network_reply.first;
260   return result;
261 }
262 
getHeadlines(int feed_id,int limit,int skip,bool show_content,bool include_attachments,bool sanitize,bool unread_only,const QNetworkProxy & proxy)263 TtRssGetHeadlinesResponse TtRssNetworkFactory::getHeadlines(int feed_id, int limit, int skip,
264                                                             bool show_content, bool include_attachments,
265                                                             bool sanitize, bool unread_only,
266                                                             const QNetworkProxy& proxy) {
267   QJsonObject json;
268 
269   json[QSL("op")] = QSL("getHeadlines");
270   json[QSL("sid")] = m_sessionId;
271   json[QSL("feed_id")] = feed_id;
272   json[QSL("force_update")] = m_forceServerSideUpdate;
273   json[QSL("limit")] = limit;
274   json[QSL("skip")] = skip;
275   json[QSL("view_mode")] = unread_only ? QSL("unread") : QSL("all_articles");
276   json[QSL("show_content")] = show_content;
277   json[QSL("include_attachments")] = include_attachments;
278   json[QSL("sanitize")] = sanitize;
279   const int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
280   QByteArray result_raw;
281   QList<QPair<QByteArray, QByteArray>> headers;
282 
283   headers << QPair<QByteArray, QByteArray>(HTTP_HEADERS_CONTENT_TYPE, TTRSS_CONTENT_TYPE_JSON);
284   headers << NetworkFactory::generateBasicAuthHeader(m_authUsername, m_authPassword);
285 
286   NetworkResult network_reply = NetworkFactory::performNetworkOperation(m_fullUrl,
287                                                                         timeout,
288                                                                         QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact),
289                                                                         result_raw,
290                                                                         QNetworkAccessManager::Operation::PostOperation,
291                                                                         headers,
292                                                                         false,
293                                                                         {},
294                                                                         {},
295                                                                         proxy);
296   TtRssGetHeadlinesResponse result(QString::fromUtf8(result_raw));
297 
298   if (result.isNotLoggedIn()) {
299     // We are not logged in.
300     login(proxy);
301     json[QSL("sid")] = m_sessionId;
302     network_reply = NetworkFactory::performNetworkOperation(m_fullUrl,
303                                                             timeout,
304                                                             QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact),
305                                                             result_raw,
306                                                             QNetworkAccessManager::Operation::PostOperation,
307                                                             headers,
308                                                             false,
309                                                             {},
310                                                             {},
311                                                             proxy);
312     result = TtRssGetHeadlinesResponse(QString::fromUtf8(result_raw));
313   }
314 
315   if (network_reply.first != QNetworkReply::NoError) {
316     qWarningNN << LOGSEC_TTRSS
317                << "getHeadlines failed with error:"
318                << QUOTE_W_SPACE_DOT(network_reply.first);
319   }
320 
321   m_lastError = network_reply.first;
322   return result;
323 }
324 
setArticleLabel(const QStringList & article_ids,const QString & label_custom_id,bool assign,const QNetworkProxy & proxy)325 TtRssResponse TtRssNetworkFactory::setArticleLabel(const QStringList& article_ids, const QString& label_custom_id,
326                                                    bool assign, const QNetworkProxy& proxy) {
327   QJsonObject json;
328 
329   json[QSL("op")] = QSL("setArticleLabel");
330   json[QSL("sid")] = m_sessionId;
331   json[QSL("article_ids")] = article_ids.join(QSL(","));
332   json[QSL("label_id")] = label_custom_id.toInt();
333   json[QSL("assign")] = assign;
334 
335   const int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
336   QByteArray result_raw;
337   QList<QPair<QByteArray, QByteArray>> headers;
338 
339   headers << QPair<QByteArray, QByteArray>(HTTP_HEADERS_CONTENT_TYPE, TTRSS_CONTENT_TYPE_JSON);
340   headers << NetworkFactory::generateBasicAuthHeader(m_authUsername, m_authPassword);
341 
342   NetworkResult network_reply = NetworkFactory::performNetworkOperation(m_fullUrl,
343                                                                         timeout,
344                                                                         QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact),
345                                                                         result_raw,
346                                                                         QNetworkAccessManager::Operation::PostOperation,
347                                                                         headers,
348                                                                         false,
349                                                                         {},
350                                                                         {},
351                                                                         proxy);
352   TtRssResponse result(QString::fromUtf8(result_raw));
353 
354   if (result.isNotLoggedIn()) {
355     // We are not logged in.
356     login(proxy);
357     json[QSL("sid")] = m_sessionId;
358     network_reply = NetworkFactory::performNetworkOperation(m_fullUrl,
359                                                             timeout,
360                                                             QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact),
361                                                             result_raw,
362                                                             QNetworkAccessManager::Operation::PostOperation,
363                                                             headers,
364                                                             false,
365                                                             {},
366                                                             {},
367                                                             proxy);
368     result = TtRssResponse(QString::fromUtf8(result_raw));
369   }
370 
371   if (network_reply.first != QNetworkReply::NoError) {
372     qWarningNN << LOGSEC_TTRSS
373                << "setArticleLabel failed with error"
374                << QUOTE_W_SPACE_DOT(network_reply.first);
375   }
376 
377   m_lastError = network_reply.first;
378   return result;
379 }
380 
updateArticles(const QStringList & ids,UpdateArticle::OperatingField field,UpdateArticle::Mode mode,const QNetworkProxy & proxy)381 TtRssUpdateArticleResponse TtRssNetworkFactory::updateArticles(const QStringList& ids,
382                                                                UpdateArticle::OperatingField field,
383                                                                UpdateArticle::Mode mode,
384                                                                const QNetworkProxy& proxy) {
385   QJsonObject json;
386 
387   json[QSL("op")] = QSL("updateArticle");
388   json[QSL("sid")] = m_sessionId;
389   json[QSL("article_ids")] = ids.join(QSL(","));
390   json[QSL("mode")] = int(mode);
391   json[QSL("field")] = int(field);
392 
393   const int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
394   QByteArray result_raw;
395   QList<QPair<QByteArray, QByteArray>> headers;
396 
397   headers << QPair<QByteArray, QByteArray>(HTTP_HEADERS_CONTENT_TYPE, TTRSS_CONTENT_TYPE_JSON);
398   headers << NetworkFactory::generateBasicAuthHeader(m_authUsername, m_authPassword);
399 
400   NetworkResult network_reply = NetworkFactory::performNetworkOperation(m_fullUrl,
401                                                                         timeout,
402                                                                         QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact),
403                                                                         result_raw,
404                                                                         QNetworkAccessManager::Operation::PostOperation,
405                                                                         headers,
406                                                                         false,
407                                                                         {},
408                                                                         {},
409                                                                         proxy);
410   TtRssUpdateArticleResponse result(QString::fromUtf8(result_raw));
411 
412   if (result.isNotLoggedIn()) {
413     // We are not logged in.
414     login(proxy);
415     json[QSL("sid")] = m_sessionId;
416     network_reply = NetworkFactory::performNetworkOperation(m_fullUrl,
417                                                             timeout,
418                                                             QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact),
419                                                             result_raw,
420                                                             QNetworkAccessManager::Operation::PostOperation,
421                                                             headers,
422                                                             false,
423                                                             {},
424                                                             {},
425                                                             proxy);
426     result = TtRssUpdateArticleResponse(QString::fromUtf8(result_raw));
427   }
428 
429   if (network_reply.first != QNetworkReply::NoError) {
430     qWarningNN << LOGSEC_TTRSS
431                << "updateArticle failed with error"
432                << QUOTE_W_SPACE_DOT(network_reply.first);
433   }
434 
435   m_lastError = network_reply.first;
436   return result;
437 }
438 
subscribeToFeed(const QString & url,int category_id,const QNetworkProxy & proxy,bool protectd,const QString & username,const QString & password)439 TtRssSubscribeToFeedResponse TtRssNetworkFactory::subscribeToFeed(const QString& url, int category_id,
440                                                                   const QNetworkProxy& proxy,
441                                                                   bool protectd, const QString& username,
442                                                                   const QString& password) {
443   QJsonObject json;
444 
445   json[QSL("op")] = QSL("subscribeToFeed");
446   json[QSL("sid")] = m_sessionId;
447   json[QSL("feed_url")] = url;
448   json[QSL("category_id")] = category_id;
449 
450   if (protectd) {
451     json[QSL("login")] = username;
452     json[QSL("password")] = password;
453   }
454 
455   const int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
456   QByteArray result_raw;
457   QList<QPair<QByteArray, QByteArray>> headers;
458 
459   headers << QPair<QByteArray, QByteArray>(HTTP_HEADERS_CONTENT_TYPE, TTRSS_CONTENT_TYPE_JSON);
460   headers << NetworkFactory::generateBasicAuthHeader(m_authUsername, m_authPassword);
461 
462   NetworkResult network_reply = NetworkFactory::performNetworkOperation(m_fullUrl,
463                                                                         timeout,
464                                                                         QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact),
465                                                                         result_raw,
466                                                                         QNetworkAccessManager::Operation::PostOperation,
467                                                                         headers,
468                                                                         false,
469                                                                         {},
470                                                                         {},
471                                                                         proxy);
472   TtRssSubscribeToFeedResponse result(QString::fromUtf8(result_raw));
473 
474   if (result.isNotLoggedIn()) {
475     // We are not logged in.
476     login(proxy);
477     json[QSL("sid")] = m_sessionId;
478     network_reply = NetworkFactory::performNetworkOperation(m_fullUrl,
479                                                             timeout,
480                                                             QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact),
481                                                             result_raw,
482                                                             QNetworkAccessManager::Operation::PostOperation,
483                                                             headers,
484                                                             false,
485                                                             {},
486                                                             {},
487                                                             proxy);
488     result = TtRssSubscribeToFeedResponse(QString::fromUtf8(result_raw));
489   }
490 
491   if (network_reply.first != QNetworkReply::NoError) {
492     qWarningNN << LOGSEC_TTRSS
493                << "updateArticle failed with error"
494                << QUOTE_W_SPACE_DOT(network_reply.first);
495   }
496 
497   m_lastError = network_reply.first;
498   return result;
499 }
500 
unsubscribeFeed(int feed_id,const QNetworkProxy & proxy)501 TtRssUnsubscribeFeedResponse TtRssNetworkFactory::unsubscribeFeed(int feed_id, const QNetworkProxy& proxy) {
502   QJsonObject json;
503 
504   json[QSL("op")] = QSL("unsubscribeFeed");
505   json[QSL("sid")] = m_sessionId;
506   json[QSL("feed_id")] = feed_id;
507   const int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
508   QByteArray result_raw;
509   QList<QPair<QByteArray, QByteArray>> headers;
510 
511   headers << QPair<QByteArray, QByteArray>(HTTP_HEADERS_CONTENT_TYPE, TTRSS_CONTENT_TYPE_JSON);
512   headers << NetworkFactory::generateBasicAuthHeader(m_authUsername, m_authPassword);
513 
514   NetworkResult network_reply = NetworkFactory::performNetworkOperation(m_fullUrl,
515                                                                         timeout,
516                                                                         QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact),
517                                                                         result_raw,
518                                                                         QNetworkAccessManager::Operation::PostOperation,
519                                                                         headers,
520                                                                         false,
521                                                                         {},
522                                                                         {},
523                                                                         proxy);
524   TtRssUnsubscribeFeedResponse result(QString::fromUtf8(result_raw));
525 
526   if (result.isNotLoggedIn()) {
527     // We are not logged in.
528     login(proxy);
529     json[QSL("sid")] = m_sessionId;
530     network_reply = NetworkFactory::performNetworkOperation(m_fullUrl,
531                                                             timeout,
532                                                             QJsonDocument(json).toJson(QJsonDocument::JsonFormat::Compact),
533                                                             result_raw,
534                                                             QNetworkAccessManager::Operation::PostOperation,
535                                                             headers,
536                                                             false,
537                                                             {},
538                                                             {},
539                                                             proxy);
540     result = TtRssUnsubscribeFeedResponse(QString::fromUtf8(result_raw));
541   }
542 
543   if (network_reply.first != QNetworkReply::NoError) {
544     qWarningNN << LOGSEC_TTRSS
545                << "getFeeds failed with error"
546                << QUOTE_W_SPACE_DOT(network_reply.first);
547   }
548 
549   m_lastError = network_reply.first;
550   return result;
551 }
552 
batchSize() const553 int TtRssNetworkFactory::batchSize() const {
554   return m_batchSize;
555 }
556 
setBatchSize(int batch_size)557 void TtRssNetworkFactory::setBatchSize(int batch_size) {
558   m_batchSize = batch_size;
559 }
560 
downloadOnlyUnreadMessages() const561 bool TtRssNetworkFactory::downloadOnlyUnreadMessages() const {
562   return m_downloadOnlyUnreadMessages;
563 }
564 
setDownloadOnlyUnreadMessages(bool download_only_unread_messages)565 void TtRssNetworkFactory::setDownloadOnlyUnreadMessages(bool download_only_unread_messages) {
566   m_downloadOnlyUnreadMessages = download_only_unread_messages;
567 }
568 
forceServerSideUpdate() const569 bool TtRssNetworkFactory::forceServerSideUpdate() const {
570   return m_forceServerSideUpdate;
571 }
572 
setForceServerSideUpdate(bool force_server_side_update)573 void TtRssNetworkFactory::setForceServerSideUpdate(bool force_server_side_update) {
574   m_forceServerSideUpdate = force_server_side_update;
575 }
576 
authIsUsed() const577 bool TtRssNetworkFactory::authIsUsed() const {
578   return m_authIsUsed;
579 }
580 
setAuthIsUsed(bool auth_is_used)581 void TtRssNetworkFactory::setAuthIsUsed(bool auth_is_used) {
582   m_authIsUsed = auth_is_used;
583 }
584 
authUsername() const585 QString TtRssNetworkFactory::authUsername() const {
586   return m_authUsername;
587 }
588 
setAuthUsername(const QString & auth_username)589 void TtRssNetworkFactory::setAuthUsername(const QString& auth_username) {
590   m_authUsername = auth_username;
591 }
592 
authPassword() const593 QString TtRssNetworkFactory::authPassword() const {
594   return m_authPassword;
595 }
596 
setAuthPassword(const QString & auth_password)597 void TtRssNetworkFactory::setAuthPassword(const QString& auth_password) {
598   m_authPassword = auth_password;
599 }
600 
TtRssResponse(const QString & raw_content)601 TtRssResponse::TtRssResponse(const QString& raw_content) {
602   m_rawContent = QJsonDocument::fromJson(raw_content.toUtf8()).object();
603 }
604 
605 TtRssResponse::~TtRssResponse() = default;
606 
isLoaded() const607 bool TtRssResponse::isLoaded() const {
608   return !m_rawContent.isEmpty();
609 }
610 
seq() const611 int TtRssResponse::seq() const {
612   if (!isLoaded()) {
613     return TTRSS_CONTENT_NOT_LOADED;
614   }
615   else {
616     return m_rawContent[QSL("seq")].toInt();
617   }
618 }
619 
status() const620 int TtRssResponse::status() const {
621   if (!isLoaded()) {
622     return TTRSS_CONTENT_NOT_LOADED;
623   }
624   else {
625     return m_rawContent[QSL("status")].toInt();
626   }
627 }
628 
isNotLoggedIn() const629 bool TtRssResponse::isNotLoggedIn() const {
630   return status() == TTRSS_API_STATUS_ERR && hasError() && error() == QSL(TTRSS_NOT_LOGGED_IN);
631 }
632 
toString() const633 QString TtRssResponse::toString() const {
634   return QJsonDocument(m_rawContent).toJson(QJsonDocument::JsonFormat::Compact);
635 }
636 
TtRssLoginResponse(const QString & raw_content)637 TtRssLoginResponse::TtRssLoginResponse(const QString& raw_content) : TtRssResponse(raw_content) {}
638 
639 TtRssLoginResponse::~TtRssLoginResponse() = default;
apiLevel() const640 int TtRssLoginResponse::apiLevel() const {
641   if (!isLoaded()) {
642     return TTRSS_CONTENT_NOT_LOADED;
643   }
644   else {
645     return m_rawContent[QSL("content")].toObject()[QSL("api_level")].toInt();
646   }
647 }
648 
sessionId() const649 QString TtRssLoginResponse::sessionId() const {
650   if (!isLoaded()) {
651     return QString();
652   }
653   else {
654     return m_rawContent[QSL("content")].toObject()[QSL("session_id")].toString();
655   }
656 }
657 
error() const658 QString TtRssResponse::error() const {
659   if (!isLoaded()) {
660     return QString();
661   }
662   else {
663     return m_rawContent[QSL("content")].toObject()[QSL("error")].toString();
664   }
665 }
666 
hasError() const667 bool TtRssResponse::hasError() const {
668   if (!isLoaded()) {
669     return false;
670   }
671   else {
672     return m_rawContent[QSL("content")].toObject().contains(QSL("error"));
673   }
674 }
675 
TtRssGetFeedsCategoriesResponse(const QString & raw_content)676 TtRssGetFeedsCategoriesResponse::TtRssGetFeedsCategoriesResponse(const QString& raw_content) : TtRssResponse(raw_content) {}
677 
678 TtRssGetFeedsCategoriesResponse::~TtRssGetFeedsCategoriesResponse() = default;
679 
feedsCategories(bool obtain_icons,const QNetworkProxy & proxy,const QString & base_address) const680 RootItem* TtRssGetFeedsCategoriesResponse::feedsCategories(bool obtain_icons, const QNetworkProxy& proxy, const QString& base_address) const {
681   auto* parent = new RootItem();
682 
683   // Chop the "api/" from the end of the address.
684   qDebugNN << LOGSEC_TTRSS
685            << "Base address to get feed icons is"
686            << QUOTE_W_SPACE_DOT(base_address);
687 
688   if (status() == TTRSS_API_STATUS_OK) {
689     // We have data, construct object tree according to data.
690     QJsonArray items_to_process = m_rawContent[QSL("content")].toObject()[QSL("categories")].toObject()[QSL("items")].toArray();
691     QVector<QPair<RootItem*, QJsonValue>> pairs; pairs.reserve(items_to_process.size());
692 
693     for (const QJsonValue& item : items_to_process) {
694       pairs.append(QPair<RootItem*, QJsonValue>(parent, item));
695     }
696 
697     while (!pairs.isEmpty()) {
698       QPair<RootItem*, QJsonValue> pair = pairs.takeFirst();
699       RootItem* act_parent = pair.first;
700       QJsonObject item = pair.second.toObject();
701       int item_id = item[QSL("bare_id")].toInt();
702       bool is_category = item.contains(QSL("type")) && item[QSL("type")].toString() == QSL(TTRSS_GFT_TYPE_CATEGORY);
703 
704       if (item_id >= 0) {
705         if (is_category) {
706           if (item_id == 0) {
707             // This is "Uncategorized" category, all its feeds belong to top-level root.
708             if (item.contains(QSL("items"))) {
709               auto ite = item[QSL("items")].toArray();
710 
711               for (const QJsonValue& child_feed : qAsConst(ite)) {
712                 pairs.append(QPair<RootItem*, QJsonValue>(parent, child_feed));
713               }
714             }
715           }
716           else {
717             auto* category = new Category();
718 
719             category->setTitle(item[QSL("name")].toString());
720             category->setCustomId(QString::number(item_id));
721             act_parent->appendChild(category);
722 
723             if (item.contains(QSL("items"))) {
724               auto ite = item[QSL("items")].toArray();
725 
726               for (const QJsonValue& child : qAsConst(ite)) {
727                 pairs.append(QPair<RootItem*, QJsonValue>(category, child));
728               }
729             }
730           }
731         }
732         else {
733           // We have feed.
734           auto* feed = new TtRssFeed();
735 
736           if (obtain_icons) {
737             QString icon_path = item[QSL("icon")].type() == QJsonValue::String ? item[QSL("icon")].toString() : QString();
738 
739             if (!icon_path.isEmpty()) {
740               // Chop the "api/" suffix out and append
741               QString full_icon_address = base_address + QL1C('/') + icon_path;
742               QIcon icon;
743               auto res = NetworkFactory::downloadIcon({ { full_icon_address, true } },
744                                                       DOWNLOAD_TIMEOUT,
745                                                       icon,
746                                                       proxy);
747 
748               if (res == QNetworkReply::NoError) {
749                 feed->setIcon(icon);
750               }
751               else {
752                 qWarningNN << LOGSEC_TTRSS
753                            << "Failed to download icon with error"
754                            << QUOTE_W_SPACE_DOT(res);
755               }
756             }
757           }
758 
759           feed->setTitle(item[QSL("name")].toString());
760           feed->setCustomId(QString::number(item_id));
761           act_parent->appendChild(feed);
762         }
763       }
764     }
765   }
766 
767   return parent;
768 }
769 
TtRssGetHeadlinesResponse(const QString & raw_content)770 TtRssGetHeadlinesResponse::TtRssGetHeadlinesResponse(const QString& raw_content) : TtRssResponse(raw_content) {}
771 
772 TtRssGetHeadlinesResponse::~TtRssGetHeadlinesResponse() = default;
773 
messages(ServiceRoot * root) const774 QList<Message> TtRssGetHeadlinesResponse::messages(ServiceRoot* root) const {
775   QList<Message> messages;
776   auto active_labels = root->labelsNode() != nullptr ? root->labelsNode()->labels() : QList<Label*>();
777   auto json_msgs = m_rawContent[QSL("content")].toArray();
778 
779   for (const QJsonValue& item : qAsConst(json_msgs)) {
780     QJsonObject mapped = item.toObject();
781     Message message;
782 
783     message.m_author = mapped[QSL("author")].toString();
784     message.m_isRead = !mapped[QSL("unread")].toBool();
785     message.m_isImportant = mapped[QSL("marked")].toBool();
786     message.m_contents = mapped[QSL("content")].toString();
787     message.m_rawContents = QJsonDocument(mapped).toJson(QJsonDocument::JsonFormat::Compact);
788 
789     auto json_labels = mapped[QSL("labels")].toArray();
790 
791     for (const QJsonValue& lbl_val : qAsConst(json_labels)) {
792       QString lbl_custom_id = QString::number(lbl_val.toArray().at(0).toInt());
793       Label* label = boolinq::from(active_labels.begin(), active_labels.end()).firstOrDefault([lbl_custom_id](Label* lbl) {
794         return lbl->customId() == lbl_custom_id;
795       });
796 
797       if (label != nullptr) {
798         message.m_assignedLabels.append(label);
799       }
800       else {
801         qWarningNN << LOGSEC_TTRSS << "Label with custom ID" << QUOTE_W_SPACE(lbl_custom_id)
802                    << "was not found. Maybe you need to perform sync-in to download it from server.";
803       }
804     }
805 
806     // Multiply by 1000 because Tiny Tiny RSS API does not include miliseconds in Unix
807     // date/time number.
808     const qint64 t = static_cast<qint64>(mapped[QSL("updated")].toDouble()) * 1000;
809 
810     message.m_created = TextFactory::parseDateTime(t);
811     message.m_createdFromFeed = true;
812     message.m_customId = QString::number(mapped[QSL("id")].toInt());
813     message.m_feedId = mapped[QSL("feed_id")].toString();
814     message.m_title = mapped[QSL("title")].toString();
815     message.m_url = mapped[QSL("link")].toString();
816 
817     if (mapped.contains(QSL("attachments"))) {
818       // Process enclosures.
819       auto json_att = mapped[QSL("attachments")].toArray();
820 
821       for (const QJsonValue& attachment : qAsConst(json_att)) {
822         QJsonObject mapped_attachemnt = attachment.toObject();
823         Enclosure enclosure;
824 
825         enclosure.m_mimeType = mapped_attachemnt[QSL("content_type")].toString();
826         enclosure.m_url = mapped_attachemnt[QSL("content_url")].toString();
827         message.m_enclosures.append(enclosure);
828       }
829     }
830 
831     messages.append(message);
832   }
833 
834   return messages;
835 }
836 
TtRssUpdateArticleResponse(const QString & raw_content)837 TtRssUpdateArticleResponse::TtRssUpdateArticleResponse(const QString& raw_content) : TtRssResponse(raw_content) {}
838 
839 TtRssUpdateArticleResponse::~TtRssUpdateArticleResponse() = default;
840 
updateStatus() const841 QString TtRssUpdateArticleResponse::updateStatus() const {
842   if (m_rawContent.contains(QSL("content"))) {
843     return m_rawContent[QSL("content")].toObject()[QSL("status")].toString();
844   }
845   else {
846     return QString();
847   }
848 }
849 
articlesUpdated() const850 int TtRssUpdateArticleResponse::articlesUpdated() const {
851   if (m_rawContent.contains(QSL("content"))) {
852     return m_rawContent[QSL("content")].toObject()[QSL("updated")].toInt();
853   }
854   else {
855     return 0;
856   }
857 }
858 
TtRssSubscribeToFeedResponse(const QString & raw_content)859 TtRssSubscribeToFeedResponse::TtRssSubscribeToFeedResponse(const QString& raw_content) : TtRssResponse(raw_content) {}
860 
861 TtRssSubscribeToFeedResponse::~TtRssSubscribeToFeedResponse() = default;
code() const862 int TtRssSubscribeToFeedResponse::code() const {
863   if (m_rawContent.contains(QSL("content"))) {
864     return m_rawContent[QSL("content")].toObject()[QSL("status")].toObject()[QSL("code")].toInt();
865   }
866   else {
867     return STF_UNKNOWN;
868   }
869 }
870 
TtRssUnsubscribeFeedResponse(const QString & raw_content)871 TtRssUnsubscribeFeedResponse::TtRssUnsubscribeFeedResponse(const QString& raw_content) : TtRssResponse(raw_content) {}
872 
873 TtRssUnsubscribeFeedResponse::~TtRssUnsubscribeFeedResponse() = default;
code() const874 QString TtRssUnsubscribeFeedResponse::code() const {
875   if (m_rawContent.contains(QSL("content"))) {
876     QJsonObject map = m_rawContent[QSL("content")].toObject();
877 
878     if (map.contains(QSL("error"))) {
879       return map[QSL("error")].toString();
880     }
881     else if (map.contains(QSL("status"))) {
882       return map[QSL("status")].toString();
883     }
884   }
885 
886   return QString();
887 }
888 
TtRssGetLabelsResponse(const QString & raw_content)889 TtRssGetLabelsResponse::TtRssGetLabelsResponse(const QString& raw_content) : TtRssResponse(raw_content) {}
890 
labels() const891 QList<RootItem*> TtRssGetLabelsResponse::labels() const {
892   QList<RootItem*> labels;
893   auto json_labels = m_rawContent[QSL("content")].toArray();
894 
895   for (const QJsonValue& lbl_val : qAsConst(json_labels)) {
896     QJsonObject lbl_obj = lbl_val.toObject();
897     Label* lbl = new Label(lbl_obj[QSL("caption")].toString(), QColor(lbl_obj[QSL("fg_color")].toString()));
898 
899     lbl->setCustomId(QString::number(lbl_obj[QSL("id")].toInt()));
900     labels.append(lbl);
901   }
902 
903   return labels;
904 }
905