Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions changedetectionio/api/Import.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import os
from changedetectionio.strtobool import strtobool
from flask_restful import abort, Resource
from flask import request
import validators
from . import auth


class Import(Resource):
def __init__(self, **kwargs):
# datastore is a black box dependency
self.datastore = kwargs['datastore']

@auth.check_token
def post(self):
"""
@api {post} /api/v1/import Import a list of watched URLs
@apiDescription Accepts a line-feed separated list of URLs to import, additionally with ?tag_uuids=(tag id), ?tag=(name), ?proxy={key}, ?dedupe=true (default true) one URL per line.
@apiExample {curl} Example usage:
curl http://localhost:5000/api/v1/import --data-binary @list-of-sites.txt -H"x-api-key:8a111a21bc2f8f1dd9b9353bbd46049a"
@apiName Import
@apiGroup Watch
@apiSuccess (200) {List} OK List of watch UUIDs added
@apiSuccess (500) {String} ERR Some other error
"""

extras = {}

if request.args.get('proxy'):
plist = self.datastore.proxy_list
if not request.args.get('proxy') in plist:
return "Invalid proxy choice, currently supported proxies are '{}'".format(', '.join(plist)), 400
else:
extras['proxy'] = request.args.get('proxy')

dedupe = strtobool(request.args.get('dedupe', 'true'))

tags = request.args.get('tag')
tag_uuids = request.args.get('tag_uuids')

if tag_uuids:
tag_uuids = tag_uuids.split(',')

urls = request.get_data().decode('utf8').splitlines()
added = []
allow_simplehost = not strtobool(os.getenv('BLOCK_SIMPLEHOSTS', 'False'))
for url in urls:
url = url.strip()
if not len(url):
continue

# If hosts that only contain alphanumerics are allowed ("localhost" for example)
if not validators.url(url, simple_host=allow_simplehost):
return f"Invalid or unsupported URL - {url}", 400

if dedupe and self.datastore.url_exists(url):
continue

new_uuid = self.datastore.add_watch(url=url, extras=extras, tag=tags, tag_uuids=tag_uuids)
added.append(new_uuid)

return added
54 changes: 54 additions & 0 deletions changedetectionio/api/SystemInfo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from flask_restful import Resource
from . import auth


class SystemInfo(Resource):
def __init__(self, **kwargs):
# datastore is a black box dependency
self.datastore = kwargs['datastore']
self.update_q = kwargs['update_q']

@auth.check_token
def get(self):
"""
@api {get} /api/v1/systeminfo Return system info
@apiDescription Return some info about the current system state
@apiExample {curl} Example usage:
curl http://localhost:5000/api/v1/systeminfo -H"x-api-key:813031b16330fe25e3780cf0325daa45"
HTTP/1.0 200
{
'queue_size': 10 ,
'overdue_watches': ["watch-uuid-list"],
'uptime': 38344.55,
'watch_count': 800,
'version': "0.40.1"
}
@apiName Get Info
@apiGroup System Information
"""
import time
overdue_watches = []

# Check all watches and report which have not been checked but should have been

for uuid, watch in self.datastore.data.get('watching', {}).items():
# see if now - last_checked is greater than the time that should have been
# this is not super accurate (maybe they just edited it) but better than nothing
t = watch.threshold_seconds()
if not t:
# Use the system wide default
t = self.datastore.threshold_seconds

time_since_check = time.time() - watch.get('last_checked')

# Allow 5 minutes of grace time before we decide it's overdue
if time_since_check - (5 * 60) > t:
overdue_watches.append(uuid)
from changedetectionio import __version__ as main_version
return {
'queue_size': self.update_q.qsize(),
'overdue_watches': overdue_watches,
'uptime': round(time.time() - self.datastore.start_time, 2),
'watch_count': len(self.datastore.data.get('watching', {})),
'version': main_version
}, 200
156 changes: 156 additions & 0 deletions changedetectionio/api/Tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
from flask_expects_json import expects_json
from flask_restful import abort, Resource
from flask import request
from . import auth

# Import schemas from __init__.py
from . import schema_tag, schema_create_tag, schema_update_tag


class Tag(Resource):
def __init__(self, **kwargs):
# datastore is a black box dependency
self.datastore = kwargs['datastore']

# Get information about a single tag
# curl http://localhost:5000/api/v1/tag/<string:uuid>
@auth.check_token
def get(self, uuid):
"""
@api {get} /api/v1/tag/:uuid Single tag - get data or toggle notification muting.
@apiDescription Retrieve tag information and set notification_muted status
@apiExample {curl} Example usage:
curl http://localhost:5000/api/v1/tag/cc0cfffa-f449-477b-83ea-0caafd1dc091 -H"x-api-key:813031b16330fe25e3780cf0325daa45"
curl "http://localhost:5000/api/v1/tag/cc0cfffa-f449-477b-83ea-0caafd1dc091?muted=muted" -H"x-api-key:813031b16330fe25e3780cf0325daa45"
@apiName Tag
@apiGroup Tag
@apiParam {uuid} uuid Tag unique ID.
@apiQuery {String} [muted] =`muted` or =`unmuted` , Sets the MUTE NOTIFICATIONS state
@apiSuccess (200) {String} OK When muted operation OR full JSON object of the tag
@apiSuccess (200) {JSON} TagJSON JSON Full JSON object of the tag
"""
from copy import deepcopy
tag = deepcopy(self.datastore.data['settings']['application']['tags'].get(uuid))
if not tag:
abort(404, message=f'No tag exists with the UUID of {uuid}')

if request.args.get('muted', '') == 'muted':
self.datastore.data['settings']['application']['tags'][uuid]['notification_muted'] = True
return "OK", 200
elif request.args.get('muted', '') == 'unmuted':
self.datastore.data['settings']['application']['tags'][uuid]['notification_muted'] = False
return "OK", 200

return tag

@auth.check_token
def delete(self, uuid):
"""
@api {delete} /api/v1/tag/:uuid Delete a tag and remove it from all watches
@apiExample {curl} Example usage:
curl http://localhost:5000/api/v1/tag/cc0cfffa-f449-477b-83ea-0caafd1dc091 -X DELETE -H"x-api-key:813031b16330fe25e3780cf0325daa45"
@apiParam {uuid} uuid Tag unique ID.
@apiName DeleteTag
@apiGroup Tag
@apiSuccess (200) {String} OK Was deleted
"""
if not self.datastore.data['settings']['application']['tags'].get(uuid):
abort(400, message='No tag exists with the UUID of {}'.format(uuid))

# Delete the tag, and any tag reference
del self.datastore.data['settings']['application']['tags'][uuid]

# Remove tag from all watches
for watch_uuid, watch in self.datastore.data['watching'].items():
if watch.get('tags') and uuid in watch['tags']:
watch['tags'].remove(uuid)

return 'OK', 204

@auth.check_token
@expects_json(schema_update_tag)
def put(self, uuid):
"""
@api {put} /api/v1/tag/:uuid Update tag information
@apiExample {curl} Example usage:
Update (PUT)
curl http://localhost:5000/api/v1/tag/cc0cfffa-f449-477b-83ea-0caafd1dc091 -X PUT -H"x-api-key:813031b16330fe25e3780cf0325daa45" -H "Content-Type: application/json" -d '{"title": "New Tag Title"}'

@apiDescription Updates an existing tag using JSON
@apiParam {uuid} uuid Tag unique ID.
@apiName UpdateTag
@apiGroup Tag
@apiSuccess (200) {String} OK Was updated
@apiSuccess (500) {String} ERR Some other error
"""
tag = self.datastore.data['settings']['application']['tags'].get(uuid)
if not tag:
abort(404, message='No tag exists with the UUID of {}'.format(uuid))

tag.update(request.json)
self.datastore.needs_write_urgent = True

return "OK", 200


@auth.check_token
# Only cares for {'title': 'xxxx'}
def post(self):
"""
@api {post} /api/v1/watch Create a single tag
@apiExample {curl} Example usage:
curl http://localhost:5000/api/v1/watch -H"x-api-key:813031b16330fe25e3780cf0325daa45" -H "Content-Type: application/json" -d '{"name": "Work related"}'
@apiName Create
@apiGroup Tag
@apiSuccess (200) {String} OK Was created
@apiSuccess (500) {String} ERR Some other error
"""

json_data = request.get_json()
title = json_data.get("title",'').strip()


new_uuid = self.datastore.add_tag(title=title)
if new_uuid:
return {'uuid': new_uuid}, 201
else:
return "Invalid or unsupported tag", 400

class Tags(Resource):
def __init__(self, **kwargs):
# datastore is a black box dependency
self.datastore = kwargs['datastore']

@auth.check_token
def get(self):
"""
@api {get} /api/v1/tags List tags
@apiDescription Return list of available tags
@apiExample {curl} Example usage:
curl http://localhost:5000/api/v1/tags -H"x-api-key:813031b16330fe25e3780cf0325daa45"
{
"cc0cfffa-f449-477b-83ea-0caafd1dc091": {
"title": "Tech News",
"notification_muted": false,
"date_created": 1677103794
},
"e6f5fd5c-dbfe-468b-b8f3-f9d6ff5ad69b": {
"title": "Shopping",
"notification_muted": true,
"date_created": 1676662819
}
}
@apiName ListTags
@apiGroup Tag Management
@apiSuccess (200) {String} OK JSON dict
"""
result = {}
for uuid, tag in self.datastore.data['settings']['application']['tags'].items():
result[uuid] = {
'date_created': tag.get('date_created', 0),
'notification_muted': tag.get('notification_muted', False),
'title': tag.get('title', ''),
'uuid': tag.get('uuid')
}

return result, 200
Loading