From 308f30b2e8a88448c431fd2b2280e168d909ebe5 Mon Sep 17 00:00:00 2001 From: dgtlmoon Date: Wed, 9 Jul 2025 15:16:22 +0200 Subject: [PATCH] UI - Adding Favicon support to watch overview lister page + FavIcon API (#3196) --- changedetectionio/api/Watch.py | 34 ++- changedetectionio/api/__init__.py | 2 +- changedetectionio/async_update_worker.py | 6 + .../watchlist/templates/watch-overview.html | 72 +++--- changedetectionio/conditions/__init__.py | 2 - .../content_fetchers/__init__.py | 1 + changedetectionio/content_fetchers/base.py | 1 + .../content_fetchers/playwright.py | 9 +- .../content_fetchers/puppeteer.py | 13 +- .../content_fetchers/res/favicon-fetcher.js | 70 ++++++ changedetectionio/flask_app.py | 25 +- changedetectionio/model/App.py | 2 +- changedetectionio/model/Watch.py | 128 ++++++++++ changedetectionio/realtime/socket_server.py | 54 ++-- changedetectionio/static/js/plugins.js | 1 + changedetectionio/static/js/realtime.js | 14 +- .../styles/scss/parts/_lister_extra.scss | 87 +++++++ .../scss/parts/_watch_table-mobile.scss | 172 +++++++++++++ .../styles/scss/parts/_watch_table.scss | 21 +- .../static/styles/scss/styles.scss | 111 +-------- changedetectionio/static/styles/styles.css | 231 +++++++++++++----- .../tests/fetchers/test_content.py | 29 ++- 22 files changed, 826 insertions(+), 259 deletions(-) create mode 100644 changedetectionio/content_fetchers/res/favicon-fetcher.js create mode 100644 changedetectionio/static/styles/scss/parts/_lister_extra.scss create mode 100644 changedetectionio/static/styles/scss/parts/_watch_table-mobile.scss diff --git a/changedetectionio/api/Watch.py b/changedetectionio/api/Watch.py index c6011934..c9a1def1 100644 --- a/changedetectionio/api/Watch.py +++ b/changedetectionio/api/Watch.py @@ -5,7 +5,7 @@ from flask_expects_json import expects_json from changedetectionio import queuedWatchMetaData from changedetectionio import worker_handler from flask_restful import abort, Resource -from flask import request, make_response +from flask import request, make_response, send_from_directory import validators from . import auth import copy @@ -191,6 +191,38 @@ class WatchSingleHistory(Resource): return response +class WatchFavicon(Resource): + def __init__(self, **kwargs): + # datastore is a black box dependency + self.datastore = kwargs['datastore'] + + @auth.check_token + def get(self, uuid): + """ + @api {get} /api/v1/watch//favicon Get Favicon for a watch + @apiDescription Requires watch `uuid` + @apiExample {curl} Example usage: + curl http://localhost:5000/api/v1/watch/cc0cfffa-f449-477b-83ea-0caafd1dc091/favicon -H"x-api-key:813031b16330fe25e3780cf0325daa45" + @apiName Get latest Favicon + @apiGroup Watch History + @apiSuccess (200) {String} OK + @apiSuccess (404) {String} ERR Not found + """ + watch = self.datastore.data['watching'].get(uuid) + if not watch: + abort(404, message=f"No watch exists with the UUID of {uuid}") + + favicon_filename = watch.get_favicon_filename() + if favicon_filename: + import mimetypes + mime, encoding = mimetypes.guess_type(favicon_filename) + response = make_response(send_from_directory(watch.watch_data_dir, favicon_filename)) + response.headers['Content-type'] = mime + response.headers['Cache-Control'] = 'max-age=300, must-revalidate' # Cache for 5 minutes, then revalidate + return response + + abort(404, message=f'No Favicon available for {uuid}') + class CreateWatch(Resource): def __init__(self, **kwargs): diff --git a/changedetectionio/api/__init__.py b/changedetectionio/api/__init__.py index 470890cf..fcaec4ab 100644 --- a/changedetectionio/api/__init__.py +++ b/changedetectionio/api/__init__.py @@ -26,7 +26,7 @@ schema_delete_notification_urls = copy.deepcopy(schema_notification_urls) schema_delete_notification_urls['required'] = ['notification_urls'] # Import all API resources -from .Watch import Watch, WatchHistory, WatchSingleHistory, CreateWatch +from .Watch import Watch, WatchHistory, WatchSingleHistory, CreateWatch, WatchFavicon from .Tags import Tags, Tag from .Import import Import from .SystemInfo import SystemInfo diff --git a/changedetectionio/async_update_worker.py b/changedetectionio/async_update_worker.py index ae0501b0..c1f5f338 100644 --- a/changedetectionio/async_update_worker.py +++ b/changedetectionio/async_update_worker.py @@ -353,6 +353,12 @@ async def async_update_worker(worker_id, q, notification_q, app, datastore): except Exception as e: pass + # Store favicon if necessary + if update_handler.fetcher.favicon_blob and update_handler.fetcher.favicon_blob.get('base64'): + watch.bump_favicon(url=update_handler.fetcher.favicon_blob.get('url'), + favicon_base_64=update_handler.fetcher.favicon_blob.get('base64') + ) + datastore.update_watch(uuid=uuid, update_obj={'fetch_time': round(time.time() - fetch_start_time, 3), 'check_count': count}) diff --git a/changedetectionio/blueprint/watchlist/templates/watch-overview.html b/changedetectionio/blueprint/watchlist/templates/watch-overview.html index 8e0e014b..e5359c3d 100644 --- a/changedetectionio/blueprint/watchlist/templates/watch-overview.html +++ b/changedetectionio/blueprint/watchlist/templates/watch-overview.html @@ -4,6 +4,7 @@ +