Skip to content

Plugins try 3 #3033

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
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
1 change: 0 additions & 1 deletion changedetectionio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ def sigshutdown_handler(_signo, _stack_frame):
global datastore
name = signal.Signals(_signo).name
logger.critical(f'Shutdown: Got Signal - {name} ({_signo}), Saving DB to disk and calling shutdown')
datastore.sync_to_json()
logger.success('Sync JSON to disk complete.')
# This will throw a SystemExit exception, because eventlet.wsgi.server doesn't know how to deal with it.
# Solution: move to gevent or other server in the future (#2014)
Expand Down
37 changes: 20 additions & 17 deletions changedetectionio/api/api_v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@
# See docs/README.md for rebuilding the docs/apidoc information

from . import api_schema
from ..model import watch_base
from ..model import schema as watch_schema

# Build a JSON Schema atleast partially based on our Watch model
watch_base_config = watch_base()
schema = api_schema.build_watch_json_schema(watch_base_config)
schema = api_schema.build_watch_json_schema(watch_schema)

schema_create_watch = copy.deepcopy(schema)
schema_create_watch['required'] = ['url']
Expand Down Expand Up @@ -53,9 +52,9 @@ def get(self, uuid):
@apiSuccess (200) {JSON} WatchJSON JSON Full JSON object of the watch
"""
from copy import deepcopy
watch = deepcopy(self.datastore.data['watching'].get(uuid))
watch = self.datastore.data['watching'].get(uuid)
if not watch:
abort(404, message='No watch exists with the UUID of {}'.format(uuid))
abort(404, message=f'No watch exists with the UUID of {uuid}')

if request.args.get('recheck'):
self.update_q.put(queuedWatchMetaData.PrioritizedItem(priority=1, item={'uuid': uuid}))
Expand All @@ -73,13 +72,16 @@ def get(self, uuid):
self.datastore.data['watching'].get(uuid).unmute()
return "OK", 200

# Return without history, get that via another API call
# Properties are not returned as a JSON, so add the required props manually
watch['history_n'] = watch.history_n
# attr .last_changed will check for the last written text snapshot on change
watch['last_changed'] = watch.last_changed
watch['viewed'] = watch.viewed
return watch

response = dict(watch.get_data())

# Add properties that aren't included in the standard dictionary items (they are properties/attr)
response['history_n'] = watch.history_n
response['last_changed'] = watch.last_changed
response['viewed'] = watch.viewed
response['title'] = watch.get('title')

return response

@auth.check_token
def delete(self, uuid):
Expand Down Expand Up @@ -114,16 +116,17 @@ def put(self, uuid):
@apiSuccess (200) {String} OK Was updated
@apiSuccess (500) {String} ERR Some other error
"""
watch = self.datastore.data['watching'].get(uuid)
if not watch:
abort(404, message='No watch exists with the UUID of {}'.format(uuid))

if not self.datastore.data['watching'].get(uuid):
abort(404, message=f'No watch exists with the UUID of {uuid}')

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

watch.update(request.json)
self.datastore.data['watching'][uuid].update(request.json)
self.datastore.data['watching'][uuid].save_data()

return "OK", 200

Expand Down
2 changes: 0 additions & 2 deletions changedetectionio/blueprint/backups/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,6 @@ def request_backup():
flash("Maximum number of backups reached, please remove some", "error")
return redirect(url_for('backups.index'))

# Be sure we're written fresh
datastore.sync_to_json()
zip_thread = threading.Thread(target=create_backup, args=(datastore.datastore_path, datastore.data.get("watching")))
zip_thread.start()
backup_threads.append(zip_thread)
Expand Down
2 changes: 1 addition & 1 deletion changedetectionio/blueprint/imports/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def import_page():
update_q.put(queuedWatchMetaData.PrioritizedItem(priority=1, item={'uuid': uuid}))

# Could be some remaining, or we could be on GET
form = forms.importForm(formdata=request.form if request.method == 'POST' else None)
form = forms.importForm(formdata=request.form if request.method == 'POST' else None, datastore=datastore)
output = render_template("import.html",
form=form,
import_url_list_remaining="\n".join(remaining_urls),
Expand Down
2 changes: 1 addition & 1 deletion changedetectionio/blueprint/imports/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from wtforms import ValidationError
from loguru import logger

from changedetectionio.forms import validate_url


class Importer():
Expand Down Expand Up @@ -151,6 +150,7 @@ def run(self,
self.new_uuids = []

from openpyxl import load_workbook
from changedetectionio.forms import validate_url

try:
wb = load_workbook(data)
Expand Down
17 changes: 10 additions & 7 deletions changedetectionio/blueprint/price_data_follower/__init__.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@

from changedetectionio.strtobool import strtobool
from flask import Blueprint, flash, redirect, url_for
from flask_login import login_required
from changedetectionio.store import ChangeDetectionStore
from changedetectionio import queuedWatchMetaData
from queue import PriorityQueue
from changedetectionio import queuedWatchMetaData
from changedetectionio.processors.constants import PRICE_DATA_TRACK_ACCEPT, PRICE_DATA_TRACK_REJECT

PRICE_DATA_TRACK_ACCEPT = 'accepted'
PRICE_DATA_TRACK_REJECT = 'rejected'

def construct_blueprint(datastore: ChangeDetectionStore, update_q: PriorityQueue):
def construct_blueprint(datastore, update_q: PriorityQueue):

price_data_follower_blueprint = Blueprint('price_data_follower', __name__)

@login_required
@price_data_follower_blueprint.route("/<string:uuid>/accept", methods=['GET'])
def accept(uuid):

old_data = datastore.data['watching'][uuid].get_data()

datastore.data['watching'][uuid] = datastore.rehydrate_entity(default_dict=old_data, processor_override='restock_diff')
datastore.data['watching'][uuid]['track_ldjson_price_data'] = PRICE_DATA_TRACK_ACCEPT
datastore.data['watching'][uuid]['processor'] = 'restock_diff'
datastore.data['watching'][uuid].clear_watch()

# Queue the watch for updating
update_q.put(queuedWatchMetaData.PrioritizedItem(priority=1, item={'uuid': uuid}))

return redirect(url_for("index"))

@login_required
Expand Down
3 changes: 3 additions & 0 deletions changedetectionio/blueprint/price_data_follower/flags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

PRICE_DATA_TRACK_ACCEPT = 'accepted'
PRICE_DATA_TRACK_REJECT = 'rejected'
24 changes: 21 additions & 3 deletions changedetectionio/blueprint/settings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,19 +71,37 @@ def settings_page():

if not os.getenv("SALTED_PASS", False) and len(form.application.form.password.encrypted_password):
datastore.data['settings']['application']['password'] = form.application.form.password.encrypted_password
datastore.needs_write_urgent = True
datastore.save_settings()
flash("Password protection enabled.", 'notice')
flask_login.logout_user()
return redirect(url_for('index'))

datastore.needs_write_urgent = True
datastore.save_settings()
flash("Settings updated.")

else:
flash("An error occurred, please see below.", "error")

# Convert to ISO 8601 format, all date/time relative events stored as UTC time
utc_time = datetime.now(ZoneInfo("UTC")).isoformat()

# Get processor plugins info
from changedetectionio.processors import get_all_plugins_info
plugins_info = get_all_plugins_info()

# Process settings including plugin toggles
if request.method == 'POST' and form.validate():
# Process the main form data
app_update = dict(deepcopy(form.data['application']))

# Don't update password with '' or False (Added by wtforms when not in submission)
if 'password' in app_update and not app_update['password']:
del (app_update['password'])

datastore.data['settings']['application'].update(app_update)
datastore.data['settings']['requests'].update(form.data['requests'])
datastore.save_settings()
flash("Settings updated.")

output = render_template("settings.html",
api_key=datastore.data['settings']['application'].get('api_access_token'),
Expand All @@ -93,6 +111,7 @@ def settings_page():
form=form,
hide_remove_pass=os.getenv("SALTED_PASS", False),
min_system_recheck_seconds=int(os.getenv('MINIMUM_SECONDS_RECHECK_TIME', 3)),
plugins_info=plugins_info,
settings_application=datastore.data['settings']['application'],
timezone_default_config=datastore.data['settings']['application'].get('timezone'),
utc_time=utc_time,
Expand All @@ -105,7 +124,6 @@ def settings_page():
def settings_reset_api_key():
secret = secrets.token_hex(16)
datastore.data['settings']['application']['api_access_token'] = secret
datastore.needs_write_urgent = True
flash("API Key was regenerated.")
return redirect(url_for('settings.settings_page')+'#api')

Expand Down
30 changes: 29 additions & 1 deletion changedetectionio/blueprint/settings/templates/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
const email_notification_prefix=JSON.parse('{{emailprefix|tojson}}');
{% endif %}
</script>

<script src="{{url_for('static_content', group='js', filename='tabs.js')}}" defer></script>
<script src="{{url_for('static_content', group='js', filename='plugins.js')}}" defer></script>
<script src="{{url_for('static_content', group='js', filename='notifications.js')}}" defer></script>
Expand All @@ -25,6 +26,7 @@
<li class="tab"><a href="#api">API</a></li>
<li class="tab"><a href="#timedate">Time &amp Date</a></li>
<li class="tab"><a href="#proxies">CAPTCHA &amp; Proxies</a></li>
<li class="tab"><a href="#plugins">Plugins</a></li>
</ul>
</div>
<div class="box-wrap inner">
Expand Down Expand Up @@ -296,11 +298,37 @@ <h4>Chrome Extension</h4>
{{ render_field(form.requests.form.extra_browsers) }}
</div>
</div>
<div class="tab-pane-inner" id="plugins">
<div class="pure-control-group">
<h4>Registered Plugins</h4>
<p>The following plugins are currently registered in the system - <a href="https://changedetection.io/plugins">Get more plugins here</a></p>

<table class="pure-table pure-table-striped">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Version</th>
</tr>
</thead>
<tbody>
{% for plugin in plugins_info %}
<tr>
<td>{{ plugin.name }}</td>
<td>{{ plugin.description }}</td>
<td>{{ plugin.version }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>

<div id="actions">
<div class="pure-control-group">
{{ render_button(form.save_button) }}
<a href="{{url_for('index')}}" class="pure-button button-small button-cancel">Back</a>
<a href="{{url_for('clear_all_history')}}" class="pure-button button-small button-error">Clear Snapshot History</a>
<a href="{{url_for('ui.clear_all_history')}}" class="pure-button button-small button-error">Clear Snapshot History</a>
</div>
</div>
</form>
Expand Down
4 changes: 3 additions & 1 deletion changedetectionio/blueprint/tags/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def form_tag_add():
def mute(uuid):
if datastore.data['settings']['application']['tags'].get(uuid):
datastore.data['settings']['application']['tags'][uuid]['notification_muted'] = not datastore.data['settings']['application']['tags'][uuid]['notification_muted']
datastore.data['settings']['application']['tags'][uuid].save_data()
return redirect(url_for('tags.tags_overview_page'))

@tags_blueprint.route("/delete/<string:uuid>", methods=['GET'])
Expand Down Expand Up @@ -176,7 +177,8 @@ def form_tag_edit_submit(uuid):

datastore.data['settings']['application']['tags'][uuid].update(form.data)
datastore.data['settings']['application']['tags'][uuid]['processor'] = 'restock_diff'
datastore.needs_write_urgent = True
datastore.data['settings']['application']['tags'][uuid].save_data()

flash("Updated")

return redirect(url_for('tags.tags_overview_page'))
Expand Down
8 changes: 8 additions & 0 deletions changedetectionio/blueprint/ui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,15 @@ def form_watch_list_checkbox_operations():
uuid = uuid.strip()
if datastore.data['watching'].get(uuid):
datastore.data['watching'][uuid.strip()]['paused'] = True
datastore.data['watching'][uuid.strip()].save_data()
flash("{} watches paused".format(len(uuids)))

elif (op == 'unpause'):
for uuid in uuids:
uuid = uuid.strip()
if datastore.data['watching'].get(uuid):
datastore.data['watching'][uuid.strip()]['paused'] = False
datastore.data['watching'][uuid.strip()].save_data()
flash("{} watches unpaused".format(len(uuids)))

elif (op == 'mark-viewed'):
Expand All @@ -184,13 +186,15 @@ def form_watch_list_checkbox_operations():
uuid = uuid.strip()
if datastore.data['watching'].get(uuid):
datastore.data['watching'][uuid.strip()]['notification_muted'] = True
datastore.data['watching'][uuid.strip()].save_data()
flash("{} watches muted".format(len(uuids)))

elif (op == 'unmute'):
for uuid in uuids:
uuid = uuid.strip()
if datastore.data['watching'].get(uuid):
datastore.data['watching'][uuid.strip()]['notification_muted'] = False
datastore.data['watching'][uuid.strip()].save_data()
flash("{} watches un-muted".format(len(uuids)))

elif (op == 'recheck'):
Expand All @@ -206,6 +210,7 @@ def form_watch_list_checkbox_operations():
uuid = uuid.strip()
if datastore.data['watching'].get(uuid):
datastore.data['watching'][uuid]["last_error"] = False
datastore.data['watching'][uuid].save_data()
flash(f"{len(uuids)} watches errors cleared")

elif (op == 'clear-history'):
Expand Down Expand Up @@ -244,6 +249,9 @@ def form_watch_list_checkbox_operations():

flash(f"{len(uuids)} watches were tagged")

for uuid in uuids:
datastore.data['watching'][uuid.strip()].save_data()

return redirect(url_for('index'))


Expand Down
Loading
Loading