Compare commits

..

12 Commits

Author SHA1 Message Date
dgtlmoon
8120f00148 0.50.11
Some checks failed
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built 📦 package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v7 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v8 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64/v8 (main) (push) Has been cancelled
2025-08-28 22:11:36 +02:00
dependabot[bot]
127abf49f1 Bump cryptography from 43.0.1 to 44.0.1 (#3399) 2025-08-28 21:20:15 +02:00
dgtlmoon
db81c3c5e2 Cryptography library - pinning version 2025-08-28 20:41:59 +02:00
dgtlmoon
9952af7a52 UI - Improving "real-time updates offline" message 2025-08-28 20:35:20 +02:00
dgtlmoon
790577c1b6 Build - Adding new cryptography library, solving apprise plugin issues (#3398) #3397 2025-08-28 20:29:21 +02:00
dgtlmoon
bab362fb7d Update api-spec.yaml
Some checks failed
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built 📦 package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v7 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v8 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64/v8 (main) (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
2025-08-28 14:38:19 +02:00
dgtlmoon
a177d02406 API - API endpoint call validation against OpenAPI specification YML also (#3386) 2025-08-28 14:36:28 +02:00
dgtlmoon
8b8f280565 API Docs - Improve descriptions
Some checks failed
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built 📦 package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
2025-08-25 13:28:04 +02:00
dgtlmoon
e752875504 API Doc rebuild
Some checks failed
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built 📦 package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
2025-08-24 16:47:07 +02:00
dgtlmoon
0a4562fc09 Bump API Docs slightly 2025-08-24 16:46:20 +02:00
dgtlmoon
c84ac2eab1 Update settings.html text
Some checks failed
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built 📦 package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
2025-08-24 00:55:44 +02:00
dgtlmoon
3ae07ac633 API - Use OpenAPI docs (#3384) 2025-08-24 00:48:17 +02:00
17 changed files with 307 additions and 201 deletions

View File

@@ -15,6 +15,10 @@ jobs:
ruff check . --select E9,F63,F7,F82 ruff check . --select E9,F63,F7,F82
# Complete check with errors treated as warnings # Complete check with errors treated as warnings
ruff check . --exit-zero ruff check . --exit-zero
- name: Validate OpenAPI spec
run: |
pip install openapi-spec-validator
python3 -c "from openapi_spec_validator import validate_spec; import yaml; validate_spec(yaml.safe_load(open('docs/api-spec.yaml')))"
test-application-3-10: test-application-3-10:
needs: lint-code needs: lint-code

View File

@@ -5,7 +5,6 @@ ARG PYTHON_VERSION=3.11
FROM python:${PYTHON_VERSION}-slim-bookworm AS builder FROM python:${PYTHON_VERSION}-slim-bookworm AS builder
# See `cryptography` pin comment in requirements.txt # See `cryptography` pin comment in requirements.txt
ARG CRYPTOGRAPHY_DONT_BUILD_RUST=1
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
g++ \ g++ \
@@ -17,6 +16,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
libxslt-dev \ libxslt-dev \
make \ make \
patch \ patch \
pkg-config \
zlib1g-dev zlib1g-dev
RUN mkdir /install RUN mkdir /install
@@ -26,6 +26,14 @@ COPY requirements.txt /requirements.txt
# Use cache mounts and multiple wheel sources for faster ARM builds # Use cache mounts and multiple wheel sources for faster ARM builds
ENV PIP_CACHE_DIR=/tmp/pip-cache ENV PIP_CACHE_DIR=/tmp/pip-cache
# Help Rust find OpenSSL for cryptography package compilation on ARM
ENV PKG_CONFIG_PATH="/usr/lib/pkgconfig:/usr/lib/arm-linux-gnueabihf/pkgconfig:/usr/lib/aarch64-linux-gnu/pkgconfig"
ENV PKG_CONFIG_ALLOW_SYSTEM_CFLAGS=1
ENV OPENSSL_DIR="/usr"
ENV OPENSSL_LIB_DIR="/usr/lib/arm-linux-gnueabihf"
ENV OPENSSL_INCLUDE_DIR="/usr/include/openssl"
# Additional environment variables for cryptography Rust build
ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1
RUN --mount=type=cache,target=/tmp/pip-cache \ RUN --mount=type=cache,target=/tmp/pip-cache \
pip install \ pip install \
--extra-index-url https://www.piwheels.org/simple \ --extra-index-url https://www.piwheels.org/simple \

View File

@@ -2,7 +2,7 @@
# Read more https://github.com/dgtlmoon/changedetection.io/wiki # Read more https://github.com/dgtlmoon/changedetection.io/wiki
__version__ = '0.50.10' __version__ = '0.50.11'
from changedetectionio.strtobool import strtobool from changedetectionio.strtobool import strtobool
from json.decoder import JSONDecodeError from json.decoder import JSONDecodeError

View File

@@ -3,7 +3,7 @@ from changedetectionio.strtobool import strtobool
from flask_restful import abort, Resource from flask_restful import abort, Resource
from flask import request from flask import request
import validators import validators
from . import auth from . import auth, validate_openapi_request
class Import(Resource): class Import(Resource):
@@ -12,6 +12,7 @@ class Import(Resource):
self.datastore = kwargs['datastore'] self.datastore = kwargs['datastore']
@auth.check_token @auth.check_token
@validate_openapi_request('importWatches')
def post(self): def post(self):
"""Import a list of watched URLs.""" """Import a list of watched URLs."""

View File

@@ -1,9 +1,7 @@
from flask_expects_json import expects_json from flask_expects_json import expects_json
from flask_restful import Resource from flask_restful import Resource, abort
from . import auth
from flask_restful import abort, Resource
from flask import request from flask import request
from . import auth from . import auth, validate_openapi_request
from . import schema_create_notification_urls, schema_delete_notification_urls from . import schema_create_notification_urls, schema_delete_notification_urls
class Notifications(Resource): class Notifications(Resource):
@@ -12,6 +10,7 @@ class Notifications(Resource):
self.datastore = kwargs['datastore'] self.datastore = kwargs['datastore']
@auth.check_token @auth.check_token
@validate_openapi_request('getNotifications')
def get(self): def get(self):
"""Return Notification URL List.""" """Return Notification URL List."""
@@ -22,6 +21,7 @@ class Notifications(Resource):
}, 200 }, 200
@auth.check_token @auth.check_token
@validate_openapi_request('addNotifications')
@expects_json(schema_create_notification_urls) @expects_json(schema_create_notification_urls)
def post(self): def post(self):
"""Create Notification URLs.""" """Create Notification URLs."""
@@ -49,6 +49,7 @@ class Notifications(Resource):
return {'notification_urls': added_urls}, 201 return {'notification_urls': added_urls}, 201
@auth.check_token @auth.check_token
@validate_openapi_request('replaceNotifications')
@expects_json(schema_create_notification_urls) @expects_json(schema_create_notification_urls)
def put(self): def put(self):
"""Replace Notification URLs.""" """Replace Notification URLs."""
@@ -71,6 +72,7 @@ class Notifications(Resource):
return {'notification_urls': clean_urls}, 200 return {'notification_urls': clean_urls}, 200
@auth.check_token @auth.check_token
@validate_openapi_request('deleteNotifications')
@expects_json(schema_delete_notification_urls) @expects_json(schema_delete_notification_urls)
def delete(self): def delete(self):
"""Delete Notification URLs.""" """Delete Notification URLs."""

View File

@@ -1,6 +1,6 @@
from flask_restful import Resource, abort from flask_restful import Resource, abort
from flask import request from flask import request
from . import auth from . import auth, validate_openapi_request
class Search(Resource): class Search(Resource):
def __init__(self, **kwargs): def __init__(self, **kwargs):
@@ -8,6 +8,7 @@ class Search(Resource):
self.datastore = kwargs['datastore'] self.datastore = kwargs['datastore']
@auth.check_token @auth.check_token
@validate_openapi_request('searchWatches')
def get(self): def get(self):
"""Search for watches by URL or title text.""" """Search for watches by URL or title text."""
query = request.args.get('q', '').strip() query = request.args.get('q', '').strip()

View File

@@ -1,5 +1,5 @@
from flask_restful import Resource from flask_restful import Resource
from . import auth from . import auth, validate_openapi_request
class SystemInfo(Resource): class SystemInfo(Resource):
@@ -9,6 +9,7 @@ class SystemInfo(Resource):
self.update_q = kwargs['update_q'] self.update_q = kwargs['update_q']
@auth.check_token @auth.check_token
@validate_openapi_request('getSystemInfo')
def get(self): def get(self):
"""Return system info.""" """Return system info."""
import time import time

View File

@@ -7,7 +7,7 @@ from flask import request
from . import auth from . import auth
# Import schemas from __init__.py # Import schemas from __init__.py
from . import schema_tag, schema_create_tag, schema_update_tag from . import schema_tag, schema_create_tag, schema_update_tag, validate_openapi_request
class Tag(Resource): class Tag(Resource):
@@ -19,6 +19,7 @@ class Tag(Resource):
# Get information about a single tag # Get information about a single tag
# curl http://localhost:5000/api/v1/tag/<string:uuid> # curl http://localhost:5000/api/v1/tag/<string:uuid>
@auth.check_token @auth.check_token
@validate_openapi_request('getTag')
def get(self, uuid): def get(self, uuid):
"""Get data for a single tag/group, toggle notification muting, or recheck all.""" """Get data for a single tag/group, toggle notification muting, or recheck all."""
from copy import deepcopy from copy import deepcopy
@@ -50,6 +51,7 @@ class Tag(Resource):
return tag return tag
@auth.check_token @auth.check_token
@validate_openapi_request('deleteTag')
def delete(self, uuid): def delete(self, uuid):
"""Delete a tag/group and remove it from all watches.""" """Delete a tag/group and remove it from all watches."""
if not self.datastore.data['settings']['application']['tags'].get(uuid): if not self.datastore.data['settings']['application']['tags'].get(uuid):
@@ -66,6 +68,7 @@ class Tag(Resource):
return 'OK', 204 return 'OK', 204
@auth.check_token @auth.check_token
@validate_openapi_request('updateTag')
@expects_json(schema_update_tag) @expects_json(schema_update_tag)
def put(self, uuid): def put(self, uuid):
"""Update tag information.""" """Update tag information."""
@@ -80,6 +83,7 @@ class Tag(Resource):
@auth.check_token @auth.check_token
@validate_openapi_request('createTag')
# Only cares for {'title': 'xxxx'} # Only cares for {'title': 'xxxx'}
def post(self): def post(self):
"""Create a single tag/group.""" """Create a single tag/group."""
@@ -100,6 +104,7 @@ class Tags(Resource):
self.datastore = kwargs['datastore'] self.datastore = kwargs['datastore']
@auth.check_token @auth.check_token
@validate_openapi_request('listTags')
def get(self): def get(self):
"""List tags/groups.""" """List tags/groups."""
result = {} result = {}

View File

@@ -11,7 +11,7 @@ from . import auth
import copy import copy
# Import schemas from __init__.py # Import schemas from __init__.py
from . import schema, schema_create_watch, schema_update_watch from . import schema, schema_create_watch, schema_update_watch, validate_openapi_request
class Watch(Resource): class Watch(Resource):
@@ -25,6 +25,7 @@ class Watch(Resource):
# @todo - version2 - ?muted and ?paused should be able to be called together, return the watch struct not "OK" # @todo - version2 - ?muted and ?paused should be able to be called together, return the watch struct not "OK"
# ?recheck=true # ?recheck=true
@auth.check_token @auth.check_token
@validate_openapi_request('getWatch')
def get(self, uuid): def get(self, uuid):
"""Get information about a single watch, recheck, pause, or mute.""" """Get information about a single watch, recheck, pause, or mute."""
from copy import deepcopy from copy import deepcopy
@@ -57,6 +58,7 @@ class Watch(Resource):
return watch return watch
@auth.check_token @auth.check_token
@validate_openapi_request('deleteWatch')
def delete(self, uuid): def delete(self, uuid):
"""Delete a watch and related history.""" """Delete a watch and related history."""
if not self.datastore.data['watching'].get(uuid): if not self.datastore.data['watching'].get(uuid):
@@ -66,6 +68,7 @@ class Watch(Resource):
return 'OK', 204 return 'OK', 204
@auth.check_token @auth.check_token
@validate_openapi_request('updateWatch')
@expects_json(schema_update_watch) @expects_json(schema_update_watch)
def put(self, uuid): def put(self, uuid):
"""Update watch information.""" """Update watch information."""
@@ -91,6 +94,7 @@ class WatchHistory(Resource):
# Get a list of available history for a watch by UUID # Get a list of available history for a watch by UUID
# curl http://localhost:5000/api/v1/watch/<string:uuid>/history # curl http://localhost:5000/api/v1/watch/<string:uuid>/history
@auth.check_token @auth.check_token
@validate_openapi_request('getWatchHistory')
def get(self, uuid): def get(self, uuid):
"""Get a list of all historical snapshots available for a watch.""" """Get a list of all historical snapshots available for a watch."""
watch = self.datastore.data['watching'].get(uuid) watch = self.datastore.data['watching'].get(uuid)
@@ -105,6 +109,7 @@ class WatchSingleHistory(Resource):
self.datastore = kwargs['datastore'] self.datastore = kwargs['datastore']
@auth.check_token @auth.check_token
@validate_openapi_request('getWatchSnapshot')
def get(self, uuid, timestamp): def get(self, uuid, timestamp):
"""Get single snapshot from watch.""" """Get single snapshot from watch."""
watch = self.datastore.data['watching'].get(uuid) watch = self.datastore.data['watching'].get(uuid)
@@ -138,6 +143,7 @@ class WatchFavicon(Resource):
self.datastore = kwargs['datastore'] self.datastore = kwargs['datastore']
@auth.check_token @auth.check_token
@validate_openapi_request('getWatchFavicon')
def get(self, uuid): def get(self, uuid):
"""Get favicon for a watch.""" """Get favicon for a watch."""
watch = self.datastore.data['watching'].get(uuid) watch = self.datastore.data['watching'].get(uuid)
@@ -172,6 +178,7 @@ class CreateWatch(Resource):
self.update_q = kwargs['update_q'] self.update_q = kwargs['update_q']
@auth.check_token @auth.check_token
@validate_openapi_request('createWatch')
@expects_json(schema_create_watch) @expects_json(schema_create_watch)
def post(self): def post(self):
"""Create a single watch.""" """Create a single watch."""
@@ -207,6 +214,7 @@ class CreateWatch(Resource):
return "Invalid or unsupported URL", 400 return "Invalid or unsupported URL", 400
@auth.check_token @auth.check_token
@validate_openapi_request('listWatches')
def get(self): def get(self):
"""List watches.""" """List watches."""
list = {} list = {}

View File

@@ -1,4 +1,9 @@
import copy import copy
import yaml
import functools
from flask import request, abort
from openapi_core import OpenAPI
from openapi_core.contrib.flask import FlaskOpenAPIRequest
from . import api_schema from . import api_schema
from ..model import watch_base from ..model import watch_base
@@ -25,6 +30,38 @@ schema_create_notification_urls['required'] = ['notification_urls']
schema_delete_notification_urls = copy.deepcopy(schema_notification_urls) schema_delete_notification_urls = copy.deepcopy(schema_notification_urls)
schema_delete_notification_urls['required'] = ['notification_urls'] schema_delete_notification_urls['required'] = ['notification_urls']
# Load OpenAPI spec for validation
_openapi_spec = None
def get_openapi_spec():
global _openapi_spec
if _openapi_spec is None:
import os
spec_path = os.path.join(os.path.dirname(__file__), '../../docs/api-spec.yaml')
with open(spec_path, 'r') as f:
spec_dict = yaml.safe_load(f)
_openapi_spec = OpenAPI.from_dict(spec_dict)
return _openapi_spec
def validate_openapi_request(operation_id):
"""Decorator to validate incoming requests against OpenAPI spec."""
def decorator(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
try:
spec = get_openapi_spec()
openapi_request = FlaskOpenAPIRequest(request)
result = spec.unmarshal_request(openapi_request, operation_id)
if result.errors:
abort(400, message=f"OpenAPI validation failed: {result.errors}")
return f(*args, **kwargs)
except Exception as e:
# If OpenAPI validation fails, log but don't break existing functionality
print(f"OpenAPI validation warning for {operation_id}: {e}")
return f(*args, **kwargs)
return wrapper
return decorator
# Import all API resources # Import all API resources
from .Watch import Watch, WatchHistory, WatchSingleHistory, CreateWatch, WatchFavicon from .Watch import Watch, WatchHistory, WatchSingleHistory, CreateWatch, WatchFavicon
from .Tags import Tags, Tag from .Tags import Tags, Tag

View File

@@ -203,7 +203,7 @@ nav
<div class="tab-pane-inner" id="api"> <div class="tab-pane-inner" id="api">
<h4>API Access</h4> <h4>API Access</h4>
<p>Drive your changedetection.io via API, More about <a href="https://github.com/dgtlmoon/changedetection.io/wiki/API-Reference">API access here</a></p> <p>Drive your changedetection.io via API, More about <a href="https://changedetection.io/docs/api_v1/index.html">API access and examples here</a>.</p>
<div class="pure-control-group"> <div class="pure-control-group">
{{ render_checkbox_field(form.application.form.api_access_token_enabled) }} {{ render_checkbox_field(form.application.form.api_access_token_enabled) }}

View File

@@ -1130,11 +1130,12 @@ ul {
} }
#realtime-conn-error { #realtime-conn-error {
position: absolute; position: fixed;
bottom: 0; bottom: 0;
left: 30px; left: 0;
background: var(--color-warning); background: var(--color-warning);
padding: 10px; padding: 10px;
font-size: 0.8rem; font-size: 0.8rem;
color: #fff; color: #fff;
opacity: 0.8;
} }

File diff suppressed because one or more lines are too long

View File

@@ -236,7 +236,7 @@
<script src="{{url_for('static_content', group='js', filename='toggle-theme.js')}}" defer></script> <script src="{{url_for('static_content', group='js', filename='toggle-theme.js')}}" defer></script>
<div id="checking-now-fixed-tab" style="display: none;"><span class="spinner"></span><span>&nbsp;Checking now</span></div> <div id="checking-now-fixed-tab" style="display: none;"><span class="spinner"></span><span>&nbsp;Checking now</span></div>
<div id="realtime-conn-error" style="display:none">Offline</div> <div id="realtime-conn-error" style="display:none">Real-time updates offline</div>
</body> </body>
</html> </html>

View File

@@ -6,7 +6,7 @@ info:
REST API for managing Page watches, Group tags, and Notifications. REST API for managing Page watches, Group tags, and Notifications.
changedetection.io can be driven by its built in simple API, in the examples below you will also find `curl` command line examples to help you. changedetection.io can be driven by its built in simple API, in the examples below you will also find `curl` command line and `python` examples to help you get started faster.
## Where to find my API key? ## Where to find my API key?
@@ -28,7 +28,7 @@ info:
For example: `x-api-key: YOUR_API_KEY` For example: `x-api-key: YOUR_API_KEY`
version: 1.0.0 version: 0.1.0
contact: contact:
name: ChangeDetection.io name: ChangeDetection.io
url: https://github.com/dgtlmoon/changedetection.io url: https://github.com/dgtlmoon/changedetection.io
@@ -89,6 +89,8 @@ tags:
Configure global notification endpoints that can be used across all your watches. Supports various Configure global notification endpoints that can be used across all your watches. Supports various
notification services including email, Discord, Slack, webhooks, and many other popular platforms. notification services including email, Discord, Slack, webhooks, and many other popular platforms.
These settings serve as defaults that can be overridden at the individual watch or tag level. These settings serve as defaults that can be overridden at the individual watch or tag level.
The notification syntax uses [https://github.com/caronc/apprise](https://github.com/caronc/apprise).
- name: Search - name: Search
description: | description: |
@@ -123,7 +125,7 @@ components:
uuid: uuid:
type: string type: string
format: uuid format: uuid
description: Unique identifier for the watch description: Unique identifier for the web page change monitor (watch)
readOnly: true readOnly: true
url: url:
type: string type: string
@@ -132,11 +134,11 @@ components:
maxLength: 5000 maxLength: 5000
title: title:
type: string type: string
description: Custom title for the watch description: Custom title for the web page change monitor (watch)
maxLength: 5000 maxLength: 5000
tag: tag:
type: string type: string
description: Tag UUID to associate with this watch description: Tag UUID to associate with this web page change monitor (watch)
maxLength: 5000 maxLength: 5000
tags: tags:
type: array type: array
@@ -145,7 +147,7 @@ components:
description: Array of tag UUIDs description: Array of tag UUIDs
paused: paused:
type: boolean type: boolean
description: Whether the watch is paused description: Whether the web page change monitor (watch) is paused
muted: muted:
type: boolean type: boolean
description: Whether notifications are muted description: Whether notifications are muted
@@ -195,7 +197,7 @@ components:
type: array type: array
items: items:
type: string type: string
description: Notification URLs for this watch description: Notification URLs for this web page change monitor (watch)
notification_title: notification_title:
type: string type: string
description: Custom notification title description: Custom notification title
@@ -265,7 +267,7 @@ components:
type: array type: array
items: items:
type: string type: string
description: Default notification URLs for watches with this tag description: Default notification URLs for web page change monitors (watches) with this tag
notification_muted: notification_muted:
type: boolean type: boolean
description: Whether notifications are muted for this tag description: Whether notifications are muted for this tag
@@ -289,7 +291,7 @@ components:
properties: properties:
watch_count: watch_count:
type: integer type: integer
description: Total number of watches description: Total number of web page change monitors (watches)
tag_count: tag_count:
type: integer type: integer
description: Total number of tags description: Total number of tags
@@ -307,7 +309,7 @@ components:
type: object type: object
additionalProperties: additionalProperties:
$ref: '#/components/schemas/Watch' $ref: '#/components/schemas/Watch'
description: Dictionary of matching watches keyed by UUID description: Dictionary of matching web page change monitors (watches) keyed by UUID
WatchHistory: WatchHistory:
type: object type: object
@@ -326,9 +328,10 @@ components:
paths: paths:
/watch: /watch:
get: get:
operationId: listWatches
tags: [Watch Management] tags: [Watch Management]
summary: List all watches summary: List all watches
description: Return concise list of available watches and basic info description: Return concise list of available web page change monitors (watches) and basic info
x-code-samples: x-code-samples:
- lang: 'curl' - lang: 'curl'
source: | source: |
@@ -388,9 +391,10 @@ paths:
last_checked: 1640998800 last_checked: 1640998800
last_changed: 1640995200 last_changed: 1640995200
post: post:
operationId: createWatch
tags: [Watch Management] tags: [Watch Management]
summary: Create a new watch summary: Create a new watch
description: Create a single watch. Requires at least 'url' to be set. description: Create a single web page change monitor (watch). Requires at least 'url' to be set.
x-code-samples: x-code-samples:
- lang: 'curl' - lang: 'curl'
source: | source: |
@@ -436,7 +440,7 @@ paths:
hours: 1 hours: 1
responses: responses:
'200': '200':
description: Watch created successfully description: Web page change monitor (watch) created successfully
content: content:
text/plain: text/plain:
schema: schema:
@@ -451,10 +455,10 @@ paths:
/watch/{uuid}: /watch/{uuid}:
get: get:
operationId: getSingleWatch operationId: getWatch
tags: [Watch Management] tags: [Watch Management]
summary: Get single watch summary: Get single watch
description: Retrieve watch information and set muted/paused status. Returns the FULL Watch JSON. description: Retrieve web page change monitor (watch) information and set muted/paused status. Returns the FULL Watch JSON.
x-code-samples: x-code-samples:
- lang: 'curl' - lang: 'curl'
source: | source: |
@@ -472,13 +476,13 @@ paths:
- name: uuid - name: uuid
in: path in: path
required: true required: true
description: Watch unique ID description: Web page change monitor (watch) unique ID
schema: schema:
type: string type: string
format: uuid format: uuid
- name: recheck - name: recheck
in: query in: query
description: Recheck this watch description: Recheck this web page change monitor (watch)
schema: schema:
type: string type: string
enum: ["1", "true"] enum: ["1", "true"]
@@ -506,7 +510,7 @@ paths:
type: string type: string
example: "OK" example: "OK"
'404': '404':
description: Watch not found description: Web page change monitor (watch) not found
content: content:
application/json: application/json:
schema: schema:
@@ -516,7 +520,7 @@ paths:
operationId: updateWatch operationId: updateWatch
tags: [Watch Management] tags: [Watch Management]
summary: Update watch summary: Update watch
description: Update an existing watch using JSON. Accepts the same structure as returned in [get single watch information](#operation/getSingleWatch). description: Update an existing web page change monitor (watch) using JSON. Accepts the same structure as returned in [get single watch information](#operation/getWatch).
x-code-samples: x-code-samples:
- lang: 'curl' - lang: 'curl'
source: | source: |
@@ -549,7 +553,7 @@ paths:
- name: uuid - name: uuid
in: path in: path
required: true required: true
description: Watch unique ID description: Web page change monitor (watch) unique ID
schema: schema:
type: string type: string
format: uuid format: uuid
@@ -561,7 +565,7 @@ paths:
$ref: '#/components/schemas/Watch' $ref: '#/components/schemas/Watch'
responses: responses:
'200': '200':
description: Watch updated successfully description: Web page change monitor (watch) updated successfully
content: content:
text/plain: text/plain:
schema: schema:
@@ -571,9 +575,10 @@ paths:
description: Server error description: Server error
delete: delete:
operationId: deleteWatch
tags: [Watch Management] tags: [Watch Management]
summary: Delete watch summary: Delete watch
description: Delete a watch and all related history description: Delete a web page change monitor (watch) and all related history
x-code-samples: x-code-samples:
- lang: 'curl' - lang: 'curl'
source: | source: |
@@ -591,13 +596,13 @@ paths:
- name: uuid - name: uuid
in: path in: path
required: true required: true
description: Watch unique ID description: Web page change monitor (watch) unique ID
schema: schema:
type: string type: string
format: uuid format: uuid
responses: responses:
'200': '200':
description: Watch deleted successfully description: Web page change monitor (watch) deleted successfully
content: content:
text/plain: text/plain:
schema: schema:
@@ -606,9 +611,10 @@ paths:
/watch/{uuid}/history: /watch/{uuid}/history:
get: get:
operationId: getWatchHistory
tags: [Watch History] tags: [Watch History]
summary: Get watch history summary: Get watch history
description: Get a list of all historical snapshots available for a watch description: Get a list of all historical snapshots available for a web page change monitor (watch)
x-code-samples: x-code-samples:
- lang: 'curl' - lang: 'curl'
source: | source: |
@@ -626,7 +632,7 @@ paths:
- name: uuid - name: uuid
in: path in: path
required: true required: true
description: Watch unique ID description: Web page change monitor (watch) unique ID
schema: schema:
type: string type: string
format: uuid format: uuid
@@ -641,13 +647,14 @@ paths:
"1640995200": "/path/to/snapshot1.txt" "1640995200": "/path/to/snapshot1.txt"
"1640998800": "/path/to/snapshot2.txt" "1640998800": "/path/to/snapshot2.txt"
'404': '404':
description: Watch not found description: Web page change monitor (watch) not found
/watch/{uuid}/history/{timestamp}: /watch/{uuid}/history/{timestamp}:
get: get:
operationId: getWatchSnapshot
tags: [Snapshots] tags: [Snapshots]
summary: Get single snapshot summary: Get single snapshot
description: Get single snapshot from watch. Use 'latest' for the most recent snapshot. description: Get single snapshot from web page change monitor (watch). Use 'latest' for the most recent snapshot.
x-code-samples: x-code-samples:
- lang: 'curl' - lang: 'curl'
source: | source: |
@@ -666,7 +673,7 @@ paths:
- name: uuid - name: uuid
in: path in: path
required: true required: true
description: Watch unique ID description: Web page change monitor (watch) unique ID
schema: schema:
type: string type: string
format: uuid format: uuid
@@ -697,9 +704,10 @@ paths:
/watch/{uuid}/favicon: /watch/{uuid}/favicon:
get: get:
operationId: getWatchFavicon
tags: [Favicon] tags: [Favicon]
summary: Get watch favicon summary: Get watch favicon
description: Get the favicon for a watch as displayed in the watch overview list. description: Get the favicon for a web page change monitor (watch) as displayed in the watch overview list.
x-code-samples: x-code-samples:
- lang: 'curl' - lang: 'curl'
source: | source: |
@@ -719,7 +727,7 @@ paths:
- name: uuid - name: uuid
in: path in: path
required: true required: true
description: Watch unique ID description: Web page change monitor (watch) unique ID
schema: schema:
type: string type: string
format: uuid format: uuid
@@ -736,6 +744,7 @@ paths:
/tags: /tags:
get: get:
operationId: listTags
tags: [Group / Tag Management] tags: [Group / Tag Management]
summary: List all tags summary: List all tags
description: Return list of available tags/groups description: Return list of available tags/groups
@@ -772,11 +781,62 @@ paths:
notification_urls: ["discord://webhook_id/webhook_token"] notification_urls: ["discord://webhook_id/webhook_token"]
notification_muted: false notification_muted: false
/tag:
post:
operationId: createTag
tags: [Group / Tag Management]
summary: Create tag
description: Create a single tag/group
x-code-samples:
- lang: 'curl'
source: |
curl -X POST "http://localhost:5000/api/v1/tag" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Important Sites"
}'
- lang: 'Python'
source: |
import requests
headers = {
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
}
data = {'title': 'Important Sites'}
response = requests.post('http://localhost:5000/api/v1/tag',
headers=headers, json=data)
print(response.json())
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Tag'
example:
title: "Important Sites"
responses:
'201':
description: Tag created successfully
content:
application/json:
schema:
type: object
properties:
uuid:
type: string
format: uuid
description: UUID of the created tag
'400':
description: Invalid or unsupported tag
/tag/{uuid}: /tag/{uuid}:
get: get:
operationId: getTag
tags: [Group / Tag Management] tags: [Group / Tag Management]
summary: Get single tag summary: Get single tag
description: Retrieve tag information, set notification_muted status, recheck all in tag. description: Retrieve tag information, set notification_muted status, recheck all web page change monitors (watches) in tag.
x-code-samples: x-code-samples:
- lang: 'curl' - lang: 'curl'
source: | source: |
@@ -806,7 +866,7 @@ paths:
enum: [muted, unmuted] enum: [muted, unmuted]
- name: recheck - name: recheck
in: query in: query
description: Queue all watches with this tag for recheck description: Queue all web page change monitors (watches) with this tag for recheck
schema: schema:
type: string type: string
enum: ["true"] enum: ["true"]
@@ -825,6 +885,7 @@ paths:
description: Tag not found description: Tag not found
put: put:
operationId: updateTag
tags: [Group / Tag Management] tags: [Group / Tag Management]
summary: Update tag summary: Update tag
description: Update an existing tag using JSON description: Update an existing tag using JSON
@@ -875,9 +936,10 @@ paths:
description: Server error description: Server error
delete: delete:
operationId: deleteTag
tags: [Group / Tag Management] tags: [Group / Tag Management]
summary: Delete tag summary: Delete tag
description: Delete a tag/group and remove it from all watches description: Delete a tag/group and remove it from all web page change monitors (watches)
x-code-samples: x-code-samples:
- lang: 'curl' - lang: 'curl'
source: | source: |
@@ -903,48 +965,10 @@ paths:
'200': '200':
description: Tag deleted successfully description: Tag deleted successfully
post:
tags: [Group / Tag Management]
summary: Create tag
description: Create a single tag/group
x-code-samples:
- lang: 'curl'
source: |
curl -X POST "http://localhost:5000/api/v1/tag/550e8400-e29b-41d4-a716-446655440000" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Important Sites"
}'
- lang: 'Python'
source: |
import requests
headers = {
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
}
tag_uuid = '550e8400-e29b-41d4-a716-446655440000'
data = {'title': 'Important Sites'}
response = requests.post(f'http://localhost:5000/api/v1/tag/{tag_uuid}',
headers=headers, json=data)
print(response.text)
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Tag'
example:
title: "Important Sites"
responses:
'200':
description: Tag created successfully
'500':
description: Server error
/notifications: /notifications:
get: get:
operationId: getNotifications
tags: [Notifications] tags: [Notifications]
summary: Get notification URLs summary: Get notification URLs
description: Return the notification URL list from the configuration description: Return the notification URL list from the configuration
@@ -969,6 +993,7 @@ paths:
$ref: '#/components/schemas/NotificationUrls' $ref: '#/components/schemas/NotificationUrls'
post: post:
operationId: addNotifications
tags: [Notifications] tags: [Notifications]
summary: Add notification URLs summary: Add notification URLs
description: Add one or more notification URLs to the configuration description: Add one or more notification URLs to the configuration
@@ -1022,6 +1047,7 @@ paths:
description: Invalid input description: Invalid input
put: put:
operationId: replaceNotifications
tags: [Notifications] tags: [Notifications]
summary: Replace notification URLs summary: Replace notification URLs
description: Replace all notification URLs with the provided list (can be empty) description: Replace all notification URLs with the provided list (can be empty)
@@ -1069,6 +1095,7 @@ paths:
description: Invalid input description: Invalid input
delete: delete:
operationId: deleteNotifications
tags: [Notifications] tags: [Notifications]
summary: Delete notification URLs summary: Delete notification URLs
description: Delete one or more notification URLs from the configuration description: Delete one or more notification URLs from the configuration
@@ -1113,9 +1140,10 @@ paths:
/search: /search:
get: get:
operationId: searchWatches
tags: [Search] tags: [Search]
summary: Search watches summary: Search watches
description: Search watches by URL or title text description: Search web page change monitors (watches) by URL or title text
x-code-samples: x-code-samples:
- lang: 'curl' - lang: 'curl'
source: | source: |
@@ -1167,6 +1195,7 @@ paths:
/import: /import:
post: post:
operationId: importWatches
tags: [Import] tags: [Import]
summary: Import watch URLs summary: Import watch URLs
description: Import a list of URLs to monitor. Accepts line-separated URLs in request body. description: Import a list of URLs to monitor. Accepts line-separated URLs in request body.
@@ -1192,17 +1221,17 @@ paths:
parameters: parameters:
- name: tag_uuids - name: tag_uuids
in: query in: query
description: Tag UUID to apply to imported watches description: Tag UUID to apply to imported web page change monitors (watches)
schema: schema:
type: string type: string
- name: tag - name: tag
in: query in: query
description: Tag name to apply to imported watches description: Tag name to apply to imported web page change monitors (watches)
schema: schema:
type: string type: string
- name: proxy - name: proxy
in: query in: query
description: Proxy key to use for imported watches description: Proxy key to use for imported web page change monitors (watches)
schema: schema:
type: string type: string
- name: dedupe - name: dedupe
@@ -1237,6 +1266,7 @@ paths:
/systeminfo: /systeminfo:
get: get:
operationId: getSystemInfo
tags: [System Information] tags: [System Information]
summary: Get system information summary: Get system information
description: Return information about the current system state description: Return information about the current system state
@@ -1263,4 +1293,4 @@ paths:
watch_count: 42 watch_count: 42
tag_count: 5 tag_count: 5
uptime: "2 days, 3:45:12" uptime: "2 days, 3:45:12"
version: "0.50.10" version: "0.50.10"

File diff suppressed because one or more lines are too long

View File

@@ -41,13 +41,16 @@ jsonpath-ng~=1.5.3
# Notification library # Notification library
apprise==1.9.3 apprise==1.9.3
# - Needed for apprise/spush, and maybe others? hopefully doesnt trigger a rust compile.
# - Requires extra wheel for rPi, adds build time for arm/v8 which is not in piwheels
# Pinned to 43.0.1 for ARM compatibility (45.x may not have pre-built ARM wheels)
# Also pinned because dependabot wants specific versions
cryptography==44.0.1
# apprise mqtt https://github.com/dgtlmoon/changedetection.io/issues/315 # apprise mqtt https://github.com/dgtlmoon/changedetection.io/issues/315
# use any version other than 2.0.x due to https://github.com/eclipse/paho.mqtt.python/issues/814 # use any version other than 2.0.x due to https://github.com/eclipse/paho.mqtt.python/issues/814
paho-mqtt!=2.0.* paho-mqtt!=2.0.*
# Requires extra wheel for rPi
#cryptography~=42.0.8
# Used for CSS filtering # Used for CSS filtering
beautifulsoup4>=4.0.0 beautifulsoup4>=4.0.0
@@ -89,6 +92,9 @@ pytest-flask ~=1.2
# Anything 4.0 and up but not 5.0 # Anything 4.0 and up but not 5.0
jsonschema ~= 4.0 jsonschema ~= 4.0
# OpenAPI validation support
openapi-core[flask] >= 0.19.0
loguru loguru