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