diff --git a/resources/desktop/com.github.rssguard.appdata.xml b/resources/desktop/com.github.rssguard.appdata.xml index fcee471f6..1e56d9827 100644 --- a/resources/desktop/com.github.rssguard.appdata.xml +++ b/resources/desktop/com.github.rssguard.appdata.xml @@ -30,7 +30,7 @@ https://martinrotter.github.io/donate/ - + none diff --git a/resources/docs/Message-filters.md b/resources/docs/Message-filters.md index c360ac398..cb791d48c 100755 --- a/resources/docs/Message-filters.md +++ b/resources/docs/Message-filters.md @@ -49,6 +49,7 @@ Here is the reference of methods and properties of some types available in your | `String url` | URL of the message. | | `String author` | Author of the message. | | `String contents` | Contents of the message. | +| `Number score` | Arbitrary number in range <0.0, 100.0>. You can use this number to order messages in a custom fashion as this attribute also has its own column in messages list. | | `Date created` | Date/time of the message. | | `Boolean isRead` | Is message read? | | `Boolean isImportant` | Is message important? | diff --git a/resources/scripts/scrapers/github-releases.py b/resources/scripts/scrapers/github-releases.py new file mode 100755 index 000000000..3cf7dd1b7 --- /dev/null +++ b/resources/scripts/scrapers/github-releases.py @@ -0,0 +1,32 @@ +# Sample file: https://api.github.com/repos///releases +# +# File is downloaded and we expect its contents via stdin. +# This script produces JSON feed. + +import re +import json +import sys +import distutils.util +from datetime import datetime + +leave_out_prereleases = distutils.util.strtobool(sys.argv[1]) +input_data = sys.stdin.read() +json_data = json.loads(input_data) + +json_feed = "{{\"title\": \"{title}\", \"items\": [{items}]}}" +items = list() + +for rel in json_data: + if rel['prerelease'] and leave_out_prereleases: + continue + + article_url = json.dumps(rel['url']) + article_title = json.dumps(rel['tag_name']) + article_time = json.dumps(datetime.strptime(rel["published_at"], "%Y-%m-%dT%H:%M:%SZ").isoformat()) + items.append("{{\"title\": {title}, \"content_html\": {html}, \"url\": {url}, \"date_published\": {date}}}".format(title=article_title, + html=article_title, + url=article_url, + date=article_time)) + +json_feed = json_feed.format(title = "Releases", items = ", ".join(items)) +print(json_feed) \ No newline at end of file diff --git a/src/librssguard/core/messagesforfiltersmodel.cpp b/src/librssguard/core/messagesforfiltersmodel.cpp index 6791aeb15..59c232cbb 100755 --- a/src/librssguard/core/messagesforfiltersmodel.cpp +++ b/src/librssguard/core/messagesforfiltersmodel.cpp @@ -10,7 +10,7 @@ MessagesForFiltersModel::MessagesForFiltersModel(QObject* parent) : QAbstractTableModel(parent) { m_headerData << tr("Read") << tr("Important") << tr("In recycle bin") << tr("Title") - << tr("URL") << tr("Author") << tr("Created on"); + << tr("URL") << tr("Author") << tr("Created on") << tr("Score"); } void MessagesForFiltersModel::setMessages(const QList& messages) { @@ -75,6 +75,9 @@ QVariant MessagesForFiltersModel::data(const QModelIndex& index, int role) const case MFM_MODEL_CREATED: return msg.m_created; + + case MFM_MODEL_SCORE: + return msg.m_score; } break; diff --git a/src/librssguard/core/messagesmodelsqllayer.cpp b/src/librssguard/core/messagesmodelsqllayer.cpp index 52dad1633..d47e3133d 100644 --- a/src/librssguard/core/messagesmodelsqllayer.cpp +++ b/src/librssguard/core/messagesmodelsqllayer.cpp @@ -4,6 +4,7 @@ #include "definitions/definitions.h" #include "miscellaneous/application.h" +#include "miscellaneous/databasequeries.h" MessagesModelSqlLayer::MessagesModelSqlLayer() : m_filter(QSL(DEFAULT_SQL_MESSAGES_FILTER)), m_fieldNames({}), m_orderByNames({}), @@ -11,31 +12,14 @@ MessagesModelSqlLayer::MessagesModelSqlLayer() m_db = qApp->database()->connection(QSL("MessagesModel")); // Used in : SELECT , FROM ....; - m_fieldNames[MSG_DB_ID_INDEX] = "Messages.id"; - m_fieldNames[MSG_DB_READ_INDEX] = "Messages.is_read"; - m_fieldNames[MSG_DB_IMPORTANT_INDEX] = "Messages.is_important"; - m_fieldNames[MSG_DB_DELETED_INDEX] = "Messages.is_deleted"; - m_fieldNames[MSG_DB_PDELETED_INDEX] = "Messages.is_pdeleted"; - m_fieldNames[MSG_DB_FEED_CUSTOM_ID_INDEX] = "Messages.feed"; - m_fieldNames[MSG_DB_TITLE_INDEX] = "Messages.title"; - m_fieldNames[MSG_DB_URL_INDEX] = "Messages.url"; - m_fieldNames[MSG_DB_AUTHOR_INDEX] = "Messages.author"; - m_fieldNames[MSG_DB_DCREATED_INDEX] = "Messages.date_created"; - m_fieldNames[MSG_DB_CONTENTS_INDEX] = "Messages.contents"; - m_fieldNames[MSG_DB_ENCLOSURES_INDEX] = "Messages.enclosures"; - m_fieldNames[MSG_DB_SCORE_INDEX] = "Messages.score"; - m_fieldNames[MSG_DB_ACCOUNT_ID_INDEX] = "Messages.account_id"; - m_fieldNames[MSG_DB_CUSTOM_ID_INDEX] = "Messages.custom_id"; - m_fieldNames[MSG_DB_CUSTOM_HASH_INDEX] = "Messages.custom_hash"; - m_fieldNames[MSG_DB_FEED_TITLE_INDEX] = "Feeds.title"; - m_fieldNames[MSG_DB_HAS_ENCLOSURES] = "CASE WHEN length(Messages.enclosures) > 10 THEN 'true' ELSE 'false' END AS has_enclosures"; + m_fieldNames = DatabaseQueries::messageTableAttributes(false); // Used in : SELECT ... FROM ... ORDER BY DESC, ASC; m_orderByNames[MSG_DB_ID_INDEX] = "Messages.id"; m_orderByNames[MSG_DB_READ_INDEX] = "Messages.is_read"; m_orderByNames[MSG_DB_IMPORTANT_INDEX] = "Messages.is_important"; - m_orderByNames[MSG_DB_PDELETED_INDEX] = "Messages.is_pdeleted"; m_orderByNames[MSG_DB_DELETED_INDEX] = "Messages.is_deleted"; + m_orderByNames[MSG_DB_PDELETED_INDEX] = "Messages.is_pdeleted"; m_orderByNames[MSG_DB_FEED_CUSTOM_ID_INDEX] = "Messages.feed"; m_orderByNames[MSG_DB_TITLE_INDEX] = "Messages.title"; m_orderByNames[MSG_DB_URL_INDEX] = "Messages.url"; diff --git a/src/librssguard/definitions/definitions.h b/src/librssguard/definitions/definitions.h index a6dc3cebb..f0aedb683 100755 --- a/src/librssguard/definitions/definitions.h +++ b/src/librssguard/definitions/definitions.h @@ -229,6 +229,7 @@ #define MFM_MODEL_URL 4 #define MFM_MODEL_AUTHOR 5 #define MFM_MODEL_CREATED 6 +#define MFM_MODEL_SCORE 7 #if defined(Q_OS_LINUX) #define OS_ID "Linux" diff --git a/src/librssguard/gui/dialogs/formmessagefiltersmanager.cpp b/src/librssguard/gui/dialogs/formmessagefiltersmanager.cpp index a1d0aed88..a1b675a22 100644 --- a/src/librssguard/gui/dialogs/formmessagefiltersmanager.cpp +++ b/src/librssguard/gui/dialogs/formmessagefiltersmanager.cpp @@ -52,6 +52,7 @@ FormMessageFiltersManager::FormMessageFiltersManager(FeedReader* reader, const Q m_ui.m_treeExistingMessages->header()->setSectionResizeMode(MFM_MODEL_ISDELETED, QHeaderView::ResizeMode::ResizeToContents); m_ui.m_treeExistingMessages->header()->setSectionResizeMode(MFM_MODEL_AUTHOR, QHeaderView::ResizeMode::ResizeToContents); m_ui.m_treeExistingMessages->header()->setSectionResizeMode(MFM_MODEL_CREATED, QHeaderView::ResizeMode::ResizeToContents); + m_ui.m_treeExistingMessages->header()->setSectionResizeMode(MFM_MODEL_SCORE, QHeaderView::ResizeMode::ResizeToContents); m_ui.m_treeExistingMessages->header()->setSectionResizeMode(MFM_MODEL_TITLE, QHeaderView::ResizeMode::Interactive); m_ui.m_treeExistingMessages->header()->setSectionResizeMode(MFM_MODEL_URL, QHeaderView::ResizeMode::Interactive); diff --git a/src/librssguard/miscellaneous/databasequeries.cpp b/src/librssguard/miscellaneous/databasequeries.cpp index 5ccf76e35..a7aef6be3 100755 --- a/src/librssguard/miscellaneous/databasequeries.cpp +++ b/src/librssguard/miscellaneous/databasequeries.cpp @@ -8,14 +8,36 @@ #include "miscellaneous/iconfactory.h" #include "network-web/oauth2service.h" #include "services/abstract/category.h" -#include "services/standard/standardcategory.h" -#include "services/standard/standardfeed.h" -#include "services/standard/standardserviceroot.h" #include #include #include +QMap DatabaseQueries::messageTableAttributes(bool only_msg_table) { + QMap field_names; + + field_names[MSG_DB_ID_INDEX] = "Messages.id"; + field_names[MSG_DB_READ_INDEX] = "Messages.is_read"; + field_names[MSG_DB_IMPORTANT_INDEX] = "Messages.is_important"; + field_names[MSG_DB_DELETED_INDEX] = "Messages.is_deleted"; + field_names[MSG_DB_PDELETED_INDEX] = "Messages.is_pdeleted"; + field_names[MSG_DB_FEED_CUSTOM_ID_INDEX] = "Messages.feed"; + field_names[MSG_DB_TITLE_INDEX] = "Messages.title"; + field_names[MSG_DB_URL_INDEX] = "Messages.url"; + field_names[MSG_DB_AUTHOR_INDEX] = "Messages.author"; + field_names[MSG_DB_DCREATED_INDEX] = "Messages.date_created"; + field_names[MSG_DB_CONTENTS_INDEX] = "Messages.contents"; + field_names[MSG_DB_ENCLOSURES_INDEX] = "Messages.enclosures"; + field_names[MSG_DB_SCORE_INDEX] = "Messages.score"; + field_names[MSG_DB_ACCOUNT_ID_INDEX] = "Messages.account_id"; + field_names[MSG_DB_CUSTOM_ID_INDEX] = "Messages.custom_id"; + field_names[MSG_DB_CUSTOM_HASH_INDEX] = "Messages.custom_hash"; + field_names[MSG_DB_FEED_TITLE_INDEX] = only_msg_table ? "Messages.feed" : "Feeds.title"; + field_names[MSG_DB_HAS_ENCLOSURES] = "CASE WHEN length(Messages.enclosures) > 10 THEN 'true' ELSE 'false' END AS has_enclosures"; + + return field_names; +} + QString DatabaseQueries::serializeCustomData(const QVariantHash& data) { if (!data.isEmpty()) { return QString::fromUtf8(QJsonDocument::fromVariant(data).toJson(QJsonDocument::JsonFormat::Indented)); @@ -115,7 +137,7 @@ bool DatabaseQueries::setLabelsForMessage(const QSqlDatabase& db, const QList DatabaseQueries::getLabels(const QSqlDatabase& db, int account_id) { +QList DatabaseQueries::getLabelsForAccount(const QSqlDatabase& db, int account_id) { QList labels; QSqlQuery q(db); @@ -654,16 +676,16 @@ QList DatabaseQueries::getUndeletedMessagesWithLabel(const QSqlDatabase QList messages; QSqlQuery q(db); - q.prepare(QSL( - "SELECT Messages.id, Messages.is_read, Messages.is_deleted, Messages.is_important, Feeds.title, Messages.title, Messages.url, Messages.author, Messages.date_created, Messages.contents, Messages.is_pdeleted, Messages.enclosures, Messages.account_id, Messages.custom_id, Messages.custom_hash, Messages.feed, CASE WHEN length(Messages.enclosures) > 10 THEN 'true' ELSE 'false' END AS has_enclosures " - "FROM Messages " - "INNER JOIN Feeds " - "ON Messages.feed = Feeds.custom_id AND Messages.account_id = :account_id AND Messages.account_id = Feeds.account_id " - "INNER JOIN LabelsInMessages " - "ON " - " Messages.is_pdeleted = 0 AND Messages.is_deleted = 0 AND " - " LabelsInMessages.account_id = :account_id AND LabelsInMessages.account_id = Messages.account_id AND " - " LabelsInMessages.label = :label AND LabelsInMessages.message = Messages.custom_id;")); + q.prepare(QSL("SELECT %1 " + "FROM Messages " + "INNER JOIN Feeds " + "ON Messages.feed = Feeds.custom_id AND Messages.account_id = :account_id AND Messages.account_id = Feeds.account_id " + "INNER JOIN LabelsInMessages " + "ON " + " Messages.is_pdeleted = 0 AND Messages.is_deleted = 0 AND " + " LabelsInMessages.account_id = :account_id AND LabelsInMessages.account_id = Messages.account_id AND " + " LabelsInMessages.label = :label AND " + " LabelsInMessages.message = Messages.custom_id;").arg(messageTableAttributes(true).values().join(QSL(", ")))); q.bindValue(QSL(":account_id"), label->getParentServiceRoot()->accountId()); q.bindValue(QSL(":label"), label->customId()); @@ -694,12 +716,14 @@ QList DatabaseQueries::getUndeletedLabelledMessages(const QSqlDatabase& QList messages; QSqlQuery q(db); - q.prepare(QSL( - "SELECT Messages.id, Messages.is_read, Messages.is_deleted, Messages.is_important, Feeds.title, Messages.title, Messages.url, Messages.author, Messages.date_created, Messages.contents, Messages.is_pdeleted, Messages.enclosures, Messages.account_id, Messages.custom_id, Messages.custom_hash, Messages.feed, CASE WHEN length(Messages.enclosures) > 10 THEN 'true' ELSE 'false' END AS has_enclosures " - "FROM Messages " - "LEFT JOIN Feeds " - "ON Messages.feed = Feeds.custom_id AND Messages.account_id = Feeds.account_id " - "WHERE Messages.is_deleted = 0 AND Messages.is_pdeleted = 0 AND Messages.account_id = :account_id AND (SELECT COUNT(*) FROM LabelsInMessages WHERE account_id = :account_id AND message = Messages.custom_id) > 0;")); + q.prepare(QSL("SELECT %1 " + "FROM Messages " + "LEFT JOIN Feeds " + "ON Messages.feed = Feeds.custom_id AND Messages.account_id = Feeds.account_id " + "WHERE Messages.is_deleted = 0 AND Messages.is_pdeleted = 0 AND Messages.account_id = :account_id AND " + " (SELECT COUNT(*) FROM LabelsInMessages " + " WHERE account_id = :account_id AND " + " message = Messages.custom_id) > 0;").arg(messageTableAttributes(true).values().join(QSL(", ")))); q.bindValue(QSL(":account_id"), account_id); if (q.exec()) { @@ -730,9 +754,10 @@ QList DatabaseQueries::getUndeletedImportantMessages(const QSqlDatabase QSqlQuery q(db); q.setForwardOnly(true); - q.prepare("SELECT id, is_read, is_deleted, is_important, custom_id, title, url, author, date_created, contents, is_pdeleted, enclosures, account_id, custom_id, custom_hash, feed, CASE WHEN length(Messages.enclosures) > 10 THEN 'true' ELSE 'false' END AS has_enclosures " - "FROM Messages " - "WHERE is_important = 1 AND is_deleted = 0 AND is_pdeleted = 0 AND account_id = :account_id;"); + q.prepare(QSL("SELECT %1 " + "FROM Messages " + "WHERE is_important = 1 AND is_deleted = 0 AND " + " is_pdeleted = 0 AND account_id = :account_id;").arg(messageTableAttributes(true).values().join(QSL(", ")))); q.bindValue(QSL(":account_id"), account_id); if (q.exec()) { @@ -764,9 +789,10 @@ QList DatabaseQueries::getUndeletedMessagesForFeed(const QSqlDatabase& QSqlQuery q(db); q.setForwardOnly(true); - q.prepare("SELECT id, is_read, is_deleted, is_important, custom_id, title, url, author, date_created, contents, is_pdeleted, enclosures, account_id, custom_id, custom_hash, feed, CASE WHEN length(Messages.enclosures) > 10 THEN 'true' ELSE 'false' END AS has_enclosures " - "FROM Messages " - "WHERE is_deleted = 0 AND is_pdeleted = 0 AND feed = :feed AND account_id = :account_id;"); + q.prepare(QSL("SELECT %1 " + "FROM Messages " + "WHERE is_deleted = 0 AND is_pdeleted = 0 AND " + " feed = :feed AND account_id = :account_id;").arg(messageTableAttributes(true).values().join(QSL(", ")))); q.bindValue(QSL(":feed"), feed_custom_id); q.bindValue(QSL(":account_id"), account_id); @@ -798,9 +824,9 @@ QList DatabaseQueries::getUndeletedMessagesForBin(const QSqlDatabase& d QSqlQuery q(db); q.setForwardOnly(true); - q.prepare("SELECT id, is_read, is_deleted, is_important, custom_id, title, url, author, date_created, contents, is_pdeleted, enclosures, account_id, custom_id, custom_hash, feed, CASE WHEN length(Messages.enclosures) > 10 THEN 'true' ELSE 'false' END AS has_enclosures " - "FROM Messages " - "WHERE is_deleted = 1 AND is_pdeleted = 0 AND account_id = :account_id;"); + q.prepare(QSL("SELECT %1 " + "FROM Messages " + "WHERE is_deleted = 1 AND is_pdeleted = 0 AND account_id = :account_id;").arg(messageTableAttributes(true).values().join(QSL(", ")))); q.bindValue(QSL(":account_id"), account_id); if (q.exec()) { @@ -831,9 +857,9 @@ QList DatabaseQueries::getUndeletedMessagesForAccount(const QSqlDatabas QSqlQuery q(db); q.setForwardOnly(true); - q.prepare("SELECT id, is_read, is_deleted, is_important, custom_id, title, url, author, date_created, contents, is_pdeleted, enclosures, account_id, custom_id, custom_hash, feed, CASE WHEN length(Messages.enclosures) > 10 THEN 'true' ELSE 'false' END AS has_enclosures " - "FROM Messages " - "WHERE is_deleted = 0 AND is_pdeleted = 0 AND account_id = :account_id;"); + q.prepare(QSL("SELECT %1 " + "FROM Messages " + "WHERE is_deleted = 0 AND is_pdeleted = 0 AND account_id = :account_id;").arg(messageTableAttributes(true).values().join(QSL(", ")))); q.bindValue(QSL(":account_id"), account_id); if (q.exec()) { @@ -851,6 +877,8 @@ QList DatabaseQueries::getUndeletedMessagesForAccount(const QSqlDatabas } } else { + auto aa = q.lastError().text(); + if (ok != nullptr) { *ok = false; } diff --git a/src/librssguard/miscellaneous/databasequeries.h b/src/librssguard/miscellaneous/databasequeries.h index 2fe9d7956..4902d73de 100644 --- a/src/librssguard/miscellaneous/databasequeries.h +++ b/src/librssguard/miscellaneous/databasequeries.h @@ -22,6 +22,7 @@ class DatabaseQueries { public: + static QMap messageTableAttributes(bool only_msg_table); // Custom data serializers. static QString serializeCustomData(const QVariantHash& data); @@ -32,7 +33,7 @@ class DatabaseQueries { static bool deassignLabelFromMessage(const QSqlDatabase& db, Label* label, const Message& msg); static bool assignLabelToMessage(const QSqlDatabase& db, Label* label, const Message& msg); static bool setLabelsForMessage(const QSqlDatabase& db, const QList& labels, const Message& msg); - static QList getLabels(const QSqlDatabase& db, int account_id); + static QList getLabelsForAccount(const QSqlDatabase& db, int account_id); static QList getLabelsForMessage(const QSqlDatabase& db, const Message& msg, const QList installed_labels); static bool updateLabel(const QSqlDatabase& db, Label* label); static bool deleteLabel(const QSqlDatabase& db, Label* label); @@ -61,8 +62,8 @@ class DatabaseQueries { static bool purgeLeftoverMessages(const QSqlDatabase& db, int account_id); // Purges message/label assignments where source message or label does not exist. - // If account ID smaller than 0 is passed, then do this for all accounts. - static bool purgeLeftoverLabelAssignments(const QSqlDatabase& db, int account_id = -1); + // If account ID smaller than 1 is passed, then do this for all accounts. + static bool purgeLeftoverLabelAssignments(const QSqlDatabase& db, int account_id = 0); static bool purgeLabelsAndLabelAssignments(const QSqlDatabase& db, int account_id); // Counts of unread/all messages. @@ -321,7 +322,7 @@ void DatabaseQueries::loadFromDatabase(ServiceRoot* root) { QSqlDatabase database = qApp->database()->connection(root->metaObject()->className()); Assignment categories = DatabaseQueries::getCategories(database, root->accountId()); Assignment feeds = DatabaseQueries::getFeeds(database, qApp->feedReader()->messageFilters(), root->accountId()); - auto labels = DatabaseQueries::getLabels(database, root->accountId()); + auto labels = DatabaseQueries::getLabelsForAccount(database, root->accountId()); root->performInitialAssembly(categories, feeds, labels); }