From 45304b9e8135ed588566763dfb6dcb15174a7360 Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Tue, 2 Feb 2021 15:04:35 +0100 Subject: [PATCH] Working on #265. --- resources/docs/Feed-formats.md | 32 ++- resources/docs/images/scrape-source-type.png | Bin 0 -> 28251 bytes resources/docs/images/scrape-source.png | Bin 0 -> 24246 bytes src/librssguard/definitions/definitions.h | 1 + .../miscellaneous/databasequeries.cpp | 18 +- .../miscellaneous/databasequeries.h | 8 +- .../abstract/gui/formaccountdetails.ui | 4 +- .../services/abstract/gui/formfeeddetails.ui | 4 +- .../standard/gui/formstandardfeeddetails.cpp | 24 +- .../standard/gui/standardfeeddetails.cpp | 212 +++++++++++------- .../standard/gui/standardfeeddetails.h | 7 +- .../standard/gui/standardfeeddetails.ui | 60 +++-- .../services/standard/standardfeed.cpp | 37 ++- .../services/standard/standardfeed.h | 6 + 14 files changed, 285 insertions(+), 128 deletions(-) create mode 100755 resources/docs/images/scrape-source-type.png create mode 100755 resources/docs/images/scrape-source.png diff --git a/resources/docs/Feed-formats.md b/resources/docs/Feed-formats.md index 3490d3648..777085b25 100755 --- a/resources/docs/Feed-formats.md +++ b/resources/docs/Feed-formats.md @@ -4,6 +4,7 @@ RSS Guard is a modular application which supports plugins. It offers well-mainta * [Tiny Tiny RSS](https://tt-rss.org) plugin: Adds ability to synchronize messages with TT-RSS instances, either self-hosted or via 3rd-party external service. * [Inoreader](https://www.inoreader.com) plugin: Adds ability to synchronize messages with Inoreader. All you need to do is create free account on their website and start rocking. * [Nextcloud News](https://apps.nextcloud.com/apps/news) plugin: Nextcloud News is a Nextcloud app which adds feed reader abilities into your Nextcloud instances. +* Google Reader API plugin: This plugin was added in RSS Guard 3.9.0 and offers two-way synchronization with services which implement Google Reader API. At this point, plugin was tested and works with Bazqux, The Old Reader and FreshRSS. * [Gmail](https://www.google.com/gmail) plugin: Yes, you are reading it right. RSS Guard can be used as very lightweight and simple e-mail client. This plugins uses [Gmail API](https://developers.google.com/gmail/api) and offers even e-mail sending. All plugins share almost all core RSS Guard's features, including labels, recycle bins, podcasts fetching or newspaper view. They are implemented in a very transparent way, making it easy to maintain them or add new ones. @@ -27,4 +28,33 @@ OPML files can be exported/imported in simple dialog. -You just select output file (in case of OPML export), check desired feeds and hit `Export to file`. \ No newline at end of file +You just select output file (in case of OPML export), check desired feeds and hit `Export to file`. + +### Websites scraping and other related advanced features +RSS Guard 3.9.0+ offers extra advanced features which were inspired by [Liferea](https://lzone.de/liferea/). + +**Only proceed if you consider yourself as power user and you know you are doing!** + +You can select source type of each feed. If you select `URL`, then RSS Guard simply downloads feed file from given location. + +However, if you choose `Script` option, then you cannot provide URL of your feed and you rely on custom script to obtain your script and provide its contents to **standard output**. Resulting data written to standard output **MUST** be valid feed file, for example RSS or ATOM XML file. + + + +Any errors in your script must be written to **error output**. + +Note that you must provide full execution line to your custom script, including interpreter binary path and name. Some examples of valid execution lines are: + +| Command | Explanation | +|---------|-------------| +| `bash -c "curl 'https://github.com/martinrotter.atom'"` | Downloads ATOM feed file with Bash and Curl. | +| `Powershell "Invoke-WebRequest 'https://github.com/martinrotter.atom' | Select-Object -ExpandProperty Content"` | Downloads ATOM feed file with Powershell. | +| `php tweeper.php https://twitter.com/NSACareers` | Downloads RSS feed file with [Tweeper](https://git.ao2.it/tweeper.git/). Tweeper is utility which is able to produce RSS feed from Twitter. | + + + +Note that the above examples are cross-platform and you can use the exact same command on Windows, Linux or Mac OS X, if your operating system is properly configured. + +RSS Guard offers placeholder `%data%` which is automatically replaced with full path to RSS Guard's [user data folder](Documentation.md#portable-user-data). You can, therefore, use something like this as source script line: `bash %data%/scripts/download-feed.sh`. + +Also, working directory of process executing the script is set to RSS Guard's user data folder. \ No newline at end of file diff --git a/resources/docs/images/scrape-source-type.png b/resources/docs/images/scrape-source-type.png new file mode 100755 index 0000000000000000000000000000000000000000..c2295d39010ab2da82b8d78703477d68dadb6965 GIT binary patch literal 28251 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 literal 0 HcmV?d00001 diff --git a/resources/docs/images/scrape-source.png b/resources/docs/images/scrape-source.png new file mode 100755 index 0000000000000000000000000000000000000000..7d5bcc65a23e22f15b18f76a9fee5cac195e4084 GIT binary patch literal 24246 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 getStandardAccounts(const QSqlDatabase& db, bool* ok = nullptr); template diff --git a/src/librssguard/services/abstract/gui/formaccountdetails.ui b/src/librssguard/services/abstract/gui/formaccountdetails.ui index c38f978a4..8d2c579f0 100644 --- a/src/librssguard/services/abstract/gui/formaccountdetails.ui +++ b/src/librssguard/services/abstract/gui/formaccountdetails.ui @@ -6,8 +6,8 @@ 0 0 - 400 - 300 + 500 + 450 diff --git a/src/librssguard/services/abstract/gui/formfeeddetails.ui b/src/librssguard/services/abstract/gui/formfeeddetails.ui index bdd44ddcf..016b3d393 100644 --- a/src/librssguard/services/abstract/gui/formfeeddetails.ui +++ b/src/librssguard/services/abstract/gui/formfeeddetails.ui @@ -6,8 +6,8 @@ 0 0 - 471 - 352 + 500 + 450 diff --git a/src/librssguard/services/standard/gui/formstandardfeeddetails.cpp b/src/librssguard/services/standard/gui/formstandardfeeddetails.cpp index 015aabe37..ff8ee9c15 100644 --- a/src/librssguard/services/standard/gui/formstandardfeeddetails.cpp +++ b/src/librssguard/services/standard/gui/formstandardfeeddetails.cpp @@ -22,7 +22,7 @@ FormStandardFeedDetails::FormStandardFeedDetails(ServiceRoot* service_root, QWid insertCustomTab(m_authDetails, tr("Network"), 2); activateTab(0); - connect(m_standardFeedDetails->ui.m_btnFetchMetadata, &QPushButton::clicked, this, &FormStandardFeedDetails::guessFeed); + connect(m_standardFeedDetails->m_ui.m_btnFetchMetadata, &QPushButton::clicked, this, &FormStandardFeedDetails::guessFeed); connect(m_standardFeedDetails->m_actionFetchIcon, &QAction::triggered, this, &FormStandardFeedDetails::guessIconOnly); } @@ -47,34 +47,36 @@ int FormStandardFeedDetails::addEditFeed(StandardFeed* input_feed, RootItem* par } void FormStandardFeedDetails::guessFeed() { - m_standardFeedDetails->guessFeed(m_standardFeedDetails->ui.m_txtUrl->lineEdit()->text(), + m_standardFeedDetails->guessFeed(m_standardFeedDetails->m_ui.m_txtSource->lineEdit()->text(), m_authDetails->m_txtUsername->lineEdit()->text(), m_authDetails->m_txtPassword->lineEdit()->text()); } void FormStandardFeedDetails::guessIconOnly() { - m_standardFeedDetails->guessIconOnly(m_standardFeedDetails->ui.m_txtUrl->lineEdit()->text(), + m_standardFeedDetails->guessIconOnly(m_standardFeedDetails->m_ui.m_txtSource->lineEdit()->text(), m_authDetails->m_txtUsername->lineEdit()->text(), m_authDetails->m_txtPassword->lineEdit()->text()); } void FormStandardFeedDetails::apply() { RootItem* parent = - static_cast(m_standardFeedDetails->ui.m_cmbParentCategory->itemData( - m_standardFeedDetails->ui.m_cmbParentCategory->currentIndex()).value()); + static_cast(m_standardFeedDetails->m_ui.m_cmbParentCategory->itemData( + m_standardFeedDetails->m_ui.m_cmbParentCategory->currentIndex()).value()); StandardFeed::Type type = - static_cast(m_standardFeedDetails->ui.m_cmbType->itemData(m_standardFeedDetails->ui.m_cmbType->currentIndex()).value()); + static_cast(m_standardFeedDetails->m_ui.m_cmbType->itemData(m_standardFeedDetails->m_ui.m_cmbType->currentIndex()).value()); auto* new_feed = new StandardFeed(); // Setup data for new_feed. - new_feed->setTitle(m_standardFeedDetails->ui.m_txtTitle->lineEdit()->text()); + new_feed->setTitle(m_standardFeedDetails->m_ui.m_txtTitle->lineEdit()->text()); new_feed->setCreationDate(QDateTime::currentDateTime()); - new_feed->setDescription(m_standardFeedDetails->ui.m_txtDescription->lineEdit()->text()); - new_feed->setIcon(m_standardFeedDetails->ui.m_btnIcon->icon()); - new_feed->setEncoding(m_standardFeedDetails->ui.m_cmbEncoding->currentText()); + new_feed->setDescription(m_standardFeedDetails->m_ui.m_txtDescription->lineEdit()->text()); + new_feed->setIcon(m_standardFeedDetails->m_ui.m_btnIcon->icon()); + new_feed->setEncoding(m_standardFeedDetails->m_ui.m_cmbEncoding->currentText()); new_feed->setType(type); - new_feed->setUrl(m_standardFeedDetails->ui.m_txtUrl->lineEdit()->text()); + new_feed->setSourceType(m_standardFeedDetails->sourceType()); + new_feed->setPostProcessScript(m_standardFeedDetails->m_ui.m_txtPostProcessScript->lineEdit()->text()); + new_feed->setUrl(m_standardFeedDetails->m_ui.m_txtSource->lineEdit()->text()); new_feed->setPasswordProtected(m_authDetails->m_gbAuthentication->isChecked()); new_feed->setUsername(m_authDetails->m_txtUsername->lineEdit()->text()); new_feed->setPassword(m_authDetails->m_txtPassword->lineEdit()->text()); diff --git a/src/librssguard/services/standard/gui/standardfeeddetails.cpp b/src/librssguard/services/standard/gui/standardfeeddetails.cpp index 928ca7ac8..e39cc3a91 100755 --- a/src/librssguard/services/standard/gui/standardfeeddetails.cpp +++ b/src/librssguard/services/standard/gui/standardfeeddetails.cpp @@ -2,33 +2,44 @@ #include "services/standard/gui/standardfeeddetails.h" +#include "gui/guiutilities.h" #include "miscellaneous/iconfactory.h" #include "network-web/networkfactory.h" #include "services/abstract/category.h" -#include "services/standard/standardfeed.h" #include #include #include #include +#include #include StandardFeedDetails::StandardFeedDetails(QWidget* parent) : QWidget(parent) { - ui.setupUi(this); + m_ui.setupUi(this); - ui.m_txtTitle->lineEdit()->setPlaceholderText(tr("Feed title")); - ui.m_txtTitle->lineEdit()->setToolTip(tr("Set title for your feed.")); - ui.m_txtDescription->lineEdit()->setPlaceholderText(tr("Feed description")); - ui.m_txtDescription->lineEdit()->setToolTip(tr("Set description for your feed.")); - ui.m_txtUrl->lineEdit()->setPlaceholderText(tr("Full feed url including scheme")); - ui.m_txtUrl->lineEdit()->setToolTip(tr("Set url for your feed.")); + m_ui.m_txtTitle->lineEdit()->setPlaceholderText(tr("Feed title")); + m_ui.m_txtTitle->lineEdit()->setToolTip(tr("Set title for your feed.")); + m_ui.m_txtDescription->lineEdit()->setPlaceholderText(tr("Feed description")); + m_ui.m_txtDescription->lineEdit()->setToolTip(tr("Set description for your feed.")); + m_ui.m_txtSource->lineEdit()->setPlaceholderText(tr("Full feed source identifier")); + m_ui.m_txtSource->lineEdit()->setToolTip(tr("Full feed source identifier which can be URL.")); + m_ui.m_txtPostProcessScript->lineEdit()->setPlaceholderText(tr("Full command to execute")); + m_ui.m_txtPostProcessScript->lineEdit()->setToolTip(tr("You can enter full command including interpreter here.")); + + // Add source types. + m_ui.m_cmbSourceType->addItem(StandardFeed::sourceTypeToString(StandardFeed::SourceType::Url), + QVariant::fromValue(StandardFeed::SourceType::Url)); + m_ui.m_cmbSourceType->addItem(StandardFeed::sourceTypeToString(StandardFeed::SourceType::Script), + QVariant::fromValue(StandardFeed::SourceType::Script)); + m_ui.m_txtPostProcessScript->setStatus(WidgetWithStatus::StatusType::Ok, + tr("Here you can enter script executaion line, including interpreter.")); // Add standard feed types. - ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Atom10), QVariant::fromValue(int(StandardFeed::Type::Atom10))); - ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Rdf), QVariant::fromValue(int(StandardFeed::Type::Rdf))); - ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Rss0X), QVariant::fromValue(int(StandardFeed::Type::Rss0X))); - ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Rss2X), QVariant::fromValue(int(StandardFeed::Type::Rss2X))); - ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Json), QVariant::fromValue(int(StandardFeed::Type::Json))); + m_ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Atom10), QVariant::fromValue(int(StandardFeed::Type::Atom10))); + m_ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Rdf), QVariant::fromValue(int(StandardFeed::Type::Rdf))); + m_ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Rss0X), QVariant::fromValue(int(StandardFeed::Type::Rss0X))); + m_ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Rss2X), QVariant::fromValue(int(StandardFeed::Type::Rss2X))); + m_ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Json), QVariant::fromValue(int(StandardFeed::Type::Json))); // Load available encodings. const QList encodings = QTextCodec::availableCodecs(); @@ -43,7 +54,7 @@ StandardFeedDetails::StandardFeedDetails(QWidget* parent) : QWidget(parent) { return lhs.toLower() < rhs.toLower(); }); - ui.m_cmbEncoding->addItems(encoded_encodings); + m_ui.m_cmbEncoding->addItems(encoded_encodings); // Setup menu & actions for icon selection. m_iconMenu = new QMenu(tr("Icon selection"), this); @@ -59,20 +70,36 @@ StandardFeedDetails::StandardFeedDetails(QWidget* parent) : QWidget(parent) { m_iconMenu->addAction(m_actionFetchIcon); m_iconMenu->addAction(m_actionLoadIconFromFile); m_iconMenu->addAction(m_actionUseDefaultIcon); - ui.m_btnIcon->setMenu(m_iconMenu); - ui.m_txtUrl->lineEdit()->setFocus(Qt::TabFocusReason); + m_ui.m_btnIcon->setMenu(m_iconMenu); + m_ui.m_txtSource->lineEdit()->setFocus(Qt::TabFocusReason); // Set feed metadata fetch label. - ui.m_lblFetchMetadata->setStatus(WidgetWithStatus::StatusType::Information, - tr("No metadata fetched so far."), - tr("No metadata fetched so far.")); + m_ui.m_lblFetchMetadata->setStatus(WidgetWithStatus::StatusType::Information, + tr("No metadata fetched so far."), + tr("No metadata fetched so far.")); - connect(ui.m_txtTitle->lineEdit(), &BaseLineEdit::textChanged, this, &StandardFeedDetails::onTitleChanged); - connect(ui.m_txtDescription->lineEdit(), &BaseLineEdit::textChanged, this, &StandardFeedDetails::onDescriptionChanged); - connect(ui.m_txtUrl->lineEdit(), &BaseLineEdit::textChanged, this, &StandardFeedDetails::onUrlChanged); + connect(m_ui.m_txtTitle->lineEdit(), &BaseLineEdit::textChanged, this, &StandardFeedDetails::onTitleChanged); + connect(m_ui.m_txtDescription->lineEdit(), &BaseLineEdit::textChanged, this, &StandardFeedDetails::onDescriptionChanged); + connect(m_ui.m_cmbSourceType, QOverload::of(&QComboBox::currentIndexChanged), + this, [this]() { + onUrlChanged(m_ui.m_txtSource->lineEdit()->text()); + }); + connect(m_ui.m_txtSource->lineEdit(), &BaseLineEdit::textChanged, this, &StandardFeedDetails::onUrlChanged); connect(m_actionLoadIconFromFile, &QAction::triggered, this, &StandardFeedDetails::onLoadIconFromFile); connect(m_actionUseDefaultIcon, &QAction::triggered, this, &StandardFeedDetails::onUseDefaultIcon); + setTabOrder(m_ui.m_cmbParentCategory, m_ui.m_cmbType); + setTabOrder(m_ui.m_cmbType, m_ui.m_cmbEncoding); + setTabOrder(m_ui.m_cmbEncoding, m_ui.m_txtTitle->lineEdit()); + setTabOrder(m_ui.m_txtTitle->lineEdit(), m_ui.m_txtDescription->lineEdit()); + setTabOrder(m_ui.m_txtDescription->lineEdit(), m_ui.m_cmbSourceType); + setTabOrder(m_ui.m_cmbSourceType, m_ui.m_txtSource->lineEdit()); + setTabOrder(m_ui.m_txtSource->lineEdit(), m_ui.m_txtPostProcessScript->lineEdit()); + setTabOrder(m_ui.m_txtPostProcessScript->lineEdit(), m_ui.m_btnFetchMetadata); + setTabOrder(m_ui.m_btnFetchMetadata, m_ui.m_btnIcon); + + GuiUtilities::setLabelAsNotice(*m_ui.m_lblScriptInfo, false); + onTitleChanged(QString()); onDescriptionChanged(QString()); onUrlChanged(QString()); @@ -87,17 +114,17 @@ void StandardFeedDetails::guessIconOnly(const QString& url, const QString& usern if (result.first != nullptr) { // Icon or whole feed was guessed. - ui.m_btnIcon->setIcon(result.first->icon()); + m_ui.m_btnIcon->setIcon(result.first->icon()); if (result.second == QNetworkReply::NoError) { - ui.m_lblFetchMetadata->setStatus(WidgetWithStatus::StatusType::Ok, - tr("Icon fetched successfully."), - tr("Icon metadata fetched.")); + m_ui.m_lblFetchMetadata->setStatus(WidgetWithStatus::StatusType::Ok, + tr("Icon fetched successfully."), + tr("Icon metadata fetched.")); } else { - ui.m_lblFetchMetadata->setStatus(WidgetWithStatus::StatusType::Warning, - tr("Result: %1.").arg(NetworkFactory::networkErrorText(result.second)), - tr("Icon metadata not fetched.")); + m_ui.m_lblFetchMetadata->setStatus(WidgetWithStatus::StatusType::Warning, + tr("Result: %1.").arg(NetworkFactory::networkErrorText(result.second)), + tr("Icon metadata not fetched.")); } // Remove temporary feed object. @@ -105,9 +132,9 @@ void StandardFeedDetails::guessIconOnly(const QString& url, const QString& usern } else { // No feed guessed, even no icon available. - ui.m_lblFetchMetadata->setStatus(WidgetWithStatus::StatusType::Error, - tr("Error: %1.").arg(NetworkFactory::networkErrorText(result.second)), - tr("No icon fetched.")); + m_ui.m_lblFetchMetadata->setStatus(WidgetWithStatus::StatusType::Error, + tr("Error: %1.").arg(NetworkFactory::networkErrorText(result.second)), + tr("No icon fetched.")); } } @@ -120,28 +147,28 @@ void StandardFeedDetails::guessFeed(const QString& url, const QString& username, if (result.first != nullptr) { // Icon or whole feed was guessed. - ui.m_btnIcon->setIcon(result.first->icon()); - ui.m_txtTitle->lineEdit()->setText(result.first->title()); - ui.m_txtDescription->lineEdit()->setText(result.first->description()); - ui.m_cmbType->setCurrentIndex(ui.m_cmbType->findData(QVariant::fromValue((int) result.first->type()))); - int encoding_index = ui.m_cmbEncoding->findText(result.first->encoding(), Qt::MatchFixedString); + m_ui.m_btnIcon->setIcon(result.first->icon()); + m_ui.m_txtTitle->lineEdit()->setText(result.first->title()); + m_ui.m_txtDescription->lineEdit()->setText(result.first->description()); + m_ui.m_cmbType->setCurrentIndex(m_ui.m_cmbType->findData(QVariant::fromValue((int) result.first->type()))); + int encoding_index = m_ui.m_cmbEncoding->findText(result.first->encoding(), Qt::MatchFixedString); if (encoding_index >= 0) { - ui.m_cmbEncoding->setCurrentIndex(encoding_index); + m_ui.m_cmbEncoding->setCurrentIndex(encoding_index); } else { - ui.m_cmbEncoding->setCurrentIndex(ui.m_cmbEncoding->findText(DEFAULT_FEED_ENCODING, Qt::MatchFixedString)); + m_ui.m_cmbEncoding->setCurrentIndex(m_ui.m_cmbEncoding->findText(DEFAULT_FEED_ENCODING, Qt::MatchFixedString)); } if (result.second == QNetworkReply::NoError) { - ui.m_lblFetchMetadata->setStatus(WidgetWithStatus::StatusType::Ok, - tr("All metadata fetched successfully."), - tr("Feed and icon metadata fetched.")); + m_ui.m_lblFetchMetadata->setStatus(WidgetWithStatus::StatusType::Ok, + tr("All metadata fetched successfully."), + tr("Feed and icon metadata fetched.")); } else { - ui.m_lblFetchMetadata->setStatus(WidgetWithStatus::StatusType::Warning, - tr("Result: %1.").arg(NetworkFactory::networkErrorText(result.second)), - tr("Feed or icon metadata not fetched.")); + m_ui.m_lblFetchMetadata->setStatus(WidgetWithStatus::StatusType::Warning, + tr("Result: %1.").arg(NetworkFactory::networkErrorText(result.second)), + tr("Feed or icon metadata not fetched.")); } // Remove temporary feed object. @@ -149,43 +176,58 @@ void StandardFeedDetails::guessFeed(const QString& url, const QString& username, } else { // No feed guessed, even no icon available. - ui.m_lblFetchMetadata->setStatus(WidgetWithStatus::StatusType::Error, - tr("Error: %1.").arg(NetworkFactory::networkErrorText(result.second)), - tr("No metadata fetched.")); + m_ui.m_lblFetchMetadata->setStatus(WidgetWithStatus::StatusType::Error, + tr("Error: %1.").arg(NetworkFactory::networkErrorText(result.second)), + tr("No metadata fetched.")); } } void StandardFeedDetails::onTitleChanged(const QString& new_title) { if (new_title.simplified().size() >= MIN_CATEGORY_NAME_LENGTH) { - ui.m_txtTitle->setStatus(LineEditWithStatus::StatusType::Ok, tr("Feed name is ok.")); + m_ui.m_txtTitle->setStatus(LineEditWithStatus::StatusType::Ok, tr("Feed name is ok.")); } else { - ui.m_txtTitle->setStatus(LineEditWithStatus::StatusType::Error, tr("Feed name is too short.")); + m_ui.m_txtTitle->setStatus(LineEditWithStatus::StatusType::Error, tr("Feed name is too short.")); } } void StandardFeedDetails::onDescriptionChanged(const QString& new_description) { if (new_description.simplified().isEmpty()) { - ui.m_txtDescription->setStatus(LineEditWithStatus::StatusType::Warning, tr("Description is empty.")); + m_ui.m_txtDescription->setStatus(LineEditWithStatus::StatusType::Warning, tr("Description is empty.")); } else { - ui.m_txtDescription->setStatus(LineEditWithStatus::StatusType::Ok, tr("The description is ok.")); + m_ui.m_txtDescription->setStatus(LineEditWithStatus::StatusType::Ok, tr("The description is ok.")); } } void StandardFeedDetails::onUrlChanged(const QString& new_url) { - if (QRegularExpression(URL_REGEXP).match(new_url).hasMatch()) { - // New url is well-formed. - ui.m_txtUrl->setStatus(LineEditWithStatus::StatusType::Ok, tr("The URL is ok.")); + if (sourceType() == StandardFeed::SourceType::Url) { + if (QRegularExpression(URL_REGEXP).match(new_url).hasMatch()) { + m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Ok, tr("The URL is ok.")); + } + else if (!new_url.simplified().isEmpty()) { + m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Warning, + tr("The URL does not meet standard pattern. " + "Does your URL start with \"http://\" or \"https://\" prefix.")); + } + else { + m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Error, tr("The URL is empty.")); + } } - else if (!new_url.simplified().isEmpty()) { - // New url is not well-formed but is not empty on the other hand. - ui.m_txtUrl->setStatus(LineEditWithStatus::StatusType::Warning, - tr(R"(The URL does not meet standard pattern. Does your URL start with "http://" or "https://" prefix.)")); + else if (sourceType() == StandardFeed::SourceType::Script) { + if (QRegularExpression(SCRIPT_SOURCE_TYPE_REGEXP).match(new_url).hasMatch()) { + m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Ok, tr("The source is ok.")); + } + else if (!new_url.simplified().isEmpty()) { + m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Warning, + tr("The source needs to include \"#\" separator.")); + } + else { + m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Error, tr("The source is empty.")); + } } else { - // New url is empty. - ui.m_txtUrl->setStatus(LineEditWithStatus::StatusType::Error, tr("The URL is empty.")); + m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Ok, tr("The source is ok.")); } } @@ -206,63 +248,69 @@ void StandardFeedDetails::onLoadIconFromFile() { dialog.setLabelText(QFileDialog::DialogLabel::FileType, tr("Icon type:")); if (dialog.exec() == QDialog::DialogCode::Accepted) { - ui.m_btnIcon->setIcon(QIcon(dialog.selectedFiles().value(0))); + m_ui.m_btnIcon->setIcon(QIcon(dialog.selectedFiles().value(0))); } } void StandardFeedDetails::onUseDefaultIcon() { - ui.m_btnIcon->setIcon(QIcon()); + m_ui.m_btnIcon->setIcon(QIcon()); +} + +StandardFeed::SourceType StandardFeedDetails::sourceType() const { + return m_ui.m_cmbSourceType->currentData().value(); } void StandardFeedDetails::prepareForNewFeed(RootItem* parent_to_select, const QString& url) { // Make sure that "default" icon is used as the default option for new // feed. m_actionUseDefaultIcon->trigger(); - int default_encoding_index = ui.m_cmbEncoding->findText(DEFAULT_FEED_ENCODING); + int default_encoding_index = m_ui.m_cmbEncoding->findText(DEFAULT_FEED_ENCODING); if (default_encoding_index >= 0) { - ui.m_cmbEncoding->setCurrentIndex(default_encoding_index); + m_ui.m_cmbEncoding->setCurrentIndex(default_encoding_index); } if (parent_to_select != nullptr) { if (parent_to_select->kind() == RootItem::Kind::Category) { - ui.m_cmbParentCategory->setCurrentIndex(ui.m_cmbParentCategory->findData(QVariant::fromValue((void*)parent_to_select))); + m_ui.m_cmbParentCategory->setCurrentIndex(m_ui.m_cmbParentCategory->findData(QVariant::fromValue((void*)parent_to_select))); } else if (parent_to_select->kind() == RootItem::Kind::Feed) { - int target_item = ui.m_cmbParentCategory->findData(QVariant::fromValue((void*)parent_to_select->parent())); + int target_item = m_ui.m_cmbParentCategory->findData(QVariant::fromValue((void*)parent_to_select->parent())); if (target_item >= 0) { - ui.m_cmbParentCategory->setCurrentIndex(target_item); + m_ui.m_cmbParentCategory->setCurrentIndex(target_item); } } } if (!url.isEmpty()) { - ui.m_txtUrl->lineEdit()->setText(url); + m_ui.m_txtSource->lineEdit()->setText(url); } else if (Application::clipboard()->mimeData()->hasText()) { - ui.m_txtUrl->lineEdit()->setText(Application::clipboard()->text()); + m_ui.m_txtSource->lineEdit()->setText(Application::clipboard()->text()); } - ui.m_txtUrl->setFocus(); + m_ui.m_txtSource->setFocus(); } void StandardFeedDetails::setExistingFeed(StandardFeed* feed) { - ui.m_cmbParentCategory->setCurrentIndex(ui.m_cmbParentCategory->findData(QVariant::fromValue((void*)feed->parent()))); - ui.m_txtTitle->lineEdit()->setText(feed->title()); - ui.m_txtDescription->lineEdit()->setText(feed->description()); - ui.m_btnIcon->setIcon(feed->icon()); - ui.m_txtUrl->lineEdit()->setText(feed->url()); - ui.m_cmbType->setCurrentIndex(ui.m_cmbType->findData(QVariant::fromValue(int(feed->type())))); - ui.m_cmbEncoding->setCurrentIndex(ui.m_cmbEncoding->findData(feed->encoding(), - Qt::ItemDataRole::DisplayRole, - Qt::MatchFlag::MatchFixedString)); + m_ui.m_cmbSourceType->setCurrentIndex(m_ui.m_cmbSourceType->findData(QVariant::fromValue(feed->sourceType()))); + m_ui.m_cmbParentCategory->setCurrentIndex(m_ui.m_cmbParentCategory->findData(QVariant::fromValue((void*)feed->parent()))); + m_ui.m_txtTitle->lineEdit()->setText(feed->title()); + m_ui.m_txtDescription->lineEdit()->setText(feed->description()); + m_ui.m_btnIcon->setIcon(feed->icon()); + m_ui.m_txtSource->lineEdit()->setText(feed->url()); + m_ui.m_txtPostProcessScript->lineEdit()->setText(feed->postProcessScript()); + m_ui.m_cmbType->setCurrentIndex(m_ui.m_cmbType->findData(QVariant::fromValue(int(feed->type())))); + m_ui.m_cmbEncoding->setCurrentIndex(m_ui.m_cmbEncoding->findData(feed->encoding(), + Qt::ItemDataRole::DisplayRole, + Qt::MatchFlag::MatchFixedString)); } void StandardFeedDetails::loadCategories(const QList& categories, RootItem* root_item) { - ui.m_cmbParentCategory->addItem(root_item->fullIcon(), root_item->title(), QVariant::fromValue((void*) root_item)); + m_ui.m_cmbParentCategory->addItem(root_item->fullIcon(), root_item->title(), QVariant::fromValue((void*) root_item)); for (Category* category : categories) { - ui.m_cmbParentCategory->addItem(category->fullIcon(), category->title(), QVariant::fromValue((void*) category)); + m_ui.m_cmbParentCategory->addItem(category->fullIcon(), category->title(), QVariant::fromValue((void*) category)); } } diff --git a/src/librssguard/services/standard/gui/standardfeeddetails.h b/src/librssguard/services/standard/gui/standardfeeddetails.h index 6c83dfadd..cabfbb8ab 100755 --- a/src/librssguard/services/standard/gui/standardfeeddetails.h +++ b/src/librssguard/services/standard/gui/standardfeeddetails.h @@ -7,11 +7,12 @@ #include "ui_standardfeeddetails.h" +#include "services/standard/standardfeed.h" + #include class Category; class RootItem; -class StandardFeed; class StandardFeedDetails : public QWidget { Q_OBJECT @@ -37,13 +38,15 @@ class StandardFeedDetails : public QWidget { void onLoadIconFromFile(); void onUseDefaultIcon(); + StandardFeed::SourceType sourceType() const; + private: void prepareForNewFeed(RootItem* parent_to_select, const QString& url); void setExistingFeed(StandardFeed* feed); void loadCategories(const QList& categories, RootItem* root_item); private: - Ui::StandardFeedDetails ui; + Ui::StandardFeedDetails m_ui; QMenu* m_iconMenu{}; QAction* m_actionLoadIconFromFile{}; QAction* m_actionUseDefaultIcon{}; diff --git a/src/librssguard/services/standard/gui/standardfeeddetails.ui b/src/librssguard/services/standard/gui/standardfeeddetails.ui index 95cb8be88..8fbce5426 100755 --- a/src/librssguard/services/standard/gui/standardfeeddetails.ui +++ b/src/librssguard/services/standard/gui/standardfeeddetails.ui @@ -7,7 +7,7 @@ 0 0 429 - 260 + 321 @@ -103,17 +103,31 @@ - URL + Source - m_txtUrl + m_txtSource - + + + + + + + + + 1 + 0 + + + + + - + Fetch metadata @@ -123,7 +137,7 @@ - + @@ -147,7 +161,7 @@ - + Icon @@ -157,7 +171,7 @@ - + @@ -194,6 +208,29 @@ + + + + + + + Post-process script + + + + + + + You can use URL as a source of your feed or you can produce your feed with custom script. Also, you can post-process generated feed data with yet another script if you wish. These are advanced features and make sure to read the documentation before your use them. + + + Qt::AlignCenter + + + true + + + @@ -210,13 +247,6 @@ 1 - - m_cmbParentCategory - m_cmbType - m_cmbEncoding - m_btnFetchMetadata - m_btnIcon - diff --git a/src/librssguard/services/standard/standardfeed.cpp b/src/librssguard/services/standard/standardfeed.cpp index f79ed3ee7..ed3740321 100644 --- a/src/librssguard/services/standard/standardfeed.cpp +++ b/src/librssguard/services/standard/standardfeed.cpp @@ -35,13 +35,14 @@ StandardFeed::StandardFeed(RootItem* parent_item) m_networkError = QNetworkReply::NetworkError::NoError; m_type = Type::Rss0X; m_sourceType = SourceType::Url; - m_encoding = QString(); + m_encoding = m_postProcessScript = QString(); } StandardFeed::StandardFeed(const StandardFeed& other) : Feed(other) { m_networkError = other.networkError(); m_type = other.type(); + m_postProcessScript = other.postProcessScript(); m_sourceType = other.sourceType(); m_encoding = other.encoding(); } @@ -112,6 +113,22 @@ QString StandardFeed::typeToString(StandardFeed::Type type) { } } +QString StandardFeed::sourceTypeToString(StandardFeed::SourceType type) { + switch (type) { + case StandardFeed::SourceType::Url: + return QSL("URL"); + + case StandardFeed::SourceType::Script: + return tr("Script"); + + case StandardFeed::SourceType::LocalFile: + return tr("Local file"); + + default: + return tr("Unknown"); + } +} + void StandardFeed::fetchMetadataForItself() { QPair metadata = guessFeed(url(), username(), @@ -141,6 +158,14 @@ void StandardFeed::fetchMetadataForItself() { } } +QString StandardFeed::postProcessScript() const { + return m_postProcessScript; +} + +void StandardFeed::setPostProcessScript(const QString& post_process_script) { + m_postProcessScript = post_process_script; +} + StandardFeed::SourceType StandardFeed::sourceType() const { return m_sourceType; } @@ -370,9 +395,11 @@ bool StandardFeed::addItself(RootItem* parent) { // Now, add feed to persistent storage. QSqlDatabase database = qApp->database()->connection(metaObject()->className()); bool ok; - int new_id = DatabaseQueries::addStandardFeed(database, parent->id(), parent->getParentServiceRoot()->accountId(), title(), - description(), creationDate(), icon(), encoding(), url(), passwordProtected(), - username(), password(), autoUpdateType(), autoUpdateInitialInterval(), type(), &ok); + int new_id = DatabaseQueries::addStandardFeed(database, parent->id(), parent->getParentServiceRoot()->accountId(), + title(), description(), creationDate(), icon(), encoding(), url(), + passwordProtected(), username(), password(), autoUpdateType(), + autoUpdateInitialInterval(), sourceType(), postProcessScript(), + type(), &ok); if (!ok) { // Query failed. @@ -396,6 +423,7 @@ bool StandardFeed::editItself(StandardFeed* new_feed_data) { new_feed_data->encoding(), new_feed_data->url(), new_feed_data->passwordProtected(), new_feed_data->username(), new_feed_data->password(), new_feed_data->autoUpdateType(), new_feed_data->autoUpdateInitialInterval(), + new_feed_data->sourceType(), new_feed_data->postProcessScript(), new_feed_data->type())) { // Persistent storage update failed, no way to continue now. qWarningNN << LOGSEC_CORE @@ -417,6 +445,7 @@ bool StandardFeed::editItself(StandardFeed* new_feed_data) { original_feed->setAutoUpdateInitialInterval(new_feed_data->autoUpdateInitialInterval()); original_feed->setType(new_feed_data->type()); original_feed->setSourceType(new_feed_data->sourceType()); + original_feed->setPostProcessScript(new_feed_data->postProcessScript()); // Editing is done. return true; diff --git a/src/librssguard/services/standard/standardfeed.h b/src/librssguard/services/standard/standardfeed.h index e3578aee8..355bc61d7 100644 --- a/src/librssguard/services/standard/standardfeed.h +++ b/src/librssguard/services/standard/standardfeed.h @@ -71,6 +71,9 @@ class StandardFeed : public Feed { QString encoding() const; void setEncoding(const QString& encoding); + QString postProcessScript() const; + void setPostProcessScript(const QString& post_process_script); + QNetworkReply::NetworkError networkError() const; QList obtainNewMessages(bool* error_during_obtaining); @@ -87,6 +90,7 @@ class StandardFeed : public Feed { // Converts particular feed type to string. static QString typeToString(Type type); + static QString sourceTypeToString(SourceType type); public slots: void fetchMetadataForItself(); @@ -94,11 +98,13 @@ class StandardFeed : public Feed { private: SourceType m_sourceType; Type m_type; + QString m_postProcessScript; QNetworkReply::NetworkError m_networkError; QString m_encoding; }; +Q_DECLARE_METATYPE(StandardFeed::SourceType) Q_DECLARE_METATYPE(StandardFeed::Type) #endif // FEEDSMODELFEED_H