reader 3.23__tar.gz → 3.25__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {reader-3.23 → reader-3.25}/CHANGES.rst +69 -0
- {reader-3.23/src/reader.egg-info → reader-3.25}/PKG-INFO +4 -3
- {reader-3.23 → reader-3.25}/README.rst +1 -0
- {reader-3.23 → reader-3.25}/docs/api.rst +14 -0
- {reader-3.23 → reader-3.25}/docs/app.rst +11 -0
- {reader-3.23 → reader-3.25}/docs/conf.py +4 -1
- {reader-3.23 → reader-3.25}/docs/guide.rst +31 -2
- {reader-3.23 → reader-3.25}/docs/install.rst +10 -7
- {reader-3.23 → reader-3.25}/docs/internal.rst +29 -22
- {reader-3.23 → reader-3.25}/docs/plugins.rst +1 -0
- reader-3.25/docs/screenshots/empty.png +0 -0
- reader-3.25/docs/screenshots/help.png +0 -0
- reader-3.25/docs/screenshots/import-select.png +0 -0
- {reader-3.23 → reader-3.25}/pyproject.toml +8 -3
- {reader-3.23 → reader-3.25}/src/reader/__init__.py +4 -1
- {reader-3.23 → reader-3.25}/src/reader/_app/__init__.py +49 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/legacy/templates/entry.html +9 -1
- {reader-3.23 → reader-3.25}/src/reader/_app/static/style.css +6 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/templates/add_feed.html +10 -1
- {reader-3.23 → reader-3.25}/src/reader/_app/templates/change_feed_title.html +1 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/templates/delete_feed.html +1 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/templates/entries.html +74 -18
- {reader-3.23 → reader-3.25}/src/reader/_app/templates/entry.html +2 -2
- {reader-3.23 → reader-3.25}/src/reader/_app/templates/feeds.html +5 -1
- reader-3.25/src/reader/_app/templates/help.html +137 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/templates/import_feeds.html +12 -2
- {reader-3.23 → reader-3.25}/src/reader/_app/templates/layout_base.html +11 -1
- {reader-3.23 → reader-3.25}/src/reader/_app/templates/macros.html +49 -3
- {reader-3.23 → reader-3.25}/src/reader/_cli.py +17 -6
- {reader-3.23 → reader-3.25}/src/reader/_config_utils.py +7 -3
- reader-3.25/src/reader/_logging.py +80 -0
- reader-3.25/src/reader/_parser/__init__.py +976 -0
- {reader-3.23 → reader-3.25}/src/reader/_parser/feedparser.py +100 -13
- reader-3.25/src/reader/_parser/http.py +299 -0
- {reader-3.23 → reader-3.25}/src/reader/_parser/jsonfeed.py +35 -33
- {reader-3.23 → reader-3.25}/src/reader/_plugins/legacy/enclosure_tags.py +1 -1
- {reader-3.23 → reader-3.25}/src/reader/_plugins/legacy/preview_feed_list.py +13 -8
- {reader-3.23 → reader-3.25}/src/reader/_plugins/sqlite_releases.py +4 -2
- {reader-3.23 → reader-3.25}/src/reader/_storage/_entries.py +56 -19
- {reader-3.23 → reader-3.25}/src/reader/_storage/_feeds.py +65 -39
- {reader-3.23 → reader-3.25}/src/reader/_storage/_html_utils.py +9 -4
- {reader-3.23 → reader-3.25}/src/reader/_storage/_schema.py +45 -1
- {reader-3.23 → reader-3.25}/src/reader/_storage/_search.py +3 -13
- {reader-3.23 → reader-3.25}/src/reader/_storage/_sqlite_utils.py +2 -0
- {reader-3.23 → reader-3.25}/src/reader/_types.py +15 -99
- reader-3.23/src/reader/_update.py → reader-3.25/src/reader/_update/__init__.py +95 -196
- reader-3.25/src/reader/_update/base.py +206 -0
- reader-3.25/src/reader/_update/hooks.py +119 -0
- {reader-3.23 → reader-3.25}/src/reader/_utils.py +25 -76
- {reader-3.23 → reader-3.25}/src/reader/core.py +26 -51
- reader-3.25/src/reader/discover.py +143 -0
- reader-3.25/src/reader/plugins/autodiscover.py +91 -0
- {reader-3.23 → reader-3.25}/src/reader/plugins/entry_dedupe.py +29 -40
- {reader-3.23 → reader-3.25}/src/reader/plugins/mark_as_read.py +5 -7
- {reader-3.23 → reader-3.25}/src/reader/plugins/readtime.py +8 -10
- {reader-3.23 → reader-3.25}/src/reader/plugins/ua_fallback.py +7 -12
- {reader-3.23 → reader-3.25}/src/reader/types.py +64 -9
- {reader-3.23 → reader-3.25/src/reader.egg-info}/PKG-INFO +4 -3
- {reader-3.23 → reader-3.25}/src/reader.egg-info/SOURCES.txt +13 -4
- {reader-3.23 → reader-3.25}/src/reader.egg-info/requires.txt +2 -2
- {reader-3.23 → reader-3.25}/tests/conftest.py +3 -39
- {reader-3.23 → reader-3.25}/tests/data/empty.atom.py +1 -0
- {reader-3.23 → reader-3.25}/tests/data/full.atom.py +4 -3
- {reader-3.23 → reader-3.25}/tests/data/full.json.py +7 -3
- {reader-3.23 → reader-3.25}/tests/data/full.rss.py +3 -2
- {reader-3.23 → reader-3.25}/tests/data/invalid.json.py +3 -2
- {reader-3.23 → reader-3.25}/tests/fakeparser.py +1 -2
- reader-3.25/tests/fixtures.py +48 -0
- {reader-3.23 → reader-3.25}/tests/test__types.py +14 -5
- {reader-3.23 → reader-3.25}/tests/test__utils.py +0 -19
- {reader-3.23 → reader-3.25}/tests/test_cli.py +22 -5
- {reader-3.23 → reader-3.25}/tests/test_config_utils.py +8 -0
- reader-3.25/tests/test_discover.py +48 -0
- {reader-3.23 → reader-3.25}/tests/test_html_utils.py +1 -1
- {reader-3.23 → reader-3.25}/tests/test_lazy_imports.py +1 -3
- reader-3.25/tests/test_logging.py +41 -0
- {reader-3.23 → reader-3.25}/tests/test_parser.py +235 -30
- reader-3.25/tests/test_plugins_autodiscover.py +69 -0
- {reader-3.23 → reader-3.25}/tests/test_plugins_ua_fallback.py +1 -1
- {reader-3.23 → reader-3.25}/tests/test_reader.py +18 -15
- {reader-3.23 → reader-3.25}/tests/test_reader_hooks.py +18 -1
- {reader-3.23 → reader-3.25}/tests/test_reader_search.py +7 -2
- {reader-3.23 → reader-3.25}/tests/test_reader_update.py +11 -9
- {reader-3.23 → reader-3.25}/tests/test_storage.py +37 -0
- {reader-3.23 → reader-3.25}/tests/test_types.py +33 -0
- reader-3.23/docs/screenshots/import-select.png +0 -0
- reader-3.23/src/reader/_parser/__init__.py +0 -502
- reader-3.23/src/reader/_parser/_lazy.py +0 -542
- reader-3.23/src/reader/_parser/http.py +0 -107
- reader-3.23/src/reader/_parser/requests/__init__.py +0 -176
- reader-3.23/src/reader/_parser/requests/_lazy.py +0 -162
- {reader-3.23 → reader-3.25}/LICENSE +0 -0
- {reader-3.23 → reader-3.25}/MANIFEST.in +0 -0
- {reader-3.23 → reader-3.25}/docs/changelog.rst +0 -0
- {reader-3.23 → reader-3.25}/docs/cli.rst +0 -0
- {reader-3.23 → reader-3.25}/docs/config.rst +0 -0
- {reader-3.23 → reader-3.25}/docs/contributing.rst +0 -0
- {reader-3.23 → reader-3.25}/docs/dev.rst +0 -0
- {reader-3.23 → reader-3.25}/docs/index.rst +0 -0
- {reader-3.23 → reader-3.25}/docs/screenshots/entries-filters-light.png +0 -0
- {reader-3.23 → reader-3.25}/docs/screenshots/entries.png +0 -0
- {reader-3.23 → reader-3.25}/docs/screenshots/entry-image-light.png +0 -0
- {reader-3.23 → reader-3.25}/docs/screenshots/entry.png +0 -0
- {reader-3.23 → reader-3.25}/docs/screenshots/feed.png +0 -0
- {reader-3.23 → reader-3.25}/docs/screenshots/feeds.png +0 -0
- {reader-3.23 → reader-3.25}/docs/screenshots/import-result.png +0 -0
- {reader-3.23 → reader-3.25}/docs/tutorial.rst +0 -0
- {reader-3.23 → reader-3.25}/docs/why.rst +0 -0
- {reader-3.23 → reader-3.25}/examples/config.toml +0 -0
- {reader-3.23 → reader-3.25}/examples/custom_headers.py +0 -0
- {reader-3.23 → reader-3.25}/examples/feed_slugs.py +0 -0
- {reader-3.23 → reader-3.25}/examples/parser_only.py +0 -0
- {reader-3.23 → reader-3.25}/examples/podcast.py +0 -0
- {reader-3.23 → reader-3.25}/examples/terminal.py +0 -0
- {reader-3.23 → reader-3.25}/run.sh +0 -0
- {reader-3.23 → reader-3.25}/scripts/backup.sh +0 -0
- {reader-3.23 → reader-3.25}/scripts/bench.py +0 -0
- {reader-3.23 → reader-3.25}/scripts/debug_storage_stats.py +0 -0
- {reader-3.23 → reader-3.25}/scripts/entry_dedupe_backtest.py +0 -0
- {reader-3.23 → reader-3.25}/scripts/jscontrols.html +0 -0
- {reader-3.23 → reader-3.25}/scripts/jscontrols.py +0 -0
- {reader-3.23 → reader-3.25}/scripts/lines.sh +0 -0
- {reader-3.23 → reader-3.25}/scripts/release.py +0 -0
- {reader-3.23 → reader-3.25}/scripts/vendor_app_stuff.py +0 -0
- {reader-3.23 → reader-3.25}/setup.cfg +0 -0
- {reader-3.23 → reader-3.25}/src/reader/__main__.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/cli.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/ext.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/forms.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/legacy/__init__.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/legacy/api_thing.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/legacy/static/controls.js +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/legacy/static/style.css +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/legacy/templates/add_entry.html +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/legacy/templates/entries.html +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/legacy/templates/feeds.html +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/legacy/templates/layout.html +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/legacy/templates/macros.html +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/legacy/templates/metadata.html +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/legacy/templates/tags.html +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/legacy/wsgi.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/static/theme.js +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/static/vendor/README +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/static/vendor/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/static/vendor/bootstrap-icons@1.13.1/font/fonts/bootstrap-icons.woff +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/static/vendor/bootstrap-icons@1.13.1/font/fonts/bootstrap-icons.woff2 +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/static/vendor/bootstrap@5.3.8/dist/css/bootstrap.min.css +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/static/vendor/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/static/vendor/htmx-ext-response-targets@2.0.4/dist/response-targets.min.js +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/static/vendor/htmx.org@2.0.8/dist/htmx.min.js +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/templates/layout.html +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_app/wsgi.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_hash_utils.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_parser/_http_utils.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_parser/_url_utils.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_parser/file.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_plugins/__init__.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_plugins/cli_status.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_plugins/legacy/__init__.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_plugins/legacy/share.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_plugins/legacy/templates/preview_feed_list.html +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_plugins/timer.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_storage/__init__.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_storage/_base.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_storage/_changes.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_storage/_sql_utils.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_storage/_tags.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/__init__.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/__init__.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/api.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/datetimes/__init__.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/datetimes/asctime.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/datetimes/greek.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/datetimes/hungarian.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/datetimes/iso8601.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/datetimes/korean.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/datetimes/perforce.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/datetimes/rfc822.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/datetimes/w3dtf.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/encodings.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/exceptions.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/html.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/http.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/mixin.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/namespaces/__init__.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/namespaces/_base.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/namespaces/admin.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/namespaces/cc.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/namespaces/dc.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/namespaces/georss.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/namespaces/itunes.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/namespaces/mediarss.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/namespaces/psc.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/parsers/__init__.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/parsers/json.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/parsers/loose.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/parsers/strict.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/py.typed +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/sanitizer.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/sgml.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/urls.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/_vendor/feedparser/util.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/exceptions.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/opml.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/plugins/__init__.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/plugins/_loader.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/plugins/enclosure_dedupe.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader/py.typed +0 -0
- {reader-3.23 → reader-3.25}/src/reader/utils.py +0 -0
- {reader-3.23 → reader-3.25}/src/reader.egg-info/dependency_links.txt +0 -0
- {reader-3.23 → reader-3.25}/src/reader.egg-info/top_level.txt +0 -0
- {reader-3.23 → reader-3.25}/tests/_plugins/__init__.py +0 -0
- {reader-3.23 → reader-3.25}/tests/_plugins/builtin.py +0 -0
- {reader-3.23 → reader-3.25}/tests/_plugins/error_during_import.py +0 -0
- {reader-3.23 → reader-3.25}/tests/_plugins/error_import_error.py +0 -0
- {reader-3.23 → reader-3.25}/tests/data/10.json +0 -0
- {reader-3.23 → reader-3.25}/tests/data/10.json.py +0 -0
- {reader-3.23 → reader-3.25}/tests/data/custom +0 -0
- {reader-3.23 → reader-3.25}/tests/data/empty.atom +0 -0
- {reader-3.23 → reader-3.25}/tests/data/empty.json +0 -0
- {reader-3.23 → reader-3.25}/tests/data/empty.json.py +0 -0
- {reader-3.23 → reader-3.25}/tests/data/empty.rss +0 -0
- {reader-3.23 → reader-3.25}/tests/data/empty.rss.py +0 -0
- {reader-3.23 → reader-3.25}/tests/data/full.atom +0 -0
- {reader-3.23 → reader-3.25}/tests/data/full.json +0 -0
- {reader-3.23 → reader-3.25}/tests/data/full.rss +0 -0
- {reader-3.23 → reader-3.25}/tests/data/invalid.json +0 -0
- {reader-3.23 → reader-3.25}/tests/data/relative.atom +0 -0
- {reader-3.23 → reader-3.25}/tests/data/relative.atom.py +0 -0
- {reader-3.23 → reader-3.25}/tests/data/relative.rss +0 -0
- {reader-3.23 → reader-3.25}/tests/data/relative.rss.py +0 -0
- {reader-3.23 → reader-3.25}/tests/data/sqlite_releases.html +0 -0
- {reader-3.23 → reader-3.25}/tests/data/unknown.json +0 -0
- {reader-3.23 → reader-3.25}/tests/data/unknown.json.py +0 -0
- {reader-3.23 → reader-3.25}/tests/reader_methods.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_app_legacy.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_app_wsgi.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_bench.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_changes.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_exceptions.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_hash_utils.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_opml.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_plugins_cli_status.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_plugins_enclosure_dedupe.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_plugins_entry_dedupe.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_plugins_loader.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_plugins_mark_as_read.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_plugins_preview_feed_list.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_plugins_readtime.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_plugins_sqlite_releases.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_reader_counts.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_reader_deprecations.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_reader_filter.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_reader_integration.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_reader_lifecycle.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_reader_private.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_reader_sort.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_reader_utils.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_search.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_sql_utils.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_sqlite_utils.py +0 -0
- {reader-3.23 → reader-3.25}/tests/test_tags.py +0 -0
- {reader-3.23 → reader-3.25}/tests/utils.py +0 -0
- {reader-3.23 → reader-3.25}/tox.ini +0 -0
|
@@ -6,6 +6,75 @@ Changelog
|
|
|
6
6
|
:no-index:
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
Version 3.25
|
|
10
|
+
------------
|
|
11
|
+
|
|
12
|
+
Released 2026-05-25
|
|
13
|
+
|
|
14
|
+
* Stable feed autodiscovery support. (:issue:`404`)
|
|
15
|
+
|
|
16
|
+
* Add :mod:`~reader.plugins.autodiscover` built-in plugin.
|
|
17
|
+
* Add :mod:`reader.discover` module providing low level discovery.
|
|
18
|
+
* In the web app, show :mod:`~reader.plugins.autodiscover` suggestions when adding a feed.
|
|
19
|
+
|
|
20
|
+
* Fix bugs that may cause the 3.24 database migration to fail. (:issue:`405`)
|
|
21
|
+
|
|
22
|
+
* Don't fail for ``John Doe (...) (email)`` RSS authors.
|
|
23
|
+
* Support missing :attr:`EntrySource` ``author`` field during migration.
|
|
24
|
+
|
|
25
|
+
Note there should be no integrity issues,
|
|
26
|
+
as migrations happen inside a transaction.
|
|
27
|
+
|
|
28
|
+
* Simplify parser lazy initialization (make it explicit and opt-in by plugins).
|
|
29
|
+
* Allow before/after update hook functions to take *fewer* arguments than documented.
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
Version 3.24
|
|
33
|
+
------------
|
|
34
|
+
|
|
35
|
+
Released 2026-05-20
|
|
36
|
+
|
|
37
|
+
* Support multiple authors with details (link, email).
|
|
38
|
+
Thanks to `Anshul Mittal`_ for the PR.
|
|
39
|
+
(:issue:`391`)
|
|
40
|
+
|
|
41
|
+
* Add :attr:`Feed.authors`, :attr:`Entry.authors`,
|
|
42
|
+
and :attr:`EntrySource.authors` attributes.
|
|
43
|
+
* Add matching :attr:`~Feed.authors_str` property.
|
|
44
|
+
* Deprecate :attr:`~Feed.author` attribute.
|
|
45
|
+
|
|
46
|
+
* Fix bug introduced in `version 3.15 <Version 3.15_>`__
|
|
47
|
+
causing feeds to not be retrieved in parallel
|
|
48
|
+
even if :meth:`~Reader.update_feeds` was called with ``workers`` > 1.
|
|
49
|
+
|
|
50
|
+
* Use :ref:`structured logging <logging>` internally,
|
|
51
|
+
with the possibility to opt into native `structlog`_ logging.
|
|
52
|
+
(:issue:`147`)
|
|
53
|
+
|
|
54
|
+
* Use :meth:`datetime.datetime.fromisoformat` instead of external dependency ``iso8601``.
|
|
55
|
+
Thanks to `Sebastian Bradley`_ for the PR.
|
|
56
|
+
(:issue:`398`)
|
|
57
|
+
|
|
58
|
+
* Refactor updater internals to allow reusing logic in alternate implementations.
|
|
59
|
+
|
|
60
|
+
* :doc:`app` improvements.
|
|
61
|
+
|
|
62
|
+
* Add empty state and contextual help; :ref:`screenshots <screenshots-help>`.
|
|
63
|
+
* Import feeds improvements (select all/none, accept only XML / OPML files).
|
|
64
|
+
|
|
65
|
+
* Add ``--json`` option to the ``list`` CLI commands.
|
|
66
|
+
Thanks to `Puneet Dixit`_ for the PR.
|
|
67
|
+
(:issue:`394`)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
.. _Puneet Dixit: https://github.com/puneetdixit200
|
|
72
|
+
.. _Anshul Mittal: https://github.com/anderson688
|
|
73
|
+
.. _Sebastian Bradley: https://github.com/bazbrad765
|
|
74
|
+
|
|
75
|
+
.. _structlog: https://www.structlog.org/
|
|
76
|
+
|
|
77
|
+
|
|
9
78
|
Version 3.23
|
|
10
79
|
------------
|
|
11
80
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: reader
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.25
|
|
4
4
|
Summary: A Python feed reader library.
|
|
5
5
|
Author: lemon24
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -39,12 +39,12 @@ Requires-Dist: typing-extensions>=4
|
|
|
39
39
|
Requires-Dist: feedparser>=6
|
|
40
40
|
Requires-Dist: requests>=2.18
|
|
41
41
|
Requires-Dist: werkzeug>2
|
|
42
|
-
Requires-Dist: iso8601>=1
|
|
43
42
|
Requires-Dist: beautifulsoup4>=4.5
|
|
43
|
+
Requires-Dist: structlog
|
|
44
44
|
Provides-Extra: all
|
|
45
45
|
Requires-Dist: reader[app,cli,unstable-plugins]; extra == "all"
|
|
46
46
|
Provides-Extra: cli
|
|
47
|
-
Requires-Dist: click
|
|
47
|
+
Requires-Dist: click!=8.4.0,>=7; extra == "cli"
|
|
48
48
|
Provides-Extra: app
|
|
49
49
|
Requires-Dist: flask>=0.10; extra == "app"
|
|
50
50
|
Requires-Dist: humanize>=4; extra == "app"
|
|
@@ -114,6 +114,7 @@ you'd probably call it a fat model.
|
|
|
114
114
|
* full-text search articles
|
|
115
115
|
* get statistics on feed and user activity
|
|
116
116
|
* import / export feeds as OPML
|
|
117
|
+
* automatically discover feeds in web pages
|
|
117
118
|
* write plugins to extend its functionality
|
|
118
119
|
* skip all the low level stuff and focus on what makes your feed reader different
|
|
119
120
|
|
|
@@ -50,6 +50,7 @@ you'd probably call it a fat model.
|
|
|
50
50
|
* full-text search articles
|
|
51
51
|
* get statistics on feed and user activity
|
|
52
52
|
* import / export feeds as OPML
|
|
53
|
+
* automatically discover feeds in web pages
|
|
53
54
|
* write plugins to extend its functionality
|
|
54
55
|
* skip all the low level stuff and focus on what makes your feed reader different
|
|
55
56
|
|
|
@@ -27,12 +27,17 @@ Data objects
|
|
|
27
27
|
|
|
28
28
|
.. autoclass:: Feed
|
|
29
29
|
:members:
|
|
30
|
+
:inherited-members:
|
|
31
|
+
|
|
32
|
+
.. autoclass:: Author
|
|
33
|
+
:members:
|
|
30
34
|
|
|
31
35
|
.. autoclass:: ExceptionInfo
|
|
32
36
|
:members:
|
|
33
37
|
|
|
34
38
|
.. autoclass:: Entry
|
|
35
39
|
:members:
|
|
40
|
+
:inherited-members:
|
|
36
41
|
|
|
37
42
|
.. autoclass:: Content
|
|
38
43
|
:members:
|
|
@@ -42,6 +47,7 @@ Data objects
|
|
|
42
47
|
|
|
43
48
|
.. autoclass:: EntrySource
|
|
44
49
|
:members:
|
|
50
|
+
:inherited-members:
|
|
45
51
|
|
|
46
52
|
.. autoclass:: EntrySearchResult
|
|
47
53
|
:members:
|
|
@@ -227,6 +233,8 @@ Constants
|
|
|
227
233
|
Utilities
|
|
228
234
|
---------
|
|
229
235
|
|
|
236
|
+
.. autofunction:: reader.enable_structlog
|
|
237
|
+
|
|
230
238
|
.. autofunction:: reader.utils.archive_entries
|
|
231
239
|
|
|
232
240
|
|
|
@@ -235,3 +243,9 @@ Utilities
|
|
|
235
243
|
---------------
|
|
236
244
|
|
|
237
245
|
.. automodule:: reader.opml
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
``reader.discover``
|
|
249
|
+
-------------------
|
|
250
|
+
|
|
251
|
+
.. automodule:: reader.discover
|
|
@@ -74,6 +74,17 @@ Screenshots
|
|
|
74
74
|
|
|
75
75
|
import feeds (result)
|
|
76
76
|
|
|
77
|
+
.. _screenshots-help:
|
|
78
|
+
|
|
79
|
+
.. figure:: screenshots/empty.png
|
|
80
|
+
:width: 240px
|
|
81
|
+
|
|
82
|
+
new user / empty state
|
|
83
|
+
|
|
84
|
+
.. figure:: screenshots/help.png
|
|
85
|
+
:width: 240px
|
|
86
|
+
|
|
87
|
+
contextual help
|
|
77
88
|
|
|
78
89
|
|
|
79
90
|
Serving the web app
|
|
@@ -19,8 +19,11 @@ for name in [
|
|
|
19
19
|
'werkzeug.exceptions',
|
|
20
20
|
'werkzeug.http',
|
|
21
21
|
'yaml',
|
|
22
|
+
'structlog',
|
|
23
|
+
'structlog.contextvars',
|
|
24
|
+
'structlog.stdlib',
|
|
22
25
|
]:
|
|
23
|
-
sys.modules[name] = unittest.mock.
|
|
26
|
+
sys.modules[name] = unittest.mock.MagicMock()
|
|
24
27
|
|
|
25
28
|
import reader
|
|
26
29
|
|
|
@@ -374,7 +374,7 @@ As seen in the previous sections,
|
|
|
374
374
|
updated=datetime.datetime(2020, 2, 28, 9, 34, 2, tzinfo=datetime.timezone.utc),
|
|
375
375
|
title='Hello Internet',
|
|
376
376
|
link='http://www.hellointernet.fm/',
|
|
377
|
-
|
|
377
|
+
authors=(Author(name='CGP Grey'),),
|
|
378
378
|
subtitle='CGP Grey and Brady Haran talk about YouTube, life, work, whatever.',
|
|
379
379
|
version='rss20',
|
|
380
380
|
user_title=None,
|
|
@@ -386,9 +386,10 @@ As seen in the previous sections,
|
|
|
386
386
|
To get all the feeds, use the :meth:`~Reader.get_feeds` method::
|
|
387
387
|
|
|
388
388
|
>>> for feed in reader.get_feeds():
|
|
389
|
+
... authors = ", ".join(a.name for a in feed.authors if a.name) or 'unknown author'
|
|
389
390
|
... print(
|
|
390
391
|
... feed.title or feed.url,
|
|
391
|
-
... f"by {
|
|
392
|
+
... f"by {authors},",
|
|
392
393
|
... f"updated on {feed.updated or 'never'}",
|
|
393
394
|
... )
|
|
394
395
|
...
|
|
@@ -1001,6 +1002,34 @@ depending on their type attribute or feedparser defaults:
|
|
|
1001
1002
|
|
|
1002
1003
|
|
|
1003
1004
|
|
|
1005
|
+
.. _logging:
|
|
1006
|
+
|
|
1007
|
+
Logging
|
|
1008
|
+
-------
|
|
1009
|
+
|
|
1010
|
+
*reader* uses structured logging internally, through `structlog`_.
|
|
1011
|
+
|
|
1012
|
+
By default, output goes to the standard library :mod:`logging` module;
|
|
1013
|
+
if you don't care about structlog,
|
|
1014
|
+
configure :mod:`logging` as you normally would.
|
|
1015
|
+
The *reader* root logger is called ``reader``;
|
|
1016
|
+
the names of its children are an implementation detail,
|
|
1017
|
+
and may change without any notice.
|
|
1018
|
+
|
|
1019
|
+
To enable native structlog logging,
|
|
1020
|
+
call :func:`enable_structlog` **before** configuring structlog::
|
|
1021
|
+
|
|
1022
|
+
import reader, structlog
|
|
1023
|
+
|
|
1024
|
+
reader.enable_structlog()
|
|
1025
|
+
structlog.configure(...)
|
|
1026
|
+
|
|
1027
|
+
... # do stuff with reader
|
|
1028
|
+
|
|
1029
|
+
.. _structlog: https://www.structlog.org/
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
|
|
1004
1033
|
.. _errors:
|
|
1005
1034
|
|
|
1006
1035
|
Errors and exceptions
|
|
@@ -13,14 +13,11 @@ Dependencies
|
|
|
13
13
|
|
|
14
14
|
These packages will be installed automatically when installing *reader*:
|
|
15
15
|
|
|
16
|
-
* `feedparser`_ parses
|
|
17
|
-
* `requests`_ retrieves feeds from the internet
|
|
18
|
-
it replaces feedparser's default use of :mod:`urllib`
|
|
19
|
-
to make it easier to write plugins.
|
|
16
|
+
* `feedparser`_ parses Atom and RSSfeeds.
|
|
17
|
+
* `requests`_ retrieves feeds from the internet.
|
|
20
18
|
* `werkzeug`_ provides HTTP utilities.
|
|
21
|
-
* `
|
|
22
|
-
* `
|
|
23
|
-
to the search index.
|
|
19
|
+
* `beautifulsoup4`_ is used to strip HTML tags and by various plugins.
|
|
20
|
+
* `structlog`_ is used for structured logging.
|
|
24
21
|
* `typing-extensions`_ is used for :mod:`typing` backports.
|
|
25
22
|
|
|
26
23
|
*reader* also depends on the :mod:`sqlite3` standard library module
|
|
@@ -58,12 +55,18 @@ As of version |version|, *reader* has the following extras:
|
|
|
58
55
|
* Specific plugins may require additional dependencies;
|
|
59
56
|
refer to their documentation for details.
|
|
60
57
|
|
|
58
|
+
Beautiful Soup supports `multiple parsers`_,
|
|
59
|
+
and will use the best available one;
|
|
60
|
+
for speed, install `lxml`_.
|
|
61
61
|
|
|
62
62
|
.. _beautifulsoup4: https://www.crummy.com/software/BeautifulSoup/
|
|
63
|
+
.. _multiple parsers: https://www.crummy.com/software/BeautifulSoup/bs4/doc/#installing-a-parser
|
|
64
|
+
.. _lxml: https://lxml.de/
|
|
63
65
|
.. _feedparser: https://feedparser.readthedocs.io/en/latest/
|
|
64
66
|
.. _requests: https://requests.readthedocs.io/
|
|
65
67
|
.. _werkzeug: https://werkzeug.palletsprojects.com/
|
|
66
68
|
.. _iso8601: http://pyiso8601.readthedocs.org/
|
|
69
|
+
.. _structlog: https://www.structlog.org/
|
|
67
70
|
.. _typing-extensions: https://pypi.org/project/typing-extensions/
|
|
68
71
|
.. _JSON1: https://www.sqlite.org/json1.html
|
|
69
72
|
.. _FTS5: https://www.sqlite.org/fts5.html
|
|
@@ -31,16 +31,6 @@ Parser
|
|
|
31
31
|
:special-members: __call__
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
.. module:: reader._parser.requests
|
|
35
|
-
|
|
36
|
-
.. autoclass:: SessionFactory(...)
|
|
37
|
-
:members:
|
|
38
|
-
:special-members: __call__
|
|
39
|
-
|
|
40
|
-
.. autoclass:: SessionWrapper(...)
|
|
41
|
-
:members:
|
|
42
|
-
|
|
43
|
-
|
|
44
34
|
Protocols
|
|
45
35
|
~~~~~~~~~
|
|
46
36
|
|
|
@@ -79,18 +69,6 @@ Protocols
|
|
|
79
69
|
:show-inheritance:
|
|
80
70
|
|
|
81
71
|
|
|
82
|
-
.. module:: reader._parser.requests
|
|
83
|
-
:no-index:
|
|
84
|
-
|
|
85
|
-
.. autoclass:: RequestHook
|
|
86
|
-
:members:
|
|
87
|
-
:special-members: __call__
|
|
88
|
-
|
|
89
|
-
.. autoclass:: ResponseHook
|
|
90
|
-
:members:
|
|
91
|
-
:special-members: __call__
|
|
92
|
-
|
|
93
|
-
|
|
94
72
|
Data objects
|
|
95
73
|
~~~~~~~~~~~~
|
|
96
74
|
|
|
@@ -113,6 +91,35 @@ Data objects
|
|
|
113
91
|
:members:
|
|
114
92
|
|
|
115
93
|
|
|
94
|
+
Built-in retrievers and parsers
|
|
95
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
96
|
+
|
|
97
|
+
.. module:: reader._parser.http
|
|
98
|
+
|
|
99
|
+
.. autoclass:: HTTPRetriever
|
|
100
|
+
:members: request_hooks, response_hooks
|
|
101
|
+
|
|
102
|
+
.. autoclass:: RequestHook
|
|
103
|
+
:members:
|
|
104
|
+
:special-members: __call__
|
|
105
|
+
|
|
106
|
+
.. autoclass:: ResponseHook
|
|
107
|
+
:members:
|
|
108
|
+
:special-members: __call__
|
|
109
|
+
|
|
110
|
+
.. module:: reader._parser.file
|
|
111
|
+
|
|
112
|
+
.. autoclass:: FileRetriever
|
|
113
|
+
|
|
114
|
+
.. module:: reader._parser.feedparser
|
|
115
|
+
|
|
116
|
+
.. autoclass:: FeedparserParser
|
|
117
|
+
|
|
118
|
+
.. module:: reader._parser.jsonfeed
|
|
119
|
+
|
|
120
|
+
.. autoclass:: JSONFeedParser
|
|
121
|
+
|
|
122
|
+
|
|
116
123
|
.. _storage:
|
|
117
124
|
|
|
118
125
|
Storage
|
|
@@ -16,6 +16,7 @@ This is a list of built-in plugins that are considered stable.
|
|
|
16
16
|
See the :ref:`Plugins <plugins>` section of the user guide
|
|
17
17
|
for details on how built-in plugins are loaded.
|
|
18
18
|
|
|
19
|
+
.. automodule:: reader.plugins.autodiscover
|
|
19
20
|
.. automodule:: reader.plugins.enclosure_dedupe
|
|
20
21
|
.. automodule:: reader.plugins.entry_dedupe
|
|
21
22
|
.. automodule:: reader.plugins.mark_as_read
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -40,10 +40,9 @@ dependencies = [
|
|
|
40
40
|
"requests>=2.18",
|
|
41
41
|
# for _http_utils
|
|
42
42
|
"werkzeug>2",
|
|
43
|
-
# for JSON Feed date parsing
|
|
44
|
-
"iso8601>=1",
|
|
45
43
|
# search
|
|
46
44
|
"beautifulsoup4>=4.5",
|
|
45
|
+
"structlog",
|
|
47
46
|
]
|
|
48
47
|
dynamic = ["version"]
|
|
49
48
|
|
|
@@ -60,7 +59,8 @@ Changelog = "https://reader.readthedocs.io/en/latest/changelog.html"
|
|
|
60
59
|
all = ["reader[cli,app,unstable-plugins]"]
|
|
61
60
|
|
|
62
61
|
cli = [
|
|
63
|
-
|
|
62
|
+
# != 8.4.0 because of https://github.com/pallets/click/issues/3458
|
|
63
|
+
"click>=7,!=8.4.0",
|
|
64
64
|
]
|
|
65
65
|
app = [
|
|
66
66
|
"flask>=0.10",
|
|
@@ -186,10 +186,15 @@ module = [
|
|
|
186
186
|
"reader.plugins.ua_fallback",
|
|
187
187
|
"reader.plugins.readtime",
|
|
188
188
|
"reader.plugins.mark_as_read",
|
|
189
|
+
"reader.plugins.autodiscover",
|
|
189
190
|
"reader._vendor.feedparser.*",
|
|
190
191
|
]
|
|
191
192
|
ignore_errors = true
|
|
192
193
|
|
|
194
|
+
[tool.black]
|
|
195
|
+
skip-string-normalization = true
|
|
196
|
+
extend-exclude = "src/reader/_vendor/.*"
|
|
197
|
+
|
|
193
198
|
[tool.isort]
|
|
194
199
|
profile = "black"
|
|
195
200
|
py_version = 311
|
|
@@ -37,7 +37,7 @@ Print the titles of the unread entries::
|
|
|
37
37
|
|
|
38
38
|
"""
|
|
39
39
|
|
|
40
|
-
__version__ = '3.
|
|
40
|
+
__version__ = '3.25'
|
|
41
41
|
|
|
42
42
|
# isort: off
|
|
43
43
|
|
|
@@ -67,6 +67,7 @@ from .types import (
|
|
|
67
67
|
FeedToImport as FeedToImport,
|
|
68
68
|
FeedImportResult as FeedImportResult,
|
|
69
69
|
FeedExport as FeedExport,
|
|
70
|
+
Author as Author,
|
|
70
71
|
)
|
|
71
72
|
|
|
72
73
|
from .exceptions import (
|
|
@@ -106,6 +107,8 @@ USER_AGENT = f'python-reader/{__version__} (+{SOURCE_URL})'
|
|
|
106
107
|
# Prevent any logging output by default. If no handler is set,
|
|
107
108
|
# the messages bubble up to the root logger and get printed on stderr.
|
|
108
109
|
# https://docs.python.org/3/howto/logging.html#library-config
|
|
110
|
+
|
|
109
111
|
import logging # noqa: E402
|
|
112
|
+
from ._logging import enable_structlog as enable_structlog # noqa: E402
|
|
110
113
|
|
|
111
114
|
logging.getLogger('reader').addHandler(logging.NullHandler())
|
|
@@ -88,6 +88,15 @@ def entries():
|
|
|
88
88
|
if feed_url := form.feed.data:
|
|
89
89
|
feed = reader.get_feed(feed_url)
|
|
90
90
|
|
|
91
|
+
autodiscover = None
|
|
92
|
+
if feed and feed.last_exception and not feed.last_updated:
|
|
93
|
+
autodiscover_tag = reader.make_reader_reserved_name('autodiscover')
|
|
94
|
+
hostname = urlparse(feed.url).hostname
|
|
95
|
+
autodiscover = [
|
|
96
|
+
(link, hostname and urlparse(link['href']).hostname == hostname)
|
|
97
|
+
for link in reader.get_tag(feed, autodiscover_tag, None)
|
|
98
|
+
]
|
|
99
|
+
|
|
91
100
|
kwargs = dict(form.data)
|
|
92
101
|
|
|
93
102
|
if not (feed or kwargs.get('starting_after')):
|
|
@@ -106,6 +115,7 @@ def entries():
|
|
|
106
115
|
form=form,
|
|
107
116
|
entries=entries,
|
|
108
117
|
feed=feed,
|
|
118
|
+
autodiscover=autodiscover,
|
|
109
119
|
limit=limit,
|
|
110
120
|
)
|
|
111
121
|
|
|
@@ -277,6 +287,31 @@ def add_feed():
|
|
|
277
287
|
return render_template('add_feed.html', form=form)
|
|
278
288
|
|
|
279
289
|
|
|
290
|
+
@blueprint.route('/feeds/change', methods=['POST'])
|
|
291
|
+
def change_feed_url():
|
|
292
|
+
# NOTE: currently only for post-add autodiscover change,
|
|
293
|
+
# actual "change feed URL" UX needs design (e.g. needs preview)
|
|
294
|
+
|
|
295
|
+
reader = get_reader()
|
|
296
|
+
old = request.form['old']
|
|
297
|
+
new = request.form['new']
|
|
298
|
+
|
|
299
|
+
try:
|
|
300
|
+
reader.change_feed_url(old, new)
|
|
301
|
+
except FeedExistsError:
|
|
302
|
+
flash(f"Feed {new} already exists.", 'secondary')
|
|
303
|
+
return redirect(url_for('.entries', feed=old), code=303)
|
|
304
|
+
else:
|
|
305
|
+
# TODO: updating should be out of band
|
|
306
|
+
try:
|
|
307
|
+
reader.update_feed(new)
|
|
308
|
+
except UpdateError:
|
|
309
|
+
pass
|
|
310
|
+
else:
|
|
311
|
+
flash("Changed and updated feed.", 'success')
|
|
312
|
+
return redirect(url_for('.entries', feed=new), code=303)
|
|
313
|
+
|
|
314
|
+
|
|
280
315
|
@blueprint.route('/feeds/import', methods=['GET', 'POST'])
|
|
281
316
|
def import_feeds():
|
|
282
317
|
reader = get_reader()
|
|
@@ -290,6 +325,10 @@ def import_feeds():
|
|
|
290
325
|
parsed_feeds = [asdict(f) for f in opml.parse(file.stream)]
|
|
291
326
|
except opml.OPMLError as e:
|
|
292
327
|
error = str(e)
|
|
328
|
+
else:
|
|
329
|
+
if not parsed_feeds:
|
|
330
|
+
parsed_feeds = None
|
|
331
|
+
error = "file contains no feeds"
|
|
293
332
|
else:
|
|
294
333
|
feeds = (
|
|
295
334
|
FeedToImport(**json.loads(f)) for f in request.form.getlist('feed')
|
|
@@ -312,6 +351,11 @@ def entry():
|
|
|
312
351
|
return render_template('entry.html', entry=entry)
|
|
313
352
|
|
|
314
353
|
|
|
354
|
+
@blueprint.route('/help')
|
|
355
|
+
def help():
|
|
356
|
+
return render_template('help.html')
|
|
357
|
+
|
|
358
|
+
|
|
315
359
|
def stream_template(template_name_or_list, **kwargs):
|
|
316
360
|
# Ensure flashed messages get removed from the session,
|
|
317
361
|
# otherwise they keep adding up and never disappear.
|
|
@@ -385,6 +429,11 @@ def _find_static(folder, filename):
|
|
|
385
429
|
return matches[0]
|
|
386
430
|
|
|
387
431
|
|
|
432
|
+
@blueprint.app_template_global()
|
|
433
|
+
def is_new_user():
|
|
434
|
+
return not list(get_reader().get_feeds(limit=1))
|
|
435
|
+
|
|
436
|
+
|
|
388
437
|
def create_app(reader_config):
|
|
389
438
|
app = Flask(__name__)
|
|
390
439
|
app.config['SECRET_KEY'] = 'secret'
|
|
@@ -18,7 +18,15 @@
|
|
|
18
18
|
<ul class="controls">
|
|
19
19
|
|
|
20
20
|
<li>
|
|
21
|
-
{% if entry.
|
|
21
|
+
{% if entry.authors %}
|
|
22
|
+
by
|
|
23
|
+
{% for author in entry.authors -%}
|
|
24
|
+
{% if author.href %}<a href="{{ author.href }}" title="{{ author.email or '' }}">{% endif %}
|
|
25
|
+
{{- author.name or author.email or 'unknown' -}}
|
|
26
|
+
{% if author.href %}</a>{% endif %}
|
|
27
|
+
{%- if not loop.last %}, {% endif %}
|
|
28
|
+
{%- endfor %}
|
|
29
|
+
{% endif %}
|
|
22
30
|
in <a href="{{ url_for('.entries', feed=entry.feed.url) }}">{{ entry.feed_resolved_title or feed.url }}</a>
|
|
23
31
|
<li>
|
|
24
32
|
{%- set published = entry.published or entry.updated_not_none -%}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
{% set help_topic = 'add-feed' %}
|
|
1
2
|
{% extends "layout.html" %}
|
|
2
3
|
|
|
3
4
|
{% import "macros.html" as macros %}
|
|
@@ -12,7 +13,15 @@
|
|
|
12
13
|
|
|
13
14
|
<h1>Add feed</h1>
|
|
14
15
|
|
|
15
|
-
<p>
|
|
16
|
+
<p>
|
|
17
|
+
Paste a feed or website URL:
|
|
18
|
+
<a
|
|
19
|
+
href="{{ url_for('.help', _anchor=help_topic) }}"
|
|
20
|
+
class="link-secondary d-inline-block px-2"
|
|
21
|
+
>
|
|
22
|
+
<i class="bi bi-question-lg" aria-label="help: adding feeds"></i>
|
|
23
|
+
</a>
|
|
24
|
+
</p>
|
|
16
25
|
|
|
17
26
|
<form action="{{ url_for(request.endpoint) }}" method="post" class="my-2 mb-4">
|
|
18
27
|
{#- TODO: aria stuff #}
|