diff --git a/src/librssguard/CMakeLists.txt b/src/librssguard/CMakeLists.txt index eb58d70cd..eaadab8de 100644 --- a/src/librssguard/CMakeLists.txt +++ b/src/librssguard/CMakeLists.txt @@ -229,6 +229,8 @@ set(SOURCES network-web/oauth2service.h network-web/oauthhttphandler.cpp network-web/oauthhttphandler.h + network-web/readability.cpp + network-web/readability.h network-web/silentnetworkaccessmanager.cpp network-web/silentnetworkaccessmanager.h network-web/webfactory.cpp diff --git a/src/librssguard/gui/webbrowser.cpp b/src/librssguard/gui/webbrowser.cpp index 4592f3491..ddf54f9b6 100644 --- a/src/librssguard/gui/webbrowser.cpp +++ b/src/librssguard/gui/webbrowser.cpp @@ -11,6 +11,7 @@ #include "miscellaneous/application.h" #include "miscellaneous/iconfactory.h" #include "network-web/networkfactory.h" +#include "network-web/readability.h" #include "network-web/webfactory.h" #include "services/abstract/serviceroot.h" @@ -36,7 +37,10 @@ WebBrowser::WebBrowser(QWidget* parent) : TabContent(parent), m_actionStop(m_webView->pageAction(QWebEnginePage::WebAction::Stop)), m_actionOpenInSystemBrowser(new QAction(qApp->icons()->fromTheme(QSL("document-open")), tr("Open this website in system web browser"), - this)) { + this)), + m_actionReadabilePage(new QAction(qApp->icons()->fromTheme(QSL("document-preview")), + tr("View website in reader mode"), + this)) { // Initialize the components and layout. initializeLayout(); setFocusProxy(m_txtLocation); @@ -64,6 +68,7 @@ void WebBrowser::createConnections() { }); connect(m_actionOpenInSystemBrowser, &QAction::triggered, this, &WebBrowser::openCurrentSiteInSystemBrowser); + connect(m_actionReadabilePage, &QAction::triggered, this, &WebBrowser::readabilePage); connect(m_txtLocation, &LocationLineEdit::submitted, this, static_cast(&WebBrowser::loadUrl)); @@ -79,6 +84,8 @@ void WebBrowser::createConnections() { connect(m_webView, &WebViewer::iconChanged, this, &WebBrowser::onIconChanged); connect(m_webView->page(), &WebPage::windowCloseRequested, this, &WebBrowser::closeRequested); + connect(qApp->web()->readability(), &Readability::htmlReadabled, this, &WebBrowser::setReadabledHtml); + connect(qApp->web()->readability(), &Readability::errorOnHtmlReadabiliting, this, &WebBrowser::readabilityFailed); } void WebBrowser::updateUrl(const QUrl& url) { @@ -167,6 +174,13 @@ void WebBrowser::loadMessage(const Message& message, RootItem* root) { loadMessages({ message }, root); } +void WebBrowser::readabilePage() { + m_actionReadabilePage->setEnabled(false); + m_webView->page()->toHtml([this](const QString& htm) { + qApp->web()->readability()->makeHtmlReadable(htm, m_webView->url().toString()); + }); +} + bool WebBrowser::eventFilter(QObject* watched, QEvent* event) { Q_UNUSED(watched) @@ -208,6 +222,18 @@ void WebBrowser::onIconChanged(const QIcon& icon) { emit iconChanged(m_index, icon); } +void WebBrowser::setReadabledHtml(const QString& better_html) { + m_webView->setHtml(better_html, m_webView->url()); +} + +void WebBrowser::readabilityFailed(const QString& error) { + MessageBox::show({}, QMessageBox::Icon::Critical, + tr("Reader mode failed for this website"), + tr("Reader mode cannot be applied to current page."), + {}, + error); +} + void WebBrowser::initializeLayout() { m_toolBar->setFloatable(false); m_toolBar->setMovable(false); @@ -227,6 +253,8 @@ void WebBrowser::initializeLayout() { QWidgetAction* act_discover = new QWidgetAction(this); m_actionOpenInSystemBrowser->setEnabled(false); + m_actionReadabilePage->setEnabled(false); + act_discover->setDefaultWidget(m_btnDiscoverFeeds); // Add needed actions into toolbar. @@ -235,6 +263,7 @@ void WebBrowser::initializeLayout() { m_toolBar->addAction(m_actionReload); m_toolBar->addAction(m_actionStop); m_toolBar->addAction(m_actionOpenInSystemBrowser); + m_toolBar->addAction(m_actionReadabilePage); m_toolBar->addAction(act_discover); m_toolBar->addWidget(m_txtLocation); @@ -260,6 +289,7 @@ void WebBrowser::onLoadingStarted() { m_btnDiscoverFeeds->clearFeedAddresses(); m_loadingProgress->show(); m_actionOpenInSystemBrowser->setEnabled(false); + m_actionReadabilePage->setEnabled(false); } void WebBrowser::onLoadingProgress(int progress) { @@ -270,8 +300,13 @@ void WebBrowser::onLoadingFinished(bool success) { if (success) { auto url = m_webView->url(); - if (url.isValid() && !url.host().contains(QSL(APP_LOW_NAME))) { + if (url.isValid() && !url.host().isEmpty()) { m_actionOpenInSystemBrowser->setEnabled(true); + m_actionReadabilePage->setEnabled(true); + } + else { + m_actionOpenInSystemBrowser->setEnabled(false); + m_actionReadabilePage->setEnabled(false); } // Let's check if there are any feeds defined on the web and eventually diff --git a/src/librssguard/gui/webbrowser.h b/src/librssguard/gui/webbrowser.h index 7855111f7..3acdcf74c 100644 --- a/src/librssguard/gui/webbrowser.h +++ b/src/librssguard/gui/webbrowser.h @@ -55,6 +55,7 @@ class WebBrowser : public TabContent { virtual bool eventFilter(QObject* watched, QEvent* event); private slots: + void readabilePage(); void openCurrentSiteInSystemBrowser(); void updateUrl(const QUrl& url); void onLoadingStarted(); @@ -62,6 +63,8 @@ class WebBrowser : public TabContent { void onLoadingFinished(bool success); void onTitleChanged(const QString& new_title); void onIconChanged(const QIcon& icon); + void setReadabledHtml(const QString& better_html); + void readabilityFailed(const QString& error); signals: void closeRequested(); @@ -87,6 +90,7 @@ class WebBrowser : public TabContent { QAction* m_actionReload; QAction* m_actionStop; QAction* m_actionOpenInSystemBrowser; + QAction* m_actionReadabilePage; QList m_messages; QPointer m_root; }; diff --git a/src/librssguard/miscellaneous/settings.cpp b/src/librssguard/miscellaneous/settings.cpp index b25251b10..e671cd59c 100644 --- a/src/librssguard/miscellaneous/settings.cpp +++ b/src/librssguard/miscellaneous/settings.cpp @@ -51,7 +51,8 @@ DVALUE(bool) AdBlock::AdBlockEnabledDef = false; DKEY AdBlock::FilterLists = "filter_lists"; DVALUE(QStringList) AdBlock::FilterListsDef = { QSL("https://easylist.to/easylist/easylist.txt"), - QSL("https://easylist.to/easylist/easyprivacy.txt") + QSL("https://easylist.to/easylist/easyprivacy.txt"), + QSL("https://easylist.to/easylist/fanboy-social.txt") }; DKEY AdBlock::CustomFilters = "custom_filters"; diff --git a/src/librssguard/network-web/adblock/adblockmanager.cpp b/src/librssguard/network-web/adblock/adblockmanager.cpp index 9c56f6c26..c52d51561 100644 --- a/src/librssguard/network-web/adblock/adblockmanager.cpp +++ b/src/librssguard/network-web/adblock/adblockmanager.cpp @@ -328,46 +328,6 @@ QProcess* AdBlockManager::startServer(int port) { QDir::toNativeSeparators(m_unifiedFiltersFile) }); - /* - #if defined(Q_OS_WIN) - proc->setProgram(QSL("node.exe")); - #else - proc->setProgram(QSL("node")); - #endif - - proc->setArguments({ - QDir::toNativeSeparators(temp_server), - QString::number(port), - QDir::toNativeSeparators(m_unifiedFiltersFile) - }); - - proc->setProcessEnvironment(QProcessEnvironment::systemEnvironment()); - - auto pe = proc->processEnvironment(); - - if (!pe.contains(QSL("NODE_PATH"))) { - try { - const QString system_node_prefix = IOFactory::startProcessGetOutput( - #if defined(Q_OS_WIN) - QSL("npm.cmd") - #else - QSL("npm") - #endif - , { QSL("root"), QSL("--quiet"), QSL("-g") } - ); - - if (!system_node_prefix.isEmpty()) { - pe.insert(QSL("NODE_PATH"), system_node_prefix.simplified()); - } - } - catch (const ApplicationException& ex) { - qWarningNN << LOGSEC_ADBLOCK << "Failed to get NPM root path:" << QUOTE_W_SPACE_DOT(ex.message()); - } - } - - proc->setProcessEnvironment(pe); - */ - qDebugNN << LOGSEC_ADBLOCK << "Attempting to start AdBlock server."; return proc; } diff --git a/src/librssguard/network-web/readability.cpp b/src/librssguard/network-web/readability.cpp new file mode 100755 index 000000000..d4d5f6610 --- /dev/null +++ b/src/librssguard/network-web/readability.cpp @@ -0,0 +1,37 @@ +// For license of this file, see /LICENSE.md. + +#include "network-web/readability.h" + +#include "miscellaneous/application.h" +#include "miscellaneous/nodejs.h" + +Readability::Readability(QObject* parent) : QObject{parent} {} + +void Readability::makeHtmlReadable(const QString& html, const QString& base_url) { + QProcess* proc = new QProcess(this); + + connect(proc, QOverload::of(&QProcess::finished), this, &Readability::onReadabilityFinished); + + qApp->nodejs()->runScript(proc, + QSL("c:\\Projekty\\Moje\\build-rssguard-Desktop_Qt_6_2_3_MSVC2017_64bit-Debug\\" + "src\\rssguard\\data4\\node-packages-windows\\article-to-readable.js"), + { base_url }); + + proc->write(html.toUtf8()); + proc->closeWriteChannel(); +} + +void Readability::onReadabilityFinished(int exit_code, QProcess::ExitStatus exit_status) { + QProcess* proc = qobject_cast(sender()); + + if (exit_status == QProcess::ExitStatus::NormalExit && + exit_code == EXIT_SUCCESS) { + emit htmlReadabled(QString::fromUtf8(proc->readAllStandardOutput())); + } + else { + QString err = QString::fromUtf8(proc->readAllStandardError()); + emit errorOnHtmlReadabiliting(err); + } + + proc->deleteLater(); +} diff --git a/src/librssguard/network-web/readability.h b/src/librssguard/network-web/readability.h new file mode 100755 index 000000000..d35680290 --- /dev/null +++ b/src/librssguard/network-web/readability.h @@ -0,0 +1,26 @@ +// For license of this file, see /LICENSE.md. + +#ifndef READABILITY_H +#define READABILITY_H + +#include + +#include + +class Readability : public QObject { + Q_OBJECT + + public: + explicit Readability(QObject* parent = nullptr); + + void makeHtmlReadable(const QString& html, const QString& base_url = {}); + + private slots: + void onReadabilityFinished(int exit_code, QProcess::ExitStatus exit_status); + + signals: + void htmlReadabled(const QString& better_html); + void errorOnHtmlReadabiliting(const QString& error); +}; + +#endif // READABILITY_H diff --git a/src/librssguard/network-web/webfactory.cpp b/src/librssguard/network-web/webfactory.cpp index c26dbabea..48357ab73 100644 --- a/src/librssguard/network-web/webfactory.cpp +++ b/src/librssguard/network-web/webfactory.cpp @@ -6,6 +6,7 @@ #include "miscellaneous/application.h" #include "miscellaneous/iconfactory.h" #include "network-web/cookiejar.h" +#include "network-web/readability.h" #include #include @@ -37,6 +38,7 @@ WebFactory::WebFactory(QObject* parent) #endif m_cookieJar = new CookieJar(nullptr); + m_readability = new Readability(this); #if defined(USE_WEBENGINE) #if QT_VERSION >= 0x050D00 // Qt >= 5.13.0 @@ -363,6 +365,10 @@ CookieJar* WebFactory::cookieJar() const { return m_cookieJar; } +Readability* WebFactory::readability() const { + return m_readability; +} + void WebFactory::generateUnescapes() { m_htmlNamedEntities[QSL("AElig")] = 0x00c6; m_htmlNamedEntities[QSL("AMP")] = 38; diff --git a/src/librssguard/network-web/webfactory.h b/src/librssguard/network-web/webfactory.h index 9f9c814c0..e6d04a551 100644 --- a/src/librssguard/network-web/webfactory.h +++ b/src/librssguard/network-web/webfactory.h @@ -20,6 +20,7 @@ class NetworkUrlInterceptor; #endif class CookieJar; +class Readability; class WebFactory : public QObject { Q_OBJECT @@ -46,6 +47,7 @@ class WebFactory : public QObject { #endif CookieJar* cookieJar() const; + Readability* readability() const; void updateProxy(); bool openUrlInExternalBrowser(const QString& url) const; @@ -71,6 +73,7 @@ class WebFactory : public QObject { #endif CookieJar* m_cookieJar; + Readability* m_readability; QMap m_htmlNamedEntities; };