diff --git a/.gitignore b/.gitignore index 7ec6ce5..eb021a0 100644 --- a/.gitignore +++ b/.gitignore @@ -159,3 +159,10 @@ cython_debug/ data/ media/ staticfiles/ + +# https://github.com/lemon24/reader +*.sqlite.search +*.sqlite + +# When running the cli.py script, the following files are created +broken_feeds.csv diff --git a/app/cli.py b/app/cli.py index 4661230..3102de9 100644 --- a/app/cli.py +++ b/app/cli.py @@ -1,9 +1,11 @@ from __future__ import annotations +from pathlib import Path from typing import TYPE_CHECKING import click -from reader import Reader, UpdateError +from reader import Feed, Reader, UpdateError, UpdateResult +from reader.types import UpdatedFeed from app.dependencies import get_reader @@ -11,19 +13,44 @@ if TYPE_CHECKING: from reader import UpdatedFeed +def add_broken_feed_to_csv(feed: Feed | UpdateResult | None) -> None: + """Add a broken feed to a CSV file.""" + if feed is None: + click.echo("Feed is None.", err=True) + return + + with Path("broken_feeds.csv").open("a", encoding="utf-8") as f: + f.write(f"{feed.url}\n") + + @click.command() def update_feeds() -> None: """Update all the feeds.""" - click.echo("Updating feeds...") reader: Reader = get_reader() - for feed in reader.update_feeds_iter(): + click.echo("Updating feeds...") + + for feed in reader.update_feeds_iter(updates_enabled=True, workers=100): + url: str = feed.url value: UpdatedFeed | None | UpdateError = feed.value - if value is not None and isinstance(value, UpdateError): - click.echo(f"Error updating {feed.url}: {value}") - else: - click.echo(f"Updated {feed.url}.") + + if isinstance(value, UpdateError): + add_broken_feed_to_csv(feed) + reader.disable_feed_updates(url) + continue + + if value is None: + click.echo(f"Feed not updated: {url}") + continue + + click.echo(f"Updated feed: {url}") + click.echo("Feeds updated.") if __name__ == "__main__": + reader: Reader = get_reader() + + for feed in reader.get_feeds(updates_enabled=False): + reader.enable_feed_updates(feed) + update_feeds() diff --git a/app/routers/static.py b/app/routers/static.py index 943159b..9cca689 100644 --- a/app/routers/static.py +++ b/app/routers/static.py @@ -6,9 +6,10 @@ import time from pathlib import Path from typing import TYPE_CHECKING -from fastapi import APIRouter, File, Request, UploadFile +from fastapi import APIRouter, File, Form, Request, UploadFile from fastapi.responses import FileResponse from fastapi.templating import Jinja2Templates +from reader import FeedExistsError, InvalidFeedURLError from app.dependencies import CommonReader, CommonStats # noqa: TCH001 from app.settings import MEDIA_ROOT @@ -131,3 +132,40 @@ async def upload_page(request: Request, stats: CommonStats): async def contact(request: Request, stats: CommonStats): """Contact page.""" return templates.TemplateResponse(request=request, name="contact.html", context={"stats": stats}) + + +@static_router.post(path="/contact", summary="Contact page.", tags=["HTML"]) +async def contact_form(request: Request, stats: CommonStats, message: str = Form(...)): + """Contact page.""" + # TODO(TheLovinator): Send the message to the admin. # noqa: TD003 + return { + "message": message, + "stats": stats, + } + + +@static_router.get(path="/add", summary="Add feeds page.", tags=["HTML"]) +async def add_page(request: Request, stats: CommonStats): + """Add feeds page.""" + return templates.TemplateResponse(request=request, name="add.html", context={"stats": stats}) + + +@static_router.post(path="/add", summary="Add feeds page.", tags=["HTML"]) +async def add_feed(reader: CommonReader, stats: CommonStats, feed_urls: str = Form(...)): + """Add feeds page.""" + feed_info = [] + # Each line is a feed URL. + for feed_url in feed_urls.split("\n"): + try: + reader.add_feed(feed_url.strip()) + feed_info.append({"url": feed_url.strip(), "status": "Added"}) + except FeedExistsError as e: + feed_info.append({"url": feed_url.strip(), "status": str(e)}) + except InvalidFeedURLError as e: + feed_info.append({"url": feed_url.strip(), "status": str(e)}) + + return { + "feed_urls": feed_urls, + "stats": stats, + "feed_info": feed_info, + } diff --git a/app/testboi.py b/app/testboi.py new file mode 100644 index 0000000..eb51340 --- /dev/null +++ b/app/testboi.py @@ -0,0 +1,5 @@ +from reader import Reader, make_reader + +reader: Reader = make_reader(url="testboi.sqlite") +reader.add_feed("http://485i.com/feed/") +reader.update_feeds() diff --git a/manage.py b/manage.py deleted file mode 100755 index 8d969f1..0000000 --- a/manage.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env python -"""Django's command-line utility for administrative tasks.""" - -import os -import sys - - -def main() -> None: - """Run administrative tasks.""" - os.environ.setdefault(key="DJANGO_SETTINGS_MODULE", value="feedvault.settings") - try: - from django.core.management import execute_from_command_line # noqa: PLC0415 - except ImportError as exc: - msg = "Couldn't import Django. Have you run `poetry install` or `poetry shell`?" - raise ImportError(msg) from exc - execute_from_command_line(sys.argv) - - -if __name__ == "__main__": - main() diff --git a/poetry.lock b/poetry.lock index db4de90..cd9e6b3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1185,45 +1185,44 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "ruff" -version = "0.3.7" +version = "0.4.4" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0e8377cccb2f07abd25e84fc5b2cbe48eeb0fea9f1719cad7caedb061d70e5ce"}, - {file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:15a4d1cc1e64e556fa0d67bfd388fed416b7f3b26d5d1c3e7d192c897e39ba4b"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d28bdf3d7dc71dd46929fafeec98ba89b7c3550c3f0978e36389b5631b793663"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:379b67d4f49774ba679593b232dcd90d9e10f04d96e3c8ce4a28037ae473f7bb"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c060aea8ad5ef21cdfbbe05475ab5104ce7827b639a78dd55383a6e9895b7c51"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ebf8f615dde968272d70502c083ebf963b6781aacd3079081e03b32adfe4d58a"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d48098bd8f5c38897b03604f5428901b65e3c97d40b3952e38637b5404b739a2"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da8a4fda219bf9024692b1bc68c9cff4b80507879ada8769dc7e985755d662ea"}, - {file = "ruff-0.3.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c44e0149f1d8b48c4d5c33d88c677a4aa22fd09b1683d6a7ff55b816b5d074f"}, - {file = "ruff-0.3.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3050ec0af72b709a62ecc2aca941b9cd479a7bf2b36cc4562f0033d688e44fa1"}, - {file = "ruff-0.3.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a29cc38e4c1ab00da18a3f6777f8b50099d73326981bb7d182e54a9a21bb4ff7"}, - {file = "ruff-0.3.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b15cc59c19edca917f51b1956637db47e200b0fc5e6e1878233d3a938384b0b"}, - {file = "ruff-0.3.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e491045781b1e38b72c91247cf4634f040f8d0cb3e6d3d64d38dcf43616650b4"}, - {file = "ruff-0.3.7-py3-none-win32.whl", hash = "sha256:bc931de87593d64fad3a22e201e55ad76271f1d5bfc44e1a1887edd0903c7d9f"}, - {file = "ruff-0.3.7-py3-none-win_amd64.whl", hash = "sha256:5ef0e501e1e39f35e03c2acb1d1238c595b8bb36cf7a170e7c1df1b73da00e74"}, - {file = "ruff-0.3.7-py3-none-win_arm64.whl", hash = "sha256:789e144f6dc7019d1f92a812891c645274ed08af6037d11fc65fcbc183b7d59f"}, - {file = "ruff-0.3.7.tar.gz", hash = "sha256:d5c1aebee5162c2226784800ae031f660c350e7a3402c4d1f8ea4e97e232e3ba"}, + {file = "ruff-0.4.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:29d44ef5bb6a08e235c8249294fa8d431adc1426bfda99ed493119e6f9ea1bf6"}, + {file = "ruff-0.4.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c4efe62b5bbb24178c950732ddd40712b878a9b96b1d02b0ff0b08a090cbd891"}, + {file = "ruff-0.4.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c8e2f1e8fc12d07ab521a9005d68a969e167b589cbcaee354cb61e9d9de9c15"}, + {file = "ruff-0.4.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:60ed88b636a463214905c002fa3eaab19795679ed55529f91e488db3fe8976ab"}, + {file = "ruff-0.4.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b90fc5e170fc71c712cc4d9ab0e24ea505c6a9e4ebf346787a67e691dfb72e85"}, + {file = "ruff-0.4.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8e7e6ebc10ef16dcdc77fd5557ee60647512b400e4a60bdc4849468f076f6eef"}, + {file = "ruff-0.4.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9ddb2c494fb79fc208cd15ffe08f32b7682519e067413dbaf5f4b01a6087bcd"}, + {file = "ruff-0.4.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c51c928a14f9f0a871082603e25a1588059b7e08a920f2f9fa7157b5bf08cfe9"}, + {file = "ruff-0.4.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5eb0a4bfd6400b7d07c09a7725e1a98c3b838be557fee229ac0f84d9aa49c36"}, + {file = "ruff-0.4.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b1867ee9bf3acc21778dcb293db504692eda5f7a11a6e6cc40890182a9f9e595"}, + {file = "ruff-0.4.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1aecced1269481ef2894cc495647392a34b0bf3e28ff53ed95a385b13aa45768"}, + {file = "ruff-0.4.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9da73eb616b3241a307b837f32756dc20a0b07e2bcb694fec73699c93d04a69e"}, + {file = "ruff-0.4.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:958b4ea5589706a81065e2a776237de2ecc3e763342e5cc8e02a4a4d8a5e6f95"}, + {file = "ruff-0.4.4-py3-none-win32.whl", hash = "sha256:cb53473849f011bca6e754f2cdf47cafc9c4f4ff4570003a0dad0b9b6890e876"}, + {file = "ruff-0.4.4-py3-none-win_amd64.whl", hash = "sha256:424e5b72597482543b684c11def82669cc6b395aa8cc69acc1858b5ef3e5daae"}, + {file = "ruff-0.4.4-py3-none-win_arm64.whl", hash = "sha256:39df0537b47d3b597293edbb95baf54ff5b49589eb7ff41926d8243caa995ea6"}, + {file = "ruff-0.4.4.tar.gz", hash = "sha256:f87ea42d5cdebdc6a69761a9d0bc83ae9b3b30d0ad78952005ba6568d6c022af"}, ] [[package]] name = "setuptools" -version = "69.5.1" +version = "70.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, - {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, + {file = "setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4"}, + {file = "setuptools-70.0.0.tar.gz", hash = "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "sgmllib3k" @@ -1725,4 +1724,4 @@ watchdog = ["watchdog (>=2.3)"] [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "1306d3bb68f23a7887a7cb506ea7095ab0d66a779b0ccfaf36ab886b552cd186" +content-hash = "069d3a9e95892d8e057e97858f342469cb3543f8bde8d893c3592583b0d36948" diff --git a/pyproject.toml b/pyproject.toml index 1b9988a..ed2c0f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,23 +7,24 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.12" -python-dotenv = "^1.0.1" -reader = "^3.12" fastapi = "^0.111.0" -jinja2 = "^3.1.4" -python-multipart = "^0.0.9" humanize = "^4.9.0" +jinja2 = "^3.1.4" +python-dotenv = "^1.0.1" +python-multipart = "^0.0.9" +reader = "^3.12" [tool.poetry.group.dev.dependencies] -ruff = "^0.3.0" djlint = "^1.34.1" pre-commit = "^3.7.1" +ruff = "^0.4.4" [build-system] build-backend = "poetry.core.masonry.api" requires = ["poetry-core"] [tool.ruff] +target-version = "py312" fix = true unsafe-fixes = true preview = true diff --git a/templates/add.html b/templates/add.html new file mode 100644 index 0000000..be6af9f --- /dev/null +++ b/templates/add.html @@ -0,0 +1,16 @@ +{% extends "base.html" %} +{% block content %} +