mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2025-11-30 13:23:28 +00:00
111 lines
4.6 KiB
Python
111 lines
4.6 KiB
Python
from flask import make_response, request, url_for
|
|
from feedgen.feed import FeedGenerator
|
|
import datetime
|
|
import pytz
|
|
|
|
from ._util import generate_watch_guid, generate_watch_diff_content
|
|
|
|
|
|
def construct_single_watch_routes(rss_blueprint, datastore):
|
|
"""
|
|
Construct RSS feed routes for single watches.
|
|
|
|
Args:
|
|
rss_blueprint: The Flask blueprint to add routes to
|
|
datastore: The ChangeDetectionStore instance
|
|
"""
|
|
|
|
@rss_blueprint.route("/watch/<string:uuid>", methods=['GET'])
|
|
def rss_single_watch(uuid):
|
|
"""
|
|
Display the most recent changes for a single watch as RSS feed.
|
|
Returns RSS XML with multiple entries showing diffs between consecutive snapshots.
|
|
The number of entries is controlled by the rss_diff_length setting.
|
|
"""
|
|
# Always requires token set
|
|
app_rss_token = datastore.data['settings']['application'].get('rss_access_token')
|
|
rss_url_token = request.args.get('token')
|
|
rss_content_format = datastore.data['settings']['application'].get('rss_content_format')
|
|
|
|
if rss_url_token != app_rss_token:
|
|
return "Access denied, bad token", 403
|
|
|
|
# Get the watch by UUID
|
|
watch = datastore.data['watching'].get(uuid)
|
|
if not watch:
|
|
return f"Watch with UUID {uuid} not found", 404
|
|
|
|
# Check if watch has at least 2 history snapshots
|
|
dates = list(watch.history.keys())
|
|
if len(dates) < 2:
|
|
return f"Watch {uuid} does not have enough history snapshots to show changes (need at least 2)", 400
|
|
|
|
# Add uuid to watch for proper functioning
|
|
watch['uuid'] = uuid
|
|
|
|
# Get the number of diffs to include (default: 5)
|
|
rss_diff_length = datastore.data['settings']['application'].get('rss_diff_length', 5)
|
|
|
|
# Calculate how many diffs we can actually show (limited by available history)
|
|
# We need at least 2 snapshots to create 1 diff
|
|
max_possible_diffs = len(dates) - 1
|
|
num_diffs = min(rss_diff_length, max_possible_diffs) if rss_diff_length > 0 else max_possible_diffs
|
|
|
|
# Create RSS feed
|
|
fg = FeedGenerator()
|
|
fg.title(f'changedetection.io - {watch.label}')
|
|
fg.description('Changes')
|
|
fg.link(href='https://changedetection.io')
|
|
|
|
# Loop through history and create RSS entries for each diff
|
|
# Add entries in reverse order because feedgen reverses them
|
|
# This way, the newest change appears first in the final RSS
|
|
for i in range(num_diffs - 1, -1, -1):
|
|
# Calculate indices for this diff (working backwards from newest)
|
|
# i=0: compare dates[-2] to dates[-1] (most recent change)
|
|
# i=1: compare dates[-3] to dates[-2] (previous change)
|
|
# etc.
|
|
date_index_to = -(i + 1)
|
|
date_index_from = -(i + 2)
|
|
|
|
try:
|
|
# Generate the diff content for this pair of snapshots
|
|
content, watch_label = generate_watch_diff_content(
|
|
watch, dates, rss_content_format, datastore,
|
|
date_index_from=date_index_from,
|
|
date_index_to=date_index_to
|
|
)
|
|
|
|
# Create a unique GUID for this specific diff
|
|
timestamp_to = dates[date_index_to]
|
|
timestamp_from = dates[date_index_from]
|
|
guid = f"{watch['uuid']}/{timestamp_to}"
|
|
|
|
fe = fg.add_entry()
|
|
|
|
# Include a link to the diff page with specific versions
|
|
diff_link = {'href': url_for('ui.ui_views.diff_history_page',
|
|
uuid=watch['uuid'],
|
|
from_version=timestamp_from,
|
|
to_version=timestamp_to,
|
|
_external=True)}
|
|
fe.link(link=diff_link)
|
|
|
|
# Add timestamp info to title to distinguish different diffs
|
|
fe.title(title=f"{watch_label} - Change {i+1}")
|
|
fe.content(content=content, type='CDATA')
|
|
fe.guid(guid, permalink=False)
|
|
|
|
# Use the timestamp of the "to" snapshot for pubDate
|
|
dt = datetime.datetime.fromtimestamp(int(timestamp_to))
|
|
dt = dt.replace(tzinfo=pytz.UTC)
|
|
fe.pubDate(dt)
|
|
|
|
except (IndexError, FileNotFoundError) as e:
|
|
# Skip this diff if we can't generate it
|
|
continue
|
|
|
|
response = make_response(fg.rss_str())
|
|
response.headers.set('Content-Type', 'application/rss+xml;charset=utf-8')
|
|
return response
|