mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2025-12-27 10:20:31 +00:00
Compare commits
3 Commits
OpenAPI-va
...
API-OpenAP
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0ab1ab6be | ||
|
|
f4f716fffa | ||
|
|
6e6136aaa7 |
4
.github/workflows/test-only.yml
vendored
4
.github/workflows/test-only.yml
vendored
@@ -15,10 +15,6 @@ 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
|
||||||
|
|||||||
@@ -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, validate_openapi_request
|
from . import auth
|
||||||
|
|
||||||
|
|
||||||
class Import(Resource):
|
class Import(Resource):
|
||||||
@@ -12,7 +12,6 @@ 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."""
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
from flask_expects_json import expects_json
|
from flask_expects_json import expects_json
|
||||||
from flask_restful import Resource, abort
|
from flask_restful import Resource
|
||||||
|
from . import auth
|
||||||
|
from flask_restful import abort, Resource
|
||||||
from flask import request
|
from flask import request
|
||||||
from . import auth, validate_openapi_request
|
from . import auth
|
||||||
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):
|
||||||
@@ -10,7 +12,6 @@ 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."""
|
||||||
|
|
||||||
@@ -21,7 +22,6 @@ 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,7 +49,6 @@ 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."""
|
||||||
@@ -72,7 +71,6 @@ 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."""
|
||||||
|
|||||||
@@ -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, validate_openapi_request
|
from . import auth
|
||||||
|
|
||||||
class Search(Resource):
|
class Search(Resource):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
@@ -8,7 +8,6 @@ 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()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from flask_restful import Resource
|
from flask_restful import Resource
|
||||||
from . import auth, validate_openapi_request
|
from . import auth
|
||||||
|
|
||||||
|
|
||||||
class SystemInfo(Resource):
|
class SystemInfo(Resource):
|
||||||
@@ -9,7 +9,6 @@ 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
|
||||||
|
|||||||
@@ -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, validate_openapi_request
|
from . import schema_tag, schema_create_tag, schema_update_tag
|
||||||
|
|
||||||
|
|
||||||
class Tag(Resource):
|
class Tag(Resource):
|
||||||
@@ -19,7 +19,6 @@ 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
|
||||||
@@ -51,7 +50,6 @@ 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):
|
||||||
@@ -68,7 +66,6 @@ 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."""
|
||||||
@@ -83,7 +80,6 @@ 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."""
|
||||||
@@ -104,7 +100,6 @@ 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 = {}
|
||||||
|
|||||||
@@ -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, validate_openapi_request
|
from . import schema, schema_create_watch, schema_update_watch
|
||||||
|
|
||||||
|
|
||||||
class Watch(Resource):
|
class Watch(Resource):
|
||||||
@@ -25,7 +25,6 @@ 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
|
||||||
@@ -58,7 +57,6 @@ 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):
|
||||||
@@ -68,7 +66,6 @@ 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."""
|
||||||
@@ -94,7 +91,6 @@ 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)
|
||||||
@@ -109,7 +105,6 @@ 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)
|
||||||
@@ -143,7 +138,6 @@ 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)
|
||||||
@@ -178,7 +172,6 @@ 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."""
|
||||||
@@ -214,7 +207,6 @@ 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 = {}
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
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
|
||||||
|
|
||||||
@@ -30,38 +25,6 @@ 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
|
||||||
|
|||||||
@@ -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://changedetection.io/docs/api_v1/index.html">API access and examples here</a>.</p>
|
<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>
|
||||||
|
|
||||||
<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) }}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ info:
|
|||||||
|
|
||||||
For example: `x-api-key: YOUR_API_KEY`
|
For example: `x-api-key: YOUR_API_KEY`
|
||||||
|
|
||||||
version: 0.1.0
|
version: 1.0.0
|
||||||
contact:
|
contact:
|
||||||
name: ChangeDetection.io
|
name: ChangeDetection.io
|
||||||
url: https://github.com/dgtlmoon/changedetection.io
|
url: https://github.com/dgtlmoon/changedetection.io
|
||||||
@@ -90,8 +90,6 @@ tags:
|
|||||||
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: |
|
||||||
Search and filter your watches by URL patterns, titles, or tags. Useful for quickly finding specific
|
Search and filter your watches by URL patterns, titles, or tags. Useful for quickly finding specific
|
||||||
@@ -125,7 +123,7 @@ components:
|
|||||||
uuid:
|
uuid:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
description: Unique identifier for the web page change monitor (watch)
|
description: Unique identifier for the watch
|
||||||
readOnly: true
|
readOnly: true
|
||||||
url:
|
url:
|
||||||
type: string
|
type: string
|
||||||
@@ -134,11 +132,11 @@ components:
|
|||||||
maxLength: 5000
|
maxLength: 5000
|
||||||
title:
|
title:
|
||||||
type: string
|
type: string
|
||||||
description: Custom title for the web page change monitor (watch)
|
description: Custom title for the watch
|
||||||
maxLength: 5000
|
maxLength: 5000
|
||||||
tag:
|
tag:
|
||||||
type: string
|
type: string
|
||||||
description: Tag UUID to associate with this web page change monitor (watch)
|
description: Tag UUID to associate with this watch
|
||||||
maxLength: 5000
|
maxLength: 5000
|
||||||
tags:
|
tags:
|
||||||
type: array
|
type: array
|
||||||
@@ -147,7 +145,7 @@ components:
|
|||||||
description: Array of tag UUIDs
|
description: Array of tag UUIDs
|
||||||
paused:
|
paused:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: Whether the web page change monitor (watch) is paused
|
description: Whether the watch is paused
|
||||||
muted:
|
muted:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: Whether notifications are muted
|
description: Whether notifications are muted
|
||||||
@@ -197,7 +195,7 @@ components:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
description: Notification URLs for this web page change monitor (watch)
|
description: Notification URLs for this watch
|
||||||
notification_title:
|
notification_title:
|
||||||
type: string
|
type: string
|
||||||
description: Custom notification title
|
description: Custom notification title
|
||||||
@@ -267,7 +265,7 @@ components:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
description: Default notification URLs for web page change monitors (watches) with this tag
|
description: Default notification URLs for 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
|
||||||
@@ -291,7 +289,7 @@ components:
|
|||||||
properties:
|
properties:
|
||||||
watch_count:
|
watch_count:
|
||||||
type: integer
|
type: integer
|
||||||
description: Total number of web page change monitors (watches)
|
description: Total number of watches
|
||||||
tag_count:
|
tag_count:
|
||||||
type: integer
|
type: integer
|
||||||
description: Total number of tags
|
description: Total number of tags
|
||||||
@@ -309,7 +307,7 @@ components:
|
|||||||
type: object
|
type: object
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
$ref: '#/components/schemas/Watch'
|
$ref: '#/components/schemas/Watch'
|
||||||
description: Dictionary of matching web page change monitors (watches) keyed by UUID
|
description: Dictionary of matching watches keyed by UUID
|
||||||
|
|
||||||
WatchHistory:
|
WatchHistory:
|
||||||
type: object
|
type: object
|
||||||
@@ -328,10 +326,9 @@ 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 web page change monitors (watches) and basic info
|
description: Return concise list of available watches and basic info
|
||||||
x-code-samples:
|
x-code-samples:
|
||||||
- lang: 'curl'
|
- lang: 'curl'
|
||||||
source: |
|
source: |
|
||||||
@@ -391,10 +388,9 @@ 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 web page change monitor (watch). Requires at least 'url' to be set.
|
description: Create a single watch. Requires at least 'url' to be set.
|
||||||
x-code-samples:
|
x-code-samples:
|
||||||
- lang: 'curl'
|
- lang: 'curl'
|
||||||
source: |
|
source: |
|
||||||
@@ -440,7 +436,7 @@ paths:
|
|||||||
hours: 1
|
hours: 1
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Web page change monitor (watch) created successfully
|
description: Watch created successfully
|
||||||
content:
|
content:
|
||||||
text/plain:
|
text/plain:
|
||||||
schema:
|
schema:
|
||||||
@@ -455,10 +451,10 @@ paths:
|
|||||||
|
|
||||||
/watch/{uuid}:
|
/watch/{uuid}:
|
||||||
get:
|
get:
|
||||||
operationId: getWatch
|
operationId: getSingleWatch
|
||||||
tags: [Watch Management]
|
tags: [Watch Management]
|
||||||
summary: Get single watch
|
summary: Get single watch
|
||||||
description: Retrieve web page change monitor (watch) information and set muted/paused status. Returns the FULL Watch JSON.
|
description: Retrieve watch information and set muted/paused status. Returns the FULL Watch JSON.
|
||||||
x-code-samples:
|
x-code-samples:
|
||||||
- lang: 'curl'
|
- lang: 'curl'
|
||||||
source: |
|
source: |
|
||||||
@@ -476,13 +472,13 @@ paths:
|
|||||||
- name: uuid
|
- name: uuid
|
||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
description: Web page change monitor (watch) unique ID
|
description: Watch unique ID
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
- name: recheck
|
- name: recheck
|
||||||
in: query
|
in: query
|
||||||
description: Recheck this web page change monitor (watch)
|
description: Recheck this watch
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
enum: ["1", "true"]
|
enum: ["1", "true"]
|
||||||
@@ -510,7 +506,7 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
example: "OK"
|
example: "OK"
|
||||||
'404':
|
'404':
|
||||||
description: Web page change monitor (watch) not found
|
description: Watch not found
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
@@ -520,7 +516,7 @@ paths:
|
|||||||
operationId: updateWatch
|
operationId: updateWatch
|
||||||
tags: [Watch Management]
|
tags: [Watch Management]
|
||||||
summary: Update watch
|
summary: Update watch
|
||||||
description: Update an existing web page change monitor (watch) using JSON. Accepts the same structure as returned in [get single watch information](#operation/getWatch).
|
description: Update an existing watch using JSON. Accepts the same structure as returned in [get single watch information](#operation/getSingleWatch).
|
||||||
x-code-samples:
|
x-code-samples:
|
||||||
- lang: 'curl'
|
- lang: 'curl'
|
||||||
source: |
|
source: |
|
||||||
@@ -553,7 +549,7 @@ paths:
|
|||||||
- name: uuid
|
- name: uuid
|
||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
description: Web page change monitor (watch) unique ID
|
description: Watch unique ID
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
@@ -565,7 +561,7 @@ paths:
|
|||||||
$ref: '#/components/schemas/Watch'
|
$ref: '#/components/schemas/Watch'
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Web page change monitor (watch) updated successfully
|
description: Watch updated successfully
|
||||||
content:
|
content:
|
||||||
text/plain:
|
text/plain:
|
||||||
schema:
|
schema:
|
||||||
@@ -575,10 +571,9 @@ 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 web page change monitor (watch) and all related history
|
description: Delete a watch and all related history
|
||||||
x-code-samples:
|
x-code-samples:
|
||||||
- lang: 'curl'
|
- lang: 'curl'
|
||||||
source: |
|
source: |
|
||||||
@@ -596,13 +591,13 @@ paths:
|
|||||||
- name: uuid
|
- name: uuid
|
||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
description: Web page change monitor (watch) unique ID
|
description: Watch unique ID
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Web page change monitor (watch) deleted successfully
|
description: Watch deleted successfully
|
||||||
content:
|
content:
|
||||||
text/plain:
|
text/plain:
|
||||||
schema:
|
schema:
|
||||||
@@ -611,10 +606,9 @@ 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 web page change monitor (watch)
|
description: Get a list of all historical snapshots available for a watch
|
||||||
x-code-samples:
|
x-code-samples:
|
||||||
- lang: 'curl'
|
- lang: 'curl'
|
||||||
source: |
|
source: |
|
||||||
@@ -632,7 +626,7 @@ paths:
|
|||||||
- name: uuid
|
- name: uuid
|
||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
description: Web page change monitor (watch) unique ID
|
description: Watch unique ID
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
@@ -647,14 +641,13 @@ paths:
|
|||||||
"1640995200": "/path/to/snapshot1.txt"
|
"1640995200": "/path/to/snapshot1.txt"
|
||||||
"1640998800": "/path/to/snapshot2.txt"
|
"1640998800": "/path/to/snapshot2.txt"
|
||||||
'404':
|
'404':
|
||||||
description: Web page change monitor (watch) not found
|
description: 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 web page change monitor (watch). Use 'latest' for the most recent snapshot.
|
description: Get single snapshot from watch. Use 'latest' for the most recent snapshot.
|
||||||
x-code-samples:
|
x-code-samples:
|
||||||
- lang: 'curl'
|
- lang: 'curl'
|
||||||
source: |
|
source: |
|
||||||
@@ -673,7 +666,7 @@ paths:
|
|||||||
- name: uuid
|
- name: uuid
|
||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
description: Web page change monitor (watch) unique ID
|
description: Watch unique ID
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
@@ -704,10 +697,9 @@ 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 web page change monitor (watch) as displayed in the watch overview list.
|
description: Get the favicon for a watch as displayed in the watch overview list.
|
||||||
x-code-samples:
|
x-code-samples:
|
||||||
- lang: 'curl'
|
- lang: 'curl'
|
||||||
source: |
|
source: |
|
||||||
@@ -727,7 +719,7 @@ paths:
|
|||||||
- name: uuid
|
- name: uuid
|
||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
description: Web page change monitor (watch) unique ID
|
description: Watch unique ID
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
@@ -744,7 +736,6 @@ 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
|
||||||
@@ -781,62 +772,11 @@ 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 web page change monitors (watches) in tag.
|
description: Retrieve tag information, set notification_muted status, recheck all in tag.
|
||||||
x-code-samples:
|
x-code-samples:
|
||||||
- lang: 'curl'
|
- lang: 'curl'
|
||||||
source: |
|
source: |
|
||||||
@@ -866,7 +806,7 @@ paths:
|
|||||||
enum: [muted, unmuted]
|
enum: [muted, unmuted]
|
||||||
- name: recheck
|
- name: recheck
|
||||||
in: query
|
in: query
|
||||||
description: Queue all web page change monitors (watches) with this tag for recheck
|
description: Queue all watches with this tag for recheck
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
enum: ["true"]
|
enum: ["true"]
|
||||||
@@ -885,7 +825,6 @@ 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
|
||||||
@@ -936,10 +875,9 @@ 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 web page change monitors (watches)
|
description: Delete a tag/group and remove it from all watches
|
||||||
x-code-samples:
|
x-code-samples:
|
||||||
- lang: 'curl'
|
- lang: 'curl'
|
||||||
source: |
|
source: |
|
||||||
@@ -965,10 +903,48 @@ 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
|
||||||
@@ -993,7 +969,6 @@ 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
|
||||||
@@ -1047,7 +1022,6 @@ 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)
|
||||||
@@ -1095,7 +1069,6 @@ 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
|
||||||
@@ -1140,10 +1113,9 @@ paths:
|
|||||||
|
|
||||||
/search:
|
/search:
|
||||||
get:
|
get:
|
||||||
operationId: searchWatches
|
|
||||||
tags: [Search]
|
tags: [Search]
|
||||||
summary: Search watches
|
summary: Search watches
|
||||||
description: Search web page change monitors (watches) by URL or title text
|
description: Search watches by URL or title text
|
||||||
x-code-samples:
|
x-code-samples:
|
||||||
- lang: 'curl'
|
- lang: 'curl'
|
||||||
source: |
|
source: |
|
||||||
@@ -1195,7 +1167,6 @@ 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.
|
||||||
@@ -1221,17 +1192,17 @@ paths:
|
|||||||
parameters:
|
parameters:
|
||||||
- name: tag_uuids
|
- name: tag_uuids
|
||||||
in: query
|
in: query
|
||||||
description: Tag UUID to apply to imported web page change monitors (watches)
|
description: Tag UUID to apply to imported watches
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
- name: tag
|
- name: tag
|
||||||
in: query
|
in: query
|
||||||
description: Tag name to apply to imported web page change monitors (watches)
|
description: Tag name to apply to imported watches
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
- name: proxy
|
- name: proxy
|
||||||
in: query
|
in: query
|
||||||
description: Proxy key to use for imported web page change monitors (watches)
|
description: Proxy key to use for imported watches
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
- name: dedupe
|
- name: dedupe
|
||||||
@@ -1266,7 +1237,6 @@ 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
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -89,9 +89,6 @@ 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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user