kopia lustrzana https://github.com/dgtlmoon/changedetection.io
Customisable notifications (#123)
* Customisable notifications (#121) * Test improvements * Setup BASE_URL environment in test Co-authored-by: dtomlinson91 <53234158+dtomlinson91@users.noreply.github.com>pull/91/head
rodzic
655a350f50
commit
dad48402f1
|
@ -90,10 +90,11 @@ Just some examples
|
|||
|
||||
<img src="https://raw.githubusercontent.com/dgtlmoon/changedetection.io/master/screenshot-notifications.png" style="max-width:100%;" alt="Self-hosted web page change monitoring notifications" title="Self-hosted web page change monitoring notifications" />
|
||||
|
||||
Now you can also customise your notification content!
|
||||
|
||||
### JSON API Monitoring
|
||||
|
||||
Detect changes and monitor data in JSON API's by using the built-in JSONPath selectors as a filter.
|
||||
|
||||
Detect changes and monitor data in JSON API's by using the built-in JSONPath selectors as a filter / selector.
|
||||
|
||||

|
||||
|
||||
|
@ -101,7 +102,6 @@ This will re-parse the JSON and apply indent to the text, making it super easy t
|
|||
|
||||

|
||||
|
||||
|
||||
### Proxy
|
||||
|
||||
A proxy for ChangeDetection.io can be configured by setting environment the
|
||||
|
|
|
@ -422,7 +422,8 @@ def changedetection_app(config=None, datastore_o=None):
|
|||
|
||||
if form.trigger_check.data:
|
||||
n_object = {'watch_url': form.url.data.strip(),
|
||||
'notification_urls': form.notification_urls.data}
|
||||
'notification_urls': form.notification_urls.data,
|
||||
'uuid': uuid}
|
||||
notification_q.put(n_object)
|
||||
|
||||
flash('Notifications queued.')
|
||||
|
@ -463,6 +464,8 @@ def changedetection_app(config=None, datastore_o=None):
|
|||
form.minutes_between_check.data = int(datastore.data['settings']['requests']['minutes_between_check'])
|
||||
form.notification_urls.data = datastore.data['settings']['application']['notification_urls']
|
||||
form.extract_title_as_title.data = datastore.data['settings']['application']['extract_title_as_title']
|
||||
form.notification_title.data = datastore.data['settings']['application']['notification_title']
|
||||
form.notification_body.data = datastore.data['settings']['application']['notification_body']
|
||||
|
||||
# Password unset is a GET
|
||||
if request.values.get('removepassword') == 'true':
|
||||
|
@ -476,6 +479,8 @@ def changedetection_app(config=None, datastore_o=None):
|
|||
datastore.data['settings']['application']['notification_urls'] = form.notification_urls.data
|
||||
datastore.data['settings']['requests']['minutes_between_check'] = form.minutes_between_check.data
|
||||
datastore.data['settings']['application']['extract_title_as_title'] = form.extract_title_as_title.data
|
||||
datastore.data['settings']['application']['notification_title'] = form.notification_title.data
|
||||
datastore.data['settings']['application']['notification_body'] = form.notification_body.data
|
||||
|
||||
if len(form.notification_urls.data):
|
||||
import apprise
|
||||
|
@ -823,39 +828,22 @@ def check_for_new_version():
|
|||
app.config.exit.wait(86400)
|
||||
|
||||
def notification_runner():
|
||||
|
||||
while not app.config.exit.is_set():
|
||||
try:
|
||||
# At the moment only one thread runs (single runner)
|
||||
n_object = notification_q.get(block=False)
|
||||
except queue.Empty:
|
||||
time.sleep(1)
|
||||
pass
|
||||
|
||||
else:
|
||||
import apprise
|
||||
|
||||
# Create an Apprise instance
|
||||
# Process notifications
|
||||
try:
|
||||
apobj = apprise.Apprise()
|
||||
for url in n_object['notification_urls']:
|
||||
apobj.add(url.strip())
|
||||
|
||||
n_body = n_object['watch_url']
|
||||
|
||||
# 65 - Append URL of instance to the notification if it is set.
|
||||
base_url = os.getenv('BASE_URL')
|
||||
if base_url != None:
|
||||
n_body += "\n" + base_url
|
||||
|
||||
apobj.notify(
|
||||
body=n_body,
|
||||
# @todo This should be configurable.
|
||||
title="ChangeDetection.io Notification - {}".format(n_object['watch_url'])
|
||||
)
|
||||
from backend import notification
|
||||
notification.process_notification(n_object, datastore)
|
||||
|
||||
except Exception as e:
|
||||
print("Watch URL: {} Error {}".format(n_object['watch_url'],e))
|
||||
print("Watch URL: {} Error {}".format(n_object['watch_url'], e))
|
||||
|
||||
|
||||
|
||||
# Thread runner to check every minute, look for new watches to feed into the Queue.
|
||||
|
|
|
@ -152,3 +152,6 @@ class globalSettingsForm(Form):
|
|||
notification_urls = StringListField('Notification URL List')
|
||||
extract_title_as_title = BooleanField('Extract <title> from document and use as watch title')
|
||||
trigger_check = BooleanField('Send test notification on save')
|
||||
|
||||
notification_title = StringField('Notification Title')
|
||||
notification_body = TextAreaField('Notification Body')
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
import os
|
||||
import apprise
|
||||
|
||||
def process_notification(n_object, datastore):
|
||||
apobj = apprise.Apprise()
|
||||
for url in n_object['notification_urls']:
|
||||
apobj.add(url.strip())
|
||||
|
||||
# Get the notification body from datastore
|
||||
n_body = datastore.data['settings']['application']['notification_body']
|
||||
# Get the notification title from the datastore
|
||||
n_title = datastore.data['settings']['application']['notification_title']
|
||||
|
||||
# Insert variables into the notification content
|
||||
notification_parameters = create_notification_parameters(n_object)
|
||||
raw_notification_text = [n_body, n_title]
|
||||
|
||||
parameterised_notification_text = dict(
|
||||
[
|
||||
(i, n.replace(n, n.format(**notification_parameters)))
|
||||
for i, n in zip(['body', 'title'], raw_notification_text)
|
||||
]
|
||||
)
|
||||
|
||||
apobj.notify(
|
||||
body=parameterised_notification_text["body"],
|
||||
title=parameterised_notification_text["title"]
|
||||
)
|
||||
|
||||
|
||||
# Notification title + body content parameters get created here.
|
||||
def create_notification_parameters(n_object):
|
||||
|
||||
# in the case we send a test notification from the main settings, there is no UUID.
|
||||
uuid = n_object['uuid'] if 'uuid' in n_object else ''
|
||||
|
||||
# Create URLs to customise the notification with
|
||||
base_url = os.getenv('BASE_URL', '').strip('"')
|
||||
watch_url = n_object['watch_url']
|
||||
|
||||
if base_url != '':
|
||||
diff_url = "{}/diff/{}".format(base_url, uuid)
|
||||
preview_url = "{}/preview/{}".format(base_url, uuid)
|
||||
else:
|
||||
diff_url = preview_url = ''
|
||||
|
||||
return {
|
||||
'base_url': base_url,
|
||||
'watch_url': watch_url,
|
||||
'diff_url': diff_url,
|
||||
'preview_url': preview_url,
|
||||
'current_snapshot': n_object['current_snapshot'] if 'current_snapshot' in n_object else ''
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
window.addEventListener("load", (event) => {
|
||||
// just an example for now
|
||||
function toggleVisible(elem) {
|
||||
// theres better ways todo this
|
||||
var x = document.getElementById(elem);
|
||||
if (x.style.display === "block") {
|
||||
x.style.display = "none";
|
||||
} else {
|
||||
x.style.display = "block";
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("toggle-customise-notifications").onclick = function () {
|
||||
toggleVisible("notification-customisation");
|
||||
};
|
||||
});
|
|
@ -102,6 +102,24 @@ body:after, body:before {
|
|||
-webkit-clip-path: polygon(100% 0, 0 0, 0 77.5%, 1% 77.4%, 2% 77.1%, 3% 76.6%, 4% 75.9%, 5% 75.05%, 6% 74.05%, 7% 72.95%, 8% 71.75%, 9% 70.55%, 10% 69.3%, 11% 68.05%, 12% 66.9%, 13% 65.8%, 14% 64.8%, 15% 64%, 16% 63.35%, 17% 62.85%, 18% 62.6%, 19% 62.5%, 20% 62.65%, 21% 63%, 22% 63.5%, 23% 64.2%, 24% 65.1%, 25% 66.1%, 26% 67.2%, 27% 68.4%, 28% 69.65%, 29% 70.9%, 30% 72.15%, 31% 73.3%, 32% 74.35%, 33% 75.3%, 34% 76.1%, 35% 76.75%, 36% 77.2%, 37% 77.45%, 38% 77.5%, 39% 77.3%, 40% 76.95%, 41% 76.4%, 42% 75.65%, 43% 74.75%, 44% 73.75%, 45% 72.6%, 46% 71.4%, 47% 70.15%, 48% 68.9%, 49% 67.7%, 50% 66.55%, 51% 65.5%, 52% 64.55%, 53% 63.75%, 54% 63.15%, 55% 62.75%, 56% 62.55%, 57% 62.5%, 58% 62.7%, 59% 63.1%, 60% 63.7%, 61% 64.45%, 62% 65.4%, 63% 66.45%, 64% 67.6%, 65% 68.8%, 66% 70.05%, 67% 71.3%, 68% 72.5%, 69% 73.6%, 70% 74.65%, 71% 75.55%, 72% 76.35%, 73% 76.9%, 74% 77.3%, 75% 77.5%, 76% 77.45%, 77% 77.25%, 78% 76.8%, 79% 76.2%, 80% 75.4%, 81% 74.45%, 82% 73.4%, 83% 72.25%, 84% 71.05%, 85% 69.8%, 86% 68.55%, 87% 67.35%, 88% 66.2%, 89% 65.2%, 90% 64.3%, 91% 63.55%, 92% 63%, 93% 62.65%, 94% 62.5%, 95% 62.55%, 96% 62.8%, 97% 63.3%, 98% 63.9%, 99% 64.75%, 100% 65.7%);
|
||||
clip-path: polygon(100% 0, 0 0, 0 77.5%, 1% 77.4%, 2% 77.1%, 3% 76.6%, 4% 75.9%, 5% 75.05%, 6% 74.05%, 7% 72.95%, 8% 71.75%, 9% 70.55%, 10% 69.3%, 11% 68.05%, 12% 66.9%, 13% 65.8%, 14% 64.8%, 15% 64%, 16% 63.35%, 17% 62.85%, 18% 62.6%, 19% 62.5%, 20% 62.65%, 21% 63%, 22% 63.5%, 23% 64.2%, 24% 65.1%, 25% 66.1%, 26% 67.2%, 27% 68.4%, 28% 69.65%, 29% 70.9%, 30% 72.15%, 31% 73.3%, 32% 74.35%, 33% 75.3%, 34% 76.1%, 35% 76.75%, 36% 77.2%, 37% 77.45%, 38% 77.5%, 39% 77.3%, 40% 76.95%, 41% 76.4%, 42% 75.65%, 43% 74.75%, 44% 73.75%, 45% 72.6%, 46% 71.4%, 47% 70.15%, 48% 68.9%, 49% 67.7%, 50% 66.55%, 51% 65.5%, 52% 64.55%, 53% 63.75%, 54% 63.15%, 55% 62.75%, 56% 62.55%, 57% 62.5%, 58% 62.7%, 59% 63.1%, 60% 63.7%, 61% 64.45%, 62% 65.4%, 63% 66.45%, 64% 67.6%, 65% 68.8%, 66% 70.05%, 67% 71.3%, 68% 72.5%, 69% 73.6%, 70% 74.65%, 71% 75.55%, 72% 76.35%, 73% 76.9%, 74% 77.3%, 75% 77.5%, 76% 77.45%, 77% 77.25%, 78% 76.8%, 79% 76.2%, 80% 75.4%, 81% 74.45%, 82% 73.4%, 83% 72.25%, 84% 71.05%, 85% 69.8%, 86% 68.55%, 87% 67.35%, 88% 66.2%, 89% 65.2%, 90% 64.3%, 91% 63.55%, 92% 63%, 93% 62.65%, 94% 62.5%, 95% 62.55%, 96% 62.8%, 97% 63.3%, 98% 63.9%, 99% 64.75%, 100% 65.7%); }
|
||||
|
||||
.arrow {
|
||||
border: solid black;
|
||||
border-width: 0 3px 3px 0;
|
||||
display: inline-block;
|
||||
padding: 3px; }
|
||||
.arrow.right {
|
||||
transform: rotate(-45deg);
|
||||
-webkit-transform: rotate(-45deg); }
|
||||
.arrow.left {
|
||||
transform: rotate(135deg);
|
||||
-webkit-transform: rotate(135deg); }
|
||||
.arrow.up {
|
||||
transform: rotate(-135deg);
|
||||
-webkit-transform: rotate(-135deg); }
|
||||
.arrow.down {
|
||||
transform: rotate(45deg);
|
||||
-webkit-transform: rotate(45deg); }
|
||||
|
||||
.button-small {
|
||||
font-size: 85%; }
|
||||
|
||||
|
@ -166,6 +184,18 @@ body:after, body:before {
|
|||
.messages li.notice {
|
||||
background: rgba(255, 255, 255, 0.5); }
|
||||
|
||||
#notification-customisation {
|
||||
display: block;
|
||||
border: 1px solid #ccc;
|
||||
padding: 1rem;
|
||||
border-radius: 5px; }
|
||||
|
||||
#toggle-customise-notifications {
|
||||
cursor: pointer; }
|
||||
|
||||
#token-table.pure-table td, #token-table.pure-table th {
|
||||
font-size: 80%; }
|
||||
|
||||
#new-watch-form {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
padding: 1em;
|
||||
|
@ -260,8 +290,7 @@ footer {
|
|||
.pure-form input[type=url] {
|
||||
width: 100%; }
|
||||
.pure-form textarea {
|
||||
width: 100%;
|
||||
font-size: 14px; }
|
||||
width: 100%; }
|
||||
|
||||
@media only screen and (max-width: 760px), (min-device-width: 768px) and (max-device-width: 1024px) {
|
||||
.box {
|
||||
|
|
|
@ -72,7 +72,6 @@ section.content {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.watch-tag-list {
|
||||
color: #e70069;
|
||||
white-space: nowrap;
|
||||
|
@ -137,12 +136,33 @@ body:after, body:before {
|
|||
clip-path: polygon(100% 0, 0 0, 0 77.5%, 1% 77.4%, 2% 77.1%, 3% 76.6%, 4% 75.9%, 5% 75.05%, 6% 74.05%, 7% 72.95%, 8% 71.75%, 9% 70.55%, 10% 69.3%, 11% 68.05%, 12% 66.9%, 13% 65.8%, 14% 64.8%, 15% 64%, 16% 63.35%, 17% 62.85%, 18% 62.6%, 19% 62.5%, 20% 62.65%, 21% 63%, 22% 63.5%, 23% 64.2%, 24% 65.1%, 25% 66.1%, 26% 67.2%, 27% 68.4%, 28% 69.65%, 29% 70.9%, 30% 72.15%, 31% 73.3%, 32% 74.35%, 33% 75.3%, 34% 76.1%, 35% 76.75%, 36% 77.2%, 37% 77.45%, 38% 77.5%, 39% 77.3%, 40% 76.95%, 41% 76.4%, 42% 75.65%, 43% 74.75%, 44% 73.75%, 45% 72.6%, 46% 71.4%, 47% 70.15%, 48% 68.9%, 49% 67.7%, 50% 66.55%, 51% 65.5%, 52% 64.55%, 53% 63.75%, 54% 63.15%, 55% 62.75%, 56% 62.55%, 57% 62.5%, 58% 62.7%, 59% 63.1%, 60% 63.7%, 61% 64.45%, 62% 65.4%, 63% 66.45%, 64% 67.6%, 65% 68.8%, 66% 70.05%, 67% 71.3%, 68% 72.5%, 69% 73.6%, 70% 74.65%, 71% 75.55%, 72% 76.35%, 73% 76.9%, 74% 77.3%, 75% 77.5%, 76% 77.45%, 77% 77.25%, 78% 76.8%, 79% 76.2%, 80% 75.4%, 81% 74.45%, 82% 73.4%, 83% 72.25%, 84% 71.05%, 85% 69.8%, 86% 68.55%, 87% 67.35%, 88% 66.2%, 89% 65.2%, 90% 64.3%, 91% 63.55%, 92% 63%, 93% 62.65%, 94% 62.5%, 95% 62.55%, 96% 62.8%, 97% 63.3%, 98% 63.9%, 99% 64.75%, 100% 65.7%)
|
||||
}
|
||||
|
||||
.arrow {
|
||||
border: solid black;
|
||||
border-width: 0 3px 3px 0;
|
||||
display: inline-block;
|
||||
padding: 3px;
|
||||
&.right {
|
||||
transform: rotate(-45deg);
|
||||
-webkit-transform: rotate(-45deg);
|
||||
}
|
||||
&.left {
|
||||
transform: rotate(135deg);
|
||||
-webkit-transform: rotate(135deg);
|
||||
}
|
||||
&.up {
|
||||
transform: rotate(-135deg);
|
||||
-webkit-transform: rotate(-135deg);
|
||||
}
|
||||
&.down {
|
||||
transform: rotate(45deg);
|
||||
-webkit-transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
|
||||
.button-small {
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
|
||||
.fetch-error {
|
||||
padding-top: 1em;
|
||||
font-size: 60%;
|
||||
|
@ -221,7 +241,24 @@ body:after, body:before {
|
|||
background: rgba(255, 255, 255, .5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#notification-customisation {
|
||||
display: block;
|
||||
border: 1px solid #ccc;
|
||||
padding: 1rem;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#toggle-customise-notifications {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
#token-table {
|
||||
&.pure-table td, &.pure-table th {
|
||||
font-size: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
#new-watch-form {
|
||||
|
@ -351,7 +388,6 @@ footer {
|
|||
|
||||
textarea {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -441,5 +477,3 @@ and also iPads specifically.
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -39,7 +39,10 @@ class ChangeDetectionStore:
|
|||
'application': {
|
||||
'password': False,
|
||||
'extract_title_as_title': False,
|
||||
'notification_urls': [] # Apprise URL list
|
||||
'notification_urls': [], # Apprise URL list
|
||||
# Custom notification content
|
||||
'notification_title': 'ChangeDetection.io Notification - {watch_url}',
|
||||
'notification_body': '{base_url}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -176,7 +179,6 @@ class ChangeDetectionStore:
|
|||
|
||||
@property
|
||||
def data(self):
|
||||
|
||||
has_unviewed = False
|
||||
for uuid, v in self.__data['watching'].items():
|
||||
self.__data['watching'][uuid]['newest_history_key'] = self.get_newest_history_key(uuid)
|
||||
|
@ -191,8 +193,6 @@ class ChangeDetectionStore:
|
|||
if not self.__data['watching'][uuid]['title']:
|
||||
self.__data['watching'][uuid]['title'] = None
|
||||
|
||||
|
||||
|
||||
self.__data['has_unviewed'] = has_unviewed
|
||||
|
||||
return self.__data
|
||||
|
@ -355,7 +355,7 @@ class ChangeDetectionStore:
|
|||
if self.stop_thread:
|
||||
print("Shutting down datastore thread")
|
||||
return
|
||||
|
||||
|
||||
if self.needs_write:
|
||||
self.sync_to_json()
|
||||
time.sleep(3)
|
||||
|
|
|
@ -50,7 +50,8 @@ User-Agent: wonderbra 1.0") }}
|
|||
</fieldset>
|
||||
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.notification_urls, rows=5, placeholder="Gitter - gitter://token/room
|
||||
{{ render_field(form.notification_urls, rows=5, placeholder="Examples:
|
||||
Gitter - gitter://token/room
|
||||
Office365 - o365://TenantID:AccountEmail/ClientID/ClientSecret/TargetEmail
|
||||
AWS SNS - sns://AccessKeyID/AccessSecretKey/RegionName/+PhoneNo
|
||||
SMTPS - mailtos://user:pass@mail.domain.com?to=receivingAddress@example.com
|
||||
|
|
|
@ -2,45 +2,100 @@
|
|||
|
||||
{% block content %}
|
||||
{% from '_helpers.jinja' import render_field %}
|
||||
|
||||
<script type="text/javascript" src="static/js/settings.js"></script>
|
||||
<div class="edit-form">
|
||||
<form class="pure-form pure-form-stacked settings" action="/settings" method="POST">
|
||||
<fieldset>
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.minutes_between_check, size=5) }}
|
||||
<span class="pure-form-message-inline">Default time for all watches, when the watch does not have a specific time setting.</span>
|
||||
<span class="pure-form-message-inline">Default time for all watches, when the watch does not have a specific time setting.</span>
|
||||
</div>
|
||||
<div class="pure-control-group">
|
||||
{% if current_user.is_authenticated %}
|
||||
<a href="/settings?removepassword=true" class="pure-button pure-button-primary">Remove password</a>
|
||||
<a href="/settings?removepassword=true" class="pure-button pure-button-primary">Remove password</a>
|
||||
{% else %}
|
||||
{{ render_field(form.password, size=10) }}
|
||||
<span class="pure-form-message-inline">Password protection for your changedetection.io application.</span>
|
||||
{{ render_field(form.password, size=10) }}
|
||||
<span class="pure-form-message-inline">Password protection for your changedetection.io application.</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.extract_title_as_title) }}
|
||||
<span class="pure-form-message-inline">Note: This will automatically apply to all existing watches.</span>
|
||||
<span class="pure-form-message-inline">Note: This will automatically apply to all existing watches.</span>
|
||||
</div>
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.notification_urls, rows=5, placeholder="Gitter - gitter://token/room
|
||||
|
||||
<div class="field-group">
|
||||
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.notification_urls, rows=5, placeholder="Examples:
|
||||
Gitter - gitter://token/room
|
||||
Office365 - o365://TenantID:AccountEmail/ClientID/ClientSecret/TargetEmail
|
||||
AWS SNS - sns://AccessKeyID/AccessSecretKey/RegionName/+PhoneNo
|
||||
SMTPS - mailtos://user:pass@mail.domain.com?to=receivingAddress@example.com
|
||||
") }}
|
||||
<span class="pure-form-message-inline">Use <a target=_new href="https://github.com/caronc/apprise">AppRise URLs</a> for notification to just about any service!</span>
|
||||
</div>
|
||||
<div class="pure-controls">
|
||||
<span class="pure-form-message-inline"><label for="trigger-test-notification" class="pure-checkbox">
|
||||
<input type="checkbox" id="trigger-test-notification" name="trigger-test-notification"> Send test notification on save.</label></span>
|
||||
SMTPS - mailtos://user:pass@mail.domain.com?to=receivingAddress@example.com") }}
|
||||
<div class="pure-form-message-inline">Use <a target=_new href="https://github.com/caronc/apprise">AppRise URLs</a> for notification to just about any service!
|
||||
<a id="toggle-customise-notifications">Customise notification body: <i
|
||||
class="arrow down"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="notification-customisation" style="display:none;">
|
||||
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.notification_title, size=80) }}
|
||||
<span class="pure-form-message-inline">Title for all notifications</span>
|
||||
</div>
|
||||
<div class="pure-control-group">
|
||||
{{ render_field(form.notification_body , rows=5) }}
|
||||
<span class="pure-form-message-inline">Body for all notifications</span>
|
||||
</div>
|
||||
<div class="pure-controls">
|
||||
<span class="pure-form-message-inline">
|
||||
These tokens can be used in the notification body and title to
|
||||
customise the notification text.
|
||||
</span>
|
||||
<table class="pure-table" id="token-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Token</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>{base_url}</code></td>
|
||||
<td>The URL of the changedetection.io instance you are running.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>{watch_url}</code></td>
|
||||
<td>The URL being watched.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>{preview_url}</code></td>
|
||||
<td>The URL of the preview page generated by changedetection.io.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>{diff_url}</code></td>
|
||||
<td>The URL of the diff page generated by changedetection.io.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>{current_snapshot}</code></td>
|
||||
<td>The current snapshot value, useful when combined with JSON or CSS filters</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<span class="pure-form-message-inline">
|
||||
URLs generated by changedetection.io (such as <code>{diff_url}</code>) require the <code>BASE_URL</code> environment variable set.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<span class="pure-form-message-inline"><label for="trigger-test-notification" class="pure-checkbox">
|
||||
<input type="checkbox" id="trigger-test-notification" name="trigger-test-notification"> Send test notification on save.</label>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
<div class="pure-control-group">
|
||||
<button type="submit" class="pure-button pure-button-primary">Save</button>
|
||||
</div>
|
||||
<br/>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<a href="/" class="pure-button button-small button-cancel">Back</a>
|
||||
<a href="/scrub" class="pure-button button-small button-cancel">Delete History Snapshot Data</a>
|
||||
|
|
|
@ -22,10 +22,18 @@ def app(request):
|
|||
except FileExistsError:
|
||||
pass
|
||||
|
||||
try:
|
||||
os.unlink("{}/url-watches.json".format(datastore_path))
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
# Enable a BASE_URL for notifications to work (so we can look for diff/ etc URLs)
|
||||
os.environ["BASE_URL"] = "http://mysite.com/"
|
||||
|
||||
# Unlink test output files
|
||||
files = ['test-datastore/output.txt',
|
||||
"{}/url-watches.json".format(datastore_path),
|
||||
'test-datastore/notification.txt']
|
||||
for file in files:
|
||||
try:
|
||||
os.unlink(file)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
app_config = {'datastore_path': datastore_path}
|
||||
datastore = store.ChangeDetectionStore(datastore_path=app_config['datastore_path'], include_default_watches=False)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
|
||||
import os
|
||||
import time
|
||||
from flask import url_for
|
||||
from . util import set_original_response, set_modified_response, live_server_setup
|
||||
|
@ -22,7 +22,7 @@ def test_check_notification(client, live_server):
|
|||
)
|
||||
assert b"1 Imported" in res.data
|
||||
|
||||
# Give the thread time to pick it up
|
||||
# Give the thread time to pick up the first version
|
||||
time.sleep(3)
|
||||
|
||||
# Goto the edit page, add our ignore text
|
||||
|
@ -33,16 +33,34 @@ def test_check_notification(client, live_server):
|
|||
print (">>>> Notification URL: "+notification_url)
|
||||
res = client.post(
|
||||
url_for("edit_page", uuid="first"),
|
||||
data={"notification_urls": notification_url, "url": test_url, "tag": "", "headers": ""},
|
||||
data={"notification_urls": notification_url,
|
||||
"url": test_url,
|
||||
"tag": "",
|
||||
"headers": "",
|
||||
"trigger_check": "y"},
|
||||
follow_redirects=True
|
||||
)
|
||||
assert b"Updated watch." in res.data
|
||||
assert b"Notifications queued" in res.data
|
||||
|
||||
# Hit the edit page, be sure that we saved it
|
||||
res = client.get(
|
||||
url_for("edit_page", uuid="first"))
|
||||
assert bytes(notification_url.encode('utf-8')) in res.data
|
||||
|
||||
|
||||
# Because we hit 'send test notification on save'
|
||||
time.sleep(3)
|
||||
|
||||
# Verify what was sent as a notification, this file should exist
|
||||
with open("test-datastore/notification.txt", "r") as f:
|
||||
notification_submission = f.read()
|
||||
# Did we see the URL that had a change, in the notification?
|
||||
assert test_url in notification_submission
|
||||
|
||||
os.unlink("test-datastore/notification.txt")
|
||||
|
||||
|
||||
set_modified_response()
|
||||
|
||||
# Trigger a check
|
||||
|
@ -57,16 +75,48 @@ def test_check_notification(client, live_server):
|
|||
|
||||
assert bytes("just now".encode('utf-8')) in res.data
|
||||
|
||||
# Verify what was sent as a notification
|
||||
with open("test-datastore/notification.txt", "r") as f:
|
||||
notification_submission = f.read()
|
||||
# Did we see the URL that had a change, in the notification?
|
||||
assert test_url in notification_submission
|
||||
|
||||
# Check it triggered
|
||||
res = client.get(
|
||||
url_for("test_notification_counter"),
|
||||
# Re #65 - did we see our foobar.com BASE_URL ?
|
||||
#assert bytes("https://foobar.com".encode('utf-8')) in notification_submission
|
||||
|
||||
|
||||
## Now configure something clever, we go into custom config (non-default) mode
|
||||
|
||||
with open("test-datastore/output.txt", "w") as f:
|
||||
f.write(";jasdhflkjadshf kjhsdfkjl ahslkjf haslkjd hfaklsj hf\njl;asdhfkasj stuff we will detect\n")
|
||||
|
||||
res = client.post(
|
||||
url_for("settings_page"),
|
||||
data={"notification_title": "New ChangeDetection.io Notification - {watch_url}",
|
||||
"notification_body": "{base_url}\n{watch_url}\n{preview_url}\n{diff_url}\n{current_snapshot}\n:-)",
|
||||
"minutes_between_check": 180},
|
||||
follow_redirects=True
|
||||
)
|
||||
assert b"Settings updated." in res.data
|
||||
|
||||
assert bytes("we hit it".encode('utf-8')) in res.data
|
||||
# Trigger a check
|
||||
client.get(url_for("api_watch_checknow"), follow_redirects=True)
|
||||
|
||||
# Did we see the URL that had a change, in the notification?
|
||||
assert bytes("test-endpoint".encode('utf-8')) in res.data
|
||||
# Give the thread time to pick it up
|
||||
time.sleep(3)
|
||||
|
||||
# Re #65 - did we see our foobar.com BASE_URL ?
|
||||
assert bytes("https://foobar.com".encode('utf-8')) in res.data
|
||||
# Did the front end see it?
|
||||
res = client.get(
|
||||
url_for("index"))
|
||||
|
||||
assert bytes("just now".encode('utf-8')) in res.data
|
||||
|
||||
with open("test-datastore/notification.txt", "r") as f:
|
||||
notification_submission = f.read()
|
||||
|
||||
assert "diff/" in notification_submission
|
||||
assert "preview/" in notification_submission
|
||||
assert ":-)" in notification_submission
|
||||
assert "New ChangeDetection.io Notification - {}".format(test_url) in notification_submission
|
||||
# This should insert the {current_snapshot}
|
||||
assert "stuff we will detect" in notification_submission
|
||||
|
|
|
@ -43,27 +43,18 @@ def live_server_setup(live_server):
|
|||
with open("test-datastore/output.txt", "r") as f:
|
||||
return f.read()
|
||||
|
||||
# Where we POST to as a notification
|
||||
@live_server.app.route('/test_notification_endpoint', methods=['POST'])
|
||||
def test_notification_endpoint():
|
||||
from flask import request
|
||||
|
||||
with open("test-datastore/count.txt", "w") as f:
|
||||
f.write("we hit it\n")
|
||||
with open("test-datastore/notification.txt", "wb") as f:
|
||||
# Debug method, dump all POST to file also, used to prove #65
|
||||
data = request.stream.read()
|
||||
if data != None:
|
||||
f.write(str(data))
|
||||
f.write(data)
|
||||
|
||||
print("\n>> Test notification endpoint was hit.\n")
|
||||
return "Text was set"
|
||||
|
||||
# And this should return not zero.
|
||||
@live_server.app.route('/test_notification_counter')
|
||||
def test_notification_counter():
|
||||
try:
|
||||
with open("test-datastore/count.txt", "r") as f:
|
||||
return f.read()
|
||||
except FileNotFoundError:
|
||||
return "nope :("
|
||||
|
||||
live_server.start()
|
||||
live_server.start()
|
||||
|
|
|
@ -40,26 +40,35 @@ class update_worker(threading.Thread):
|
|||
try:
|
||||
self.datastore.update_watch(uuid=uuid, update_obj=result)
|
||||
if changed_detected:
|
||||
# A change was detected
|
||||
self.datastore.save_history_text(uuid=uuid, contents=contents, result_obj=result)
|
||||
|
||||
# A change was detected
|
||||
newest_version_file_contents = ""
|
||||
self.datastore.save_history_text(uuid=uuid, contents=contents, result_obj=result)
|
||||
watch = self.datastore.data['watching'][uuid]
|
||||
|
||||
newest_key = self.datastore.get_newest_history_key(uuid)
|
||||
if newest_key:
|
||||
with open(watch['history'][newest_key], 'r') as f:
|
||||
newest_version_file_contents = f.read().strip()
|
||||
|
||||
n_object = {
|
||||
'watch_url': self.datastore.data['watching'][uuid]['url'],
|
||||
'uuid': uuid,
|
||||
'current_snapshot': newest_version_file_contents
|
||||
}
|
||||
|
||||
# Did it have any notification alerts to hit?
|
||||
if len(watch['notification_urls']):
|
||||
print("Processing notifications for UUID: {}".format(uuid))
|
||||
n_object = {'watch_url': self.datastore.data['watching'][uuid]['url'],
|
||||
'notification_urls': watch['notification_urls']}
|
||||
n_object['notification_urls'] = watch['notification_urls']
|
||||
self.notification_q.put(n_object)
|
||||
|
||||
|
||||
# No? maybe theres a global setting, queue them all
|
||||
elif len(self.datastore.data['settings']['application']['notification_urls']):
|
||||
print("Processing GLOBAL notifications for UUID: {}".format(uuid))
|
||||
n_object = {'watch_url': self.datastore.data['watching'][uuid]['url'],
|
||||
'notification_urls': self.datastore.data['settings']['application'][
|
||||
'notification_urls']}
|
||||
n_object['notification_urls'] = self.datastore.data['settings']['application']['notification_urls']
|
||||
self.notification_q.put(n_object)
|
||||
|
||||
except Exception as e:
|
||||
print("!!!! Exception in update_worker !!!\n", e)
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue