diff --git a/src/librssguard/miscellaneous/textfactory.cpp b/src/librssguard/miscellaneous/textfactory.cpp index 7124bd699..eaee586e6 100644 --- a/src/librssguard/miscellaneous/textfactory.cpp +++ b/src/librssguard/miscellaneous/textfactory.cpp @@ -145,8 +145,8 @@ QStringList TextFactory::dateTimePatterns() { << QSL("ddd, d MMM yyyy HH:mm:ss") << QSL("dd MMM yyyy hh:mm:ss") << QSL("dd MMM yyyy") << QSL("yyyy-MM-dd HH:mm:ss.z") << QSL("yyyy-MM-dd") << QSL("yyyy") << QSL("yyyy-MM") << QSL("yyyy-MM-dd") << QSL("yyyy-MM-ddThh:mm") << QSL("yyyy-MM-ddThh:mm:ss") - << QSL("d MMM yyyy HH:mm:ss") << QSL("hh:mm:ss") << QSL("h:m:s AP") << QSL("h:mm") << QSL("H:mm") - << QSL("h:m") << QSL("h.m"); + << QSL("d MMM yyyy HH:mm:ss") << QSL("yyyyMMddThhmmss") << QSL("yyyyMMdd") << QSL("hh:mm:ss") + << QSL("h:m:s AP") << QSL("h:mm") << QSL("H:mm") << QSL("h:m") << QSL("h.m"); } QString TextFactory::encrypt(const QString& text, quint64 key) { diff --git a/src/librssguard/services/standard/gui/formdiscoverfeeds.cpp b/src/librssguard/services/standard/gui/formdiscoverfeeds.cpp index 3b8119557..8d1ec0676 100644 --- a/src/librssguard/services/standard/gui/formdiscoverfeeds.cpp +++ b/src/librssguard/services/standard/gui/formdiscoverfeeds.cpp @@ -15,6 +15,7 @@ #include "services/standard/standardfeed.h" #include "services/standard/parsers/atomparser.h" +#include "services/standard/parsers/icalparser.h" #include "services/standard/parsers/jsonparser.h" #include "services/standard/parsers/rdfparser.h" #include "services/standard/parsers/rssparser.h" @@ -31,7 +32,12 @@ FormDiscoverFeeds::FormDiscoverFeeds(ServiceRoot* service_root, GuiUtilities::applyDialogProperties(*this, qApp->icons()->fromTheme(QSL("application-rss+xml"))); - m_parsers = {new AtomParser({}), new RssParser({}), new RdfParser({}), new JsonParser({}), new SitemapParser({})}; + m_parsers = {new AtomParser({}), + new RssParser({}), + new RdfParser({}), + new IcalParser({}), + new JsonParser({}), + new SitemapParser({})}; m_btnGoAdvanced = m_ui.m_buttonBox->addButton(tr("Switch to &advanced mode"), QDialogButtonBox::ButtonRole::NoRole); m_btnGoAdvanced diff --git a/src/librssguard/services/standard/gui/standardfeeddetails.cpp b/src/librssguard/services/standard/gui/standardfeeddetails.cpp index 4d673c4c5..d2de81c5f 100644 --- a/src/librssguard/services/standard/gui/standardfeeddetails.cpp +++ b/src/librssguard/services/standard/gui/standardfeeddetails.cpp @@ -53,6 +53,8 @@ StandardFeedDetails::StandardFeedDetails(QWidget* parent) : QWidget(parent) { 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::iCalendar), + QVariant::fromValue(int(StandardFeed::Type::iCalendar))); m_ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Json), QVariant::fromValue(int(StandardFeed::Type::Json))); m_ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Sitemap), diff --git a/src/librssguard/services/standard/parsers/atomparser.h b/src/librssguard/services/standard/parsers/atomparser.h index d5365cdf4..e7784059e 100644 --- a/src/librssguard/services/standard/parsers/atomparser.h +++ b/src/librssguard/services/standard/parsers/atomparser.h @@ -21,6 +21,9 @@ class AtomParser : public FeedParser { const QString& content_type) const; protected: + virtual QDomNodeList xmlMessageElements(); + virtual QString feedAuthor() const; + virtual QString xmlMessageTitle(const QDomElement& msg_element) const; virtual QString xmlMessageDescription(const QDomElement& msg_element) const; virtual QDateTime xmlMessageDateCreated(const QDomElement& msg_element) const; @@ -28,9 +31,7 @@ class AtomParser : public FeedParser { virtual QString xmlMessageUrl(const QDomElement& msg_element) const; virtual QList xmlMessageEnclosures(const QDomElement& msg_element) const; virtual QList xmlMessageCategories(const QDomElement& msg_element) const; - virtual QDomNodeList xmlMessageElements(); virtual QString xmlMessageAuthor(const QDomElement& msg_element) const; - virtual QString feedAuthor() const; private: QString atomNamespace() const; diff --git a/src/librssguard/services/standard/parsers/feedparser.h b/src/librssguard/services/standard/parsers/feedparser.h index d8898d1ce..9b1dbd0f3 100644 --- a/src/librssguard/services/standard/parsers/feedparser.h +++ b/src/librssguard/services/standard/parsers/feedparser.h @@ -63,6 +63,19 @@ class FeedParser { virtual QList jsonMessageCategories(const QJsonObject& msg_element) const; virtual QString jsonMessageRawContents(const QJsonObject& msg_element) const; + /* + // Objects. + virtual QVariantList objMessageElements(); + virtual QString objMessageTitle(const QVariantMap& msg_element) const; + virtual QString objMessageUrl(const QVariantMap& msg_element) const; + virtual QString objMessageDescription(const QVariantMap& msg_element) const; + virtual QString objMessageAuthor(const QVariantMap& msg_element) const; + virtual QDateTime objMessageDateCreated(const QVariantMap& msg_element) const; + virtual QString objMessageId(const QVariantMap& msg_element) const; + virtual QList objMessageEnclosures(const QVariantMap& msg_element) const; + virtual QList objMessageCategories(const QVariantMap& msg_element) const; + virtual QString objMessageRawContents(const QVariantMap& msg_element) const; +*/ protected: QList xmlMrssGetEnclosures(const QDomElement& msg_element) const; QString xmlMrssTextFromPath(const QDomElement& msg_element, const QString& xml_path) const; diff --git a/src/librssguard/services/standard/parsers/icalparser.cpp b/src/librssguard/services/standard/parsers/icalparser.cpp index 7cc3efa3d..7884ced48 100644 --- a/src/librssguard/services/standard/parsers/icalparser.cpp +++ b/src/librssguard/services/standard/parsers/icalparser.cpp @@ -5,15 +5,58 @@ #include "definitions/definitions.h" #include "exceptions/applicationexception.h" #include "exceptions/feedrecognizedbutfailedexception.h" +#include "miscellaneous/application.h" +#include "miscellaneous/settings.h" +#include "miscellaneous/textfactory.h" #include "services/standard/definitions.h" IcalParser::IcalParser(const QString& data) : FeedParser(data, DataType::Other) {} IcalParser::~IcalParser() {} +QList IcalParser::discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const { + auto base_result = FeedParser::discoverFeeds(root, url, greedy); + + if (!base_result.isEmpty()) { + return base_result; + } + + QString my_url = url.toString(); + + // Test direct URL for a feed. + int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); + QByteArray data; + auto res = NetworkFactory::performNetworkOperation(my_url, + timeout, + {}, + data, + QNetworkAccessManager::Operation::GetOperation, + {}, + {}, + {}, + {}, + root->networkProxy()); + + if (res.m_networkError == QNetworkReply::NetworkError::NoError) { + try { + // 1. + auto guessed_feed = guessFeed(data, res.m_contentType); + + guessed_feed.first->setSource(my_url); + + return {guessed_feed.first}; + } + catch (...) { + qDebugNN << LOGSEC_CORE << QUOTE_W_SPACE(my_url) << "is not a direct feed file."; + } + } + + return {}; +} + QPair> IcalParser::guessFeed(const QByteArray& content, const QString& content_type) const { - if (content_type == QSL("text/calendar") || content.contains('\n')) { + if (content_type.contains(QSL("text/calendar")) || content.contains("\r\n")) { Icalendar calendar; try { @@ -27,7 +70,7 @@ QPair> IcalParser::guessFeed(const QByteArray QList icon_possible_locations; feed->setEncoding(QSL(DEFAULT_FEED_ENCODING)); - feed->setType(StandardFeed::Type::Atom10); + feed->setType(StandardFeed::Type::iCalendar); feed->setTitle(calendar.title()); return QPair>(feed, icon_possible_locations); @@ -37,10 +80,8 @@ QPair> IcalParser::guessFeed(const QByteArray } } -Icalendar::Icalendar() {} - -Icalendar::Icalendar(const QByteArray& data) { - processLines(data); +Icalendar::Icalendar(const QByteArray& data) : FeedParser(QString::fromUtf8(data), FeedParser::DataType::Ical) { + processLines(m_data); } QString Icalendar::title() const { @@ -51,9 +92,99 @@ void Icalendar::setTitle(const QString& title) { m_title = title; } -void Icalendar::processLines(const QByteArray& data) { - QString str_data = QString::fromUtf8(data); +void Icalendar::processLines(const QString& data) { + QRegularExpression regex("^BEGIN:(\\w+)\\r$(.+?)^(BEGIN|END):\\w+", + QRegularExpression::PatternOption::MultilineOption | + QRegularExpression::PatternOption::DotMatchesEverythingOption); - QStringList str_blocks = - str_data.remove(QRegularExpression("^END:\\w+$")).split(QRegularExpression(QSL("^BEGIN:\\w+$"))); + auto all_matches = regex.globalMatch(data); + + while (all_matches.hasNext()) { + auto match = all_matches.next(); + QString component = match.captured(1); + QString body = match.captured(2); + + if (component == QSL("VCALENDAR")) { + processComponentCalendar(body); + } + + if (component == QSL("VEVENT")) { + processComponentEvent(body); + } + } +} + +void Icalendar::processComponentCalendar(const QString& body) { + auto tokenized = tokenizeBody(body); + + setTitle(tokenized.value(QSL("X-WR-CALNAME"))); +} + +void Icalendar::processComponentEvent(const QString& body) { + auto tokenized = tokenizeBody(body); + + EventComponent event; + + event.setUid(tokenized.value(QSL("UID"))); + event.setTitle(tokenized.value(QSL("SUMMARY"))); + event.setDescription(tokenized.value(QSL("DESCRIPTION"))); + event.setCreated(TextFactory::parseDateTime(tokenized.value(QSL("CREATED")))); + + m_components.append(event); +} + +QMap Icalendar::tokenizeBody(const QString& body) const { + QRegularExpression regex("^(?=[A-Z-]+:)", QRegularExpression::PatternOption::MultilineOption); + auto all_matches = body.split(regex); + QMap res; + + for (const QString& match : all_matches) { + int sep = match.indexOf(':'); + QString property = match.left(sep).simplified(); + + if (property.isEmpty()) { + continue; + } + + QString value = match.mid(sep + 1); + + value = value.replace(QRegularExpression("\\r\\n\\s?"), QString()); + value = value.replace(QRegularExpression("\\r?\\n"), QSL("
")); + + res.insert(property, value); + } + + return res; +} + +QString IcalendarComponent::uid() const { + return m_uid; +} + +void IcalendarComponent::setUid(const QString& uid) { + m_uid = uid; +} + +QString EventComponent::title() const { + return m_title; +} + +void EventComponent::setTitle(const QString& title) { + m_title = title; +} + +QString EventComponent::description() const { + return m_description; +} + +void EventComponent::setDescription(const QString& description) { + m_description = description; +} + +QDateTime EventComponent::created() const { + return m_created; +} + +void EventComponent::setCreated(const QDateTime& created) { + m_created = created; } diff --git a/src/librssguard/services/standard/parsers/icalparser.h b/src/librssguard/services/standard/parsers/icalparser.h index 21894f633..9fb246839 100644 --- a/src/librssguard/services/standard/parsers/icalparser.h +++ b/src/librssguard/services/standard/parsers/icalparser.h @@ -5,27 +5,46 @@ #include "services/standard/parsers/feedparser.h" -class IcalendarComponent {}; - -class EventComponent : public IcalendarComponent {}; - -class Icalendar { +class IcalendarComponent { public: - enum class ProcessingState { - BeginCalendar, - EndCalendar, - BeginEvent, - EndEvent - } + QString uid() const; + void setUid(const QString& uid); - explicit Icalendar(); - explicit Icalendar(const QByteArray& data); + private: + QString m_uid; +}; + +class EventComponent : public IcalendarComponent { + public: + QString title() const; + void setTitle(const QString& title); + + QString description() const; + void setDescription(const QString& description); + + QDateTime created() const; + void setCreated(const QDateTime& created); + + private: + QString m_title; + QString m_description; + QDateTime m_created; +}; + +class Icalendar : public FeedParser { + public: + explicit Icalendar(const QByteArray& data = {}); QString title() const; void setTitle(const QString& title); private: - void processLines(const QByteArray& data); + void processLines(const QString& data); + void processComponentCalendar(const QString& body); + void processComponentEvent(const QString& body); + + QDateTime parseDateTime(const QString& date_time) const; + QMap tokenizeBody(const QString& body) const; private: QString m_title; @@ -37,6 +56,8 @@ class IcalParser : public FeedParser { explicit IcalParser(const QString& data); virtual ~IcalParser(); + virtual QList discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const; + virtual QPair> guessFeed(const QByteArray& content, const QString& content_type) const; }; diff --git a/src/librssguard/services/standard/parsers/jsonparser.h b/src/librssguard/services/standard/parsers/jsonparser.h index 31c46b4da..564fd1931 100644 --- a/src/librssguard/services/standard/parsers/jsonparser.h +++ b/src/librssguard/services/standard/parsers/jsonparser.h @@ -20,6 +20,7 @@ class JsonParser : public FeedParser { protected: virtual QString feedAuthor() const; virtual QJsonArray jsonMessageElements(); + virtual QString jsonMessageTitle(const QJsonObject& msg_element) const; virtual QString jsonMessageUrl(const QJsonObject& msg_element) const; virtual QString jsonMessageDescription(const QJsonObject& msg_element) const; diff --git a/src/librssguard/services/standard/standardfeed.cpp b/src/librssguard/services/standard/standardfeed.cpp index 6c03aabe6..392942574 100644 --- a/src/librssguard/services/standard/standardfeed.cpp +++ b/src/librssguard/services/standard/standardfeed.cpp @@ -19,6 +19,7 @@ #endif #include "services/standard/parsers/atomparser.h" +#include "services/standard/parsers/icalparser.h" #include "services/standard/parsers/jsonparser.h" #include "services/standard/parsers/rdfparser.h" #include "services/standard/parsers/rssparser.h" @@ -347,6 +348,7 @@ StandardFeed* StandardFeed::guessFeed(StandardFeed::SourceType source_type, parsers.append(QSharedPointer(new AtomParser({}))); parsers.append(QSharedPointer(new RssParser({}))); parsers.append(QSharedPointer(new RdfParser({}))); + parsers.append(QSharedPointer(new IcalParser({}))); parsers.append(QSharedPointer(new JsonParser({}))); parsers.append(QSharedPointer(new SitemapParser({}))); diff --git a/src/librssguard/services/standard/standardserviceroot.cpp b/src/librssguard/services/standard/standardserviceroot.cpp index 5f76c93eb..a669c50b6 100644 --- a/src/librssguard/services/standard/standardserviceroot.cpp +++ b/src/librssguard/services/standard/standardserviceroot.cpp @@ -21,6 +21,7 @@ #include "services/standard/gui/formstandardfeeddetails.h" #include "services/standard/gui/formstandardimportexport.h" #include "services/standard/parsers/atomparser.h" +#include "services/standard/parsers/icalparser.h" #include "services/standard/parsers/jsonparser.h" #include "services/standard/parsers/rdfparser.h" #include "services/standard/parsers/rssparser.h" @@ -340,6 +341,10 @@ QList StandardServiceRoot::obtainNewMessages(Feed* feed, messages = JsonParser(formatted_feed_contents).messages(); break; + case StandardFeed::Type::iCalendar: + messages = IcalParser(formatted_feed_contents).messages(); + break; + case StandardFeed::Type::Sitemap: messages = SitemapParser(formatted_feed_contents).messages();