diff --git a/resources/desktop/com.github.rssguard.appdata.xml b/resources/desktop/com.github.rssguard.appdata.xml index 573984ec7..17d7aee89 100644 --- a/resources/desktop/com.github.rssguard.appdata.xml +++ b/resources/desktop/com.github.rssguard.appdata.xml @@ -26,7 +26,7 @@ https://github.com/sponsors/martinrotter - + none diff --git a/resources/graphics/misc/miniflux.png b/resources/graphics/misc/miniflux.png new file mode 100755 index 000000000..4fc36f008 Binary files /dev/null and b/resources/graphics/misc/miniflux.png differ diff --git a/resources/rssguard.qrc b/resources/rssguard.qrc index 03cd65c7b..5b5e4770f 100644 --- a/resources/rssguard.qrc +++ b/resources/rssguard.qrc @@ -38,6 +38,7 @@ graphics/misc/image-placeholder.png graphics/misc/image-placeholder-error.png graphics/misc/inoreader.png + graphics/misc/miniflux.png graphics/misc/newsblur.png graphics/misc/nextcloud.png graphics/misc/reddit.png diff --git a/src/librssguard/services/gmail/gmailnetworkfactory.cpp b/src/librssguard/services/gmail/gmailnetworkfactory.cpp index 650d35c4a..cc0229760 100644 --- a/src/librssguard/services/gmail/gmailnetworkfactory.cpp +++ b/src/librssguard/services/gmail/gmailnetworkfactory.cpp @@ -26,11 +26,14 @@ #include #include -GmailNetworkFactory::GmailNetworkFactory(QObject* parent) : QObject(parent), - m_service(nullptr), m_username(QString()), m_batchSize(GMAIL_DEFAULT_BATCH_SIZE), - m_downloadOnlyUnreadMessages(false), - m_oauth2(new OAuth2Service(QSL(GMAIL_OAUTH_AUTH_URL), QSL(GMAIL_OAUTH_TOKEN_URL), - {}, {}, QSL(GMAIL_OAUTH_SCOPE), this)) { +GmailNetworkFactory::GmailNetworkFactory(QObject* parent) + : QObject(parent), m_service(nullptr), m_username(QString()), m_batchSize(GMAIL_DEFAULT_BATCH_SIZE), + m_downloadOnlyUnreadMessages(false), m_oauth2(new OAuth2Service(QSL(GMAIL_OAUTH_AUTH_URL), + QSL(GMAIL_OAUTH_TOKEN_URL), + {}, + {}, + QSL(GMAIL_OAUTH_SCOPE), + this)) { initializeOauth(); } @@ -54,19 +57,19 @@ void GmailNetworkFactory::setBatchSize(int batch_size) { m_batchSize = batch_size; } -QString GmailNetworkFactory::sendEmail(Mimesis::Message msg, const QNetworkProxy& custom_proxy, Message* reply_to_message) { +QString GmailNetworkFactory::sendEmail(Mimesis::Message msg, + const QNetworkProxy& custom_proxy, + Message* reply_to_message) { QString bearer = m_oauth2->bearer().toLocal8Bit(); if (bearer.isEmpty()) { - //throw ApplicationException(tr("you aren't logged in")); + // throw ApplicationException(tr("you aren't logged in")); } if (reply_to_message != nullptr) { // We need to obtain some extra information. - auto metadata = getMessageMetadata(reply_to_message->m_customId, { - QSL("References"), - QSL("Message-ID") - }, custom_proxy); + auto metadata = + getMessageMetadata(reply_to_message->m_customId, {QSL("References"), QSL("Message-ID")}, custom_proxy); if (metadata.contains(QSL("Message-ID"))) { msg["References"] = metadata.value(QSL("Message-ID")).toStdString(); @@ -120,23 +123,23 @@ void GmailNetworkFactory::initializeOauth() { m_oauth2->setClientSecretSecret(TextFactory::decrypt(QSL(GMAIL_CLIENT_SECRET), OAUTH_DECRYPTION_KEY)); #endif - m_oauth2->setRedirectUrl(QSL(OAUTH_REDIRECT_URI) + - QL1C(':') + - QString::number(GMAIL_OAUTH_REDIRECT_URI_PORT), - true); + m_oauth2->setRedirectUrl(QSL(OAUTH_REDIRECT_URI) + QL1C(':') + QString::number(GMAIL_OAUTH_REDIRECT_URI_PORT), true); connect(m_oauth2, &OAuth2Service::tokensRetrieveError, this, &GmailNetworkFactory::onTokensError); connect(m_oauth2, &OAuth2Service::authFailed, this, &GmailNetworkFactory::onAuthFailed); - connect(m_oauth2, &OAuth2Service::tokensRetrieved, this, [this](QString access_token, QString refresh_token, int expires_in) { - Q_UNUSED(expires_in) - Q_UNUSED(access_token) + connect(m_oauth2, + &OAuth2Service::tokensRetrieved, + this, + [this](QString access_token, QString refresh_token, int expires_in) { + Q_UNUSED(expires_in) + Q_UNUSED(access_token) - if (m_service != nullptr && !refresh_token.isEmpty()) { - QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className()); + if (m_service != nullptr && !refresh_token.isEmpty()) { + QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className()); - DatabaseQueries::storeNewOauthTokens(database, refresh_token, m_service->accountId()); - } - }); + DatabaseQueries::storeNewOauthTokens(database, refresh_token, m_service->accountId()); + } + }); } bool GmailNetworkFactory::downloadOnlyUnreadMessages() const { @@ -197,8 +200,7 @@ QList GmailNetworkFactory::messages(const QString& stream_id, } } catch (const NetworkException& net_ex) { - qCriticalNN << LOGSEC_GMAIL - << "Failed to get list of e-mail IDs:" << QUOTE_W_SPACE_DOT(net_ex.message()); + qCriticalNN << LOGSEC_GMAIL << "Failed to get list of e-mail IDs:" << QUOTE_W_SPACE_DOT(net_ex.message()); return {}; } @@ -300,7 +302,8 @@ QNetworkReply::NetworkError GmailNetworkFactory::markMessagesRead(RootItem::Read false, {}, {}, - custom_proxy).m_networkError; + custom_proxy) + .m_networkError; if (result != QNetworkReply::NetworkError::NoError) { return result; @@ -359,7 +362,8 @@ QNetworkReply::NetworkError GmailNetworkFactory::markMessagesStarred(RootItem::I false, {}, {}, - custom_proxy).m_networkError; + custom_proxy) + .m_networkError; if (result != QNetworkReply::NetworkError::NoError) { return result; @@ -413,29 +417,29 @@ QStringList GmailNetworkFactory::list(const QString& stream_id, } QByteArray messages_raw_data; - auto netw = NetworkFactory::performNetworkOperation(target_url, - timeout, - {}, - messages_raw_data, - QNetworkAccessManager::Operation::GetOperation, - { { QSL(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(), - bearer.toLocal8Bit() } }, - false, - {}, - {}, - custom_proxy); + auto netw = + NetworkFactory::performNetworkOperation(target_url, + timeout, + {}, + messages_raw_data, + QNetworkAccessManager::Operation::GetOperation, + {{QSL(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(), bearer.toLocal8Bit()}}, + false, + {}, + {}, + custom_proxy); if (netw.m_networkError == QNetworkReply::NetworkError::NoError) { // We parse this chunk. QString messages_data = QString::fromUtf8(messages_raw_data); message_ids << decodeLiteMessages(messages_data, next_page_token); - } else { throw NetworkException(netw.m_networkError, tr("failed to download IDs of e-mail messages")); } - } while (!next_page_token.isEmpty() && (max_results <= 0 || message_ids.size() < max_results)); + } + while (!next_page_token.isEmpty() && (max_results <= 0 || message_ids.size() < max_results)); return message_ids; } @@ -463,7 +467,8 @@ QVariantHash GmailNetworkFactory::getProfile(const QNetworkProxy& custom_proxy) false, {}, {}, - custom_proxy).m_networkError; + custom_proxy) + .m_networkError; if (result != QNetworkReply::NetworkError::NoError) { throw NetworkException(result, output); @@ -478,32 +483,30 @@ QVariantHash GmailNetworkFactory::getProfile(const QNetworkProxy& custom_proxy) void GmailNetworkFactory::onTokensError(const QString& error, const QString& error_description) { Q_UNUSED(error) - qApp->showGuiMessage(Notification::Event::LoginFailure, { - tr("Gmail: authentication error"), - tr("Click this to login again. Error is: '%1'").arg(error_description), - QSystemTrayIcon::MessageIcon::Critical }, - {}, { - tr("Login"), - [this]() { - m_oauth2->setAccessToken(QString()); - m_oauth2->setRefreshToken(QString()); - m_oauth2->login(); - } }); + qApp->showGuiMessage(Notification::Event::LoginFailure, + {tr("Gmail: authentication error"), + tr("Click this to login again. Error is: '%1'").arg(error_description), + QSystemTrayIcon::MessageIcon::Critical}, + {}, + {tr("Login"), [this]() { + m_oauth2->setAccessToken(QString()); + m_oauth2->setRefreshToken(QString()); + m_oauth2->login(); + }}); } void GmailNetworkFactory::onAuthFailed() { - qApp->showGuiMessage(Notification::Event::LoginFailure, { - tr("Gmail: authorization denied"), - tr("Click this to login again."), - QSystemTrayIcon::MessageIcon::Critical }, - {}, { - tr("Login"), - [this]() { - m_oauth2->login(); - } }); + qApp->showGuiMessage(Notification::Event::LoginFailure, + {tr("Gmail: authorization denied"), + tr("Click this to login again."), + QSystemTrayIcon::MessageIcon::Critical}, + {}, + {tr("Login"), [this]() { + m_oauth2->login(); + }}); } -bool GmailNetworkFactory::fillFullMessage(Message& msg, const QJsonObject& json, const QString& feed_id) { +bool GmailNetworkFactory::fillFullMessage(Message& msg, const QJsonObject& json, const QString& feed_id) const { QHash headers; auto json_headers = json[QSL("payload")].toObject()[QSL("headers")].toArray(); @@ -543,7 +546,7 @@ bool GmailNetworkFactory::fillFullMessage(Message& msg, const QJsonObject& json, } } - msg.m_author = headers[QSL("From")]; + msg.m_author = sanitizeEmailAuthor(headers[QSL("From")]); msg.m_title = headers[QSL("Subject")]; msg.m_createdFromFeed = true; msg.m_created = TextFactory::parseDateTime(headers[QSL("Date")]); @@ -587,7 +590,8 @@ bool GmailNetworkFactory::fillFullMessage(Message& msg, const QJsonObject& json, // We check if it is HTML. if (msg.m_contents.isEmpty()) { if (mime.contains(QL1S("text/html"))) { - msg.m_contents = QByteArray::fromBase64(body[QSL("data")].toString().toUtf8(), QByteArray::Base64Option::Base64UrlEncoding); + msg.m_contents = + QByteArray::fromBase64(body[QSL("data")].toString().toUtf8(), QByteArray::Base64Option::Base64UrlEncoding); if (msg.m_contents.contains(QSL(""))) { int strt = msg.m_contents.indexOf(QSL("")); @@ -599,20 +603,20 @@ bool GmailNetworkFactory::fillFullMessage(Message& msg, const QJsonObject& json, } } else if (backup_contents.isEmpty()) { - backup_contents = QByteArray::fromBase64(body[QSL("data")].toString().toUtf8(), QByteArray::Base64Option::Base64UrlEncoding); + backup_contents = + QByteArray::fromBase64(body[QSL("data")].toString().toUtf8(), QByteArray::Base64Option::Base64UrlEncoding); - backup_contents = backup_contents - .replace(QSL("\r\n"), QSL("\n")) - .replace(QSL("\n"), QSL("\n")) - .replace(QSL("\n"), QSL("
")); + backup_contents = backup_contents.replace(QSL("\r\n"), QSL("\n")) + .replace(QSL("\n"), QSL("\n")) + .replace(QSL("\n"), QSL("
")); } } } else if (!filename.isEmpty()) { // We have attachment. - msg.m_enclosures.append(Enclosure(filename + - QSL(GMAIL_ATTACHMENT_SEP) + body[QSL("attachmentId")].toString(), - filename + QSL(" (%1 KB)").arg(QString::number(body["size"].toInt() / 1000.0)))); + msg.m_enclosures.append(Enclosure(filename + QSL(GMAIL_ATTACHMENT_SEP) + body[QSL("attachmentId")].toString(), + filename + + QSL(" (%1 KB)").arg(QString::number(body["size"].toInt() / 1000.0)))); } } @@ -636,12 +640,10 @@ QMap GmailNetworkFactory::getMessageMetadata(const QString& ms QByteArray output; int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); - headers.append(QPair(QSL(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(), - bearer.toLocal8Bit())); + headers.append(QPair(QSL(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(), bearer.toLocal8Bit())); - QString query = QString("%1/%2?format=metadata&metadataHeaders=%3").arg(QSL(GMAIL_API_MSGS_LIST), - msg_id, - metadata.join(QSL("&metadataHeaders="))); + QString query = QString("%1/%2?format=metadata&metadataHeaders=%3") + .arg(QSL(GMAIL_API_MSGS_LIST), msg_id, metadata.join(QSL("&metadataHeaders="))); NetworkResult res = NetworkFactory::performNetworkOperation(query, timeout, QByteArray(), @@ -673,7 +675,7 @@ QMap GmailNetworkFactory::getMessageMetadata(const QString& ms QList GmailNetworkFactory::obtainAndDecodeFullMessages(const QStringList& message_ids, const QString& feed_id, - const QNetworkProxy& custom_proxy) { + const QNetworkProxy& custom_proxy) const { QHash msgs; int next_message = 0; QString bearer = m_oauth2->bearer(); @@ -687,7 +689,7 @@ QList GmailNetworkFactory::obtainAndDecodeFullMessages(const QStringLis multi->setContentType(QHttpMultiPart::ContentType::MixedType); - for (int window = next_message + 100; next_message < window && next_message < message_ids.size(); next_message++ ) { + for (int window = next_message + 100; next_message < window && next_message < message_ids.size(); next_message++) { QString msg_id = message_ids[next_message]; Message msg; QHttpPart part; @@ -706,8 +708,7 @@ QList GmailNetworkFactory::obtainAndDecodeFullMessages(const QStringLis QList output; int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); - headers.append(QPair(QSL(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(), - bearer.toLocal8Bit())); + headers.append(QPair(QSL(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(), bearer.toLocal8Bit())); NetworkResult res = NetworkFactory::performNetworkOperation(GMAIL_API_BATCH, timeout, @@ -750,7 +751,7 @@ QList GmailNetworkFactory::obtainAndDecodeFullMessages(const QStringLis return msgs.values(); } -QStringList GmailNetworkFactory::decodeLiteMessages(const QString& messages_json_data, QString& next_page_token) { +QStringList GmailNetworkFactory::decodeLiteMessages(const QString& messages_json_data, QString& next_page_token) const { QList message_ids; QJsonObject top_object = QJsonDocument::fromJson(messages_json_data.toUtf8()).object(); QJsonArray json_msgs = top_object[QSL("messages")].toArray(); @@ -766,3 +767,7 @@ QStringList GmailNetworkFactory::decodeLiteMessages(const QString& messages_json return message_ids; } + +QString GmailNetworkFactory::sanitizeEmailAuthor(const QString& author) const { + return author.mid(0, author.indexOf(QL1S(" <"))).replace(QL1S("\""), QString()); +} diff --git a/src/librssguard/services/gmail/gmailnetworkfactory.h b/src/librssguard/services/gmail/gmailnetworkfactory.h index 0b1e0398a..538a5ec72 100644 --- a/src/librssguard/services/gmail/gmailnetworkfactory.h +++ b/src/librssguard/services/gmail/gmailnetworkfactory.h @@ -68,11 +68,12 @@ class GmailNetworkFactory : public QObject { void onAuthFailed(); private: - bool fillFullMessage(Message& msg, const QJsonObject& json, const QString& feed_id); + bool fillFullMessage(Message& msg, const QJsonObject& json, const QString& feed_id) const; QList obtainAndDecodeFullMessages(const QStringList& message_ids, const QString& feed_id, - const QNetworkProxy& custom_proxy); - QStringList decodeLiteMessages(const QString& messages_json_data, QString& next_page_token); + const QNetworkProxy& custom_proxy) const; + QStringList decodeLiteMessages(const QString& messages_json_data, QString& next_page_token) const; + QString sanitizeEmailAuthor(const QString& author) const; void initializeOauth(); diff --git a/src/librssguard/services/gmail/gui/formaddeditemail.cpp b/src/librssguard/services/gmail/gui/formaddeditemail.cpp index 66cd93a86..1ab9e0602 100644 --- a/src/librssguard/services/gmail/gui/formaddeditemail.cpp +++ b/src/librssguard/services/gmail/gui/formaddeditemail.cpp @@ -41,7 +41,7 @@ FormAddEditEmail::FormAddEditEmail(GmailServiceRoot* root, QWidget* parent) m_possibleRecipients = DatabaseQueries::getAllGmailRecipients(db, m_root->accountId()); auto ctrls = recipientControls(); - for (auto* rec: qAsConst(ctrls)) { + for (auto* rec : qAsConst(ctrls)) { rec->setPossibleRecipients(m_possibleRecipients); } } @@ -58,7 +58,10 @@ void FormAddEditEmail::execForReply(Message* original_message) { m_ui.m_txtSubject->setEnabled(false); m_ui.m_txtMessage->setFocus(); - addRecipientRow(m_originalMessage->m_author); + auto from_header = + m_root->network()->getMessageMetadata(original_message->m_customId, {QSL("FROM")}, m_root->networkProxy()); + + addRecipientRow(from_header["From"]); exec(); } @@ -70,15 +73,15 @@ void FormAddEditEmail::execForForward(Message* original_message) { m_ui.m_txtMessage->setFocus(); // TODO: Obtain "To" header from Gmail API and fill it in too. - const QString forward_header = QSL("
"
-                                     "---------- Forwarded message ---------
" - "From: %1
" - "Date: %2
" - "Subject: %3
" - "To: -" - "

").arg(m_originalMessage->m_author, - m_originalMessage->m_created.toString(), - m_originalMessage->m_title); + const QString forward_header = + QSL("
"
+        "---------- Forwarded message ---------
" + "From: %1
" + "Date: %2
" + "Subject: %3
" + "To: -" + "

") + .arg(m_originalMessage->m_author, m_originalMessage->m_created.toString(), m_originalMessage->m_title); m_ui.m_txtMessage->setHtml(forward_header + m_originalMessage->m_contents); m_ui.m_txtMessage->moveCursor(QTextCursor::MoveOperation::Start); @@ -145,9 +148,10 @@ void FormAddEditEmail::onOkClicked() { msg["Reply-To"] = rec_repl.join(',').toStdString(); } - msg["Subject"] = QSL("=?utf-8?B?%1?=") - .arg(QString(m_ui.m_txtSubject->text().toUtf8().toBase64(QByteArray::Base64Option::Base64UrlEncoding))) - .toStdString(); + msg["Subject"] = + QSL("=?utf-8?B?%1?=") + .arg(QString(m_ui.m_txtSubject->text().toUtf8().toBase64(QByteArray::Base64Option::Base64UrlEncoding))) + .toStdString(); // TODO: Maybe use some more advanced subclass of QTextEdit // to allow to change formatting etc. @@ -161,8 +165,10 @@ void FormAddEditEmail::onOkClicked() { accept(); } catch (const ApplicationException& ex) { - MsgBox::show(this, QMessageBox::Icon::Critical, - tr("E-mail NOT sent"), tr("Your e-mail message wasn't sent."), + MsgBox::show(this, + QMessageBox::Icon::Critical, + tr("E-mail NOT sent"), + tr("Your e-mail message wasn't sent."), QString(), ex.message()); } diff --git a/src/librssguard/services/greader/definitions.h b/src/librssguard/services/greader/definitions.h index f7befffcb..4d74d7894 100644 --- a/src/librssguard/services/greader/definitions.h +++ b/src/librssguard/services/greader/definitions.h @@ -1,60 +1,60 @@ #ifndef GREADER_DEFINITIONS_H #define GREADER_DEFINITIONS_H -#define GREADER_DEFAULT_BATCH_SIZE 100 +#define GREADER_DEFAULT_BATCH_SIZE 100 // URLs. -#define GREADER_URL_REEDAH "https://www.reedah.com" -#define GREADER_URL_TOR "https://theoldreader.com" -#define GREADER_URL_BAZQUX "https://bazqux.com" +#define GREADER_URL_REEDAH "https://www.reedah.com" +#define GREADER_URL_TOR "https://theoldreader.com" +#define GREADER_URL_BAZQUX "https://bazqux.com" #define GREADER_URL_INOREADER "https://www.inoreader.com" // States. -#define GREADER_API_STATE_READING_LIST "state/com.google/reading-list" +#define GREADER_API_STATE_READING_LIST "state/com.google/reading-list" // Means "read" message. If both "reading-list" and "read" are specified, message is READ. If this state // is not present, message is UNREAD. -#define GREADER_API_STATE_READ "state/com.google/read" -#define GREADER_API_STATE_IMPORTANT "state/com.google/starred" +#define GREADER_API_STATE_READ "state/com.google/read" +#define GREADER_API_STATE_IMPORTANT "state/com.google/starred" -#define GREADER_API_FULL_STATE_READING_LIST "user/-/state/com.google/reading-list" -#define GREADER_API_FULL_STATE_READ "user/-/state/com.google/read" -#define GREADER_API_FULL_STATE_IMPORTANT "user/-/state/com.google/starred" +#define GREADER_API_FULL_STATE_READING_LIST "user/-/state/com.google/reading-list" +#define GREADER_API_FULL_STATE_READ "user/-/state/com.google/read" +#define GREADER_API_FULL_STATE_IMPORTANT "user/-/state/com.google/starred" // API. -#define GREADER_API_CLIENT_LOGIN "accounts/ClientLogin" -#define GREADER_API_TAG_LIST "reader/api/0/tag/list?output=json" -#define GREADER_API_SUBSCRIPTION_LIST "reader/api/0/subscription/list?output=json" -#define GREADER_API_STREAM_CONTENTS "reader/api/0/stream/contents/%1?output=json&n=%2" -#define GREADER_API_EDIT_TAG "reader/api/0/edit-tag" -#define GREADER_API_ITEM_IDS "reader/api/0/stream/items/ids?output=json&n=%2&s=%1" -#define GREADER_API_ITEM_CONTENTS "reader/api/0/stream/items/contents?output=json&n=200000" -#define GREADER_API_TOKEN "reader/api/0/token" -#define GREADER_API_USER_INFO "reader/api/0/user-info?output=json" +#define GREADER_API_CLIENT_LOGIN "accounts/ClientLogin" +#define GREADER_API_TAG_LIST "reader/api/0/tag/list?output=json" +#define GREADER_API_SUBSCRIPTION_LIST "reader/api/0/subscription/list?output=json" +#define GREADER_API_STREAM_CONTENTS "reader/api/0/stream/contents/%1?output=json&n=%2" +#define GREADER_API_EDIT_TAG "reader/api/0/edit-tag" +#define GREADER_API_ITEM_IDS "reader/api/0/stream/items/ids?output=json&n=%2&s=%1" +#define GREADER_API_ITEM_CONTENTS "reader/api/0/stream/items/contents?output=json&n=200000" +#define GREADER_API_TOKEN "reader/api/0/token" +#define GREADER_API_USER_INFO "reader/api/0/user-info?output=json" // Misc. -#define GREADET_API_ITEM_IDS_MAX 200000 -#define GREADER_API_EDIT_TAG_BATCH 200 +#define GREADET_API_ITEM_IDS_MAX 200000 +#define GREADER_API_EDIT_TAG_BATCH 200 #define GREADER_API_ITEM_CONTENTS_BATCH 999 -#define GREADER_GLOBAL_UPDATE_THRES 0.3 +#define GREADER_GLOBAL_UPDATE_THRES 0.3 // The Old Reader. -#define TOR_SPONSORED_STREAM_ID "tor/sponsored" -#define TOR_ITEM_CONTENTS_BATCH 9999 +#define TOR_SPONSORED_STREAM_ID "tor/sponsored" +#define TOR_ITEM_CONTENTS_BATCH 9999 // Inoreader. -#define INO_ITEM_CONTENTS_BATCH 250 +#define INO_ITEM_CONTENTS_BATCH 250 -#define INO_HEADER_APPID "AppId" -#define INO_HEADER_APPKEY "AppKey" +#define INO_HEADER_APPID "AppId" +#define INO_HEADER_APPKEY "AppKey" -#define INO_OAUTH_REDIRECT_URI_PORT 14488 -#define INO_OAUTH_SCOPE "read write" -#define INO_OAUTH_TOKEN_URL "https://www.inoreader.com/oauth2/token" -#define INO_OAUTH_AUTH_URL "https://www.inoreader.com/oauth2/auth" -#define INO_REG_API_URL "https://www.inoreader.com/developers/register-app" +#define INO_OAUTH_REDIRECT_URI_PORT 14488 +#define INO_OAUTH_SCOPE "read write" +#define INO_OAUTH_TOKEN_URL "https://www.inoreader.com/oauth2/token" +#define INO_OAUTH_AUTH_URL "https://www.inoreader.com/oauth2/auth" +#define INO_REG_API_URL "https://www.inoreader.com/developers/register-app" // FreshRSS. -#define FRESHRSS_BASE_URL_PATH "api/greader.php/" +#define FRESHRSS_BASE_URL_PATH "api/greader.php/" #endif // GREADER_DEFINITIONS_H diff --git a/src/librssguard/services/greader/greadernetwork.cpp b/src/librssguard/services/greader/greadernetwork.cpp index 405cb0335..c84d54de2 100644 --- a/src/librssguard/services/greader/greadernetwork.cpp +++ b/src/librssguard/services/greader/greadernetwork.cpp @@ -73,7 +73,7 @@ QNetworkReply::NetworkError GreaderNetwork::editLabels(const QString& state, args += working_subset.join(QL1C('&')); if (m_service == GreaderServiceRoot::Service::Reedah) { - args += QSL("&T=%1").arg(m_authToken); + args += QSL("&%1").arg(tokenParameter()); } // We send this batch. @@ -493,7 +493,13 @@ QList GreaderNetwork::itemContents(ServiceRoot* root, : QUrl::toPercentEncoding(id)); }) .toStdList(); - QByteArray input = FROM_STD_LIST(QStringList, inp).join(QSL("&")).toUtf8(); + QStringList inp_s = FROM_STD_LIST(QStringList, inp); + + if (m_service == GreaderServiceRoot::Service::Reedah || m_service == GreaderServiceRoot::Service::Miniflux) { + inp_s.append(tokenParameter()); + } + + QByteArray input = inp_s.join(QSL("&")).toUtf8(); QByteArray output_stream; auto result_stream = NetworkFactory::performNetworkOperation(full_url, @@ -861,7 +867,7 @@ QNetworkReply::NetworkError GreaderNetwork::clientLogin(const QNetworkProxy& pro return QNetworkReply::NetworkError::InternalServerError; } - if (m_service == GreaderServiceRoot::Service::Reedah) { + if (m_service == GreaderServiceRoot::Service::Reedah || m_service == GreaderServiceRoot::Service::Miniflux) { // We need "T=" token for editing. full_url = generateFullUrl(Operations::Token); @@ -929,6 +935,10 @@ QPair GreaderNetwork::authHeader() const { } } +QString GreaderNetwork::tokenParameter() const { + return QSL("T=%1").arg(m_authToken); +} + bool GreaderNetwork::ensureLogin(const QNetworkProxy& proxy, QNetworkReply::NetworkError* output) { if (m_service == GreaderServiceRoot::Service::Inoreader) { return !m_oauth->bearer().isEmpty(); diff --git a/src/librssguard/services/greader/greadernetwork.h b/src/librssguard/services/greader/greadernetwork.h index 30a910a37..5d3d8ac4c 100644 --- a/src/librssguard/services/greader/greadernetwork.h +++ b/src/librssguard/services/greader/greadernetwork.h @@ -12,7 +12,7 @@ class OAuth2Service; class GreaderNetwork : public QObject { - Q_OBJECT + Q_OBJECT public: enum class Operations { @@ -83,15 +83,24 @@ class GreaderNetwork : public QObject { void setOauth(OAuth2Service* oauth); // API methods. - QNetworkReply::NetworkError editLabels(const QString& state, bool assign, - const QStringList& msg_custom_ids, const QNetworkProxy& proxy); + QNetworkReply::NetworkError editLabels(const QString& state, + bool assign, + const QStringList& msg_custom_ids, + const QNetworkProxy& proxy); QVariantHash userInfo(const QNetworkProxy& proxy); - QStringList itemIds(const QString& stream_id, bool unread_only, const QNetworkProxy& proxy, int max_count = -1, + QStringList itemIds(const QString& stream_id, + bool unread_only, + const QNetworkProxy& proxy, + int max_count = -1, QDate newer_than = {}); - QList itemContents(ServiceRoot* root, const QList& stream_ids, - Feed::Status& error, const QNetworkProxy& proxy); - QList streamContents(ServiceRoot* root, const QString& stream_id, - Feed::Status& error, const QNetworkProxy& proxy); + QList itemContents(ServiceRoot* root, + const QList& stream_ids, + Feed::Status& error, + const QNetworkProxy& proxy); + QList streamContents(ServiceRoot* root, + const QString& stream_id, + Feed::Status& error, + const QNetworkProxy& proxy); QNetworkReply::NetworkError clientLogin(const QNetworkProxy& proxy); QDate newerThanFilter() const; @@ -103,6 +112,7 @@ class GreaderNetwork : public QObject { private: QPair authHeader() const; + QString tokenParameter() const; // Make sure we are logged in and if we are not, return error. bool ensureLogin(const QNetworkProxy& proxy, QNetworkReply::NetworkError* output = nullptr); @@ -112,8 +122,14 @@ class GreaderNetwork : public QObject { QString simplifyStreamId(const QString& stream_id) const; QStringList decodeItemIds(const QString& stream_json_data, QString& continuation); - QList decodeStreamContents(ServiceRoot* root, const QString& stream_json_data, const QString& stream_id, QString& continuation); - RootItem* decodeTagsSubscriptions(const QString& categories, const QString& feeds, bool obtain_icons, const QNetworkProxy& proxy); + QList decodeStreamContents(ServiceRoot* root, + const QString& stream_json_data, + const QString& stream_id, + QString& continuation); + RootItem* decodeTagsSubscriptions(const QString& categories, + const QString& feeds, + bool obtain_icons, + const QNetworkProxy& proxy); QString sanitizedBaseUrl() const; QString generateFullUrl(Operations operation) const; diff --git a/src/librssguard/services/greader/greaderserviceroot.cpp b/src/librssguard/services/greader/greaderserviceroot.cpp index 4bb309772..f16a17539 100644 --- a/src/librssguard/services/greader/greaderserviceroot.cpp +++ b/src/librssguard/services/greader/greaderserviceroot.cpp @@ -17,8 +17,7 @@ #include "services/greader/greadernetwork.h" #include "services/greader/gui/formeditgreaderaccount.h" -GreaderServiceRoot::GreaderServiceRoot(RootItem* parent) - : ServiceRoot(parent), m_network(new GreaderNetwork(this)) { +GreaderServiceRoot::GreaderServiceRoot(RootItem* parent) : ServiceRoot(parent), m_network(new GreaderNetwork(this)) { setIcon(GreaderEntryPoint().icon()); m_network->setRoot(this); } @@ -91,7 +90,8 @@ void GreaderServiceRoot::setCustomDatabaseData(const QVariantHash& data) { } void GreaderServiceRoot::aboutToBeginFeedFetching(const QList& feeds, - const QHash>& stated_messages, + const QHash>& + stated_messages, const QHash& tagged_messages) { if (m_network->intelligentSynchronization()) { m_network->prepareFeedFetching(this, feeds, stated_messages, tagged_messages, networkProxy()); @@ -118,13 +118,17 @@ QString GreaderServiceRoot::serviceToString(Service service) { case Service::Inoreader: return QSL("Inoreader"); + case Service::Miniflux: + return QSL("Miniflux"); + default: return tr("Other services"); } } QList GreaderServiceRoot::obtainNewMessages(Feed* feed, - const QHash& stated_messages, + const QHash& + stated_messages, const QHash& tagged_messages) { Feed::Status error = Feed::Status::Normal; QList msgs; @@ -207,7 +211,8 @@ void GreaderServiceRoot::saveAllCachedData(bool ignore_errors) { QList messages = j.value(); if (!messages.isEmpty()) { - QStringList custom_ids; custom_ids.reserve(messages.size()); + QStringList custom_ids; + custom_ids.reserve(messages.size()); for (const Message& msg : messages) { custom_ids.append(msg.m_customId); @@ -231,7 +236,8 @@ void GreaderServiceRoot::saveAllCachedData(bool ignore_errors) { QStringList messages = k.value(); if (!messages.isEmpty()) { - if (network()->editLabels(label_custom_id, true, messages, networkProxy()) != QNetworkReply::NetworkError::NoError && + if (network()->editLabels(label_custom_id, true, messages, networkProxy()) != + QNetworkReply::NetworkError::NoError && !ignore_errors) { addLabelsAssignmentsToCache(messages, label_custom_id, true); } @@ -247,7 +253,8 @@ void GreaderServiceRoot::saveAllCachedData(bool ignore_errors) { QStringList messages = l.value(); if (!messages.isEmpty()) { - if (network()->editLabels(label_custom_id, false, messages, networkProxy()) != QNetworkReply::NetworkError::NoError && + if (network()->editLabels(label_custom_id, false, messages, networkProxy()) != + QNetworkReply::NetworkError::NoError && !ignore_errors) { addLabelsAssignmentsToCache(messages, label_custom_id, false); } @@ -285,6 +292,10 @@ void GreaderServiceRoot::updateTitleIcon() { setIcon(qApp->icons()->miscIcon(QSL("inoreader"))); break; + case Service::Miniflux: + setIcon(qApp->icons()->miscIcon(QSL("miniflux"))); + break; + default: setIcon(GreaderEntryPoint().icon()); break; diff --git a/src/librssguard/services/greader/greaderserviceroot.h b/src/librssguard/services/greader/greaderserviceroot.h index 8dad76f66..b045d7b14 100644 --- a/src/librssguard/services/greader/greaderserviceroot.h +++ b/src/librssguard/services/greader/greaderserviceroot.h @@ -9,7 +9,7 @@ class GreaderNetwork; class GreaderServiceRoot : public ServiceRoot, public CacheForServiceRoot { - Q_OBJECT + Q_OBJECT public: enum class Service { @@ -18,9 +18,12 @@ class GreaderServiceRoot : public ServiceRoot, public CacheForServiceRoot { Bazqux = 4, Reedah = 8, Inoreader = 16, + Miniflux = 32, Other = 1024 }; + Q_ENUM(Service) + explicit GreaderServiceRoot(RootItem* parent = nullptr); virtual bool isSyncable() const; @@ -33,7 +36,8 @@ class GreaderServiceRoot : public ServiceRoot, public CacheForServiceRoot { virtual QVariantHash customDatabaseData() const; virtual void setCustomDatabaseData(const QVariantHash& data); virtual void aboutToBeginFeedFetching(const QList& feeds, - const QHash>& stated_messages, + const QHash>& + stated_messages, const QHash& tagged_messages); virtual QList obtainNewMessages(Feed* feed, const QHash& stated_messages, diff --git a/src/librssguard/services/greader/gui/greaderaccountdetails.cpp b/src/librssguard/services/greader/gui/greaderaccountdetails.cpp index 9cb2f1ee2..cd34c99c8 100644 --- a/src/librssguard/services/greader/gui/greaderaccountdetails.cpp +++ b/src/librssguard/services/greader/gui/greaderaccountdetails.cpp @@ -12,18 +12,17 @@ #include "services/greader/definitions.h" #include "services/greader/greadernetwork.h" +#include #include -GreaderAccountDetails::GreaderAccountDetails(QWidget* parent) : QWidget(parent), - m_oauth(nullptr), m_lastProxy({}) { +GreaderAccountDetails::GreaderAccountDetails(QWidget* parent) : QWidget(parent), m_oauth(nullptr), m_lastProxy({}) { m_ui.setupUi(this); - for (auto serv : { GreaderServiceRoot::Service::Bazqux, - GreaderServiceRoot::Service::FreshRss, - GreaderServiceRoot::Service::Inoreader, - GreaderServiceRoot::Service::Reedah, - GreaderServiceRoot::Service::TheOldReader, - GreaderServiceRoot::Service::Other }) { + QMetaEnum me = QMetaEnum::fromType(); + + for (int i = 0; i < me.keyCount(); i++) { + GreaderServiceRoot::Service serv = static_cast(me.value(i)); + m_ui.m_cmbService->addItem(GreaderServiceRoot::serviceToString(serv), QVariant::fromValue(serv)); } @@ -54,12 +53,13 @@ GreaderAccountDetails::GreaderAccountDetails(QWidget* parent) : QWidget(parent), false); #if defined(INOREADER_OFFICIAL_SUPPORT) - m_ui.m_lblInfo->setHelpText(tr("There are some preconfigured OAuth tokens so you do not have to fill in your " - "client ID/secret, but it is strongly recommended to obtain your " - "own as preconfigured tokens have limited global usage quota. If you wish " - "to use preconfigured tokens, simply leave all above fields to their default values even " - "if they are empty."), - true); + m_ui.m_lblInfo + ->setHelpText(tr("There are some preconfigured OAuth tokens so you do not have to fill in your " + "client ID/secret, but it is strongly recommended to obtain your " + "own as preconfigured tokens have limited global usage quota. If you wish " + "to use preconfigured tokens, simply leave all above fields to their default values even " + "if they are empty."), + true); #else m_ui.m_lblInfo->setHelpText(tr("You have to fill in your client ID/secret and also fill in correct redirect URL."), true); @@ -68,7 +68,10 @@ GreaderAccountDetails::GreaderAccountDetails(QWidget* parent) : QWidget(parent), connect(m_ui.m_txtPassword->lineEdit(), &BaseLineEdit::textChanged, this, &GreaderAccountDetails::onPasswordChanged); connect(m_ui.m_txtUsername->lineEdit(), &BaseLineEdit::textChanged, this, &GreaderAccountDetails::onUsernameChanged); connect(m_ui.m_txtUrl->lineEdit(), &BaseLineEdit::textChanged, this, &GreaderAccountDetails::onUrlChanged); - connect(m_ui.m_cmbService, QOverload::of(&QComboBox::currentIndexChanged), this, &GreaderAccountDetails::fillPredefinedUrl); + connect(m_ui.m_cmbService, + QOverload::of(&QComboBox::currentIndexChanged), + this, + &GreaderAccountDetails::fillPredefinedUrl); connect(m_ui.m_cbNewAlgorithm, &QCheckBox::toggled, m_ui.m_spinLimitMessages, &MessageCountSpinBox::setDisabled); connect(m_ui.m_txtAppId->lineEdit(), &BaseLineEdit::textChanged, this, &GreaderAccountDetails::checkOAuthValue); connect(m_ui.m_txtAppKey->lineEdit(), &BaseLineEdit::textChanged, this, &GreaderAccountDetails::checkOAuthValue); @@ -126,9 +129,7 @@ void GreaderAccountDetails::onAuthGranted() { m_ui.m_txtUsername->lineEdit()->setText(resp[QSL("userEmail")].toString()); } catch (const ApplicationException& ex) { - qCriticalNN << LOGSEC_GREADER - << "Failed to obtain profile with error:" - << QUOTE_W_SPACE_DOT(ex.message()); + qCriticalNN << LOGSEC_GREADER << "Failed to obtain profile with error:" << QUOTE_W_SPACE_DOT(ex.message()); } } @@ -198,9 +199,7 @@ void GreaderAccountDetails::performTest(const QNetworkProxy& custom_proxy) { tr("Network error, have you entered correct Nextcloud endpoint and password?")); } else { - m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Ok, - tr("You are good to go!"), - tr("Yeah.")); + m_ui.m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Ok, tr("You are good to go!"), tr("Yeah.")); } } }