Merge pull request #411 from martinrotter/version-4
Merge 4.X code into master
This commit is contained in:
commit
0617ad7d19
11
.github/workflows/rssguard.yml
vendored
11
.github/workflows/rssguard.yml
vendored
@ -3,10 +3,8 @@ name: rssguard
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["master"]
|
||||
branches: ["master", "version-4"]
|
||||
tags: ["*"]
|
||||
pull_request:
|
||||
branches: ["master"]
|
||||
|
||||
jobs:
|
||||
build-rssguard:
|
||||
@ -45,6 +43,13 @@ jobs:
|
||||
|
||||
- name: Prepare environment and compile application
|
||||
run: ${{ matrix.script_name }} "${{ matrix.os }}" "${{ matrix.use_webengine }}"
|
||||
env:
|
||||
GMAIL_CLIENT_ID: ${{ secrets.GMAIL_CLIENT_ID }}
|
||||
GMAIL_CLIENT_SECRET: ${{ secrets.GMAIL_CLIENT_SECRET }}
|
||||
INOREADER_CLIENT_ID: ${{ secrets.INOREADER_CLIENT_ID }}
|
||||
INOREADER_CLIENT_SECRET: ${{ secrets.INOREADER_CLIENT_SECRET }}
|
||||
FEEDLY_CLIENT_ID: ${{ secrets.FEEDLY_CLIENT_ID }}
|
||||
FEEDLY_CLIENT_SECRET: ${{ secrets.FEEDLY_CLIENT_SECRET }}
|
||||
|
||||
- name: Release development binaries
|
||||
uses: softprops/action-gh-release@v1
|
||||
|
@ -9,11 +9,11 @@ RSS Guard
|
||||
|
||||
### [Downloads](https://github.com/martinrotter/rssguard/releases) | [Development builds](https://github.com/martinrotter/rssguard/releases/tag/devbuild) | [Documentation](https://github.com/martinrotter/rssguard/blob/master/resources/docs/Documentation.md)
|
||||
|
||||
RSS Guard is simple, light and easy-to-use RSS/ATOM feed aggregator developed using Qt framework which supports online feed synchronization with these services:
|
||||
RSS Guard is simple RSS/ATOM feed reader for Windows, Linux or Mac OS X which supports many online RSS services:
|
||||
* [Tiny Tiny RSS](https://tt-rss.org),
|
||||
* [Inoreader](https://www.inoreader.com),
|
||||
* [Nextcloud News](https://apps.nextcloud.com/apps/news),
|
||||
* [Gmail API](https://developers.google.com/gmail/api),
|
||||
* [Gmail](https://developers.google.com/gmail/api),
|
||||
* [FreshRSS](https://freshrss.org),
|
||||
* [The Old Reader](https://theoldreader.com),
|
||||
* [Bazqux](https://bazqux.com),
|
||||
|
@ -28,10 +28,8 @@
|
||||
# of this variable can be mimicked with $INSTALL_ROOT variable on Linux. Note that
|
||||
# RSS Guard's installation is automatically relocatable, in other words, no
|
||||
# absolute OS-dependent paths are used.
|
||||
# FEEDLY_CLIENT_ID - official production client ID obtained from Feedly when setting up application
|
||||
# partnership.
|
||||
# FEEDLY_CLIENT_SECRET - official production client SECRET obtained from Feedly when setting up application
|
||||
# partnership.
|
||||
# {FEEDLY,GMAIL,INOREADER}_CLIENT_ID - preconfigured OAuth cliend ID.
|
||||
# {FEEDLY,GMAIL,INOREADER}_CLIENT_SECRET - preconfigured OAuth cliend SECRET.
|
||||
#
|
||||
# Other information:
|
||||
# - supports Windows, Linux, Mac OS X, OS/2, Android,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1172,7 +1172,7 @@ or this functionality is not implemented yet.</source>
|
||||
<name>FormEditFeedlyAccount</name>
|
||||
<message>
|
||||
<source>Service setup</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Сервисные настройки</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -2580,7 +2580,7 @@ Login tokens expiration: %2</source>
|
||||
<name>GreaderAccountDetails</name>
|
||||
<message>
|
||||
<source>Service</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Сервис</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>URL</source>
|
||||
@ -2660,11 +2660,11 @@ Login tokens expiration: %2</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>You are good to go!</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Готово!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Yeah.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Да.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Username cannot be empty.</source>
|
||||
@ -2695,7 +2695,7 @@ Login tokens expiration: %2</source>
|
||||
<name>GreaderNetwork</name>
|
||||
<message>
|
||||
<source>Other services</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Другие сервисы</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -3638,11 +3638,11 @@ version by clicking this popup notification.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Google Reader API is used by many online RSS readers. This is here to support</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Google Reader API используется многими онлайн-ридерами RSS. Доступна поддержка для</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Keep up with the topics and trends you care about, without the overwhelm. Feedly is a secure space where you can privately organize and research the topics and trends that matter to you.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Будьте в курсе интересующих вас тем и тенденций, не отвлекаясь. Feedly - это безопасное пространство, где вы можете в частном порядке организовывать и исследовать важные для вас темы и тенденции.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -3680,19 +3680,19 @@ version by clicking this popup notification.</source>
|
||||
<name>ScriptException</name>
|
||||
<message>
|
||||
<source>script line is not well-formed</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>строка скрипта сформирована неправильно</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>script threw an error</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>скрипт выдал ошибку</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>script's interpreter was not found</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>интерпретатор скрипта не найден</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>script execution took too long</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>выполнение скрипта заняло слишком много времени</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>unknown error</source>
|
||||
@ -4159,7 +4159,7 @@ Authors of this application are NOT responsible for lost data.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Display real icons of feeds in list of messages instead of read/unread icons</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Отображение реальных значков каналов в списке сообщений вместо значков прочитано/не прочитано</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -4407,11 +4407,11 @@ Type: %3</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unknown</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Неизвестно</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Metadata was not fetched.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Метаданные не были получены.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -4595,59 +4595,59 @@ Type: %3</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Source</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Источник</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Post-process script</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Скрипт пост-обработки</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>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.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Вы можете использовать URL-адрес в качестве источника своего канала или создать свой канал с помощью собственного скрипта. Кроме того, вы можете постобработать сгенерированные данные канала с помощью еще одного скрипта, если хотите. Это расширенные функции, и перед их использованием обязательно прочтите документацию.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Full feed source identifier</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Полный идентификатор источника канала</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Full feed source identifier which can be URL.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Полный идентификатор источника канала, который может быть URL-адресом.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Full command to execute</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Полная команда для выполнения</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>You can enter full command including interpreter here.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Здесь вы можете ввести полную команду, включая интерпретатор.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Here you can enter script executaion line, including interpreter.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Здесь вы можете ввести строку выполнения скрипта, включая интерпретатор.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The source is ok.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Источник подходит.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The source is empty.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Источник пуст.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The source does not seem to use "#" separator for arguments.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Источник не использует разделитель "#" для аргументов.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Command is ok.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Команда в порядке.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Command not seem to use "#" separator for arguments.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Команда не использует разделитель "#" для аргументов.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Command is empty.</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Команда пуста.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -23,10 +23,13 @@ win32 {
|
||||
lib.path = $$PREFIX
|
||||
lib.CONFIG = no_check_exist
|
||||
|
||||
redist10.files = ../../resources/scripts/redist/*
|
||||
redist10.path = $$PREFIX
|
||||
|
||||
clng.files = ../../resources/scripts/clang-format
|
||||
clng.path = $$PREFIX
|
||||
|
||||
INSTALLS += target lib clng
|
||||
INSTALLS += target lib clng redist10
|
||||
|
||||
INSTALL_HEADERS_PREFIX = $$quote($$PREFIX/include/librssguard)
|
||||
}
|
||||
|
32
pri/vars.pri
32
pri/vars.pri
@ -4,7 +4,7 @@ APP_REVERSE_NAME = "com.github.rssguard"
|
||||
APP_LOW_H_NAME = ".rssguard"
|
||||
APP_AUTHOR = "Martin Rotter"
|
||||
APP_COPYRIGHT = "(C) 2011-2021 $$APP_AUTHOR"
|
||||
APP_VERSION = "3.9.2"
|
||||
APP_VERSION = "4.0.0"
|
||||
APP_LONG_NAME = "$$APP_NAME $$APP_VERSION"
|
||||
APP_EMAIL = "rotter.martinos@gmail.com"
|
||||
APP_URL = "https://github.com/martinrotter/rssguard"
|
||||
@ -26,11 +26,9 @@ isEmpty(USE_WEBENGINE) {
|
||||
|
||||
qtHaveModule(webenginewidgets) {
|
||||
USE_WEBENGINE = true
|
||||
##message($$MSG_PREFIX: WebEngine component IS installed, enabling it.)
|
||||
}
|
||||
else {
|
||||
USE_WEBENGINE = false
|
||||
##message($$MSG_PREFIX: WebEngine component is probably NOT installed, disabling it.)
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,3 +45,31 @@ else {
|
||||
|
||||
message($$MSG_PREFIX: Enabling official Feedly support.)
|
||||
}
|
||||
|
||||
isEmpty(GMAIL_CLIENT_ID)|isEmpty(GMAIL_CLIENT_SECRET) {
|
||||
GMAIL_OFFICIAL_SUPPORT = false
|
||||
|
||||
message($$MSG_PREFIX: Gmail client ID/secret variables are not set.)
|
||||
}
|
||||
else {
|
||||
GMAIL_OFFICIAL_SUPPORT = true
|
||||
DEFINES *= GMAIL_OFFICIAL_SUPPORT
|
||||
DEFINES *= GMAIL_CLIENT_ID='"\\\"$$GMAIL_CLIENT_ID\\\""'
|
||||
DEFINES *= GMAIL_CLIENT_SECRET='"\\\"$$GMAIL_CLIENT_SECRET\\\""'
|
||||
|
||||
message($$MSG_PREFIX: Enabling official Gmail support.)
|
||||
}
|
||||
|
||||
isEmpty(INOREADER_CLIENT_ID)|isEmpty(INOREADER_CLIENT_SECRET) {
|
||||
INOREADER_OFFICIAL_SUPPORT = false
|
||||
|
||||
message($$MSG_PREFIX: Inoreader client ID/secret variables are not set.)
|
||||
}
|
||||
else {
|
||||
INOREADER_OFFICIAL_SUPPORT = true
|
||||
DEFINES *= INOREADER_OFFICIAL_SUPPORT
|
||||
DEFINES *= INOREADER_CLIENT_ID='"\\\"$$INOREADER_CLIENT_ID\\\""'
|
||||
DEFINES *= INOREADER_CLIENT_SECRET='"\\\"$$INOREADER_CLIENT_SECRET\\\""'
|
||||
|
||||
message($$MSG_PREFIX: Enabling official Inoreader support.)
|
||||
}
|
||||
|
@ -30,7 +30,7 @@
|
||||
<url type="donation">https://martinrotter.github.io/donate/</url>
|
||||
<content_rating type="oars-1.1" />
|
||||
<releases>
|
||||
<release version="3.9.2" date="2021-04-28"/>
|
||||
<release version="3.9.0" date="2021-05-07"/>
|
||||
</releases>
|
||||
<content_rating type="oars-1.0">
|
||||
<content_attribute id="violence-cartoon">none</content_attribute>
|
||||
|
@ -8,21 +8,26 @@
|
||||
* [Localizations](#localizations)
|
||||
* [Videos](#videos)
|
||||
* [Web-based and lite app variants](#web-based-and-lite-app-variants)
|
||||
* [RSS Guard 3 vs. RSS Guard 4](#rss-guard-3-vs-rss-guard-4)
|
||||
* [Features](#features)
|
||||
* [List of main features](#list-of-main-features)
|
||||
* [Core concepts](#core-concepts)
|
||||
* [Supported feed formats and online feed services](Feed-formats.md)
|
||||
* [Message filtering](Message-filters.md)
|
||||
* [Database backends](#database-backends)
|
||||
* [Google Reader API](#google-reader-api)
|
||||
* [Websites scraping](#websites-scraping)
|
||||
* [Gmail](#gmail)
|
||||
* [Feedly](#feedly)
|
||||
* [Labels](Labels.md)
|
||||
* [Downloading files](#downloading-files)
|
||||
* [External tools](#external-tools)
|
||||
* [AdBlock](#adblock)
|
||||
* [GUI tweaking](#gui-tweaking)
|
||||
* [Miscellaneous](#miscellaneous)
|
||||
* [Miscellaneous (for advanced users)](#miscellaneous-for-advanced-users)
|
||||
* [Command line interface](#cli)
|
||||
* [OS/2](#os2)
|
||||
* [How to build](#how-to-build)
|
||||
* [`%data%` placeholder](#data-placeholder)
|
||||
* [Cleaning database](#cleaning-database)
|
||||
* [Portable user data](#portable-user-data)
|
||||
* [Downloading new messages](#downloading-new-messages)
|
||||
@ -49,17 +54,18 @@ RSS Guard uses [semantic versioning](https://semver.org/). The versioning scheme
|
||||
|
||||
## How to contribute
|
||||
RSS Guard is open source application with free GNU GPLv3 license. Everyone willing to contribute is welcomed. You can contribute in many ways by:
|
||||
* fixing bugs and requesting PRs (pull requests),
|
||||
* fixing and [reporting](#reporting-bugs) bugs and requesting PRs (pull requests),
|
||||
* [localizing](#localizations) to other languages,
|
||||
* supporting author with [donations](https://github.com/sponsors/martinrotter),
|
||||
* spreading the word,
|
||||
* reporting discovered [bugs](#reporting-bugs).
|
||||
* spreading the word.
|
||||
|
||||
If you decide to contribute code, then please try to follow the style and formatting of existing source code. Also, I use [uncrustify](https://github.com/martinrotter/rssguard/blob/master/resources/scripts/uncrustify/uncrustify.cfg) to format source code.
|
||||
|
||||
## Reporting bugs
|
||||
Please report all issues/bugs/ideas to [Issues](https://github.com/martinrotter/rssguard/issues) section. Describe your problem as precisely as possible, along with steps taken leading up to the issue occurring.
|
||||
|
||||
Also, for some broader questions or general ideas, use [discussions](https://github.com/martinrotter/rssguard/discussions) rather than [issues](https://github.com/martinrotter/rssguard/issues).
|
||||
|
||||
It is a good idea to read [this](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html) before reporting the bug; it will save time and effort for everyone if all the required information is provided from the get-go.
|
||||
|
||||
## Localizations
|
||||
@ -79,7 +85,7 @@ Here you can see some videos to see RSS Guard in action:
|
||||
|
||||
## Web-based and lite app variants
|
||||
RSS Guard is distributed in two variants:
|
||||
* **Standard package with WebEngine-based bundled message viewer**: This variant displays messages with their full formatting and layout in embedded Chromium-based web viewer. This variant of RSS Guard should be nice for everyone who doesn't care about memory consumption. Also, installation packages are relatively big.
|
||||
* **Standard package with WebEngine-based bundled message viewer**: This variant displays messages with their full formatting and layout in embedded Chromium-based web viewer. This variant of RSS Guard should be nice for everyone who doesn't care about memory consumption too much. Also, installation packages are relatively big.
|
||||
|
||||
<img src="images/webengine-view.png" width="80%">
|
||||
|
||||
@ -89,6 +95,11 @@ RSS Guard is distributed in two variants:
|
||||
|
||||
If you're not sure which version to use, **use the WebEngine-based RSS Guard**.
|
||||
|
||||
## RSS Guard 3 vs. RSS Guard 4
|
||||
RSS Guard 4 is **NOT** backwards compatible with previous editions of the application!!! It stores settings in slightly different [folder](#portable-user-data) to not overwrite user data from previous versions.
|
||||
|
||||
RSS Guard 4.x contains numerous enhancements and many of them are hidden under the hood and they make application easier to maintain, easier to improve and easier to use.
|
||||
|
||||
# Features
|
||||
RSS Guard is simple (yet powerful) feed reader. It is able to fetch the most known feed formats, including RSS/RDF/ATOM/JSON. RSS Guard is developed on top of the [Qt library](http://qt-project.org) and it supports these operating systems:
|
||||
|
||||
@ -100,23 +111,19 @@ RSS Guard is simple (yet powerful) feed reader. It is able to fetch the most kno
|
||||
|
||||
## List of main features
|
||||
* **support for online feed synchronization via plugins**,
|
||||
* Tiny Tiny RSS (RSS Guard 3.0.0+),
|
||||
* Nextcloud News (RSS Guard 3.1.0+),
|
||||
* Inoreader (RSS Guard 3.5.0+),
|
||||
* Gmail with e-mail sending (RSS Guard 3.7.1+).
|
||||
* FreshRSS (RSS Guard 3.9.0+),
|
||||
* The Old Reader (RSS Guard 3.9.0+),
|
||||
* Bazqux (RSS Guard 3.9.0+),
|
||||
* Reedah (RSS Guard 3.9.0+),
|
||||
* Feedly (RSS Guard 3.9.0+).
|
||||
* Tiny Tiny RSS,
|
||||
* Nextcloud News,
|
||||
* Inoreader,
|
||||
* Gmail.
|
||||
* Google Reader API (FreshRSS, The Old Reader, Bazqux, Reedah and others),
|
||||
* Feedly.
|
||||
* core:
|
||||
* support for all feed formats (RSS/RDF/ATOM/JSON),
|
||||
* full support of podcasts (RSS/ATOM/JSON),
|
||||
* import/export of feeds to/from OPML 2.0,
|
||||
* universal plugin for online services with [Google Reader API](#google-reader-api),
|
||||
* possibility of using custom 3rd-party feed synchronization services,
|
||||
* possibility of using custom 3rd-party feed [synchronization services](Feed-formats.md),
|
||||
* feed metadata fetching including icons,
|
||||
* support for [scraping websites](Feed-formats.md#websites-scraping-and-related-advanced-features) which do not offer RSS/ATOM feeds and other related advanced features,
|
||||
* support for [scraping websites](#websites-scraping) which do not offer RSS/ATOM feeds and other related advanced features,
|
||||
* simple internal Chromium-based web viewer (or alternative version with simpler and much more lightweight internal viewer),
|
||||
* scriptable [message filtering](#message-filtering),
|
||||
* downloader with own tab and support for up to 6 parallel downloads,
|
||||
@ -126,17 +133,17 @@ RSS Guard is simple (yet powerful) feed reader. It is able to fetch the most kno
|
||||
* "portable" mode support with clever auto-detection,
|
||||
* feed categorization,
|
||||
* feed authentication (BASIC),
|
||||
* [external tools](#external-tools) - you can run your program with article URL,
|
||||
* handles tons of messages & feeds,
|
||||
* ability to backup/restore database or settings,
|
||||
* fully-featured recycle bin,
|
||||
* multiple data backend support,
|
||||
* SQLite,
|
||||
* MySQL.
|
||||
* ability to specify target database by its name (MySQL backend),
|
||||
* MariaDB.
|
||||
* ability to specify target database by its name (MariaDB backend),
|
||||
* support for `feed://` URI scheme.
|
||||
* user interface:
|
||||
* message list filter with regular expressions,
|
||||
* drag-n-drop for feed list,
|
||||
* able to show unread feeds/messages only,
|
||||
* can be controlled via keyboard,
|
||||
* fully adjustable toolbars (changeable buttons and style),
|
||||
@ -147,9 +154,18 @@ RSS Guard is simple (yet powerful) feed reader. It is able to fetch the most kno
|
||||
* tabbed interface,
|
||||
* ability to hide list of feeds/categories,
|
||||
* desktop integration via tray icon,
|
||||
* localizations to some languages,
|
||||
* localizations to many languages,
|
||||
* ability to tweak columns in displayed list of messages.
|
||||
|
||||
## Core concepts
|
||||
RSS Guard is multi-protocol and multi-account application. If you start it for the first time, `Add account` dialog will pop-up.
|
||||
|
||||
<img src="images/add-acc.png">
|
||||
|
||||
You can also display this dialog from main menu `Accounts -> Add new account`.
|
||||
|
||||
You must have added some account to start using RSS Guard. Each account provides access to some specific online service while `Standard online feeds` account is there to provide access to classic `RSS` and `ATOM` feeds. You can have activated many accounts in the same time and even multiple accounts of the same type, for example two distinct `Gmail` accounts.
|
||||
|
||||
## Database backends
|
||||
RSS Guard offers switchable database backends which hold your data. At this point, two backends are available:
|
||||
* MariaDB,
|
||||
@ -166,7 +182,7 @@ MariaDB (MySQL) backend is there for users, who want to store their data in a ce
|
||||
For database-related configuration see `Settings -> Data storage` dialog.
|
||||
|
||||
## Google Reader API
|
||||
Starting with RSS Guard 3.9.0, there is a new plugin which offers synchronization with services using Google Reader API. Plugin was so far tested with FreshRSS, The Old Reader and Bazqux. All Google Reader API enabled services should work.
|
||||
Starting with RSS Guard 3.9.0, there is a new plugin which offers synchronization with services using Google Reader API. Plugin was so far tested with FreshRSS, Reedah, The Old Reader and Bazqux. All Google Reader API enabled services should work.
|
||||
|
||||
Note that Inoreader has its own separate plugin, because it uses OAuth as authentication method, therefore it is cleaner to have separate plugin.
|
||||
|
||||
@ -178,8 +194,59 @@ Note that even when all Google Reader API enabled services should follow the API
|
||||
|
||||
For example The Old Reader does not seem to offer tags/labels functionality, therefore tags/labels in RSS Guard are not synchronized, but you can still use offline labels.
|
||||
|
||||
## Websites scraping
|
||||
> **Only proceed if you consider yourself to be a power user and you know what you are doing!**
|
||||
|
||||
RSS Guard 3.9.0+ offers extra advanced features which are inspired by [Liferea](https://lzone.de/liferea/).
|
||||
|
||||
You can select source type of each feed. If you select `URL`, then RSS Guard simply downloads feed file from given location and behave like everyone would expect.
|
||||
|
||||
However, if you choose `Script` option, then you cannot provide URL of your feed and you rely on custom script to generate feed file and provide its contents to **standard output**. Resulting data written to standard output should be valid feed file, for example RSS or ATOM XML file.
|
||||
|
||||
`Fetch it now` button also works with `Script` option. Therefore, if your source script and (optional) post-process script in cooperation deliver a valid feed file to the output, then all important metadata, like title or icon of the feed, can be automagically discovered.
|
||||
|
||||
<img src="images/scrape-source-type.png" width="50%">
|
||||
|
||||
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 and all that must be written in special format `<interpreter>#<argument1>#<argument2>#....`. The `#` character is there to separate interpreter and individual arguments. I had to select some character as separator because simply using space ` ` is not that easy as it might sound, because sometimes space could be a part of an argument sometimes argument separator etc.
|
||||
|
||||
Used script must return `0` as process exit code if everything went well, or non-zero exit code if some error happened.
|
||||
|
||||
Interpreter must be provided in all cases, arguments do not have to be. For example `bash#` is valid execution line, as well as `bash#-c#cat feed.atom`. Note the difference in interpreter's binary name suffix. Also be very carefully about arguments quoting. Some examples of valid and tested 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#-v#0#https://twitter.com/NSACareers` | Scrape Twitter RSS feed file with [Tweeper](https://git.ao2.it/tweeper.git). Tweeper is utility which is able to produce RSS feed from Twitter and other similar social platforms. |
|
||||
|
||||
<img src="images/scrape-source.png" width="50%">
|
||||
|
||||
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-placeholder) `%data%` which is automatically replaced with full path to RSS Guard's [user data folder](Documentation.md#portable-user-data), allowing you to make your configuration fully portable. 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.
|
||||
|
||||
There are some examples of website scrapers [here](https://github.com/martinrotter/rssguard/tree/master/resources/scripts/scrapers), most of the are written in Python 3, thus their execution line is `python#script.py`.
|
||||
|
||||
After your source feed data are downloaded either via URL or custom script, you can optionally post-process the data with one more custom script, which will take **raw source data as input** and must produce processed valid feed data to **standard output** while printing all error messages to **error output**.
|
||||
|
||||
Format of post-process script execution line is the same as above.
|
||||
|
||||
<img src="images/scrape-post.png" width="50%">
|
||||
|
||||
Typical post-processing filter might do things like advanced CSS formatting, localization of contents to another language or filtering of feed file entries or removing ads:
|
||||
|
||||
| Command | Explanation |
|
||||
|---------|-------------|
|
||||
| `bash#-c#xmllint --format -` | Pretty-print input XML feed data. |
|
||||
|
||||
It's completely up to you if you decide to only use script as `Source` of the script or separate your custom functionality between `Source` script and `Post-process` script. Sometimes you might need different `Source` scripts for different online sources and the same `Post-process` script and vice versa.
|
||||
|
||||
## Gmail
|
||||
RSS Guard includes Gmail plugin, which allows users to receive and send (!!!) e-mail messages. Plugin uses [Gmail API](https://developers.google.com/gmail/api) and offers some e-mail client-like features:
|
||||
RSS Guard includes Gmail plugin, which allows users to receive and send e-mail messages in a very simple fashion. Plugin uses [Gmail API](https://developers.google.com/gmail/api) and offers some e-mail client-like features:
|
||||
* Sending e-mail messages.
|
||||
|
||||
<img src="images/gmail-new-email.png">
|
||||
@ -197,8 +264,8 @@ Sadly, some builds of RSS Guard do not have embedded production Feedly API keys
|
||||
<img src="images/feedly-details.png">
|
||||
|
||||
There are two big downsides of using `developer access token`:
|
||||
* It expires after one month and must be renewed.
|
||||
* It only allows maximum of 250 API calls per day.
|
||||
* It expires after one month and must be manually renewed.
|
||||
* It allows maximum of 250 API calls per day.
|
||||
|
||||
## Downloading files
|
||||
RSS Guard offers simple embedded file downloader.
|
||||
@ -211,10 +278,18 @@ You can right click on any item in embedded web browser and hit `Save as` button
|
||||
|
||||
You can download up to 6 files simultaneously.
|
||||
|
||||
## AdBlock
|
||||
[Web-based variant](#web-based-and-lite-app-variants) of RSS Guard offers ad-blocking functionality. AdBlock uses standard AdBlock-Plus-like scripts, thus allowing you to use EasyList etc. AdBlock supports all fundamental features of AdBlock-Plus format, including element hiding rules and site-wide blocking.
|
||||
## External tools
|
||||
RSS Guard allows you to define a set of custom tools which you can subsequently launch with article URL being passed as parameter to your tool's binary. The feature also supports passing direct links to various media source such as pictures or videos, when used with internal web browser's context menu.
|
||||
|
||||
You can find its settings in `Web browser & tabs` section of main menu. AdBlock is of course available only in [WebEngine-based](#web-based-and-lite-app-variants) version of the app.
|
||||
<img src="images/ext-tools-message.png" width="80%">
|
||||
<img src="images/ext-tools-web.png" width="80%">
|
||||
|
||||
## AdBlock
|
||||
[Web-based variant](#web-based-and-lite-app-variants) of RSS Guard offers ad-blocking functionality via [Adblocker](https://github.com/cliqz-oss/adblocker). Adblocker offers similar performance to [uBlock Origin](https://github.com/gorhill/uBlock).
|
||||
|
||||
You need to have have [Node.js](https://nodejs.org) installed to have ad-blocking in RSS Guard working. Also, the implementation requires additional [npm](https://www.npmjs.com) modules to be installed, specifically: `@cliqz/adblocker`, `concat-stream`, `psl`, `node-fetch`.
|
||||
|
||||
The way ad-blocking internally works is that RSS Guard starts local HTTP browser which provides API determine which elements of website should (or should not) be blocked. RSS Guard then asks the server about each file to be downloaded.
|
||||
|
||||
## GUI tweaking
|
||||
RSS Guard's GUI is very customizable. You can, for example, hide many GUI elements. There are even people who use RSS Guard on mobile devices powered by Linux like PinePhone or Librem devices.
|
||||
@ -231,14 +306,14 @@ Many people have very widescreen monitors nowadays and RSS Guard offers you hori
|
||||
|
||||
<img src="images/gui-layout-orientation.png" width="80%">
|
||||
|
||||
# Miscellaneous
|
||||
Here you can find some useful insights into RSS Guard's modus operandi.
|
||||
# Miscellaneous (for advanced users)
|
||||
Here you can find some useful advanced insights into RSS Guard's modus operandi.
|
||||
|
||||
## CLI
|
||||
RSS Guard offers CLI (command line interface). For overview of its features, run `rssguard --help` in your terminal. You will see the overview of the interface.
|
||||
|
||||
```
|
||||
RSS Guard
|
||||
rssguard.exe [options] [url-1 ... url-n]
|
||||
|
||||
Options:
|
||||
-l, --log <log-file> Write application debug log to file. Note that
|
||||
@ -247,13 +322,31 @@ Options:
|
||||
single instance application mode.
|
||||
-s, --no-single-instance Allow running of multiple application
|
||||
instances.
|
||||
-n, --no-debug-output Completely disable stdout/stderr outputs.
|
||||
-?, -h, --help Displays help on commandline options.
|
||||
--help-all Displays help including Qt specific options.
|
||||
-v, --version Displays version information.
|
||||
|
||||
Arguments:
|
||||
urls List of URL addresses pointing to individual
|
||||
online feeds which should be added.
|
||||
```
|
||||
|
||||
## OS/2
|
||||
RSS Guard can run on OS/2 and if you want to compile it by yourself, you need to make sure that your OS/2 distro is up-to-date and you have all dependencies installed:
|
||||
RSS Guard can add feeds passed as URLs via command line arguments. Feed URI [scheme](https://en.wikipedia.org/wiki/Feed_URI_scheme) is supported, so that you can call RSS Guard like this:
|
||||
|
||||
```
|
||||
rssguard.exe "feed://archlinux.org/feeds/news"
|
||||
rssguard.exe "feed:https//archlinux.org/feeds/news"
|
||||
rssguard.exe "https://archlinux.org/feeds/news"
|
||||
```
|
||||
|
||||
So in order to comfortably add feed directly to RSS Guard from you browser without copying its URL manually, you have to "open" RSS Guard "with" feed URL passed as parameter. There are [extensions](https://addons.mozilla.org/en-GB/firefox/addon/open-with/) which can do it.
|
||||
|
||||
## How to build
|
||||
RSS Guard is C++ application and all common build instructions can be found in top of [project file](https://github.com/martinrotter/rssguard/blob/master/build.pro).
|
||||
|
||||
### OS/2-specifics
|
||||
RSS Guard can run on OS/2 and if you want to compile it by yourself, you need to make sure that your OS/2 distribution is up-to-date and you have all dependencies installed:
|
||||
* `os2-base`,
|
||||
* all `gcc-*` packages,
|
||||
* `libc` and `libcx` up-to-date,
|
||||
@ -262,13 +355,21 @@ RSS Guard can run on OS/2 and if you want to compile it by yourself, you need to
|
||||
* `binutils`,
|
||||
* all relevant `qt5-*` packages.
|
||||
|
||||
Make sure you really have all development dependencies installed as OS/2 is known to thro erratic errors if you miss some important compile-time dependency.
|
||||
|
||||
After your dependecies are installed, then you can compile via standard `qmake -> make -> make install` steps and package with
|
||||
|
||||
```
|
||||
7z.exe a -t7z -mmt -mx9 "rssguard.7z" "<build-folder\src\rssguard\app\*"
|
||||
```
|
||||
|
||||
command.
|
||||
|
||||
## `%data%` placeholder
|
||||
RSS Guard stores its data and settings in single folder. What exact folder it is is described [here](#portable-user-data). RSS Guard allows you to use the folder programmatically in some special contexts via `%data%` placeholder. You can use this placeholder in these RSS Guard contexts:
|
||||
* Contents of your [message filters](Message-filters.md) - you can therefore place some scripts under your user data folder and include it via `JavaScript` into your message filter.
|
||||
* `source` and `post-process script` attributes of for [scraping](#websites-scraping) feed - you can use the placeholder to load scripts to generate/process feed from user data folder.
|
||||
|
||||
## Cleaning database
|
||||
Your RSS Guard's database can grow really big over time, therefore you might need to do its cleanup regularly. There is a dialog `Cleanup database` in `Tools` menu to do just that for you, but note that RSS Guard should run just fine even with tens of thousands of messages.
|
||||
|
||||
@ -277,11 +378,11 @@ Your RSS Guard's database can grow really big over time, therefore you might nee
|
||||
## Portable user data
|
||||
RSS Guard checks "config directory" (this is `C:\Users\<user>\AppData\Local` directory on Windows) for existence of file:
|
||||
```
|
||||
RSS Guard\data\config\config.ini
|
||||
RSS Guard 4\data\config\config.ini
|
||||
```
|
||||
If that file exists, then RSS Guard will use the file (this is called _non-portable **FALLBACK** settings_). If this file is not found, then application will check if its root path (folder, in which RSS Guard executable is installed) is writable, and if it is, it will store settings in it, in subfolder:
|
||||
```
|
||||
data\config\config.ini
|
||||
data4\config\config.ini
|
||||
```
|
||||
This is _fully-portable mode_. Check `About RSS Guard -> Resources` dialog to find more info on significant paths used.
|
||||
|
||||
@ -291,11 +392,12 @@ RSS Guard on Linux, Android or Mac OS automatically uses non-portable user data
|
||||
Here is the rough workflow which is performed when you hit `Feeds & categories -> Update all items` or `Feeds & categories -> Update selected items`. At that point of time this happens:
|
||||
1. RSS Guard creates a list of all/selected feeds.
|
||||
2. Sequentially, for each feed do:
|
||||
* a. Download all available messages from online source.
|
||||
* b. Sequentially, for each downloaded message, do:
|
||||
* a. Download all available messages from online source or generate it with script.
|
||||
* b. Post-process messages with post-process script, if configured.
|
||||
* c. Sequentially, for each downloaded message, do:
|
||||
* 1. Sanitize title of the message. This includes replacing all non-breaking spaces with normal spaces, removing all leading spaces, replacing all multiple consecutive spaces with single space. Contents of message are converted from [percent-encoding](https://en.wikipedia.org/wiki/Percent-encoding).
|
||||
* 2. Run all [message filters](#message-filtering), one by one, one the message. Cache read/important message attributes (or labels changes) changed by filters to queue which is later synchronized back to online feed service.
|
||||
* 3. Store the message into RSS Guard's database, creating completely new DB entry for it, or replacing existing message. **Note that two messages are considered as the same message if they have identical URL, author and title and they belong to the same feed.** This does not stand for synchronized feeds (TT-RSS, Inoreader and others) where each message has assigned special ID which identifies the message uniquely.
|
||||
* 3. Store the message into RSS Guard's [database](#database-backends), creating completely new DB entry for it, or replacing existing message. **Note that two messages are considered as the same message if they have identical URL, author and title and they belong to the same feed.** This does not stand for synchronized feeds (TT-RSS, Inoreader and others) where each message has assigned special ID which identifies the message uniquely.
|
||||
|
||||
## Generating debug log file
|
||||
If you run into problems with RSS Guard and you need your problems fixed, you should provide log file from the time when problem occurred. RSS Guard writes all important information to standard output, which is usually calling terminal.
|
||||
|
@ -12,7 +12,7 @@ Official downloads are available [here](https://github.com/martinrotter/rssguard
|
||||
Development builds can be downloaded [here](https://github.com/martinrotter/rssguard/releases/tag/devbuild).
|
||||
|
||||
## Installation packages naming
|
||||
**Windows builds** of RSS Guard are generated automatically by the tool called AppVeyor. These builds have auto-generated names. In RSS Guard [downloads page](https://github.com/martinrotter/rssguard/releases) you can see filenames like:
|
||||
**All builds** of RSS Guard are generated automatically by GitHub. These builds have auto-generated names. In RSS Guard [downloads page](https://github.com/martinrotter/rssguard/releases) you can see filenames like:
|
||||
* `rssguard-3.4.2-7bad9d1-nowebengine-win32.7z`,
|
||||
* `rssguard-3.4.2-7bad9d1-win32.7z`,
|
||||
* `rssguard-3.4.2-95ee6be-nowebengine-win32.exe`,
|
||||
@ -25,4 +25,6 @@ The structure of these filenames is quite trivial and easily understandable for
|
||||
* `<platform>` = `win32` (This is the target platform which the application can run on.),
|
||||
* `<fileformat>` = `exe` (This is self-explanatory.).
|
||||
|
||||
Note that same file naming scheme for development builds might be little different. Specifically, `<version>` field is omitted.
|
||||
Note that same file naming scheme for development builds might be little different. Specifically, `<version>` field is omitted.
|
||||
|
||||
If you use `7z` packages on Windows, then you need to manually install all needed MSVC++ runtime libraries. Their installers are included inside the archive.
|
@ -43,52 +43,4 @@ OPML files can be exported/imported in simple dialog.
|
||||
<img src="images/im-ex-feeds.png" width="80%">
|
||||
<img src="images/im-ex-feeds-dialog.png" width="50%">
|
||||
|
||||
You just select output file (in case of OPML export), check desired feeds and hit `Export to file`.
|
||||
|
||||
### Websites scraping and related advanced features
|
||||
|
||||
> **Only proceed if you consider yourself to be a power user and you know what you are doing!**
|
||||
|
||||
RSS Guard 3.9.0+ offers extra advanced features which are inspired by [Liferea](https://lzone.de/liferea/).
|
||||
|
||||
You can select source type of each feed. If you select `URL`, then RSS Guard simply downloads feed file from given location and behave like everyone would expect.
|
||||
|
||||
However, if you choose `Script` option, then you cannot provide URL of your feed and you rely on custom script to generate feed file and provide its contents to **standard output**. Resulting data written to standard output should be valid feed file, for example RSS or ATOM XML file.
|
||||
|
||||
`Fetch it now` button also works with `Script` option. Therefore, if your source script and (optional) post-process script in cooperation deliver a valid feed file to the output, then all important metadata, like title or icon of the feed, can be automagically discovered.
|
||||
|
||||
<img src="images/scrape-source-type.png" width="50%">
|
||||
|
||||
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 and all that must be written in special format `<interpreter>#<argument1>#<argument2>#....`. The `#` character is there to separate interpreter and individual arguments.
|
||||
|
||||
Interpreter must be provided in all cases, arguments do not have to be. For example `bash.exe#` is valid execution line, as well as `bash#-c#cat feed.atom`. Note the difference in interpreter's binary name suffix. Also be very carefuly about arguments quoting. Some examples of valid and tested 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#-v#0#https://twitter.com/NSACareers` | Scrape Twitter RSS feed file with [Tweeper](https://git.ao2.it/tweeper.git). Tweeper is utility which is able to produce RSS feed from Twitter and other similar social platforms. |
|
||||
|
||||
<img src="images/scrape-source.png" width="50%">
|
||||
|
||||
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.
|
||||
|
||||
There are some examples of website scrapers [here](https://github.com/martinrotter/rssguard/tree/master/resources/scripts/scrapers), most of the are written in Python 3, thus their execution line is `python.exe#script.py`.
|
||||
|
||||
After your source feed data are downloaded either via URL or custom script, you can optionally post-process the data with one more custom script, which will take **raw source data as input** and must produce processed valid feed data to **standard output** while printing all error messages to **error output**.
|
||||
|
||||
Format of post-process script execution line is the same as above.
|
||||
|
||||
<img src="images/scrape-post.png" width="50%">
|
||||
|
||||
Typical post-processing filter might do things like advanced CSS formatting of feed file entries, removing some ads or simply pretty-printing XML data:
|
||||
|
||||
| Command | Explanation |
|
||||
|---------|-------------|
|
||||
| `bash#-c#xmllint --format -` | Pretty-print input XML feed data. |
|
||||
You just select output file (in case of OPML export), check desired feeds and hit `Export to file`.
|
@ -6,39 +6,40 @@ RSS Guard supports _automagic_ message filtering. The filtering system is automa
|
||||
foreach (feed in feeds_to_update) do
|
||||
messages = download_messages(feed)
|
||||
filtered_messages = filter_messages(messages)
|
||||
|
||||
save_messages_to_database(filtered_messages)
|
||||
```
|
||||
As you can see, RSS Guard processes all feeds scheduled for message downloading one by one; downloading new messages, feeding them to filtering system and then saving all approved messages to RSS Guard's database.
|
||||
|
||||
## Writing message filter
|
||||
|
||||
Message filter consists of arbitrary JavaScript code which must provide function with prototype
|
||||
|
||||
```js
|
||||
function filterMessage() { }
|
||||
```
|
||||
|
||||
This function must be fast and must return values which belong to enumeration `FilteringAction` from this [file](https://github.com/martinrotter/rssguard/blob/master/src/librssguard/core/message.h). You can you either direct numerical value of each enumerant, for example `2` or you can use self-descriptive enumerant name, for example `MessageObject.Ignore`. Named enumerants are supported in RSS Guard 3.8.1+. RSS Guard 3.7.1+ also offers names `MSG_ACCEPT` and `MSG_IGNORE` as aliases for `MessageObject.Accept` and `MessageObject.Ignore`.
|
||||
This function must be fast and must return values which belong to enumeration `FilteringAction` from this [file](https://github.com/martinrotter/rssguard/blob/master/src/librssguard/core/messageobject.h). You can you use either direct numerical value of each enumerant, for example `2` or you can use self-descriptive enumerant name, for example `MessageObject.Ignore`. There are also names `MSG_ACCEPT` and `MSG_IGNORE` as aliases for `MessageObject.Accept` and `MessageObject.Ignore`.
|
||||
|
||||
Each message is accessible in your script via global variable named `msg` of type `MessageObject`, see this [file](https://github.com/martinrotter/rssguard/blob/master/src/librssguard/core/message.h) for the declaration. Some properties are writable, allowing you to change contents of the message before it is written to DB. You can mark message important, parse its description or perhaps change author name or even assign some label to it!!!
|
||||
Each message is accessible in your script via global variable named `msg` of type `MessageObject`, see this [file](https://github.com/martinrotter/rssguard/blob/master/src/librssguard/core/messageobject.h) for the declaration. Some properties are writable, allowing you to change contents of the message before it is written to DB. You can mark message important, parse its description or perhaps change author name or even assign some label to it!!!
|
||||
|
||||
RSS Guard 3.8.0+ offers also list of labels assigned to each message. You can therefore do actions in your filtering script based on which labels are assigned to the message. The property is called `assignedLabels` and is array of `Label` objects. Each `Label` in the array offers these properties: `title` (title of the label), `color` (color of the label) and `customId` (account-specific ID of the label). If you change assigned labels to the message, then the change will be eventually synchronized back to server if respective plugin supports it.
|
||||
You can use [special placeholders](Documentation.md#data-placeholder) within message filter.
|
||||
|
||||
Also, there is a special variable named `utils`. This variable is of type `FilterUtils` and offers some useful utility [functions](#utils-object) for you to use in your filters.
|
||||
|
||||
RSS Guard also offers list of labels assigned to each message. You can therefore do actions in your filtering script based on which labels are assigned to the message. The property is called `assignedLabels` and is array of `Label` objects. If you change assigned labels to the message, then the change will be eventually synchronized back to server if respective plugin supports it.
|
||||
|
||||
Passed message also offers special function
|
||||
```js
|
||||
MessageObject.isDuplicateWithAttribute(DuplicationAttributeCheck)
|
||||
Boolean MessageObject.isDuplicateWithAttribute(DuplicationAttributeCheck)
|
||||
```
|
||||
|
||||
which allows you to perform runtime check for existence of the message in RSS Guard's database. The parameter is integer value from enumeration `DuplicationAttributeCheck` from this [file](https://github.com/martinrotter/rssguard/blob/master/src/librssguard/core/message.h) and specifies how exactly you want to determine if given message is "duplicate". Again, you can use direct integer value or enumerant name.
|
||||
which allows you to perform runtime check for existence of the message in RSS Guard's database. The parameter is integer value from enumeration `DuplicationAttributeCheck` from this [file](https://github.com/martinrotter/rssguard/blob/master/src/librssguard/core/messageobject.h) and specifies how exactly you want to determine if given message is "duplicate". Again, you can use direct integer values or enumerant names.
|
||||
|
||||
For example if you want to check if there is already another message with same author in database, then you call `msg.isDuplicateWithAttribute(MessageObject.SameAuthor)`. Enumeration even supports "flags" approach, thus you can combine multiple checks via bitwise `OR` operation in single call, for example like this: `msg.isDuplicateWithAttribute(MessageObject.SameAuthor | MessageObject.SameUrl)`.
|
||||
For example if you want to check if there is already another message with same author in database, then you call `msg.isDuplicateWithAttribute(MessageObject.SameAuthor)`. Values of the enumeration can be combined via bitwise `|` operation in single call, for example like this: `msg.isDuplicateWithAttribute(MessageObject.SameAuthor | MessageObject.SameUrl)`.
|
||||
|
||||
## API reference
|
||||
Here is the reference of methods and properties of some types available in your filtering scipts.
|
||||
|
||||
### `MessageObject` class
|
||||
|
||||
| Property/method | Description |
|
||||
|---|---|
|
||||
| `Array<Label> assignedLabels` | `READ-ONLY` List of labels assigned to the message. |
|
||||
@ -49,14 +50,16 @@ 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. |
|
||||
| `String rawContents` | This is RAW contents of the message as it was obtained from remote service/feed. You can expect raw `XML` or `JSON` element data here. Note that this attribute has some value only if `alreadyStoredInDb` returns `false`. In other words, this attribute is not persistently stored inside RSS Guard's DB. |
|
||||
| `Number score` | Arbitrary number in range <0.0, 100.0>. You can use this number to sort 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? |
|
||||
| `Boolean isDeleted` | Is message placed in recycle bin? Available in RSS Guard 3.8.4+. |
|
||||
| `Boolean isDeleted` | Is message placed in recycle bin? |
|
||||
| `Boolean isDuplicateWithAttribute(DuplicationAttributeCheck)` | Allows you to test if this particular message is already stored in RSS Guard's DB. |
|
||||
| `Boolean assignLabel(String)` | Assigns label to this message. The passed `String` value is the `customId` property of `Label` type. See its API reference for relevant info. Available in RSS Guard 3.8.1+. |
|
||||
| `Boolean deassignLabel(String)` | Removes label from this message. The passed `String` value is the `customId` property of `Label` type. See its API reference for relevant info. Available in RSS Guard 3.8.1+. |
|
||||
| `Boolean alreadyStoredInDb` | `READ-ONLY` Returns true if this message is already stored in DB. This function is the way to check if the filter is being run automatically for newly downloaded messages or manually for already existing messages. Available in RSS Guard 3.8.4+. |
|
||||
| `Boolean assignLabel(String)` | Assigns label to this message. The passed `String` value is the `customId` property of `Label` type. See its API reference for relevant info. |
|
||||
| `Boolean deassignLabel(String)` | Removes label from this message. The passed `String` value is the `customId` property of `Label` type. See its API reference for relevant info. |
|
||||
| `Boolean alreadyStoredInDb` | `READ-ONLY` Returns true if this message is already stored in DB. This function is the way to check if the filter is being run automatically for newly downloaded messages or manually for already existing messages.
|
||||
|
||||
### `Label` class
|
||||
| Property/method | Description |
|
||||
@ -68,20 +71,26 @@ Here is the reference of methods and properties of some types available in your
|
||||
### `FilteringAction` enum
|
||||
| Enumerant name | Integer value | Description |
|
||||
|---|---|---|
|
||||
| Accept | 1 | Message is accepted and will be added to DB or updated in DB. |
|
||||
| Ignore | 2 | Message is ignored and will be **NOT** added to DB or updated in DB, but is not purged from DB if already exists. |
|
||||
| Purge | 4 | Existing message is purged from the DB completely. |
|
||||
| `Accept` | 1 | Message is accepted and will be added to DB or updated in DB. |
|
||||
| `Ignore` | 2 | Message is ignored and will be **NOT** added to DB or updated in DB, but is not purged from DB if already exists. |
|
||||
| `Purge` | 4 | Existing message is purged from the DB completely. Behavior is the same as `Ignore` when there is new incoming message. |
|
||||
|
||||
Note that `MessageObject` attributes which can be synchronized back to service are synchronized even if you return `Purge` or `Ignore`. In other words: even if you filter ignores the message you can still tweak its properties which will get synchronized back to your server.
|
||||
|
||||
### `DuplicationAttributeCheck` enum
|
||||
| Enumerant name | Integer value | Description |
|
||||
|---|---|---|
|
||||
| SameTitle | 1 | Check if message has same title as some another messages. |
|
||||
| SameUrl | 2 | Check if message has same URL as some another messages. |
|
||||
| SameAuthor | 4 | Check if message has same author as some another messages. |
|
||||
| SameDateCreated | 8 | Check if message has same date of creation as some another messages. |
|
||||
| AllFeedsSameAccount | 16 | Perform the check across all feeds from your account, not just "current" feed. |
|
||||
| `SameTitle` | 1 | Check if message has same title as some another messages. |
|
||||
| `SameUrl` | 2 | Check if message has same URL as some another messages. |
|
||||
| `SameAuthor` | 4 | Check if message has same author as some another messages. |
|
||||
| `SameDateCreated` | 8 | Check if message has same date of creation as some another messages. |
|
||||
| `AllFeedsSameAccount` | 16 | Perform the check across all feeds from your account, not just "current" feed. |
|
||||
|
||||
## `utils` object
|
||||
| Method | How to call | Description |
|
||||
|---|---|---|
|
||||
| `String hostname()` | `utils.hostname()` | Returns name of your PC. |
|
||||
| `String fromXmlToJson(String)` | `utils.fromXmlToJson('<h1>hello</h1>')` | Converts `XML` string into `JSON`. |
|
||||
|
||||
## Examples
|
||||
Accept only messages from "Bob" while also marking them important.
|
||||
@ -105,11 +114,48 @@ function filterMessage() {
|
||||
}
|
||||
```
|
||||
|
||||
Dump RAW data of each message to RSS Guard's [debug output](Documentation.md#generating-debug-log-file).
|
||||
```js
|
||||
function filterMessage() {
|
||||
console.log(msg.rawContents);
|
||||
return MessageObject.Accept;
|
||||
}
|
||||
```
|
||||
|
||||
The above script produces this kind of debug output when running for Tiny Tiny RSS.
|
||||
```
|
||||
...
|
||||
...
|
||||
time=" 34.360" type="debug" -> feed-downloader: Hooking message took 4 microseconds.
|
||||
time=" 34.361" type="debug" -> {"always_display_attachments":false,"attachments":[],"author":"Aleš Kapica","comments_count":0,"comments_link":"","content":"<p>\nNaposledy jsem psal o čuňačení v MediaWiki asi před půl rokem, kdy jsem chtěl upozornit na to, že jsem přepracoval svoji původní šablonu Images tak, aby bylo možné používat výřezy z obrázků a stránek generovaných z DjVu a PDF dokumentů. Blogpost nebyl nijak extra hodnocen, takže mě vcelku nepřekvapuje, jak se do hlavní vývojové větve MediaWiki dostávají čím dál větší prasečiny.\n</p>","feed_id":"5903","feed_title":"abclinuxu - blogy","flavor_image":"","flavor_stream":"","guid":"{\"ver\":2,\"uid\":\"52\",\"hash\":\"SHA1:5b49e4d8f612984889ba25e7834e80604c795ff8\"}","id":6958843,"is_updated":false,"labels":[],"lang":"","link":"http://www.abclinuxu.cz/blog/kenyho_stesky/2021/1/cunacime-v-mediawiki-responzivni-obsah-ii","marked":false,"note":null,"published":false,"score":0,"tags":[""],"title":"Čuňačíme v MediaWiki - responzivní obsah II.","unread":true,"updated":1610044674}
|
||||
time=" 34.361" type="debug" -> feed-downloader: Running filter script, it took 348 microseconds.
|
||||
time=" 34.361" type="debug" -> feed-downloader: Hooking message took 4 microseconds.
|
||||
time=" 34.361" type="debug" -> {"always_display_attachments":false,"attachments":[],"author":"kol-ouch","comments_count":0,"comments_link":"","content":"Ahoj, 1. 6. se blíží, tak začínám řešit co s bambilionem fotek na google photos. \n<p class=\"separator\"></p>\nZa sebe můžu říct, že gp mi vyhovují - ne snad úplně tím, že jsou zadarmo, ale hlavně způsobem práce s fotkami, možnostmi vyhledávání v nich podle obsahu, vykopírování textu z nich, provázaností s mapami, recenzemi, možnostmi sdílení, automatickým seskupováním a podobně.","feed_id":"5903","feed_title":"abclinuxu - blogy","flavor_image":"","flavor_stream":"","guid":"{\"ver\":2,\"uid\":\"52\",\"hash\":\"SHA1:1277107408b159882b95ca7151a0ec0160a3971a\"}","id":6939327,"is_updated":false,"labels":[],"lang":"","link":"http://www.abclinuxu.cz/blog/Co_to_je/2021/1/kam-s-fotkama","marked":false,"note":null,"published":false,"score":0,"tags":[""],"title":"Kam s fotkama?","unread":true,"updated":1609750800}
|
||||
...
|
||||
...
|
||||
```
|
||||
|
||||
For RSS 2.0 message, the result might look like this.
|
||||
```
|
||||
...
|
||||
...
|
||||
time=" 3.568" type="debug" -> feed-downloader: Hooking message took 6 microseconds.
|
||||
time=" 3.568" type="debug" -> <item>
|
||||
<title><![CDATA[Man Utd's Cavani 'not comfortable' in England, says father]]></title>
|
||||
<description><![CDATA[Manchester United striker Edinson Cavani "does not feel comfortable" and could move back to his native South America, his father said.]]></description>
|
||||
<link>https://www.bbc.co.uk/sport/football/56341983</link>
|
||||
<guid isPermaLink="true">https://www.bbc.co.uk/sport/football/56341983</guid>
|
||||
<pubDate>Tue, 09 Mar 2021 23:46:03 GMT</pubDate>
|
||||
</item>
|
||||
|
||||
time=" 3.568" type="debug" -> feed-downloader: Running filter script, it took 416 microseconds.
|
||||
```
|
||||
|
||||
Write details of available labels and assign the first label to the message.
|
||||
```js
|
||||
function filterMessage() {
|
||||
console.log('Number of assigned labels ' + msg.assignedLabels.length);
|
||||
console.log('Number of available labels ' + msg.availableLabels.length);
|
||||
console.log('Number of assigned labels: ' + msg.assignedLabels.length);
|
||||
console.log('Number of available labels: ' + msg.availableLabels.length);
|
||||
|
||||
var i;
|
||||
for (i = 0; i < msg.availableLabels.length; i++) {
|
||||
@ -131,10 +177,10 @@ function filterMessage() {
|
||||
}
|
||||
```
|
||||
|
||||
Make sure that your receive only one message with particular URL and all other messages with same URL are subsequently ignored.
|
||||
Make sure that your receive only one message with particular URL across all your feeds (from same plugin) and all other messages with same URL are subsequently ignored.
|
||||
```js
|
||||
function filterMessage() {
|
||||
if (msg.isDuplicateWithAttribute(MessageObject.SameUrl)) {
|
||||
if (msg.isDuplicateWithAttribute(MessageObject.SameUrl | MessageObject.AllFeedsSameAccount)) {
|
||||
return MessageObject.Ignore;
|
||||
}
|
||||
else {
|
||||
|
BIN
resources/docs/images/add-acc.png
Executable file
BIN
resources/docs/images/add-acc.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
resources/docs/images/ext-tools-message.png
Executable file
BIN
resources/docs/images/ext-tools-message.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 920 KiB |
BIN
resources/docs/images/ext-tools-web.png
Executable file
BIN
resources/docs/images/ext-tools-web.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 879 KiB |
BIN
resources/graphics/misc/reedah.png
Normal file
BIN
resources/graphics/misc/reedah.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.8 KiB |
File diff suppressed because one or more lines are too long
@ -4,7 +4,6 @@
|
||||
!define APP_VERSION "@APP_VERSION@"
|
||||
!define APP_NAME "@APP_NAME@"
|
||||
!define EXE_NAME "@EXE_NAME@"
|
||||
;!define README_FILE "README"
|
||||
!define LICENSE_FILE "@PWD@\..\..\resources\text\COPYING_GNU_GPL"
|
||||
!define MUI_ICON "@PWD@\..\..\resources\graphics\@APP_LOW_NAME@.ico"
|
||||
!define MUI_UNICON "@PWD@\..\..\resources\graphics\@APP_LOW_NAME@.ico"
|
||||
|
@ -23,7 +23,7 @@ SetCompressor /SOLID /FINAL lzma
|
||||
|
||||
; Default installation folder.
|
||||
InstallDir "C:\${APP_NAME}"
|
||||
InstallDirRegKey HKCU "Software\${APP_NAME}" "Install Directory"
|
||||
InstallDirRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "InstallLocation"
|
||||
|
||||
; Require administrator access.
|
||||
RequestExecutionLevel admin
|
||||
@ -131,9 +131,12 @@ NotInstalled:
|
||||
ExecWait '"$INSTDIR\vc_redist.x64.exe" /install /quiet /norestart'
|
||||
Delete "$INSTDIR\vc_redist.x64.exe"
|
||||
|
||||
; Store installation folder.
|
||||
WriteRegStr HKCU "Software\${APP_NAME}" "Install Directory" $INSTDIR
|
||||
|
||||
ExecWait '"$INSTDIR\vc_redist_2010.x86.exe" /q /norestart'
|
||||
Delete "$INSTDIR\vc_redist_2010.x86.exe"
|
||||
|
||||
ExecWait '"$INSTDIR\vc_redist_2010.x64.exe" /q /norestart'
|
||||
Delete "$INSTDIR\vc_redist_2010.x64.exe"
|
||||
|
||||
; Create uninstaller.
|
||||
WriteUninstaller "$INSTDIR\Uninstall.exe"
|
||||
|
||||
@ -141,6 +144,7 @@ NotInstalled:
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "DisplayName" "${APP_NAME}"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "DisplayIcon" "$INSTDIR\${EXE_NAME}"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "DisplayVersion" "${APP_VERSION}"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "InstallLocation" "$INSTDIR"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "UninstallString" "$\"$INSTDIR\Uninstall.exe$\""
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "QuietUninstallString" "$\"$INSTDIR\Uninstall.exe$\" /S"
|
||||
SectionEnd
|
||||
|
@ -5,6 +5,9 @@
|
||||
<file>text/COPYING_MIT</file>
|
||||
<file>text/COPYING_GNU_GPL</file>
|
||||
<file>text/COPYING_GNU_GPL_HTML</file>
|
||||
|
||||
<file>scripts/adblock/adblock-server.js</file>
|
||||
<file>scripts/public_suffix_list.dat</file>
|
||||
|
||||
<file>graphics/rssguard.ico</file>
|
||||
|
||||
@ -24,6 +27,7 @@
|
||||
<file>graphics/misc/image-placeholder.png</file>
|
||||
<file>graphics/misc/inoreader.png</file>
|
||||
<file>graphics/misc/nextcloud.png</file>
|
||||
<file>graphics/misc/reedah.png</file>
|
||||
<file>graphics/misc/theoldreader.png</file>
|
||||
<file>graphics/misc/tt-rss.png</file>
|
||||
|
||||
|
118
resources/scripts/adblock/adblock-server.js
Executable file
118
resources/scripts/adblock/adblock-server.js
Executable file
@ -0,0 +1,118 @@
|
||||
// Simple local HTTP server providing ad-blocking functionality via https://github.com/cliqz-oss/adblocker
|
||||
//
|
||||
// How to install:
|
||||
// npm i -g @cliqz/adblocker
|
||||
// npm i -g concat-stream
|
||||
// npm i -g tldts-experimental
|
||||
// npm i -g node-fetch
|
||||
//
|
||||
// How to run:
|
||||
// NODE_PATH="C:\Users\<user>\AppData\Roaming\npm\node_modules" node ./adblock-server.js "<port>" "<filters-file-path>"
|
||||
//
|
||||
// How to use:
|
||||
// curl -i -X POST --data '
|
||||
// {
|
||||
// "url": "http://gompoozu.net",
|
||||
// "url_type": "main_frame",
|
||||
// "filter": true,
|
||||
// "cosmetic": true
|
||||
// }' 'http://localhost:<port>'
|
||||
|
||||
const fs = require('fs');
|
||||
const tldts = require('tldts-experimental');
|
||||
const adblock = require('@cliqz/adblocker')
|
||||
const http = require('http');
|
||||
const concat = require('concat-stream');
|
||||
const constants = require('node:http2');
|
||||
const fetch = require("node-fetch");
|
||||
const cluster = require('cluster');
|
||||
|
||||
const numCPUs = require('os').cpus().length;
|
||||
const port = process.argv[2];
|
||||
const filtersFile = process.argv[3];
|
||||
const engine = adblock.FiltersEngine.parse(fs.readFileSync(filtersFile, 'utf-8'));
|
||||
const hostname = '127.0.0.1';
|
||||
|
||||
if (cluster.isPrimary) {
|
||||
console.log(`Primary ${process.pid} is running`);
|
||||
|
||||
// Fork workers.
|
||||
for (let i = 0; i < numCPUs; i++) {
|
||||
cluster.fork();
|
||||
}
|
||||
|
||||
cluster.on('exit', (worker, code, signal) => {
|
||||
console.log(`worker ${worker.process.pid} died`);
|
||||
});
|
||||
}
|
||||
else {
|
||||
const server = http.createServer((req, res) => {
|
||||
try {
|
||||
console.log(new Date());
|
||||
|
||||
const chunks = [];
|
||||
req.on('data', chunk => chunks.push(chunk));
|
||||
req.on('end', () => {
|
||||
console.log(new Date());
|
||||
|
||||
try {
|
||||
const jsonData = Buffer.concat(chunks);
|
||||
const jsonStruct = JSON.parse(jsonData.toString());
|
||||
|
||||
const askUrl = jsonStruct['url'];
|
||||
const askFilter = jsonStruct['filter'];
|
||||
const askCosmetic = jsonStruct['cosmetic'];
|
||||
const askUrlType = jsonStruct['url_type'];
|
||||
const fullUrl = new URL(askUrl);
|
||||
|
||||
resultJson = {};
|
||||
|
||||
if (askFilter) {
|
||||
const adblockMatch = engine.match(adblock.Request.fromRawDetails({
|
||||
type: askUrlType,
|
||||
url: askUrl,
|
||||
}));
|
||||
|
||||
resultJson["filter"] = adblockMatch;
|
||||
console.log(`adblocker: Filter is:\n${JSON.stringify(adblockMatch)}.`)
|
||||
}
|
||||
|
||||
if (askCosmetic) {
|
||||
const adblockCosmetic = engine.getCosmeticsFilters({
|
||||
url: askUrl,
|
||||
hostname: fullUrl.hostname,
|
||||
domain: tldts.parse(fullUrl).domain
|
||||
});
|
||||
|
||||
resultJson["cosmetic"] = adblockCosmetic;
|
||||
console.log(`adblocker: Cosmetic is:\n${JSON.stringify(adblockCosmetic)}.`)
|
||||
}
|
||||
|
||||
res.statusCode = 200;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify(resultJson));
|
||||
|
||||
console.log(new Date());
|
||||
}
|
||||
catch (inner_error) {
|
||||
console.error(`adblocker: ${inner_error}.`);
|
||||
|
||||
res.statusCode = 500;
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.end(String(inner_error));
|
||||
}
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`adblocker: ${inner_error}.`);
|
||||
|
||||
res.statusCode = 500;
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.end(String(error));
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(port, hostname, () => {
|
||||
console.log(`adblocker: Server started at local port ${port}.`);
|
||||
});
|
||||
}
|
@ -52,7 +52,7 @@ git_tag=$(git describe --tags `git rev-list --tags --max-count=1`)
|
||||
git_revision=$(git rev-parse --short HEAD)
|
||||
|
||||
mkdir rssguard-build && cd rssguard-build
|
||||
qmake .. "USE_WEBENGINE=$webengine"
|
||||
qmake .. "USE_WEBENGINE=$webengine" "FEEDLY_CLIENT_ID=$FEEDLY_CLIENT_ID" "FEEDLY_CLIENT_SECRET=$FEEDLY_CLIENT_SECRET" "GMAIL_CLIENT_ID=$GMAIL_CLIENT_ID" "GMAIL_CLIENT_SECRET=$GMAIL_CLIENT_SECRET" "INOREADER_CLIENT_ID=$INOREADER_CLIENT_ID" "INOREADER_CLIENT_SECRET=$INOREADER_CLIENT_SECRET"
|
||||
make
|
||||
make install
|
||||
cd "src/rssguard"
|
||||
|
@ -57,7 +57,7 @@ cd "$old_pwd"
|
||||
# Build application.
|
||||
mkdir "rssguard-build"
|
||||
cd "rssguard-build"
|
||||
& "$qt_qmake" "..\build.pro" "-r" "USE_WEBENGINE=$webengine" "CONFIG-=debug" "CONFIG-=debug_and_release" "CONFIG*=release"
|
||||
& "$qt_qmake" "..\build.pro" "-r" "USE_WEBENGINE=$webengine" "FEEDLY_CLIENT_ID=$env:FEEDLY_CLIENT_ID" "FEEDLY_CLIENT_SECRET=$env:FEEDLY_CLIENT_SECRET" "GMAIL_CLIENT_ID=$env:GMAIL_CLIENT_ID" "GMAIL_CLIENT_SECRET=$env:GMAIL_CLIENT_SECRET" "INOREADER_CLIENT_ID=$env:INOREADER_CLIENT_ID" "INOREADER_CLIENT_SECRET=$env:INOREADER_CLIENT_SECRET" "CONFIG-=debug" "CONFIG-=debug_and_release" "CONFIG*=release"
|
||||
nmake.exe
|
||||
|
||||
cd "src\rssguard"
|
||||
|
13680
resources/scripts/public_suffix_list.dat
Executable file
13680
resources/scripts/public_suffix_list.dat
Executable file
File diff suppressed because it is too large
Load Diff
BIN
resources/scripts/redist/vc_redist_2010.x64.exe
Executable file
BIN
resources/scripts/redist/vc_redist_2010.x64.exe
Executable file
Binary file not shown.
BIN
resources/scripts/redist/vc_redist_2010.x86.exe
Executable file
BIN
resources/scripts/redist/vc_redist_2010.x86.exe
Executable file
Binary file not shown.
27
resources/scripts/scrapers/funnyjunk-embed-gifs.py
Normal file
27
resources/scripts/scrapers/funnyjunk-embed-gifs.py
Normal file
@ -0,0 +1,27 @@
|
||||
# Embeds GIFs into individual messages from Funnyjunk.com
|
||||
# Sample input file whose contents must be provided as stdin: "https://funnyjunk.com/rss/most_popular.rss"
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import urllib.request
|
||||
import xml.etree.ElementTree as ET
|
||||
from datetime import datetime
|
||||
|
||||
input_data = sys.stdin.read()
|
||||
|
||||
tree = ET.fromstring(input_data)
|
||||
pattern = re.compile("href=\"(https://[^<>]+\.(gif|png|jpg))\"")
|
||||
|
||||
for ite in tree.find("channel").iter("item"):
|
||||
link = ite.find("link").text
|
||||
#print(link)
|
||||
response = urllib.request.urlopen(link)
|
||||
text = response.read().decode("utf-8")
|
||||
for pic_link in re.findall(pattern, text):
|
||||
new = ET.SubElement(ite, "enclosure")
|
||||
new.set("url", pic_link[0])
|
||||
new.set("type", "image/" + pic_link[1])
|
||||
#print(ET.tostring(ite, encoding="unicode"))
|
||||
|
||||
print(ET.tostring(tree, encoding="unicode"))
|
40
resources/scripts/scrapers/github-releases.py
Executable file
40
resources/scripts/scrapers/github-releases.py
Executable file
@ -0,0 +1,40 @@
|
||||
# Use this script as "post-process" script, let RSS Guard download
|
||||
# the "releases" file itself.
|
||||
#
|
||||
# This script expects one command line argument:
|
||||
# true -> ignore prereleases
|
||||
# false -> include prereleases
|
||||
#
|
||||
# Sample input file whose contents must be provided as stdin: https://api.github.com/repos/<user>/<repo>/releases
|
||||
#
|
||||
# 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_author = json.dumps(rel["author"]["login"])
|
||||
article_url = json.dumps(rel["html_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}, \"authors\": [{{\"name\": {author}}}], \"content_html\": {html}, \"url\": {url}, \"date_published\": {date}}}".format(title=article_title,
|
||||
html=article_title,
|
||||
url=article_url,
|
||||
date=article_time,
|
||||
author=article_author))
|
||||
|
||||
json_feed = json_feed.format(title = "Releases", items = ", ".join(items))
|
||||
print(json_feed)
|
31
resources/scripts/scrapers/njcom.py
Normal file
31
resources/scripts/scrapers/njcom.py
Normal file
@ -0,0 +1,31 @@
|
||||
# Obtains articles from "connect.nj.com"
|
||||
# Sample input file whose contents must be provided as stdin: "https://connect.nj.com/staff/bklapisch/posts.html"
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import html
|
||||
import urllib.request
|
||||
from html.parser import HTMLParser
|
||||
from bs4 import BeautifulSoup
|
||||
from datetime import datetime
|
||||
|
||||
sys.stdin.reconfigure(encoding='utf-8')
|
||||
input_data = sys.stdin.read()
|
||||
soup = BeautifulSoup(input_data, 'html.parser')
|
||||
json_feed = "{{\"title\": {title}, \"items\": [{items}]}}"
|
||||
items = list()
|
||||
|
||||
for content in soup.find_all("article"):
|
||||
article_url = json.dumps(content.find("h2").a["href"])
|
||||
article_title = json.dumps(content.find("h2").text.replace(" ", ""))
|
||||
article_contents = json.dumps(str(content.find("p")))
|
||||
article_time = json.dumps(content.find("time")["datetime"])
|
||||
items.append("{{\"title\": {title}, \"content_html\": {html}, \"url\": {url}, \"date_published\": {date}}}".format(
|
||||
title=article_title,
|
||||
html=article_contents,
|
||||
url=article_url,
|
||||
date=article_time))
|
||||
|
||||
json_feed = json_feed.format(title = json.dumps(soup.title.text), items = ", ".join(items))
|
||||
print(json_feed)
|
26
resources/scripts/scrapers/nvidia-driver-articles.py
Normal file
26
resources/scripts/scrapers/nvidia-driver-articles.py
Normal file
@ -0,0 +1,26 @@
|
||||
# Parses output of Nvidia's GDC web service, which provides
|
||||
# list of articles.
|
||||
# Sample input file whose contents must be provided as stdin: "https://www.nvidia.com/bin/nvidiaGDC/servlet/article.json?locale=en_US®ion=us&type=both&tag=drivers&offset=0s"
|
||||
# This scripts outputs JSON feed 1.1: https://jsonfeed.org/version/1.1
|
||||
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
json_data = json.loads(sys.stdin.read())
|
||||
json_feed = "{{\"title\": \"{title}\", \"items\": [{items}]}}"
|
||||
items = list()
|
||||
|
||||
json_root = json_data[0]
|
||||
|
||||
for ite in json_root["articlePagesList"]:
|
||||
article_author = json.dumps(ite["authorName"])
|
||||
article_url = json.dumps(ite["articlePath"])
|
||||
article_title = json.dumps(ite["articleTitle"])
|
||||
article_time = json.dumps(datetime.strptime(ite["articleDate"], "%B %d, %Y").isoformat())
|
||||
article_contents = json.dumps(ite["articleShortDescription"])
|
||||
|
||||
items.append("{{\"title\": {title}, \"authors\": [{{\"name\": {author}}}], \"content_text\": {html}, \"url\": {url}, \"date_published\": {date}}}".format(title=article_title, html=article_contents, url=article_url, date=article_time, author=article_author))
|
||||
|
||||
json_feed = json_feed.format(title="Nvidia " + json_root["articleLocalizedTag"], items=", ".join(items))
|
||||
print(json_feed)
|
33
resources/scripts/scrapers/product-hunt.py
Executable file
33
resources/scripts/scrapers/product-hunt.py
Executable file
@ -0,0 +1,33 @@
|
||||
# Obtains producthunt's articles.
|
||||
# Sample input file whose contents must be provided as stdin: "https://www.producthunt.com/topics/XXXX"
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import urllib.request
|
||||
from html.parser import HTMLParser
|
||||
from bs4 import BeautifulSoup
|
||||
from datetime import datetime
|
||||
|
||||
input_data = sys.stdin.read()
|
||||
soup = BeautifulSoup(input_data, 'html.parser')
|
||||
|
||||
json_feed = "{{\"title\": {title}, \"items\": [{items}]}}"
|
||||
items = list()
|
||||
|
||||
regex_batch = re.compile('^styles_postContent__.+$')
|
||||
for post_content in soup.find_all("div", {"class" : regex_batch}):
|
||||
regex_single = re.compile('^styles_content__.+$')
|
||||
for content in post_content.find_all("div", {"class" : regex_single}):
|
||||
article_url = json.dumps("https://www.producthunt.com" + content.p.a["href"])
|
||||
article_title = json.dumps(content.h3.text)
|
||||
article_contents = json.dumps(content.p.text)
|
||||
article_time = json.dumps(datetime.utcnow().isoformat())
|
||||
items.append("{{\"title\": {title}, \"content_html\": {html}, \"url\": {url}, \"date_published\": {date}}}".format(
|
||||
title=article_title,
|
||||
html=article_contents,
|
||||
url=article_url,
|
||||
date=article_time))
|
||||
|
||||
json_feed = json_feed.format(title = json.dumps(soup.title.text), items = ", ".join(items))
|
||||
print(json_feed)
|
55
resources/scripts/scrapers/scrape-as-rss2.py
Executable file
55
resources/scripts/scrapers/scrape-as-rss2.py
Executable file
@ -0,0 +1,55 @@
|
||||
# Downloads full articles for RSS 2.0 feed and replaces original articles.
|
||||
#
|
||||
# Make sure to have all dependencies installed:
|
||||
# pip3 install asyncio (if using parallel version of the script)
|
||||
#
|
||||
# You must provide raw RSS 2.0 UTF-8 feed XML data as input, for example with curl:
|
||||
# curl 'http://rss.cnn.com/rss/edition.rss' | python ./scrape-rss2.py "4"
|
||||
#
|
||||
# You must provide three command line arguments:
|
||||
# scrape-rss2.py [NUMBER-OF-PARALLEL-THREADS]
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import html
|
||||
import urllib.request
|
||||
import distutils.util
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
no_threads = int(sys.argv[1])
|
||||
|
||||
if no_threads > 1:
|
||||
import asyncio
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
sys.stdin.reconfigure(encoding='utf-8')
|
||||
rss_data = sys.stdin.read()
|
||||
rss_document = ET.fromstring(rss_data)
|
||||
|
||||
def process_article(article):
|
||||
try:
|
||||
link = "https://us-central1-technews-251304.cloudfunctions.net/article-parser?url=" + article.find("link").text
|
||||
response = urllib.request.urlopen(link)
|
||||
text = response.read().decode("utf-8")
|
||||
js = json.loads(text)
|
||||
|
||||
if int(js["error"]) == 0:
|
||||
article.find("description").text = js["data"]["content"]
|
||||
except:
|
||||
pass
|
||||
|
||||
# Scrape articles.
|
||||
if no_threads > 1:
|
||||
with ThreadPoolExecutor(max_workers = no_threads) as executor:
|
||||
futures = []
|
||||
for article in rss_document.findall(".//item"):
|
||||
futures.append(executor.submit(process_article, article))
|
||||
for future in futures:
|
||||
future.result()
|
||||
else:
|
||||
for article in rss_document.findall(".//item"):
|
||||
process_article(article)
|
||||
|
||||
print(ET.tostring(rss_document, encoding = "unicode"))
|
56
resources/scripts/scrapers/scrape-rss2.py
Executable file
56
resources/scripts/scrapers/scrape-rss2.py
Executable file
@ -0,0 +1,56 @@
|
||||
# Downloads full articles for RSS 2.0 feed and replaces original articles.
|
||||
#
|
||||
# Make sure to have all dependencies installed:
|
||||
# pip3 install newspaper3k
|
||||
# pip3 install asyncio (if using parallel version of the script)
|
||||
#
|
||||
# You must provide raw RSS 2.0 UTF-8 feed XML data as input, for example with curl:
|
||||
# curl 'http://rss.cnn.com/rss/edition.rss' | python ./scrape-rss2.py "4"
|
||||
#
|
||||
# You must provide three command line arguments:
|
||||
# scrape-rss2.py [NUMBER-OF-PARALLEL-THREADS]
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import html
|
||||
import requests
|
||||
import distutils.util
|
||||
import xml.etree.ElementTree as ET
|
||||
from newspaper import Article
|
||||
|
||||
no_threads = int(sys.argv[1])
|
||||
|
||||
if no_threads > 1:
|
||||
import asyncio
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
sys.stdin.reconfigure(encoding='utf-8')
|
||||
rss_data = sys.stdin.read()
|
||||
rss_document = ET.fromstring(rss_data)
|
||||
|
||||
def process_article(article):
|
||||
try:
|
||||
link = article.find("link").text
|
||||
|
||||
f = Article(link, keep_article_html = True)
|
||||
f.download()
|
||||
f.parse()
|
||||
article.find("description").text = f.article_html
|
||||
except:
|
||||
pass
|
||||
|
||||
# Scrape articles.
|
||||
if no_threads > 1:
|
||||
with ThreadPoolExecutor(max_workers = no_threads) as executor:
|
||||
futures = []
|
||||
for article in rss_document.findall(".//item"):
|
||||
futures.append(executor.submit(process_article, article))
|
||||
for future in futures:
|
||||
future.result()
|
||||
else:
|
||||
for article in rss_document.findall(".//item"):
|
||||
process_article(article)
|
||||
|
||||
print(ET.tostring(rss_document, encoding = "unicode"))
|
31
resources/scripts/scrapers/search-xml-feeds.py
Executable file
31
resources/scripts/scrapers/search-xml-feeds.py
Executable file
@ -0,0 +1,31 @@
|
||||
# Produces the list of links to XML feeds as extracted from input list of generic URLs.
|
||||
# This script expects to have the file path passed as the only input parameter
|
||||
|
||||
import re
|
||||
import sys
|
||||
import urllib.request
|
||||
from urllib.parse import urljoin
|
||||
|
||||
urls_file = sys.argv[1]
|
||||
|
||||
with open(urls_file) as f:
|
||||
urls_lines = [line.rstrip() for line in f]
|
||||
|
||||
regexp_link = re.compile("<link[^>]+type=\"application\/(?:atom\+xml|rss\+xml|feed\+json|json)\"[^>]*>")
|
||||
regexp_href = re.compile("href=\"([^\"]+)\"")
|
||||
|
||||
for url in urls_lines:
|
||||
# Download HTML data.
|
||||
try:
|
||||
url_response = urllib.request.urlopen(url)
|
||||
html = url_response.read().decode("utf-8")
|
||||
except:
|
||||
continue
|
||||
|
||||
# Search for XML feeds with regexps.
|
||||
for link_tag in re.findall(regexp_link, html):
|
||||
for link_xml_feed in re.findall(regexp_href, link_tag):
|
||||
if link_xml_feed.startswith("/"):
|
||||
print(urljoin(url, "/") + link_xml_feed[1:])
|
||||
else:
|
||||
print(link_xml_feed)
|
70
resources/scripts/scrapers/translate-rss2.py
Executable file
70
resources/scripts/scrapers/translate-rss2.py
Executable file
@ -0,0 +1,70 @@
|
||||
# Translates entries of RSS 2.0 feed into different locale.
|
||||
#
|
||||
# Make sure to have all dependencies installed:
|
||||
# pip3 install googletrans==4.0.0-rc1
|
||||
# pip3 install asyncio (if using parallel version of the script)
|
||||
#
|
||||
# You must provide raw RSS 2.0 UTF-8 feed XML data as input, for example with curl:
|
||||
# curl 'https://phys.org/rss-feed/' | python ./translate-rss2.py "en" "pt_BR" "true"
|
||||
#
|
||||
# You must provide three command line arguments:
|
||||
# translate-rss2.py [FROM-LANGUAGE] [TO-LANGUAGE] [RUN-PARALLEL]
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import html
|
||||
import requests
|
||||
import distutils.util
|
||||
import xml.etree.ElementTree as ET
|
||||
from googletrans import Translator
|
||||
|
||||
lang_from = sys.argv[1]
|
||||
lang_to = sys.argv[2]
|
||||
parallel = bool(distutils.util.strtobool(sys.argv[3]))
|
||||
|
||||
if parallel:
|
||||
import asyncio
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
sys.stdin.reconfigure(encoding='utf-8')
|
||||
rss_data = sys.stdin.read()
|
||||
rss_document = ET.fromstring(rss_data)
|
||||
translator = Translator()
|
||||
|
||||
def translate_string(to_translate):
|
||||
try:
|
||||
translated_text = translator.translate(to_translate, src = lang_from, dest = lang_to)
|
||||
|
||||
if not parallel:
|
||||
time.sleep(0.2)
|
||||
|
||||
return translated_text.text
|
||||
except:
|
||||
return to_translate
|
||||
|
||||
def process_article(article):
|
||||
title = article.find("title")
|
||||
title.text = translate_string(title.text)
|
||||
|
||||
contents = article.find("description")
|
||||
contents.text = translate_string(" ".join(contents.itertext()))
|
||||
|
||||
# Translate title.
|
||||
title = rss_document.find(".//channel").find("title")
|
||||
title.text = translate_string(title.text)
|
||||
|
||||
# Translate articles.
|
||||
if parallel:
|
||||
with ThreadPoolExecutor(max_workers = 2) as executor:
|
||||
futures = []
|
||||
for article in rss_document.findall(".//item"):
|
||||
futures.append(executor.submit(process_article, article))
|
||||
for future in futures:
|
||||
future.result()
|
||||
else:
|
||||
for article in rss_document.findall(".//item"):
|
||||
process_article(article)
|
||||
|
||||
print(ET.tostring(rss_document, encoding = "unicode"))
|
67
resources/scripts/scrapers/twitter.py
Executable file
67
resources/scripts/scrapers/twitter.py
Executable file
@ -0,0 +1,67 @@
|
||||
# Generates JSON feed from Twitter timeline URL.
|
||||
#
|
||||
# This script expects one input parameter:
|
||||
# twitter.py [twitter-user-name]
|
||||
#
|
||||
# For example:
|
||||
# twitter.py 'NASA'
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import requests
|
||||
from datetime import datetime
|
||||
|
||||
twitter_url = "https://twitter.com/" + sys.argv[1]
|
||||
twitter_username = twitter_url[twitter_url.rfind("/") + 1:]
|
||||
twitter_bearer = "Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA"
|
||||
|
||||
# Download RAW Twitter HTML data and extract token.
|
||||
url_response = url_response = requests.get(twitter_url)
|
||||
twitter_html = url_response.text
|
||||
twitter_token = re.search("gt=(\d+);", twitter_html).group(1)
|
||||
|
||||
url_response = requests.get("https://twitter.com/i/api/graphql/hc-pka9A7gyS3xODIafnrQ/UserByScreenName?variables=%7B%22screen_name%22%3A%22sportscenter%22%2C%22withHighlightedLabel%22%3Atrue%7D", headers = {
|
||||
"x-guest-token": twitter_token,
|
||||
"Authorization": twitter_bearer
|
||||
})
|
||||
twitter_id = json.loads(url_response.text)["data"]["user"]["rest_id"]
|
||||
|
||||
# Obtain JSON Twitter data with token.
|
||||
twitter_json_url = "https://twitter.com/i/api/2/timeline/profile/{user_id}.json?include_profile_interstitial_type=1&include_blocking=1&include_blocked_by=1&include_followed_by=1&include_want_retweets=1&include_mute_edge=1&include_can_dm=1&include_can_media_tag=1&skip_status=1&cards_platform=Web-12&include_cards=1&include_ext_alt_text=true&include_quote_count=true&include_reply_count=1&tweet_mode=extended&include_entities=true&include_user_entities=true&include_ext_media_color=true&include_ext_media_availability=true&send_error_codes=true&simple_quoted_tweet=true&include_tweet_replies=false&count=50&userId={user_id}&ext=mediaStats&2ChighlightedLabel".format(user_id = twitter_id)
|
||||
|
||||
url_response = requests.get(twitter_json_url, headers = {
|
||||
"x-guest-token": twitter_token,
|
||||
"Authorization": twitter_bearer
|
||||
})
|
||||
|
||||
# Convert to JSON feed.
|
||||
json_data = json.loads(url_response.text)
|
||||
json_root = json_data["globalObjects"]["tweets"]
|
||||
json_feed = "{{\"title\": \"{title}\", \"home_page_url\": \"{url}\", \"items\": [{items}]}}"
|
||||
items = list()
|
||||
|
||||
for ite in json_root:
|
||||
article = json_root[ite]
|
||||
|
||||
if "urls" in article["entities"] and len(article["entities"]["urls"]) > 0:
|
||||
article_url = json.dumps(article["entities"]["urls"][0]["expanded_url"])
|
||||
else:
|
||||
article_url = json.dumps("")
|
||||
|
||||
article_title = json.dumps(article["full_text"][:75] + (article["full_text"][75:] and '...'))
|
||||
article_time = json.dumps(datetime.strptime(article["created_at"], "%a %b %d %H:%M:%S %z %Y").isoformat())
|
||||
article_contents = json.dumps(article["full_text"])
|
||||
|
||||
items.append("{{\"title\": {title}, \"authors\": [{{\"name\": {author}}}], \"content_text\": {text}, \"url\": {url}, \"date_published\": {date}}}".format(
|
||||
title = article_title,
|
||||
text = article_contents,
|
||||
author = json.dumps(twitter_username),
|
||||
url = article_url,
|
||||
date = article_time))
|
||||
|
||||
json_feed = json_feed.format(
|
||||
title = "twitter.com/" + twitter_username,
|
||||
url = twitter_url,
|
||||
items = ", ".join(items))
|
||||
print(json_feed)
|
@ -3,7 +3,7 @@
|
||||
import urllib.request
|
||||
import re
|
||||
import json
|
||||
from html.parser import HTMLParser
|
||||
from newspaper import Article
|
||||
|
||||
url = "https://en.wikipedia.org/wiki/Main_Page"
|
||||
response = urllib.request.urlopen(url)
|
||||
@ -15,21 +15,20 @@ articles_li = re.findall("<li>([\S\n\t\v ]+?)<\/li>", text_li)
|
||||
# Iterate all articles and generate JSON feed entries.
|
||||
wiki_base_url = "https://en.wikipedia.org"
|
||||
|
||||
class HTMLFilter(HTMLParser):
|
||||
text = ""
|
||||
def handle_data(self, data):
|
||||
self.text += data
|
||||
|
||||
json_feed = "{{\"title\": \"Wikipedia - In the news\", \"items\": [{items}]}}"
|
||||
items = list()
|
||||
|
||||
for article in articles_li:
|
||||
article_url = json.dumps(wiki_base_url + re.search("^.+?href=\"(.+?)\"", article).group(1))
|
||||
f = HTMLFilter()
|
||||
f.feed(article)
|
||||
f.text
|
||||
article_title = json.dumps(f.text)
|
||||
article_html = json.dumps("<div>{}</div>".format(article))
|
||||
article_url = wiki_base_url + re.search("^.+?href=\"(.+?)\"", article).group(1)
|
||||
|
||||
f = Article(article_url, keep_article_html = True)
|
||||
f.download()
|
||||
f.parse()
|
||||
|
||||
article_url = json.dumps(article_url)
|
||||
article_title = json.dumps(f.title)
|
||||
article_html = json.dumps(f.article_html)
|
||||
items.append("{{\"title\": {title}, \"content_html\": {html}, \"url\": {url}}}".format(title=article_title,
|
||||
html=article_html,
|
||||
url=article_url))
|
||||
|
@ -1,47 +1,7 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>sql/db_init_mysql.sql</file>
|
||||
<file>sql/db_update_mysql_1_2.sql</file>
|
||||
<file>sql/db_update_mysql_2_3.sql</file>
|
||||
<file>sql/db_update_mysql_3_4.sql</file>
|
||||
<file>sql/db_update_mysql_4_5.sql</file>
|
||||
<file>sql/db_update_mysql_5_6.sql</file>
|
||||
<file>sql/db_update_mysql_6_7.sql</file>
|
||||
<file>sql/db_update_mysql_7_8.sql</file>
|
||||
<file>sql/db_update_mysql_8_9.sql</file>
|
||||
<file>sql/db_update_mysql_9_10.sql</file>
|
||||
<file>sql/db_update_mysql_10_11.sql</file>
|
||||
<file>sql/db_update_mysql_11_12.sql</file>
|
||||
<file>sql/db_update_mysql_12_13.sql</file>
|
||||
<file>sql/db_update_mysql_13_14.sql</file>
|
||||
<file>sql/db_update_mysql_14_15.sql</file>
|
||||
<file>sql/db_update_mysql_15_16.sql</file>
|
||||
<file>sql/db_update_mysql_16_17.sql</file>
|
||||
<file>sql/db_update_mysql_17_18.sql</file>
|
||||
<file>sql/db_update_mysql_18_19.sql</file>
|
||||
<file>sql/db_update_mysql_19_20.sql</file>
|
||||
<file>sql/db_update_mysql_20_21.sql</file>
|
||||
|
||||
<file>sql/db_init_sqlite.sql</file>
|
||||
<file>sql/db_update_sqlite_1_2.sql</file>
|
||||
<file>sql/db_update_sqlite_2_3.sql</file>
|
||||
<file>sql/db_update_sqlite_3_4.sql</file>
|
||||
<file>sql/db_update_sqlite_4_5.sql</file>
|
||||
<file>sql/db_update_sqlite_5_6.sql</file>
|
||||
<file>sql/db_update_sqlite_6_7.sql</file>
|
||||
<file>sql/db_update_sqlite_7_8.sql</file>
|
||||
<file>sql/db_update_sqlite_8_9.sql</file>
|
||||
<file>sql/db_update_sqlite_9_10.sql</file>
|
||||
<file>sql/db_update_sqlite_10_11.sql</file>
|
||||
<file>sql/db_update_sqlite_11_12.sql</file>
|
||||
<file>sql/db_update_sqlite_12_13.sql</file>
|
||||
<file>sql/db_update_sqlite_13_14.sql</file>
|
||||
<file>sql/db_update_sqlite_14_15.sql</file>
|
||||
<file>sql/db_update_sqlite_15_16.sql</file>
|
||||
<file>sql/db_update_sqlite_16_17.sql</file>
|
||||
<file>sql/db_update_sqlite_17_18.sql</file>
|
||||
<file>sql/db_update_sqlite_18_19.sql</file>
|
||||
<file>sql/db_update_sqlite_19_20.sql</file>
|
||||
<file>sql/db_update_sqlite_20_21.sql</file>
|
||||
</qresource>
|
||||
</RCC>
|
@ -4,189 +4,4 @@ CREATE DATABASE IF NOT EXISTS ## CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_c
|
||||
-- !
|
||||
USE ##;
|
||||
-- !
|
||||
DROP TABLE IF EXISTS Information;
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS Information (
|
||||
id INTEGER AUTO_INCREMENT PRIMARY KEY,
|
||||
inf_key TEXT NOT NULL,
|
||||
inf_value TEXT NOT NULL
|
||||
);
|
||||
-- !
|
||||
INSERT INTO Information VALUES (1, 'schema_version', '21');
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS Accounts (
|
||||
id INTEGER AUTO_INCREMENT PRIMARY KEY,
|
||||
type TEXT NOT NULL CHECK (type != ''),
|
||||
proxy_type INTEGER NOT NULL DEFAULT 0 CHECK (proxy_type >= 0),
|
||||
proxy_host TEXT,
|
||||
proxy_port INTEGER,
|
||||
proxy_username TEXT,
|
||||
proxy_password TEXT
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS TtRssAccounts (
|
||||
id INTEGER,
|
||||
username TEXT NOT NULL,
|
||||
password TEXT,
|
||||
auth_protected INTEGER(1) NOT NULL DEFAULT 0 CHECK (auth_protected >= 0 AND auth_protected <= 1),
|
||||
auth_username TEXT,
|
||||
auth_password TEXT,
|
||||
url TEXT NOT NULL,
|
||||
force_update INTEGER(1) NOT NULL DEFAULT 0 CHECK (force_update >= 0 AND force_update <= 1),
|
||||
update_only_unread INTEGER(1) NOT NULL DEFAULT 0 CHECK (update_only_unread >= 0 AND update_only_unread <= 1),
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS OwnCloudAccounts (
|
||||
id INTEGER,
|
||||
username TEXT NOT NULL,
|
||||
password TEXT,
|
||||
url TEXT NOT NULL,
|
||||
force_update INTEGER(1) NOT NULL DEFAULT 0 CHECK (force_update >= 0 AND force_update <= 1),
|
||||
msg_limit INTEGER NOT NULL DEFAULT -1 CHECK (msg_limit >= -1),
|
||||
update_only_unread INTEGER(1) NOT NULL DEFAULT 0 CHECK (update_only_unread >= 0 AND update_only_unread <= 1),
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS InoreaderAccounts (
|
||||
id INTEGER,
|
||||
username TEXT NOT NULL,
|
||||
app_id TEXT,
|
||||
app_key TEXT,
|
||||
redirect_url TEXT,
|
||||
refresh_token TEXT,
|
||||
msg_limit INTEGER NOT NULL DEFAULT -1 CHECK (msg_limit >= -1),
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS GmailAccounts (
|
||||
id INTEGER,
|
||||
username TEXT NOT NULL,
|
||||
app_id TEXT,
|
||||
app_key TEXT,
|
||||
redirect_url TEXT,
|
||||
refresh_token TEXT,
|
||||
msg_limit INTEGER NOT NULL DEFAULT -1 CHECK (msg_limit >= -1),
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS GoogleReaderApiAccounts (
|
||||
id INTEGER,
|
||||
type INTEGER NOT NULL CHECK (type >= 1),
|
||||
username TEXT NOT NULL,
|
||||
password TEXT,
|
||||
url TEXT NOT NULL,
|
||||
msg_limit INTEGER NOT NULL DEFAULT -1 CHECK (msg_limit >= -1),
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS FeedlyAccounts (
|
||||
id INTEGER,
|
||||
username TEXT NOT NULL,
|
||||
developer_access_token TEXT,
|
||||
refresh_token TEXT,
|
||||
msg_limit INTEGER NOT NULL DEFAULT -1 CHECK (msg_limit >= -1),
|
||||
update_only_unread INTEGER(1) NOT NULL DEFAULT 0 CHECK (update_only_unread >= 0 AND update_only_unread <= 1),
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
DROP TABLE IF EXISTS Categories;
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS Categories (
|
||||
id INTEGER AUTO_INCREMENT PRIMARY KEY,
|
||||
parent_id TEXT NOT NULL,
|
||||
title VARCHAR(100) NOT NULL CHECK (title != ''),
|
||||
description TEXT,
|
||||
date_created BIGINT,
|
||||
icon BLOB,
|
||||
account_id INTEGER NOT NULL,
|
||||
custom_id TEXT,
|
||||
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
DROP TABLE IF EXISTS Feeds;
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS Feeds (
|
||||
id INTEGER AUTO_INCREMENT PRIMARY KEY,
|
||||
title TEXT NOT NULL CHECK (title != ''),
|
||||
description TEXT,
|
||||
date_created BIGINT,
|
||||
icon BLOB,
|
||||
category INTEGER NOT NULL CHECK (category >= -1),
|
||||
encoding TEXT,
|
||||
source_type INTEGER,
|
||||
url VARCHAR(1000),
|
||||
post_process TEXT,
|
||||
protected INTEGER(1) NOT NULL CHECK (protected >= 0 AND protected <= 1),
|
||||
username TEXT,
|
||||
password TEXT,
|
||||
update_type INTEGER(1) NOT NULL CHECK (update_type >= 0),
|
||||
update_interval INTEGER NOT NULL DEFAULT 15 CHECK (update_interval >= 1),
|
||||
type INTEGER,
|
||||
account_id INTEGER NOT NULL,
|
||||
custom_id TEXT,
|
||||
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
DROP TABLE IF EXISTS Messages;
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS Messages (
|
||||
id INTEGER AUTO_INCREMENT PRIMARY KEY,
|
||||
is_read INTEGER(1) NOT NULL DEFAULT 0 CHECK (is_read >= 0 AND is_read <= 1),
|
||||
is_deleted INTEGER(1) NOT NULL DEFAULT 0 CHECK (is_deleted >= 0 AND is_deleted <= 1),
|
||||
is_important INTEGER(1) NOT NULL DEFAULT 0 CHECK (is_important >= 0 AND is_important <= 1),
|
||||
feed TEXT NOT NULL,
|
||||
title TEXT NOT NULL CHECK (title != ''),
|
||||
url TEXT,
|
||||
author TEXT,
|
||||
date_created BIGINT NOT NULL CHECK (date_created != 0),
|
||||
contents TEXT,
|
||||
is_pdeleted INTEGER(1) NOT NULL DEFAULT 0 CHECK (is_pdeleted >= 0 AND is_pdeleted <= 1),
|
||||
enclosures TEXT,
|
||||
account_id INTEGER NOT NULL,
|
||||
custom_id TEXT,
|
||||
custom_hash TEXT,
|
||||
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS MessageFilters (
|
||||
id INTEGER AUTO_INCREMENT PRIMARY KEY,
|
||||
name TEXT NOT NULL CHECK (name != ''),
|
||||
script TEXT NOT NULL CHECK (script != '')
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS MessageFiltersInFeeds (
|
||||
filter INTEGER NOT NULL,
|
||||
feed_custom_id TEXT NOT NULL,
|
||||
account_id INTEGER NOT NULL,
|
||||
|
||||
FOREIGN KEY (filter) REFERENCES MessageFilters (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id) ON DELETE CASCADE
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS Labels (
|
||||
id INTEGER AUTO_INCREMENT PRIMARY KEY,
|
||||
name TEXT NOT NULL CHECK (name != ''),
|
||||
color VARCHAR(7),
|
||||
custom_id TEXT,
|
||||
account_id INTEGER NOT NULL,
|
||||
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS LabelsInMessages (
|
||||
label TEXT NOT NULL, /* Custom ID of label. */
|
||||
message TEXT NOT NULL, /* Custom ID of message. */
|
||||
account_id INTEGER NOT NULL,
|
||||
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id) ON DELETE CASCADE
|
||||
);
|
||||
!! db_init_sqlite.sql
|
@ -1,183 +1,100 @@
|
||||
DROP TABLE IF EXISTS Information;
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS Information (
|
||||
id INTEGER PRIMARY KEY,
|
||||
inf_key TEXT NOT NULL,
|
||||
inf_value TEXT NOT NULL
|
||||
CREATE TABLE Information (
|
||||
inf_key TEXT NOT NULL UNIQUE CHECK (inf_key != ''),
|
||||
inf_value TEXT
|
||||
);
|
||||
-- !
|
||||
INSERT INTO Information VALUES (1, 'schema_version', '21');
|
||||
INSERT INTO Information VALUES ('schema_version', '1');
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS Accounts (
|
||||
id INTEGER PRIMARY KEY,
|
||||
type TEXT NOT NULL CHECK (type != ''),
|
||||
proxy_type INTEGER NOT NULL CHECK (proxy_type >= 0) DEFAULT 0,
|
||||
CREATE TABLE Accounts (
|
||||
id $$,
|
||||
type TEXT NOT NULL CHECK (type != ''), /* ID of the account type. Each account defines its own, for example 'ttrss'. */
|
||||
proxy_type INTEGER NOT NULL DEFAULT 0 CHECK (proxy_type >= 0),
|
||||
proxy_host TEXT,
|
||||
proxy_port INTEGER,
|
||||
proxy_username TEXT,
|
||||
proxy_password TEXT
|
||||
proxy_password TEXT,
|
||||
/* Custom column for (serialized) custom account-specific data. */
|
||||
custom_data TEXT
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS TtRssAccounts (
|
||||
id INTEGER,
|
||||
username TEXT NOT NULL,
|
||||
password TEXT,
|
||||
auth_protected INTEGER(1) NOT NULL CHECK (auth_protected >= 0 AND auth_protected <= 1) DEFAULT 0,
|
||||
auth_username TEXT,
|
||||
auth_password TEXT,
|
||||
url TEXT NOT NULL,
|
||||
force_update INTEGER(1) NOT NULL CHECK (force_update >= 0 AND force_update <= 1) DEFAULT 0,
|
||||
update_only_unread INTEGER(1) NOT NULL CHECK (update_only_unread >= 0 AND update_only_unread <= 1) DEFAULT 0,
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS OwnCloudAccounts (
|
||||
id INTEGER,
|
||||
username TEXT NOT NULL,
|
||||
password TEXT,
|
||||
url TEXT NOT NULL,
|
||||
force_update INTEGER(1) NOT NULL CHECK (force_update >= 0 AND force_update <= 1) DEFAULT 0,
|
||||
msg_limit INTEGER NOT NULL DEFAULT -1 CHECK (msg_limit >= -1),
|
||||
update_only_unread INTEGER(1) NOT NULL CHECK (update_only_unread >= 0 AND update_only_unread <= 1) DEFAULT 0,
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS InoreaderAccounts (
|
||||
id INTEGER,
|
||||
username TEXT NOT NULL,
|
||||
app_id TEXT,
|
||||
app_key TEXT,
|
||||
redirect_url TEXT,
|
||||
refresh_token TEXT,
|
||||
msg_limit INTEGER NOT NULL DEFAULT -1 CHECK (msg_limit >= -1),
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS GmailAccounts (
|
||||
id INTEGER,
|
||||
username TEXT NOT NULL,
|
||||
app_id TEXT,
|
||||
app_key TEXT,
|
||||
redirect_url TEXT,
|
||||
refresh_token TEXT,
|
||||
msg_limit INTEGER NOT NULL DEFAULT -1 CHECK (msg_limit >= -1),
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS GoogleReaderApiAccounts (
|
||||
id INTEGER,
|
||||
type INTEGER NOT NULL CHECK (type >= 1),
|
||||
username TEXT NOT NULL,
|
||||
password TEXT,
|
||||
url TEXT NOT NULL,
|
||||
msg_limit INTEGER NOT NULL DEFAULT -1 CHECK (msg_limit >= -1),
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS FeedlyAccounts (
|
||||
id INTEGER,
|
||||
username TEXT NOT NULL,
|
||||
developer_access_token TEXT,
|
||||
refresh_token TEXT,
|
||||
msg_limit INTEGER NOT NULL DEFAULT -1 CHECK (msg_limit >= -1),
|
||||
update_only_unread INTEGER(1) NOT NULL DEFAULT 0 CHECK (update_only_unread >= 0 AND update_only_unread <= 1),
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
DROP TABLE IF EXISTS Categories;
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS Categories (
|
||||
id INTEGER PRIMARY KEY,
|
||||
parent_id INTEGER NOT NULL,
|
||||
CREATE TABLE Categories (
|
||||
id $$,
|
||||
parent_id INTEGER NOT NULL CHECK (parent_id >= -1), /* Root categories contain -1 here. */
|
||||
title TEXT NOT NULL CHECK (title != ''),
|
||||
description TEXT,
|
||||
date_created INTEGER,
|
||||
icon BLOB,
|
||||
date_created BIGINT,
|
||||
icon °°,
|
||||
account_id INTEGER NOT NULL,
|
||||
custom_id TEXT,
|
||||
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id)
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id) ON DELETE CASCADE
|
||||
);
|
||||
-- !
|
||||
DROP TABLE IF EXISTS Feeds;
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS Feeds (
|
||||
id INTEGER PRIMARY KEY,
|
||||
CREATE TABLE Feeds (
|
||||
id $$,
|
||||
title TEXT NOT NULL CHECK (title != ''),
|
||||
description TEXT,
|
||||
date_created INTEGER,
|
||||
icon BLOB,
|
||||
category INTEGER NOT NULL CHECK (category >= -1),
|
||||
encoding TEXT,
|
||||
source_type INTEGER,
|
||||
url TEXT,
|
||||
post_process TEXT,
|
||||
protected INTEGER(1) NOT NULL CHECK (protected >= 0 AND protected <= 1),
|
||||
username TEXT,
|
||||
password TEXT,
|
||||
update_type INTEGER(1) NOT NULL CHECK (update_type >= 0),
|
||||
update_interval INTEGER NOT NULL CHECK (update_interval >= 1) DEFAULT 15,
|
||||
type INTEGER,
|
||||
date_created BIGINT,
|
||||
icon °°,
|
||||
category INTEGER NOT NULL CHECK (category >= -1), /* Physical category ID, also root feeds contain -1 here. */
|
||||
source TEXT,
|
||||
update_type INTEGER NOT NULL CHECK (update_type >= 0),
|
||||
update_interval INTEGER NOT NULL DEFAULT 15 CHECK (update_interval >= 1),
|
||||
account_id INTEGER NOT NULL,
|
||||
custom_id TEXT,
|
||||
custom_id TEXT NOT NULL CHECK (custom_id != ''), /* Custom ID cannot be empty, it must contain either service-specific ID, or Feeds/id. */
|
||||
/* Custom column for (serialized) custom account-specific data. */
|
||||
custom_data TEXT,
|
||||
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id)
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id) ON DELETE CASCADE
|
||||
);
|
||||
-- !
|
||||
DROP TABLE IF EXISTS Messages;
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS Messages (
|
||||
id INTEGER PRIMARY KEY,
|
||||
is_read INTEGER(1) NOT NULL CHECK (is_read >= 0 AND is_read <= 1) DEFAULT 0,
|
||||
is_deleted INTEGER(1) NOT NULL CHECK (is_deleted >= 0 AND is_deleted <= 1) DEFAULT 0,
|
||||
is_important INTEGER(1) NOT NULL CHECK (is_important >= 0 AND is_important <= 1) DEFAULT 0,
|
||||
feed TEXT NOT NULL,
|
||||
CREATE TABLE Messages (
|
||||
id $$,
|
||||
is_read INTEGER NOT NULL DEFAULT 0 CHECK (is_read >= 0 AND is_read <= 1),
|
||||
is_important INTEGER NOT NULL DEFAULT 0 CHECK (is_important >= 0 AND is_important <= 1),
|
||||
is_deleted INTEGER NOT NULL DEFAULT 0 CHECK (is_deleted >= 0 AND is_deleted <= 1),
|
||||
is_pdeleted INTEGER NOT NULL DEFAULT 0 CHECK (is_pdeleted >= 0 AND is_pdeleted <= 1),
|
||||
feed TEXT NOT NULL, /* Points to Feeds/custom_id. */
|
||||
title TEXT NOT NULL CHECK (title != ''),
|
||||
url TEXT,
|
||||
author TEXT,
|
||||
date_created INTEGER NOT NULL CHECK (date_created != 0),
|
||||
date_created BIGINT NOT NULL CHECK (date_created >= 0),
|
||||
contents TEXT,
|
||||
is_pdeleted INTEGER(1) NOT NULL CHECK (is_pdeleted >= 0 AND is_pdeleted <= 1) DEFAULT 0,
|
||||
enclosures TEXT,
|
||||
score REAL NOT NULL DEFAULT 0.0 CHECK (score >= 0.0 AND score <= 100.0),
|
||||
account_id INTEGER NOT NULL,
|
||||
custom_id TEXT,
|
||||
custom_hash TEXT,
|
||||
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id)
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id) ON DELETE CASCADE
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS MessageFilters (
|
||||
id INTEGER PRIMARY KEY,
|
||||
CREATE TABLE MessageFilters (
|
||||
id $$,
|
||||
name TEXT NOT NULL CHECK (name != ''),
|
||||
script TEXT NOT NULL CHECK (script != '')
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS MessageFiltersInFeeds (
|
||||
CREATE TABLE MessageFiltersInFeeds (
|
||||
filter INTEGER NOT NULL,
|
||||
feed_custom_id TEXT NOT NULL,
|
||||
feed_custom_id TEXT NOT NULL, /* Points to Feeds/custom_id. */
|
||||
account_id INTEGER NOT NULL,
|
||||
|
||||
FOREIGN KEY (filter) REFERENCES MessageFilters (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id) ON DELETE CASCADE
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS Labels (
|
||||
id INTEGER PRIMARY KEY,
|
||||
CREATE TABLE Labels (
|
||||
id $$,
|
||||
name TEXT NOT NULL CHECK (name != ''),
|
||||
color VARCHAR(7),
|
||||
custom_id TEXT,
|
||||
account_id INTEGER NOT NULL,
|
||||
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id)
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id) ON DELETE CASCADE
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS LabelsInMessages (
|
||||
CREATE TABLE LabelsInMessages (
|
||||
label TEXT NOT NULL, /* Custom ID of label. */
|
||||
message TEXT NOT NULL, /* Custom ID of message. */
|
||||
account_id INTEGER NOT NULL,
|
||||
|
@ -1,15 +0,0 @@
|
||||
USE ##;
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS GmailAccounts (
|
||||
id INTEGER,
|
||||
username TEXT NOT NULL,
|
||||
app_id TEXT,
|
||||
app_key TEXT,
|
||||
redirect_url TEXT,
|
||||
refresh_token TEXT,
|
||||
msg_limit INTEGER NOT NULL DEFAULT -1 CHECK (msg_limit >= -1),
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '11' WHERE inf_key = 'schema_version';
|
@ -1,6 +0,0 @@
|
||||
USE ##;
|
||||
-- !
|
||||
ALTER TABLE Feeds
|
||||
MODIFY url VARCHAR(1000);
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '12' WHERE inf_key = 'schema_version';
|
@ -1,6 +0,0 @@
|
||||
USE ##;
|
||||
-- !
|
||||
ALTER TABLE TtRssAccounts
|
||||
ADD COLUMN update_only_unread INTEGER(1) NOT NULL DEFAULT 0 CHECK (update_only_unread >= 0 AND update_only_unread <= 1);
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '13' WHERE inf_key = 'schema_version';
|
@ -1,6 +0,0 @@
|
||||
USE ##;
|
||||
-- !
|
||||
ALTER TABLE OwnCloudAccounts
|
||||
ADD COLUMN update_only_unread INTEGER(1) NOT NULL DEFAULT 0 CHECK (update_only_unread >= 0 AND update_only_unread <= 1);
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '14' WHERE inf_key = 'schema_version';
|
@ -1,18 +0,0 @@
|
||||
USE ##;
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS MessageFilters (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL CHECK (name != ''),
|
||||
script TEXT NOT NULL CHECK (script != '')
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS MessageFiltersInFeeds (
|
||||
filter INTEGER NOT NULL,
|
||||
feed_custom_id TEXT NOT NULL,
|
||||
account_id INTEGER NOT NULL,
|
||||
|
||||
FOREIGN KEY (filter) REFERENCES MessageFilters (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id) ON DELETE CASCADE
|
||||
);
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '15' WHERE inf_key = 'schema_version';
|
@ -1,21 +0,0 @@
|
||||
USE ##;
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS Labels (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL CHECK (name != ''),
|
||||
color VARCHAR(7),
|
||||
custom_id TEXT,
|
||||
account_id INTEGER NOT NULL,
|
||||
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS LabelsInMessages (
|
||||
label TEXT NOT NULL, /* Custom ID of label. */
|
||||
message TEXT NOT NULL, /* Custom ID of message. */
|
||||
account_id INTEGER NOT NULL,
|
||||
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id) ON DELETE CASCADE
|
||||
);
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '16' WHERE inf_key = 'schema_version';
|
@ -1,11 +0,0 @@
|
||||
USE ##;
|
||||
-- !
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
-- !
|
||||
ALTER TABLE Labels MODIFY id INTEGER AUTO_INCREMENT;
|
||||
-- !
|
||||
ALTER TABLE MessageFilters MODIFY id INTEGER AUTO_INCREMENT;
|
||||
-- !
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '17' WHERE inf_key = 'schema_version';
|
@ -1,13 +0,0 @@
|
||||
USE ##;
|
||||
-- !
|
||||
ALTER TABLE Accounts ADD COLUMN proxy_type INTEGER NOT NULL DEFAULT 0 CHECK (proxy_type >= 0);
|
||||
-- !
|
||||
ALTER TABLE Accounts ADD COLUMN proxy_host TEXT;
|
||||
-- !
|
||||
ALTER TABLE Accounts ADD COLUMN proxy_port INTEGER;
|
||||
-- !
|
||||
ALTER TABLE Accounts ADD COLUMN proxy_username TEXT;
|
||||
-- !
|
||||
ALTER TABLE Accounts ADD COLUMN proxy_password TEXT;
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '18' WHERE inf_key = 'schema_version';
|
@ -1,12 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS GoogleReaderApiAccounts (
|
||||
id INTEGER,
|
||||
type INTEGER NOT NULL CHECK (type >= 1),
|
||||
username TEXT NOT NULL,
|
||||
password TEXT,
|
||||
url TEXT NOT NULL,
|
||||
msg_limit INTEGER NOT NULL DEFAULT -1 CHECK (msg_limit >= -1),
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '19' WHERE inf_key = 'schema_version';
|
@ -1,33 +0,0 @@
|
||||
CREATE TABLE backup_feeds AS SELECT * FROM Feeds;
|
||||
-- !
|
||||
DROP TABLE Feeds;
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS Feeds (
|
||||
id INTEGER AUTO_INCREMENT PRIMARY KEY,
|
||||
title TEXT NOT NULL CHECK (title != ''),
|
||||
description TEXT,
|
||||
date_created BIGINT,
|
||||
icon BLOB,
|
||||
category INTEGER NOT NULL CHECK (category >= -1),
|
||||
encoding TEXT,
|
||||
source_type INTEGER,
|
||||
url VARCHAR(1000),
|
||||
post_process TEXT,
|
||||
protected INTEGER(1) NOT NULL CHECK (protected >= 0 AND protected <= 1),
|
||||
username TEXT,
|
||||
password TEXT,
|
||||
update_type INTEGER(1) NOT NULL CHECK (update_type >= 0),
|
||||
update_interval INTEGER NOT NULL DEFAULT 15 CHECK (update_interval >= 1),
|
||||
type INTEGER,
|
||||
account_id INTEGER NOT NULL,
|
||||
custom_id TEXT,
|
||||
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
INSERT INTO Feeds (id, title, description, date_created, icon, category, encoding, url, protected, username, password, update_type, update_interval, type, account_id, custom_id)
|
||||
SELECT id, title, description, date_created, icon, category, encoding, url, protected, username, password, update_type, update_interval, type, account_id, custom_id FROM backup_feeds;
|
||||
-- !
|
||||
DROP TABLE backup_feeds;
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '20' WHERE inf_key = 'schema_version';
|
@ -1,4 +0,0 @@
|
||||
ALTER TABLE Messages
|
||||
ADD COLUMN is_pdeleted INTEGER(1) NOT NULL DEFAULT 0 CHECK (is_pdeleted >= 0 AND is_pdeleted <= 1);
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '2' WHERE inf_key = 'schema_version';
|
@ -1,12 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS FeedlyAccounts (
|
||||
id INTEGER,
|
||||
username TEXT NOT NULL,
|
||||
developer_access_token TEXT,
|
||||
refresh_token TEXT,
|
||||
msg_limit INTEGER NOT NULL DEFAULT -1 CHECK (msg_limit >= -1),
|
||||
update_only_unread INTEGER(1) NOT NULL DEFAULT 0 CHECK (update_only_unread >= 0 AND update_only_unread <= 1),
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '21' WHERE inf_key = 'schema_version';
|
@ -1,6 +0,0 @@
|
||||
USE ##;
|
||||
-- !
|
||||
ALTER TABLE Messages
|
||||
ADD COLUMN enclosures TEXT;
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '3' WHERE inf_key = 'schema_version';
|
@ -1,70 +0,0 @@
|
||||
USE ##;
|
||||
-- !
|
||||
CREATE TABLE Accounts (
|
||||
id INTEGER PRIMARY KEY,
|
||||
type TEXT NOT NULL
|
||||
);
|
||||
-- !
|
||||
INSERT INTO Accounts (type) VALUES ('std-rss');
|
||||
-- !
|
||||
DROP TABLE IF EXISTS FeedsData;
|
||||
-- !
|
||||
CREATE TABLE TtRssAccounts (
|
||||
id INTEGER,
|
||||
username TEXT NOT NULL,
|
||||
password TEXT,
|
||||
auth_protected INTEGER(1) NOT NULL DEFAULT 0 CHECK (auth_protected >= 0 AND auth_protected <= 1),
|
||||
auth_username TEXT,
|
||||
auth_password TEXT,
|
||||
url TEXT NOT NULL,
|
||||
force_update INTEGER(1) NOT NULL DEFAULT 0 CHECK (force_update >= 0 AND force_update <= 1),
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
ALTER TABLE Messages
|
||||
ADD COLUMN account_id INTEGER NOT NULL DEFAULT 1;
|
||||
-- !
|
||||
ALTER TABLE Messages
|
||||
ADD COLUMN custom_id TEXT;
|
||||
-- !
|
||||
ALTER TABLE Messages
|
||||
DROP FOREIGN KEY feed;
|
||||
-- !
|
||||
ALTER TABLE Messages
|
||||
MODIFY feed TEXT NOT NULL;
|
||||
-- !
|
||||
ALTER TABLE Messages
|
||||
MODIFY author TEXT;
|
||||
-- !
|
||||
ALTER TABLE Messages
|
||||
MODIFY url TEXT;
|
||||
-- !
|
||||
ALTER TABLE Feeds
|
||||
ADD COLUMN account_id INTEGER NOT NULL DEFAULT 1;
|
||||
-- !
|
||||
ALTER TABLE Feeds
|
||||
ADD COLUMN custom_id TEXT;
|
||||
-- !
|
||||
ALTER TABLE Feeds
|
||||
MODIFY date_created BIGINT;
|
||||
-- !
|
||||
ALTER TABLE Feeds
|
||||
MODIFY encoding TEXT;
|
||||
-- !
|
||||
ALTER TABLE Feeds
|
||||
MODIFY url VARCHAR(100);
|
||||
-- !
|
||||
ALTER TABLE Feeds
|
||||
MODIFY type INTEGER;
|
||||
-- !
|
||||
ALTER TABLE Categories
|
||||
ADD COLUMN account_id INTEGER NOT NULL DEFAULT 1;
|
||||
-- !
|
||||
ALTER TABLE Categories
|
||||
ADD COLUMN custom_id TEXT;
|
||||
-- !
|
||||
ALTER TABLE Categories
|
||||
MODIFY date_created BIGINT;
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '4' WHERE inf_key = 'schema_version';
|
@ -1,25 +0,0 @@
|
||||
USE ##;
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS OwnCloudAccounts (
|
||||
id INTEGER,
|
||||
username TEXT NOT NULL,
|
||||
password TEXT,
|
||||
url TEXT NOT NULL,
|
||||
force_update INTEGER(1) NOT NULL DEFAULT 0 CHECK (force_update >= 0 AND force_update <= 1),
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
UPDATE Categories
|
||||
SET custom_id = (SELECT id FROM Categories t WHERE t.id = Categories.id)
|
||||
WHERE Categories.custom_id IS NULL OR Categories.custom_id = '';
|
||||
-- !
|
||||
UPDATE Feeds
|
||||
SET custom_id = (SELECT id FROM Feeds t WHERE t.id = Feeds.id)
|
||||
WHERE Feeds.custom_id IS NULL OR Feeds.custom_id = '';
|
||||
-- !
|
||||
UPDATE Messages
|
||||
SET custom_id = (SELECT id FROM Messages t WHERE t.id = Messages.id)
|
||||
WHERE Messages.custom_id IS NULL OR Messages.custom_id = '';
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '5' WHERE inf_key = 'schema_version';
|
@ -1,6 +0,0 @@
|
||||
USE ##;
|
||||
-- !
|
||||
ALTER TABLE Messages
|
||||
ADD COLUMN custom_hash TEXT;
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '6' WHERE inf_key = 'schema_version';
|
@ -1,22 +0,0 @@
|
||||
-- !
|
||||
ALTER DATABASE ##
|
||||
CHARACTER SET = utf8mb4
|
||||
COLLATE = utf8mb4_unicode_ci;
|
||||
-- !
|
||||
USE ##;
|
||||
-- !
|
||||
ALTER TABLE Messages
|
||||
CONVERT TO CHARACTER SET utf8mb4
|
||||
COLLATE utf8mb4_unicode_ci;
|
||||
-- !
|
||||
ALTER TABLE Messages
|
||||
CHANGE title title TEXT
|
||||
CHARACTER SET utf8mb4
|
||||
COLLATE utf8mb4_unicode_ci;
|
||||
-- !
|
||||
ALTER TABLE Messages
|
||||
CHANGE contents contents TEXT
|
||||
CHARACTER SET utf8mb4
|
||||
COLLATE utf8mb4_unicode_ci;
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '7' WHERE inf_key = 'schema_version';
|
@ -1,25 +0,0 @@
|
||||
USE ##;
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS Labels (
|
||||
id INTEGER AUTO_INCREMENT PRIMARY KEY,
|
||||
account_id INTEGER NOT NULL,
|
||||
color_fg TEXT NOT NULL,
|
||||
color_bg TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
custom_id TEXT,
|
||||
custom_hash TEXT,
|
||||
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS LabelsInMessages (
|
||||
id INTEGER AUTO_INCREMENT PRIMARY KEY,
|
||||
message_custom_id TEXT NOT NULL,
|
||||
label_custom_id TEXT NOT NULL,
|
||||
account_id INTEGER NOT NULL,
|
||||
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '8' WHERE inf_key = 'schema_version';
|
@ -1,10 +0,0 @@
|
||||
USE ##;
|
||||
-- !
|
||||
ALTER TABLE OwnCloudAccounts
|
||||
ADD COLUMN msg_limit INTEGER NOT NULL DEFAULT -1 CHECK (msg_limit >= -1);
|
||||
-- !
|
||||
DROP TABLE IF EXISTS Labels;
|
||||
-- !
|
||||
DROP TABLE IF EXISTS LabelsInMessages;
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '9' WHERE inf_key = 'schema_version';
|
@ -1,15 +0,0 @@
|
||||
USE ##;
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS InoreaderAccounts (
|
||||
id INTEGER,
|
||||
username TEXT NOT NULL,
|
||||
app_id TEXT,
|
||||
app_key TEXT,
|
||||
redirect_url TEXT,
|
||||
refresh_token TEXT,
|
||||
msg_limit INTEGER NOT NULL DEFAULT -1 CHECK (msg_limit >= -1),
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '10' WHERE inf_key = 'schema_version';
|
@ -1,13 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS GmailAccounts (
|
||||
id INTEGER,
|
||||
username TEXT NOT NULL,
|
||||
app_id TEXT,
|
||||
app_key TEXT,
|
||||
redirect_url TEXT,
|
||||
refresh_token TEXT,
|
||||
msg_limit INTEGER NOT NULL DEFAULT -1 CHECK (msg_limit >= -1),
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '11' WHERE inf_key = 'schema_version';
|
@ -1 +0,0 @@
|
||||
UPDATE Information SET inf_value = '12' WHERE inf_key = 'schema_version';
|
@ -1,24 +0,0 @@
|
||||
CREATE TABLE backup_ta AS SELECT * FROM TtRssAccounts;
|
||||
-- !
|
||||
DROP TABLE TtRssAccounts;
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS TtRssAccounts (
|
||||
id INTEGER,
|
||||
username TEXT NOT NULL,
|
||||
password TEXT,
|
||||
auth_protected INTEGER(1) NOT NULL CHECK (auth_protected >= 0 AND auth_protected <= 1) DEFAULT 0,
|
||||
auth_username TEXT,
|
||||
auth_password TEXT,
|
||||
url TEXT NOT NULL,
|
||||
force_update INTEGER(1) NOT NULL CHECK (force_update >= 0 AND force_update <= 1) DEFAULT 0,
|
||||
update_only_unread INTEGER(1) NOT NULL CHECK (update_only_unread >= 0 AND update_only_unread <= 1) DEFAULT 0,
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
INSERT INTO TtRssAccounts (id, username, password, auth_protected, auth_username, auth_password, url, force_update, update_only_unread)
|
||||
SELECT id, username, password, auth_protected, auth_username, auth_password, url, force_update, 0 FROM backup_ta;
|
||||
-- !
|
||||
DROP TABLE backup_ta;
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '13' WHERE inf_key = 'schema_version';
|
@ -1,22 +0,0 @@
|
||||
CREATE TABLE backup_ta AS SELECT * FROM OwnCloudAccounts;
|
||||
-- !
|
||||
DROP TABLE OwnCloudAccounts;
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS OwnCloudAccounts (
|
||||
id INTEGER,
|
||||
username TEXT NOT NULL,
|
||||
password TEXT,
|
||||
url TEXT NOT NULL,
|
||||
force_update INTEGER(1) NOT NULL CHECK (force_update >= 0 AND force_update <= 1) DEFAULT 0,
|
||||
msg_limit INTEGER NOT NULL DEFAULT -1 CHECK (msg_limit >= -1),
|
||||
update_only_unread INTEGER(1) NOT NULL CHECK (update_only_unread >= 0 AND update_only_unread <= 1) DEFAULT 0,
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
INSERT INTO OwnCloudAccounts (id, username, password, url, force_update, msg_limit, update_only_unread)
|
||||
SELECT id, username, password, url, force_update, msg_limit, 0 FROM backup_ta;
|
||||
-- !
|
||||
DROP TABLE backup_ta;
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '14' WHERE inf_key = 'schema_version';
|
@ -1,16 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS MessageFilters (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL CHECK (name != ''),
|
||||
script TEXT NOT NULL CHECK (script != '')
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS MessageFiltersInFeeds (
|
||||
filter INTEGER NOT NULL,
|
||||
feed_custom_id TEXT NOT NULL,
|
||||
account_id INTEGER NOT NULL,
|
||||
|
||||
FOREIGN KEY (filter) REFERENCES MessageFilters (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id) ON DELETE CASCADE
|
||||
);
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '15' WHERE inf_key = 'schema_version';
|
@ -1,19 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS Labels (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL CHECK (name != ''),
|
||||
color VARCHAR(7),
|
||||
custom_id TEXT,
|
||||
account_id INTEGER NOT NULL,
|
||||
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS LabelsInMessages (
|
||||
label TEXT NOT NULL, /* Custom ID of label. */
|
||||
message TEXT NOT NULL, /* Custom ID of message. */
|
||||
account_id INTEGER NOT NULL,
|
||||
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id) ON DELETE CASCADE
|
||||
);
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '16' WHERE inf_key = 'schema_version';
|
@ -1 +0,0 @@
|
||||
UPDATE Information SET inf_value = '17' WHERE inf_key = 'schema_version';
|
@ -1,23 +0,0 @@
|
||||
CREATE TABLE backup_acc AS SELECT * FROM Accounts;
|
||||
-- !
|
||||
PRAGMA foreign_keys = OFF;
|
||||
-- !
|
||||
DROP TABLE Accounts;
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS Accounts (
|
||||
id INTEGER PRIMARY KEY,
|
||||
type TEXT NOT NULL CHECK (type != ''),
|
||||
proxy_type INTEGER NOT NULL CHECK (proxy_type >= 0) DEFAULT 0,
|
||||
proxy_host TEXT,
|
||||
proxy_port INTEGER,
|
||||
proxy_username TEXT,
|
||||
proxy_password TEXT
|
||||
);
|
||||
-- !
|
||||
INSERT INTO Accounts (id, type) SELECT id, type FROM backup_acc;
|
||||
-- !
|
||||
DROP TABLE backup_acc;
|
||||
-- !
|
||||
PRAGMA foreign_keys = ON;
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '18' WHERE inf_key = 'schema_version';
|
@ -1,12 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS GoogleReaderApiAccounts (
|
||||
id INTEGER,
|
||||
type INTEGER NOT NULL CHECK (type >= 1),
|
||||
username TEXT NOT NULL,
|
||||
password TEXT,
|
||||
url TEXT NOT NULL,
|
||||
msg_limit INTEGER NOT NULL DEFAULT -1 CHECK (msg_limit >= -1),
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '19' WHERE inf_key = 'schema_version';
|
@ -1,33 +0,0 @@
|
||||
CREATE TABLE backup_feeds AS SELECT * FROM Feeds;
|
||||
-- !
|
||||
DROP TABLE Feeds;
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS Feeds (
|
||||
id INTEGER PRIMARY KEY,
|
||||
title TEXT NOT NULL CHECK (title != ''),
|
||||
description TEXT,
|
||||
date_created INTEGER,
|
||||
icon BLOB,
|
||||
category INTEGER NOT NULL CHECK (category >= -1),
|
||||
encoding TEXT,
|
||||
source_type INTEGER,
|
||||
url TEXT,
|
||||
post_process TEXT,
|
||||
protected INTEGER(1) NOT NULL CHECK (protected >= 0 AND protected <= 1),
|
||||
username TEXT,
|
||||
password TEXT,
|
||||
update_type INTEGER(1) NOT NULL CHECK (update_type >= 0),
|
||||
update_interval INTEGER NOT NULL CHECK (update_interval >= 1) DEFAULT 15,
|
||||
type INTEGER,
|
||||
account_id INTEGER NOT NULL,
|
||||
custom_id TEXT,
|
||||
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
INSERT INTO Feeds (id, title, description, date_created, icon, category, encoding, url, protected, username, password, update_type, update_interval, type, account_id, custom_id)
|
||||
SELECT id, title, description, date_created, icon, category, encoding, url, protected, username, password, update_type, update_interval, type, account_id, custom_id FROM backup_feeds;
|
||||
-- !
|
||||
DROP TABLE backup_feeds;
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '20' WHERE inf_key = 'schema_version';
|
@ -1,4 +0,0 @@
|
||||
ALTER TABLE Messages
|
||||
ADD COLUMN is_pdeleted INTEGER(1) NOT NULL DEFAULT 0 CHECK (is_pdeleted >= 0 AND is_pdeleted <= 1);
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '2' WHERE inf_key = 'schema_version';
|
@ -1,12 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS FeedlyAccounts (
|
||||
id INTEGER,
|
||||
username TEXT NOT NULL,
|
||||
developer_access_token TEXT,
|
||||
refresh_token TEXT,
|
||||
msg_limit INTEGER NOT NULL DEFAULT -1 CHECK (msg_limit >= -1),
|
||||
update_only_unread INTEGER(1) NOT NULL DEFAULT 0 CHECK (update_only_unread >= 0 AND update_only_unread <= 1),
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '21' WHERE inf_key = 'schema_version';
|
@ -1,4 +0,0 @@
|
||||
ALTER TABLE Messages
|
||||
ADD COLUMN enclosures TEXT;
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '3' WHERE inf_key = 'schema_version';
|
@ -1,103 +0,0 @@
|
||||
CREATE TABLE Accounts (
|
||||
id INTEGER PRIMARY KEY,
|
||||
type TEXT NOT NULL
|
||||
);
|
||||
-- !
|
||||
INSERT INTO Accounts (type) VALUES ('std-rss');
|
||||
-- !
|
||||
DROP TABLE IF EXISTS FeedsData;
|
||||
-- !
|
||||
CREATE TABLE TtRssAccounts (
|
||||
id INTEGER,
|
||||
username TEXT NOT NULL,
|
||||
password TEXT,
|
||||
auth_protected INTEGER(1) NOT NULL CHECK (auth_protected >= 0 AND auth_protected <= 1) DEFAULT 0,
|
||||
auth_username TEXT,
|
||||
auth_password TEXT,
|
||||
url TEXT NOT NULL,
|
||||
force_update INTEGER(1) NOT NULL CHECK (force_update >= 0 AND force_update <= 1) DEFAULT 0,
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE backup_Messages AS SELECT * FROM Messages;
|
||||
-- !
|
||||
DROP TABLE Messages;
|
||||
-- !
|
||||
CREATE TABLE Messages (
|
||||
id INTEGER PRIMARY KEY,
|
||||
is_read INTEGER(1) NOT NULL CHECK (is_read >= 0 AND is_read <= 1) DEFAULT 0,
|
||||
is_deleted INTEGER(1) NOT NULL CHECK (is_deleted >= 0 AND is_deleted <= 1) DEFAULT 0,
|
||||
is_important INTEGER(1) NOT NULL CHECK (is_important >= 0 AND is_important <= 1) DEFAULT 0,
|
||||
feed TEXT NOT NULL,
|
||||
title TEXT NOT NULL CHECK (title != ''),
|
||||
url TEXT,
|
||||
author TEXT,
|
||||
date_created INTEGER NOT NULL CHECK (date_created != 0),
|
||||
contents TEXT,
|
||||
is_pdeleted INTEGER(1) NOT NULL CHECK (is_pdeleted >= 0 AND is_pdeleted <= 1) DEFAULT 0,
|
||||
enclosures TEXT,
|
||||
account_id INTEGER NOT NULL,
|
||||
custom_id TEXT,
|
||||
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
INSERT INTO Messages (id, is_read, is_deleted, is_important, feed, title, url, author, date_created, contents, is_pdeleted, enclosures, account_id)
|
||||
SELECT id, is_read, is_deleted, is_important, feed, title, url, author, date_created, contents, is_pdeleted, enclosures, 1 FROM backup_Messages;
|
||||
-- !
|
||||
DROP TABLE backup_Messages;
|
||||
-- !
|
||||
CREATE TABLE backup_Feeds AS SELECT * FROM Feeds;
|
||||
-- !
|
||||
DROP TABLE Feeds;
|
||||
-- !
|
||||
CREATE TABLE Feeds (
|
||||
id INTEGER PRIMARY KEY,
|
||||
title TEXT NOT NULL CHECK (title != ''),
|
||||
description TEXT,
|
||||
date_created INTEGER,
|
||||
icon BLOB,
|
||||
category INTEGER NOT NULL CHECK (category >= -1),
|
||||
encoding TEXT,
|
||||
url TEXT,
|
||||
protected INTEGER(1) NOT NULL CHECK (protected >= 0 AND protected <= 1),
|
||||
username TEXT,
|
||||
password TEXT,
|
||||
update_type INTEGER(1) NOT NULL CHECK (update_type >= 0),
|
||||
update_interval INTEGER NOT NULL CHECK (update_interval >= 1) DEFAULT 15,
|
||||
type INTEGER,
|
||||
account_id INTEGER NOT NULL,
|
||||
custom_id TEXT,
|
||||
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
INSERT INTO Feeds (id, title, description, date_created, icon, category, encoding, url, protected, username, password, update_type, update_type, type, account_id)
|
||||
SELECT id, title, description, date_created, icon, category, encoding, url, protected, username, password, update_type, update_type, type, 1 FROM backup_Feeds;
|
||||
-- !
|
||||
DROP TABLE backup_Feeds;
|
||||
-- !
|
||||
CREATE TABLE backup_Categories AS SELECT * FROM Categories;
|
||||
-- !
|
||||
DROP TABLE Categories;
|
||||
-- !
|
||||
CREATE TABLE Categories (
|
||||
id INTEGER PRIMARY KEY,
|
||||
parent_id INTEGER NOT NULL,
|
||||
title TEXT NOT NULL CHECK (title != ''),
|
||||
description TEXT,
|
||||
date_created INTEGER,
|
||||
icon BLOB,
|
||||
account_id INTEGER NOT NULL,
|
||||
custom_id TEXT,
|
||||
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
INSERT INTO Categories (id, parent_id, title, description, date_created, icon, account_id)
|
||||
SELECT id, parent_id, title, description, date_created, icon, 1 FROM backup_Categories;
|
||||
-- !
|
||||
DROP TABLE backup_Categories;
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '4' WHERE inf_key = 'schema_version';
|
@ -1,23 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS OwnCloudAccounts (
|
||||
id INTEGER,
|
||||
username TEXT NOT NULL,
|
||||
password TEXT,
|
||||
url TEXT NOT NULL,
|
||||
force_update INTEGER(1) NOT NULL CHECK (force_update >= 0 AND force_update <= 1) DEFAULT 0,
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
UPDATE Categories
|
||||
SET custom_id = (SELECT id FROM Categories t WHERE t.id = Categories.id)
|
||||
WHERE Categories.custom_id IS NULL OR Categories.custom_id = '';
|
||||
-- !
|
||||
UPDATE Feeds
|
||||
SET custom_id = (SELECT id FROM Feeds t WHERE t.id = Feeds.id)
|
||||
WHERE Feeds.custom_id IS NULL OR Feeds.custom_id = '';
|
||||
-- !
|
||||
UPDATE Messages
|
||||
SET custom_id = (SELECT id FROM Messages t WHERE t.id = Messages.id)
|
||||
WHERE Messages.custom_id IS NULL OR Messages.custom_id = '';
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '5' WHERE inf_key = 'schema_version';
|
@ -1,30 +0,0 @@
|
||||
CREATE TABLE backup_Messages AS SELECT * FROM Messages;
|
||||
-- !
|
||||
DROP TABLE Messages;
|
||||
-- !
|
||||
CREATE TABLE Messages (
|
||||
id INTEGER PRIMARY KEY,
|
||||
is_read INTEGER(1) NOT NULL CHECK (is_read >= 0 AND is_read <= 1) DEFAULT 0,
|
||||
is_deleted INTEGER(1) NOT NULL CHECK (is_deleted >= 0 AND is_deleted <= 1) DEFAULT 0,
|
||||
is_important INTEGER(1) NOT NULL CHECK (is_important >= 0 AND is_important <= 1) DEFAULT 0,
|
||||
feed TEXT NOT NULL,
|
||||
title TEXT NOT NULL CHECK (title != ''),
|
||||
url TEXT,
|
||||
author TEXT,
|
||||
date_created INTEGER NOT NULL CHECK (date_created != 0),
|
||||
contents TEXT,
|
||||
is_pdeleted INTEGER(1) NOT NULL CHECK (is_pdeleted >= 0 AND is_pdeleted <= 1) DEFAULT 0,
|
||||
enclosures TEXT,
|
||||
account_id INTEGER NOT NULL,
|
||||
custom_id TEXT,
|
||||
custom_hash TEXT,
|
||||
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
INSERT INTO Messages (id, is_read, is_deleted, is_important, feed, title, url, author, date_created, contents, is_pdeleted, enclosures, account_id, custom_id)
|
||||
SELECT id, is_read, is_deleted, is_important, feed, title, url, author, date_created, contents, is_pdeleted, enclosures, account_id, custom_id FROM backup_Messages;
|
||||
-- !
|
||||
DROP TABLE backup_Messages;
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '6' WHERE inf_key = 'schema_version';
|
@ -1 +0,0 @@
|
||||
UPDATE Information SET inf_value = '7' WHERE inf_key = 'schema_version';
|
@ -1,25 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS Labels (
|
||||
id INTEGER PRIMARY KEY,
|
||||
account_id INTEGER NOT NULL,
|
||||
color_fg TEXT NOT NULL,
|
||||
color_bg TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
custom_id TEXT,
|
||||
custom_hash TEXT,
|
||||
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS LabelsInMessages (
|
||||
id INTEGER PRIMARY KEY,
|
||||
message_custom_id TEXT NOT NULL,
|
||||
label_custom_id TEXT NOT NULL,
|
||||
account_id INTEGER NOT NULL,
|
||||
|
||||
FOREIGN KEY (message_custom_id) REFERENCES Messages (custom_id),
|
||||
FOREIGN KEY (label_custom_id) REFERENCES Labels (custom_id),
|
||||
FOREIGN KEY (account_id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '8' WHERE inf_key = 'schema_version';
|
@ -1,25 +0,0 @@
|
||||
CREATE TABLE backup_oa AS SELECT * FROM OwnCloudAccounts;
|
||||
-- !
|
||||
DROP TABLE OwnCloudAccounts;
|
||||
-- !
|
||||
CREATE TABLE IF NOT EXISTS OwnCloudAccounts (
|
||||
id INTEGER,
|
||||
username TEXT NOT NULL,
|
||||
password TEXT,
|
||||
url TEXT NOT NULL,
|
||||
force_update INTEGER(1) NOT NULL CHECK (force_update >= 0 AND force_update <= 1) DEFAULT 0,
|
||||
msg_limit INTEGER NOT NULL DEFAULT -1 CHECK (msg_limit >= -1),
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
INSERT INTO OwnCloudAccounts (id, username, password, url, force_update)
|
||||
SELECT id, username, password, url, force_update FROM backup_oa;
|
||||
-- !
|
||||
DROP TABLE backup_oa;
|
||||
-- !
|
||||
DROP TABLE IF EXISTS Labels;
|
||||
-- !
|
||||
DROP TABLE IF EXISTS LabelsInMessages;
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '9' WHERE inf_key = 'schema_version';
|
@ -1,13 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS InoreaderAccounts (
|
||||
id INTEGER,
|
||||
username TEXT NOT NULL,
|
||||
app_id TEXT,
|
||||
app_key TEXT,
|
||||
redirect_url TEXT,
|
||||
refresh_token TEXT,
|
||||
msg_limit INTEGER NOT NULL DEFAULT -1 CHECK (msg_limit >= -1),
|
||||
|
||||
FOREIGN KEY (id) REFERENCES Accounts (id)
|
||||
);
|
||||
-- !
|
||||
UPDATE Information SET inf_value = '10' WHERE inf_key = 'schema_version';
|
1770
src/librssguard/3rd-party/boolinq/boolinq.h
vendored
1770
src/librssguard/3rd-party/boolinq/boolinq.h
vendored
File diff suppressed because it is too large
Load Diff
@ -25,7 +25,7 @@
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "miscellaneous/simplecrypt/simplecrypt.h"
|
||||
#include "3rd-party/sc/simplecrypt.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QCryptographicHash>
|
@ -37,7 +37,7 @@ bool FeedDownloader::isUpdateRunning() const {
|
||||
}
|
||||
|
||||
void FeedDownloader::updateAvailableFeeds() {
|
||||
for (const Feed* feed : m_feeds) {
|
||||
for (const Feed* feed : qAsConst(m_feeds)) {
|
||||
auto* cache = dynamic_cast<CacheForServiceRoot*>(feed->getParentServiceRoot());
|
||||
|
||||
if (cache != nullptr) {
|
||||
@ -114,16 +114,16 @@ void FeedDownloader::stopRunningUpdate() {
|
||||
void FeedDownloader::updateOneFeed(Feed* feed) {
|
||||
qDebugNN << LOGSEC_FEEDDOWNLOADER
|
||||
<< "Downloading new messages for feed ID '"
|
||||
<< feed->customId() << "' URL: '" << feed->url() << "' title: '" << feed->title() << "' in thread: '"
|
||||
<< feed->customId() << "' URL: '" << feed->source() << "' title: '" << feed->title() << "' in thread: '"
|
||||
<< QThread::currentThreadId() << "'.";
|
||||
|
||||
bool error_during_obtaining = false;
|
||||
int acc_id = feed->getParentServiceRoot()->accountId();
|
||||
QElapsedTimer tmr; tmr.start();
|
||||
QList<Message> msgs = feed->obtainNewMessages(&error_during_obtaining);
|
||||
QList<Message> msgs = feed->getParentServiceRoot()->obtainNewMessages({ feed }, &error_during_obtaining);
|
||||
|
||||
qDebugNN << LOGSEC_FEEDDOWNLOADER << "Downloaded " << msgs.size() << " messages for feed ID '"
|
||||
<< feed->customId() << "' URL: '" << feed->url() << "' title: '" << feed->title() << "' in thread: '"
|
||||
<< feed->customId() << "' URL: '" << feed->source() << "' title: '" << feed->title() << "' in thread: '"
|
||||
<< QThread::currentThreadId() << "'. Operation took " << tmr.nsecsElapsed() / 1000 << " microseconds.";
|
||||
|
||||
// Now, sanitize messages (tweak encoding etc.).
|
||||
@ -137,8 +137,8 @@ void FeedDownloader::updateOneFeed(Feed* feed) {
|
||||
|
||||
bool is_main_thread = QThread::currentThread() == qApp->thread();
|
||||
QSqlDatabase database = is_main_thread ?
|
||||
qApp->database()->connection(metaObject()->className()) :
|
||||
qApp->database()->connection(QSL("feed_upd"));
|
||||
qApp->database()->driver()->connection(metaObject()->className()) :
|
||||
qApp->database()->driver()->connection(QSL("feed_upd"));
|
||||
|
||||
// Perform per-message filtering.
|
||||
QJSEngine filter_engine;
|
||||
@ -202,9 +202,9 @@ void FeedDownloader::updateOneFeed(Feed* feed) {
|
||||
}
|
||||
catch (const FilteringException& ex) {
|
||||
qCriticalNN << LOGSEC_FEEDDOWNLOADER
|
||||
<< "Error when evaluating filtering JS function: '"
|
||||
<< ex.message()
|
||||
<< "'. Accepting message.";
|
||||
<< "Error when evaluating filtering JS function: "
|
||||
<< QUOTE_W_SPACE_DOT(ex.message())
|
||||
<< " Accepting message.";
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -226,7 +226,7 @@ void FeedDownloader::updateOneFeed(Feed* feed) {
|
||||
}
|
||||
|
||||
// Process changed labels.
|
||||
for (Label* lbl : msg_backup.m_assignedLabels) {
|
||||
for (Label* lbl : qAsConst(msg_backup.m_assignedLabels)) {
|
||||
if (!msg_orig->m_assignedLabels.contains(lbl)) {
|
||||
// Label is not there anymore, it was deassigned.
|
||||
lbl->deassignFromMessage(*msg_orig);
|
||||
@ -238,7 +238,7 @@ void FeedDownloader::updateOneFeed(Feed* feed) {
|
||||
}
|
||||
}
|
||||
|
||||
for (Label* lbl : msg_orig->m_assignedLabels) {
|
||||
for (Label* lbl : qAsConst(msg_orig->m_assignedLabels)) {
|
||||
if (!msg_backup.m_assignedLabels.contains(lbl)) {
|
||||
// Label is in new message, but is not in old message, it
|
||||
// was newly assigned.
|
||||
@ -290,7 +290,7 @@ void FeedDownloader::updateOneFeed(Feed* feed) {
|
||||
|
||||
// Now make sure, that messages are actually stored to SQL in a locked state.
|
||||
qDebugNN << LOGSEC_FEEDDOWNLOADER << "Saving messages of feed ID '"
|
||||
<< feed->customId() << "' URL: '" << feed->url() << "' title: '" << feed->title() << "' in thread: '"
|
||||
<< feed->customId() << "' URL: '" << feed->source() << "' title: '" << feed->title() << "' in thread: '"
|
||||
<< QThread::currentThreadId() << "'.";
|
||||
|
||||
int updated_messages = feed->updateMessages(msgs, error_during_obtaining);
|
||||
|
@ -3,9 +3,9 @@
|
||||
#include "core/feedsmodel.h"
|
||||
|
||||
#include "3rd-party/boolinq/boolinq.h"
|
||||
#include "database/databasefactory.h"
|
||||
#include "definitions/definitions.h"
|
||||
#include "gui/dialogs/formmain.h"
|
||||
#include "miscellaneous/databasefactory.h"
|
||||
#include "miscellaneous/feedreader.h"
|
||||
#include "miscellaneous/iconfactory.h"
|
||||
#include "miscellaneous/textfactory.h"
|
||||
@ -58,7 +58,7 @@ FeedsModel::~FeedsModel() {
|
||||
QMimeData* FeedsModel::mimeData(const QModelIndexList& indexes) const {
|
||||
auto* mime_data = new QMimeData();
|
||||
QByteArray encoded_data;
|
||||
QDataStream stream(&encoded_data, QIODevice::WriteOnly);
|
||||
QDataStream stream(&encoded_data, QIODevice::OpenModeFlag::WriteOnly);
|
||||
|
||||
for (const QModelIndex& index : indexes) {
|
||||
if (index.column() != 0) {
|
||||
@ -99,7 +99,7 @@ bool FeedsModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
QDataStream stream(&dragged_items_data, QIODevice::ReadOnly);
|
||||
QDataStream stream(&dragged_items_data, QIODevice::OpenModeFlag::ReadOnly);
|
||||
|
||||
while (!stream.atEnd()) {
|
||||
quintptr pointer_to_item; stream >> pointer_to_item;
|
||||
@ -261,6 +261,11 @@ void FeedsModel::removeItem(RootItem* deleting_item) {
|
||||
beginRemoveRows(parent_index, index.row(), index.row());
|
||||
parent_item->removeChild(deleting_item);
|
||||
endRemoveRows();
|
||||
|
||||
if (deleting_item->kind() != RootItem::Kind::ServiceRoot) {
|
||||
deleting_item->getParentServiceRoot()->updateCounts(true);
|
||||
}
|
||||
|
||||
deleting_item->deleteLater();
|
||||
notifyWithCounts();
|
||||
}
|
||||
@ -292,8 +297,9 @@ void FeedsModel::reassignNodeToNewParent(RootItem* original_node, RootItem* new_
|
||||
|
||||
QList<ServiceRoot*>FeedsModel::serviceRoots() const {
|
||||
QList<ServiceRoot*> roots;
|
||||
auto ch = m_rootItem->childItems();
|
||||
|
||||
for (RootItem* root : m_rootItem->childItems()) {
|
||||
for (RootItem* root : qAsConst(ch)) {
|
||||
if (root->kind() == RootItem::Kind::ServiceRoot) {
|
||||
roots.append(root->toServiceRoot());
|
||||
}
|
||||
@ -304,8 +310,9 @@ QList<ServiceRoot*>FeedsModel::serviceRoots() const {
|
||||
|
||||
QList<Feed*>FeedsModel::feedsForScheduledUpdate(bool auto_update_now) {
|
||||
QList<Feed*>feeds_for_update;
|
||||
auto stf = m_rootItem->getSubTreeFeeds();
|
||||
|
||||
for (Feed* feed : m_rootItem->getSubTreeFeeds()) {
|
||||
for (Feed* feed : qAsConst(stf)) {
|
||||
switch (feed->autoUpdateType()) {
|
||||
case Feed::AutoUpdateType::DontAutoUpdate:
|
||||
|
||||
@ -481,6 +488,7 @@ bool FeedsModel::addServiceAccount(ServiceRoot* root, bool freshly_activated) {
|
||||
connect(root, &ServiceRoot::reloadMessageListRequested, this, &FeedsModel::reloadMessageListRequested);
|
||||
connect(root, &ServiceRoot::itemExpandRequested, this, &FeedsModel::itemExpandRequested);
|
||||
connect(root, &ServiceRoot::itemExpandStateSaveRequested, this, &FeedsModel::itemExpandStateSaveRequested);
|
||||
|
||||
root->start(freshly_activated);
|
||||
|
||||
return true;
|
||||
@ -488,8 +496,9 @@ bool FeedsModel::addServiceAccount(ServiceRoot* root, bool freshly_activated) {
|
||||
|
||||
bool FeedsModel::restoreAllBins() {
|
||||
bool result = true;
|
||||
auto srts = serviceRoots();
|
||||
|
||||
for (ServiceRoot* root : serviceRoots()) {
|
||||
for (ServiceRoot* root : qAsConst(srts)) {
|
||||
RecycleBin* bin_of_root = root->recycleBin();
|
||||
|
||||
if (bin_of_root != nullptr) {
|
||||
@ -502,8 +511,9 @@ bool FeedsModel::restoreAllBins() {
|
||||
|
||||
bool FeedsModel::emptyAllBins() {
|
||||
bool result = true;
|
||||
auto srts = serviceRoots();
|
||||
|
||||
for (ServiceRoot* root : serviceRoots()) {
|
||||
for (ServiceRoot* root : qAsConst(srts)) {
|
||||
RecycleBin* bin_of_root = root->recycleBin();
|
||||
|
||||
if (bin_of_root != nullptr) {
|
||||
@ -515,8 +525,10 @@ bool FeedsModel::emptyAllBins() {
|
||||
}
|
||||
|
||||
void FeedsModel::loadActivatedServiceAccounts() {
|
||||
auto serv = qApp->feedReader()->feedServices();
|
||||
|
||||
// Iterate all globally available feed "service plugins".
|
||||
for (const ServiceEntryPoint* entry_point : qApp->feedReader()->feedServices()) {
|
||||
for (const ServiceEntryPoint* entry_point : qAsConst(serv)) {
|
||||
// Load all stored root nodes from the entry point and add those to the model.
|
||||
QList<ServiceRoot*> roots = entry_point->initializeSubtree();
|
||||
|
||||
@ -526,14 +538,16 @@ void FeedsModel::loadActivatedServiceAccounts() {
|
||||
}
|
||||
|
||||
if (serviceRoots().isEmpty()) {
|
||||
QTimer::singleShot(3000, qApp->mainForm(), []() {
|
||||
QTimer::singleShot(2000, qApp->mainForm(), []() {
|
||||
qApp->mainForm()->showAddAccountDialog();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void FeedsModel::stopServiceAccounts() {
|
||||
for (ServiceRoot* account : serviceRoots()) {
|
||||
auto serv = serviceRoots();
|
||||
|
||||
for (ServiceRoot* account : qAsConst(serv)) {
|
||||
account->stop();
|
||||
}
|
||||
}
|
||||
@ -549,3 +563,18 @@ bool FeedsModel::markItemRead(RootItem* item, RootItem::ReadStatus read) {
|
||||
bool FeedsModel::markItemCleared(RootItem* item, bool clean_read_only) {
|
||||
return item->cleanMessages(clean_read_only);
|
||||
}
|
||||
|
||||
QVariant FeedsModel::data(const QModelIndex& index, int role) const {
|
||||
switch (role) {
|
||||
case Qt::ItemDataRole::FontRole:
|
||||
return itemForIndex(index)->countOfUnreadMessages() > 0 ? m_boldFont : m_normalFont;
|
||||
|
||||
case Qt::ItemDataRole::ToolTipRole:
|
||||
if (!qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::EnableTooltipsFeedsMessages)).toBool()) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
default:
|
||||
return itemForIndex(index)->data(index.column(), role);;
|
||||
}
|
||||
}
|
||||
|
@ -156,14 +156,4 @@ class RSSGUARD_DLLSPEC FeedsModel : public QAbstractItemModel {
|
||||
QFont m_boldFont;
|
||||
};
|
||||
|
||||
inline QVariant FeedsModel::data(const QModelIndex& index, int role) const {
|
||||
switch (role) {
|
||||
case Qt::FontRole:
|
||||
return itemForIndex(index)->countOfUnreadMessages() > 0 ? m_boldFont : m_normalFont;
|
||||
|
||||
default:
|
||||
return itemForIndex(index)->data(index.column(), role);;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // FEEDSMODEL_H
|
||||
|
@ -13,11 +13,17 @@
|
||||
FeedsProxyModel::FeedsProxyModel(FeedsModel* source_model, QObject* parent)
|
||||
: QSortFilterProxyModel(parent), m_sourceModel(source_model), m_selectedItem(nullptr), m_showUnreadOnly(false) {
|
||||
setObjectName(QSL("FeedsProxyModel"));
|
||||
setSortRole(Qt::EditRole);
|
||||
setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||
setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
setFilterKeyColumn(-1);
|
||||
setFilterRole(Qt::EditRole);
|
||||
|
||||
setSortRole(Qt::ItemDataRole::EditRole);
|
||||
setSortCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive);
|
||||
|
||||
#if QT_VERSION >= 0x050A00 // Qt >= 5.10.0
|
||||
setRecursiveFilteringEnabled(true);
|
||||
#endif
|
||||
|
||||
setFilterKeyColumn(FDS_MODEL_TITLE_INDEX);
|
||||
setFilterRole(LOWER_TITLE_ROLE);
|
||||
|
||||
setDynamicSortFilter(true);
|
||||
setSourceModel(m_sourceModel);
|
||||
|
||||
@ -30,6 +36,7 @@ FeedsProxyModel::FeedsProxyModel(FeedsModel* source_model, QObject* parent)
|
||||
RootItem::Kind::Feed,
|
||||
RootItem::Kind::Labels,
|
||||
RootItem::Kind::Important,
|
||||
RootItem::Kind::Unread,
|
||||
RootItem::Kind::Bin
|
||||
};
|
||||
}
|
||||
@ -41,9 +48,9 @@ FeedsProxyModel::~FeedsProxyModel() {
|
||||
QModelIndexList FeedsProxyModel::match(const QModelIndex& start, int role, const QVariant& value, int hits, Qt::MatchFlags flags) const {
|
||||
QModelIndexList result;
|
||||
const int match_type = flags & 0x0F;
|
||||
const Qt::CaseSensitivity cs = Qt::CaseInsensitive;
|
||||
const bool recurse = (flags& Qt::MatchRecursive) > 0;
|
||||
const bool wrap = (flags& Qt::MatchWrap) > 0;
|
||||
const Qt::CaseSensitivity cs = Qt::CaseSensitivity::CaseInsensitive;
|
||||
const bool recurse = (flags& Qt::MatchFlag::MatchRecursive) > 0;
|
||||
const bool wrap = (flags& Qt::MatchFlag::MatchWrap) > 0;
|
||||
const bool all_hits = (hits == -1);
|
||||
QString entered_text;
|
||||
const QModelIndex p = parent(start);
|
||||
|
72
src/librssguard/core/filterutils.cpp
Executable file
72
src/librssguard/core/filterutils.cpp
Executable file
@ -0,0 +1,72 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#include "core/filterutils.h"
|
||||
|
||||
#include "definitions/definitions.h"
|
||||
|
||||
#include <QDomDocument>
|
||||
#include <QHostInfo>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
|
||||
FilterUtils::FilterUtils(QObject* parent) : QObject(parent) {}
|
||||
|
||||
FilterUtils::~FilterUtils() {
|
||||
qDebugNN << "Destroying FilterUtils instance.";
|
||||
}
|
||||
|
||||
QString FilterUtils::hostname() const {
|
||||
return QHostInfo::localHostName();
|
||||
}
|
||||
|
||||
QString jsonEscapeString(const QString& s) {
|
||||
return QString(QJsonDocument(QJsonArray() << s).toJson(QJsonDocument::JsonFormat::Compact)).mid(2).chopped(2);
|
||||
}
|
||||
|
||||
QString jsonProcessXmlElement(const QDomElement& elem) {
|
||||
QStringList attrs;
|
||||
|
||||
for (int i = 0; i < elem.attributes().size(); i++) {
|
||||
attrs << QSL("\"%1\": \"%2\"").arg(jsonEscapeString(elem.attributes().item(i).toAttr().name()),
|
||||
jsonEscapeString(elem.attributes().item(i).toAttr().value()));
|
||||
}
|
||||
|
||||
QStringList elems;
|
||||
|
||||
for (int i = 0; i < elem.childNodes().size(); i++) {
|
||||
if (!elem.childNodes().at(i).isElement()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
elems << QSL("\"%1\": %2").arg(elem.childNodes().at(i).toElement().tagName(),
|
||||
jsonProcessXmlElement(elem.childNodes().at(i).toElement()));
|
||||
}
|
||||
|
||||
if (attrs.isEmpty()) {
|
||||
if (elems.isEmpty()) {
|
||||
return QSL("\"%1\"").arg(jsonEscapeString(elem.text()));
|
||||
}
|
||||
else {
|
||||
return QSL("{%1}").arg(elems.join(QSL(",\n")));
|
||||
}
|
||||
}
|
||||
else if (elems.isEmpty()) {
|
||||
return QSL("{%1, \"__text\": \"%2\"}").arg(attrs.join(QSL(",\n")),
|
||||
jsonEscapeString(elem.text()));
|
||||
}
|
||||
else {
|
||||
return QSL("{%1, %2}").arg(attrs.join(QSL(",\n")),
|
||||
elems.join(QSL(",\n")));
|
||||
}
|
||||
}
|
||||
|
||||
QString FilterUtils::fromXmlToJson(const QString& xml) const {
|
||||
QDomDocument xml_doc;
|
||||
|
||||
xml_doc.setContent(xml);
|
||||
|
||||
QString json = QSL("%1").arg(jsonProcessXmlElement(xml_doc.documentElement()));
|
||||
|
||||
return QSL("{\"%1\": %2}").arg(xml_doc.documentElement().tagName(),
|
||||
json);
|
||||
}
|
23
src/librssguard/core/filterutils.h
Executable file
23
src/librssguard/core/filterutils.h
Executable file
@ -0,0 +1,23 @@
|
||||
// For license of this file, see <project-root-folder>/LICENSE.md.
|
||||
|
||||
#ifndef FILTERUTILS_H
|
||||
#define FILTERUTILS_H
|
||||
|
||||
#include <QDomElement>
|
||||
#include <QObject>
|
||||
|
||||
class FilterUtils : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FilterUtils(QObject* parent = nullptr);
|
||||
~FilterUtils();
|
||||
|
||||
// Returns hostname or empty string if failed.
|
||||
Q_INVOKABLE QString hostname() const;
|
||||
|
||||
// Converts XML -> JSON or returns empty string if failed.
|
||||
Q_INVOKABLE QString fromXmlToJson(const QString& xml) const;
|
||||
};
|
||||
|
||||
#endif // FILTERUTILS_H
|
@ -20,13 +20,14 @@ Enclosure::Enclosure(QString url, QString mime) : m_url(std::move(url)), m_mimeT
|
||||
|
||||
QList<Enclosure> Enclosures::decodeEnclosuresFromString(const QString& enclosures_data) {
|
||||
QList<Enclosure> enclosures;
|
||||
|
||||
for (const QString& single_enclosure : enclosures_data.split(ENCLOSURES_OUTER_SEPARATOR,
|
||||
auto enc = enclosures_data.split(ENCLOSURES_OUTER_SEPARATOR,
|
||||
#if QT_VERSION >= 0x050F00 // Qt >= 5.15.0
|
||||
Qt::SplitBehaviorFlags::SkipEmptyParts)) {
|
||||
Qt::SplitBehaviorFlags::SkipEmptyParts);
|
||||
#else
|
||||
QString::SkipEmptyParts)) {
|
||||
QString::SplitBehavior::SkipEmptyParts);
|
||||
#endif
|
||||
|
||||
for (const QString& single_enclosure : qAsConst(enc)) {
|
||||
Enclosure enclosure;
|
||||
|
||||
if (single_enclosure.contains(ECNLOSURES_INNER_SEPARATOR)) {
|
||||
@ -63,9 +64,10 @@ QString Enclosures::encodeEnclosuresToString(const QList<Enclosure>& enclosures)
|
||||
}
|
||||
|
||||
Message::Message() {
|
||||
m_title = m_url = m_author = m_contents = m_feedId = m_customId = m_customHash = "";
|
||||
m_title = m_url = m_author = m_contents = m_rawContents = m_feedId = m_customId = m_customHash = "";
|
||||
m_enclosures = QList<Enclosure>();
|
||||
m_accountId = m_id = 0;
|
||||
m_score = 0.0;
|
||||
m_isRead = m_isImportant = m_isDeleted = false;
|
||||
m_assignedLabels = QList<Label*>();
|
||||
}
|
||||
@ -106,6 +108,7 @@ Message Message::fromSqlRecord(const QSqlRecord& record, bool* result) {
|
||||
message.m_created = TextFactory::parseDateTime(record.value(MSG_DB_DCREATED_INDEX).value<qint64>());
|
||||
message.m_contents = record.value(MSG_DB_CONTENTS_INDEX).toString();
|
||||
message.m_enclosures = Enclosures::decodeEnclosuresFromString(record.value(MSG_DB_ENCLOSURES_INDEX).toString());
|
||||
message.m_score = record.value(MSG_DB_SCORE_INDEX).toDouble();
|
||||
message.m_accountId = record.value(MSG_DB_ACCOUNT_ID_INDEX).toInt();
|
||||
message.m_customId = record.value(MSG_DB_CUSTOM_ID_INDEX).toString();
|
||||
message.m_customHash = record.value(MSG_DB_CUSTOM_HASH_INDEX).toString();
|
||||
@ -125,31 +128,34 @@ QDataStream& operator<<(QDataStream& out, const Message& my_obj) {
|
||||
<< my_obj.m_id
|
||||
<< my_obj.m_isImportant
|
||||
<< my_obj.m_isRead
|
||||
<< my_obj.m_isDeleted;
|
||||
<< my_obj.m_isDeleted
|
||||
<< my_obj.m_score;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
QDataStream& operator>>(QDataStream& in, Message& my_obj) {
|
||||
int accountId;
|
||||
QString customHash;
|
||||
QString customId;
|
||||
QString feedId;
|
||||
int account_id;
|
||||
QString custom_hash;
|
||||
QString custom_id;
|
||||
QString feed_id;
|
||||
int id;
|
||||
bool isImportant;
|
||||
bool isRead;
|
||||
bool isDeleted;
|
||||
bool is_important;
|
||||
bool is_read;
|
||||
bool is_deleted;
|
||||
double score;
|
||||
|
||||
in >> accountId >> customHash >> customId >> feedId >> id >> isImportant >> isRead >> isDeleted;
|
||||
in >> account_id >> custom_hash >> custom_id >> feed_id >> id >> is_important >> is_read >> is_deleted >> score;
|
||||
|
||||
my_obj.m_accountId = accountId;
|
||||
my_obj.m_customHash = customHash;
|
||||
my_obj.m_customId = customId;
|
||||
my_obj.m_feedId = feedId;
|
||||
my_obj.m_accountId = account_id;
|
||||
my_obj.m_customHash = custom_hash;
|
||||
my_obj.m_customId = custom_id;
|
||||
my_obj.m_feedId = feed_id;
|
||||
my_obj.m_id = id;
|
||||
my_obj.m_isImportant = isImportant;
|
||||
my_obj.m_isRead = isRead;
|
||||
my_obj.m_isDeleted = isDeleted;
|
||||
my_obj.m_isImportant = is_important;
|
||||
my_obj.m_isRead = is_read;
|
||||
my_obj.m_isDeleted = is_deleted;
|
||||
my_obj.m_score = score;
|
||||
|
||||
return in;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user