mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2026-04-18 17:08:06 +00:00
Compare commits
8 Commits
ticket-16-
...
quick-setu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30b0130837 | ||
|
|
f6e518497a | ||
|
|
63e91a3d66 | ||
|
|
3034d047c2 | ||
|
|
2620818ba7 | ||
|
|
9fe4f95990 | ||
|
|
ffd2a89d60 | ||
|
|
8f40f19328 |
@@ -70,6 +70,10 @@ Docker standalone
|
|||||||
$ docker run -d --restart always -p "127.0.0.1:5000:5000" -v datastore-volume:/datastore --name changedetection.io dgtlmoon/changedetection.io
|
$ docker run -d --restart always -p "127.0.0.1:5000:5000" -v datastore-volume:/datastore --name changedetection.io dgtlmoon/changedetection.io
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
See the install instructions at the wiki https://github.com/dgtlmoon/changedetection.io/wiki/Microsoft-Windows
|
||||||
|
|
||||||
### Python Pip
|
### Python Pip
|
||||||
|
|
||||||
Check out our pypi page https://pypi.org/project/changedetection.io/
|
Check out our pypi page https://pypi.org/project/changedetection.io/
|
||||||
@@ -163,9 +167,6 @@ See the wiki https://github.com/dgtlmoon/changedetection.io/wiki/Proxy-configura
|
|||||||
|
|
||||||
Raspberry Pi and linux/arm/v6 linux/arm/v7 arm64 devices are supported! See the wiki for [details](https://github.com/dgtlmoon/changedetection.io/wiki/Fetching-pages-with-WebDriver)
|
Raspberry Pi and linux/arm/v6 linux/arm/v7 arm64 devices are supported! See the wiki for [details](https://github.com/dgtlmoon/changedetection.io/wiki/Fetching-pages-with-WebDriver)
|
||||||
|
|
||||||
## Windows support?
|
|
||||||
|
|
||||||
YES! See the wiki https://github.com/dgtlmoon/changedetection.io/wiki/Microsoft-Windows
|
|
||||||
|
|
||||||
## Support us
|
## Support us
|
||||||
|
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
@app.route("/rss", methods=['GET'])
|
@app.route("/rss", methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
def rss():
|
def rss():
|
||||||
|
from . import diff
|
||||||
limit_tag = request.args.get('tag')
|
limit_tag = request.args.get('tag')
|
||||||
|
|
||||||
# Sort by last_changed and add the uuid which is usually the key..
|
# Sort by last_changed and add the uuid which is usually the key..
|
||||||
@@ -301,13 +301,25 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
fg.link(href='https://changedetection.io')
|
fg.link(href='https://changedetection.io')
|
||||||
|
|
||||||
for watch in sorted_watches:
|
for watch in sorted_watches:
|
||||||
|
|
||||||
|
dates = list(watch['history'].keys())
|
||||||
|
# Re #521 - Don't bother processing this one if theres less than 2 snapshots, means we never had a change detected.
|
||||||
|
if len(dates) < 2:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Convert to int, sort and back to str again
|
||||||
|
# @todo replace datastore getter that does this automatically
|
||||||
|
dates = [int(i) for i in dates]
|
||||||
|
dates.sort(reverse=True)
|
||||||
|
dates = [str(i) for i in dates]
|
||||||
|
prev_fname = watch['history'][dates[1]]
|
||||||
|
|
||||||
if not watch['viewed']:
|
if not watch['viewed']:
|
||||||
# Re #239 - GUID needs to be individual for each event
|
# Re #239 - GUID needs to be individual for each event
|
||||||
# @todo In the future make this a configurable link back (see work on BASE_URL https://github.com/dgtlmoon/changedetection.io/pull/228)
|
# @todo In the future make this a configurable link back (see work on BASE_URL https://github.com/dgtlmoon/changedetection.io/pull/228)
|
||||||
guid = "{}/{}".format(watch['uuid'], watch['last_changed'])
|
guid = "{}/{}".format(watch['uuid'], watch['last_changed'])
|
||||||
fe = fg.add_entry()
|
fe = fg.add_entry()
|
||||||
|
|
||||||
|
|
||||||
# Include a link to the diff page, they will have to login here to see if password protection is enabled.
|
# Include a link to the diff page, they will have to login here to see if password protection is enabled.
|
||||||
# Description is the page you watch, link takes you to the diff JS UI page
|
# Description is the page you watch, link takes you to the diff JS UI page
|
||||||
base_url = datastore.data['settings']['application']['base_url']
|
base_url = datastore.data['settings']['application']['base_url']
|
||||||
@@ -316,12 +328,16 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
|
|
||||||
diff_link = {'href': "{}{}".format(base_url, url_for('diff_history_page', uuid=watch['uuid']))}
|
diff_link = {'href': "{}{}".format(base_url, url_for('diff_history_page', uuid=watch['uuid']))}
|
||||||
|
|
||||||
# @todo use title if it exists
|
|
||||||
fe.link(link=diff_link)
|
fe.link(link=diff_link)
|
||||||
fe.title(title=watch['url'])
|
|
||||||
|
|
||||||
# @todo in the future <description><![CDATA[<html><body>Any code html is valid.</body></html>]]></description>
|
# @todo watch should be a getter - watch.get('title') (internally if URL else..)
|
||||||
fe.description(description=watch['url'])
|
|
||||||
|
watch_title = watch.get('title') if watch.get('title') else watch.get('url')
|
||||||
|
fe.title(title=watch_title)
|
||||||
|
latest_fname = watch['history'][dates[0]]
|
||||||
|
|
||||||
|
html_diff = diff.render_diff(prev_fname, latest_fname, include_equal=False, line_feed_sep="</br>")
|
||||||
|
fe.description(description="<![CDATA[<html><body><h4>{}</h4>{}</body></html>".format(watch_title, html_diff))
|
||||||
|
|
||||||
fe.guid(guid, permalink=False)
|
fe.guid(guid, permalink=False)
|
||||||
dt = datetime.datetime.fromtimestamp(int(watch['newest_history_key']))
|
dt = datetime.datetime.fromtimestamp(int(watch['newest_history_key']))
|
||||||
@@ -387,6 +403,38 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
# AJAX endpoint for sending a test
|
||||||
|
@app.route("/notification/send-test", methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def ajax_callback_send_notification_test():
|
||||||
|
|
||||||
|
import apprise
|
||||||
|
apobj = apprise.Apprise()
|
||||||
|
|
||||||
|
# validate URLS
|
||||||
|
if not len(request.form['notification_urls'].strip()):
|
||||||
|
return make_response({'error': 'No Notification URLs set'}, 400)
|
||||||
|
|
||||||
|
for server_url in request.form['notification_urls'].splitlines():
|
||||||
|
if len(server_url.strip()):
|
||||||
|
if not apobj.add(server_url):
|
||||||
|
message = '{} is not a valid AppRise URL.'.format(server_url)
|
||||||
|
return make_response({'error': message}, 400)
|
||||||
|
|
||||||
|
try:
|
||||||
|
n_object = {'watch_url': request.form['window_url'],
|
||||||
|
'notification_urls': request.form['notification_urls'].splitlines(),
|
||||||
|
'notification_title': request.form['notification_title'].strip(),
|
||||||
|
'notification_body': request.form['notification_body'].strip(),
|
||||||
|
'notification_format': request.form['notification_format'].strip()
|
||||||
|
}
|
||||||
|
notification_q.put(n_object)
|
||||||
|
except Exception as e:
|
||||||
|
return make_response({'error': str(e)}, 400)
|
||||||
|
|
||||||
|
return 'OK'
|
||||||
|
|
||||||
@app.route("/scrub", methods=['GET', 'POST'])
|
@app.route("/scrub", methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def scrub_page():
|
def scrub_page():
|
||||||
@@ -548,20 +596,6 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
# Queue the watch for immediate recheck
|
# Queue the watch for immediate recheck
|
||||||
update_q.put(uuid)
|
update_q.put(uuid)
|
||||||
|
|
||||||
if form.trigger_check.data:
|
|
||||||
if len(form.notification_urls.data):
|
|
||||||
n_object = {'watch_url': form.url.data.strip(),
|
|
||||||
'notification_urls': form.notification_urls.data,
|
|
||||||
'notification_title': form.notification_title.data,
|
|
||||||
'notification_body': form.notification_body.data,
|
|
||||||
'notification_format': form.notification_format.data,
|
|
||||||
'uuid': uuid
|
|
||||||
}
|
|
||||||
notification_q.put(n_object)
|
|
||||||
flash('Test notification queued.')
|
|
||||||
else:
|
|
||||||
flash('No notification URLs set, cannot send test.', 'error')
|
|
||||||
|
|
||||||
# Diff page [edit] link should go back to diff page
|
# Diff page [edit] link should go back to diff page
|
||||||
if request.args.get("next") and request.args.get("next") == 'diff' and not form.save_and_preview_button.data:
|
if request.args.get("next") and request.args.get("next") == 'diff' and not form.save_and_preview_button.data:
|
||||||
return redirect(url_for('diff_history_page', uuid=uuid))
|
return redirect(url_for('diff_history_page', uuid=uuid))
|
||||||
@@ -587,7 +621,8 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
watch=datastore.data['watching'][uuid],
|
watch=datastore.data['watching'][uuid],
|
||||||
form=form,
|
form=form,
|
||||||
using_default_minutes=using_default_minutes,
|
using_default_minutes=using_default_minutes,
|
||||||
current_base_url = datastore.data['settings']['application']['base_url']
|
current_base_url=datastore.data['settings']['application']['base_url'],
|
||||||
|
emailprefix=os.getenv('NOTIFICATION_MAIL_BUTTON_PREFIX', False)
|
||||||
)
|
)
|
||||||
|
|
||||||
return output
|
return output
|
||||||
@@ -612,6 +647,7 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
form.notification_body.data = datastore.data['settings']['application']['notification_body']
|
form.notification_body.data = datastore.data['settings']['application']['notification_body']
|
||||||
form.notification_format.data = datastore.data['settings']['application']['notification_format']
|
form.notification_format.data = datastore.data['settings']['application']['notification_format']
|
||||||
form.base_url.data = datastore.data['settings']['application']['base_url']
|
form.base_url.data = datastore.data['settings']['application']['base_url']
|
||||||
|
form.real_browser_save_screenshot.data = datastore.data['settings']['application']['real_browser_save_screenshot']
|
||||||
|
|
||||||
if request.method == 'POST' and form.data.get('removepassword_button') == True:
|
if request.method == 'POST' and form.data.get('removepassword_button') == True:
|
||||||
# Password unset is a GET, but we can lock the session to a salted env password to always need the password
|
# Password unset is a GET, but we can lock the session to a salted env password to always need the password
|
||||||
@@ -634,19 +670,7 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
datastore.data['settings']['application']['global_subtractive_selectors'] = form.global_subtractive_selectors.data
|
datastore.data['settings']['application']['global_subtractive_selectors'] = form.global_subtractive_selectors.data
|
||||||
datastore.data['settings']['application']['global_ignore_text'] = form.global_ignore_text.data
|
datastore.data['settings']['application']['global_ignore_text'] = form.global_ignore_text.data
|
||||||
datastore.data['settings']['application']['ignore_whitespace'] = form.ignore_whitespace.data
|
datastore.data['settings']['application']['ignore_whitespace'] = form.ignore_whitespace.data
|
||||||
|
datastore.data['settings']['application']['real_browser_save_screenshot'] = form.real_browser_save_screenshot.data
|
||||||
if form.trigger_check.data:
|
|
||||||
if len(form.notification_urls.data):
|
|
||||||
n_object = {'watch_url': "Test from changedetection.io!",
|
|
||||||
'notification_urls': form.notification_urls.data,
|
|
||||||
'notification_title': form.notification_title.data,
|
|
||||||
'notification_body': form.notification_body.data,
|
|
||||||
'notification_format': form.notification_format.data,
|
|
||||||
}
|
|
||||||
notification_q.put(n_object)
|
|
||||||
flash('Test notification queued.')
|
|
||||||
else:
|
|
||||||
flash('No notification URLs set, cannot send test.', 'error')
|
|
||||||
|
|
||||||
if not os.getenv("SALTED_PASS", False) and form.password.encrypted_password:
|
if not os.getenv("SALTED_PASS", False) and form.password.encrypted_password:
|
||||||
datastore.data['settings']['application']['password'] = form.password.encrypted_password
|
datastore.data['settings']['application']['password'] = form.password.encrypted_password
|
||||||
@@ -663,7 +687,8 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
output = render_template("settings.html",
|
output = render_template("settings.html",
|
||||||
form=form,
|
form=form,
|
||||||
current_base_url = datastore.data['settings']['application']['base_url'],
|
current_base_url = datastore.data['settings']['application']['base_url'],
|
||||||
hide_remove_pass=os.getenv("SALTED_PASS", False))
|
hide_remove_pass=os.getenv("SALTED_PASS", False),
|
||||||
|
emailprefix=os.getenv('NOTIFICATION_MAIL_BUTTON_PREFIX', False))
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
@@ -763,6 +788,9 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
previous_version_file_contents = "Unable to read {}.\n".format(previous_file)
|
previous_version_file_contents = "Unable to read {}.\n".format(previous_file)
|
||||||
|
|
||||||
|
|
||||||
|
screenshot_url = datastore.get_screenshot(uuid)
|
||||||
|
|
||||||
output = render_template("diff.html", watch_a=watch,
|
output = render_template("diff.html", watch_a=watch,
|
||||||
newest=newest_version_file_contents,
|
newest=newest_version_file_contents,
|
||||||
previous=previous_version_file_contents,
|
previous=previous_version_file_contents,
|
||||||
@@ -773,7 +801,8 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
current_previous_version=str(previous_version),
|
current_previous_version=str(previous_version),
|
||||||
current_diff_url=watch['url'],
|
current_diff_url=watch['url'],
|
||||||
extra_title=" - Diff - {}".format(watch['title'] if watch['title'] else watch['url']),
|
extra_title=" - Diff - {}".format(watch['title'] if watch['title'] else watch['url']),
|
||||||
left_sticky=True)
|
left_sticky=True,
|
||||||
|
screenshot=screenshot_url)
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
@@ -833,15 +862,17 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
else:
|
else:
|
||||||
content.append({'line': "No history found", 'classes': ''})
|
content.append({'line': "No history found", 'classes': ''})
|
||||||
|
|
||||||
|
screenshot_url = datastore.get_screenshot(uuid)
|
||||||
output = render_template("preview.html",
|
output = render_template("preview.html",
|
||||||
content=content,
|
content=content,
|
||||||
extra_stylesheets=extra_stylesheets,
|
extra_stylesheets=extra_stylesheets,
|
||||||
ignored_line_numbers=ignored_line_numbers,
|
ignored_line_numbers=ignored_line_numbers,
|
||||||
triggered_line_numbers=trigger_line_numbers,
|
triggered_line_numbers=trigger_line_numbers,
|
||||||
current_diff_url=watch['url'],
|
current_diff_url=watch['url'],
|
||||||
|
screenshot=screenshot_url,
|
||||||
watch=watch,
|
watch=watch,
|
||||||
uuid=uuid)
|
uuid=uuid)
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
@app.route("/settings/notification-logs", methods=['GET'])
|
@app.route("/settings/notification-logs", methods=['GET'])
|
||||||
@@ -954,6 +985,28 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
|
|
||||||
@app.route("/static/<string:group>/<string:filename>", methods=['GET'])
|
@app.route("/static/<string:group>/<string:filename>", methods=['GET'])
|
||||||
def static_content(group, filename):
|
def static_content(group, filename):
|
||||||
|
if group == 'screenshot':
|
||||||
|
|
||||||
|
from flask import make_response
|
||||||
|
|
||||||
|
# Could be sensitive, follow password requirements
|
||||||
|
if datastore.data['settings']['application']['password'] and not flask_login.current_user.is_authenticated:
|
||||||
|
abort(403)
|
||||||
|
|
||||||
|
# These files should be in our subdirectory
|
||||||
|
try:
|
||||||
|
# set nocache, set content-type
|
||||||
|
watch_dir = datastore_o.datastore_path + "/" + filename
|
||||||
|
response = make_response(send_from_directory(filename="last-screenshot.png", directory=watch_dir, path=watch_dir + "/last-screenshot.png"))
|
||||||
|
response.headers['Content-type'] = 'image/png'
|
||||||
|
response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
|
||||||
|
response.headers['Pragma'] = 'no-cache'
|
||||||
|
response.headers['Expires'] = 0
|
||||||
|
return response
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
# These files should be in our subdirectory
|
# These files should be in our subdirectory
|
||||||
try:
|
try:
|
||||||
return send_from_directory("static/{}".format(group), path=filename)
|
return send_from_directory("static/{}".format(group), path=filename)
|
||||||
@@ -984,10 +1037,14 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
flash("Error")
|
flash("Error")
|
||||||
return redirect(url_for('index'))
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/delete", methods=['GET'])
|
@app.route("/api/delete", methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
def api_delete():
|
def api_delete():
|
||||||
uuid = request.args.get('uuid')
|
uuid = request.args.get('uuid')
|
||||||
|
# More for testing, possible to return the first/only
|
||||||
|
if uuid == 'first':
|
||||||
|
uuid = list(datastore.data['watching'].keys()).pop()
|
||||||
datastore.delete(uuid)
|
datastore.delete(uuid)
|
||||||
flash('Deleted.')
|
flash('Deleted.')
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,14 @@ class Fetcher():
|
|||||||
# Should set self.error, self.status_code and self.content
|
# Should set self.error, self.status_code and self.content
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def quit(self):
|
||||||
|
return
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def screenshot(self):
|
||||||
|
return
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_last_status_code(self):
|
def get_last_status_code(self):
|
||||||
return self.status_code
|
return self.status_code
|
||||||
@@ -116,16 +124,16 @@ class html_webdriver(Fetcher):
|
|||||||
# request_body, request_method unused for now, until some magic in the future happens.
|
# request_body, request_method unused for now, until some magic in the future happens.
|
||||||
|
|
||||||
# check env for WEBDRIVER_URL
|
# check env for WEBDRIVER_URL
|
||||||
driver = webdriver.Remote(
|
self.driver = webdriver.Remote(
|
||||||
command_executor=self.command_executor,
|
command_executor=self.command_executor,
|
||||||
desired_capabilities=DesiredCapabilities.CHROME,
|
desired_capabilities=DesiredCapabilities.CHROME,
|
||||||
proxy=self.proxy)
|
proxy=self.proxy)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
driver.get(url)
|
self.driver.get(url)
|
||||||
except WebDriverException as e:
|
except WebDriverException as e:
|
||||||
# Be sure we close the session window
|
# Be sure we close the session window
|
||||||
driver.quit()
|
self.quit()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# @todo - how to check this? is it possible?
|
# @todo - how to check this? is it possible?
|
||||||
@@ -135,26 +143,33 @@ class html_webdriver(Fetcher):
|
|||||||
|
|
||||||
# @todo - dom wait loaded?
|
# @todo - dom wait loaded?
|
||||||
time.sleep(int(os.getenv("WEBDRIVER_DELAY_BEFORE_CONTENT_READY", 5)))
|
time.sleep(int(os.getenv("WEBDRIVER_DELAY_BEFORE_CONTENT_READY", 5)))
|
||||||
self.content = driver.page_source
|
self.content = self.driver.page_source
|
||||||
self.headers = {}
|
self.headers = {}
|
||||||
|
|
||||||
driver.quit()
|
def screenshot(self):
|
||||||
|
return self.driver.get_screenshot_as_png()
|
||||||
|
|
||||||
|
# Does the connection to the webdriver work? run a test connection.
|
||||||
def is_ready(self):
|
def is_ready(self):
|
||||||
from selenium import webdriver
|
from selenium import webdriver
|
||||||
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||||
from selenium.common.exceptions import WebDriverException
|
from selenium.common.exceptions import WebDriverException
|
||||||
|
|
||||||
driver = webdriver.Remote(
|
self.driver = webdriver.Remote(
|
||||||
command_executor=self.command_executor,
|
command_executor=self.command_executor,
|
||||||
desired_capabilities=DesiredCapabilities.CHROME)
|
desired_capabilities=DesiredCapabilities.CHROME)
|
||||||
|
|
||||||
# driver.quit() seems to cause better exceptions
|
# driver.quit() seems to cause better exceptions
|
||||||
driver.quit()
|
self.quit()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def quit(self):
|
||||||
|
if self.driver:
|
||||||
|
try:
|
||||||
|
self.driver.quit()
|
||||||
|
except Exception as e:
|
||||||
|
print("Exception in chrome shutdown/quit" + str(e))
|
||||||
|
|
||||||
# "html_requests" is listed as the default fetcher in store.py!
|
# "html_requests" is listed as the default fetcher in store.py!
|
||||||
class html_requests(Fetcher):
|
class html_requests(Fetcher):
|
||||||
fetcher_description = "Basic fast Plaintext/HTTP Client"
|
fetcher_description = "Basic fast Plaintext/HTTP Client"
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class perform_site_check():
|
|||||||
timestamp = int(time.time()) # used for storage etc too
|
timestamp = int(time.time()) # used for storage etc too
|
||||||
|
|
||||||
changed_detected = False
|
changed_detected = False
|
||||||
|
screenshot = False # as bytes
|
||||||
stripped_text_from_html = ""
|
stripped_text_from_html = ""
|
||||||
|
|
||||||
watch = self.datastore.data['watching'][uuid]
|
watch = self.datastore.data['watching'][uuid]
|
||||||
@@ -171,5 +172,9 @@ class perform_site_check():
|
|||||||
if not watch['title'] or not len(watch['title']):
|
if not watch['title'] or not len(watch['title']):
|
||||||
update_obj['title'] = html_tools.extract_element(find='title', html_content=fetcher.content)
|
update_obj['title'] = html_tools.extract_element(find='title', html_content=fetcher.content)
|
||||||
|
|
||||||
|
if self.datastore.data['settings']['application'].get('real_browser_save_screenshot', True):
|
||||||
|
screenshot = fetcher.screenshot()
|
||||||
|
|
||||||
return changed_detected, update_obj, text_content_before_ignored_filter
|
fetcher.quit()
|
||||||
|
|
||||||
|
return changed_detected, update_obj, text_content_before_ignored_filter, screenshot
|
||||||
|
|||||||
@@ -306,7 +306,6 @@ class commonSettingsForm(Form):
|
|||||||
notification_title = StringField('Notification Title', default=default_notification_title, validators=[validators.Optional(), ValidateTokensList()])
|
notification_title = StringField('Notification Title', default=default_notification_title, validators=[validators.Optional(), ValidateTokensList()])
|
||||||
notification_body = TextAreaField('Notification Body', default=default_notification_body, validators=[validators.Optional(), ValidateTokensList()])
|
notification_body = TextAreaField('Notification Body', default=default_notification_body, validators=[validators.Optional(), ValidateTokensList()])
|
||||||
notification_format = SelectField('Notification Format', choices=valid_notification_formats.keys(), default=default_notification_format)
|
notification_format = SelectField('Notification Format', choices=valid_notification_formats.keys(), default=default_notification_format)
|
||||||
trigger_check = BooleanField('Send test notification on save')
|
|
||||||
fetch_backend = RadioField(u'Fetch Method', choices=content_fetcher.available_fetchers(), validators=[ValidateContentFetcherIsReady()])
|
fetch_backend = RadioField(u'Fetch Method', choices=content_fetcher.available_fetchers(), validators=[ValidateContentFetcherIsReady()])
|
||||||
extract_title_as_title = BooleanField('Extract <title> from document and use as watch title', default=False)
|
extract_title_as_title = BooleanField('Extract <title> from document and use as watch title', default=False)
|
||||||
|
|
||||||
@@ -345,7 +344,6 @@ class watchForm(commonSettingsForm):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
class globalSettingsForm(commonSettingsForm):
|
class globalSettingsForm(commonSettingsForm):
|
||||||
|
|
||||||
password = SaltyPasswordField()
|
password = SaltyPasswordField()
|
||||||
minutes_between_check = html5.IntegerField('Maximum time in minutes until recheck',
|
minutes_between_check = html5.IntegerField('Maximum time in minutes until recheck',
|
||||||
[validators.NumberRange(min=1)])
|
[validators.NumberRange(min=1)])
|
||||||
@@ -355,4 +353,5 @@ class globalSettingsForm(commonSettingsForm):
|
|||||||
global_ignore_text = StringListField('Ignore Text', [ValidateListRegex()])
|
global_ignore_text = StringListField('Ignore Text', [ValidateListRegex()])
|
||||||
ignore_whitespace = BooleanField('Ignore whitespace')
|
ignore_whitespace = BooleanField('Ignore whitespace')
|
||||||
save_button = SubmitField('Save', render_kw={"class": "pure-button pure-button-primary"})
|
save_button = SubmitField('Save', render_kw={"class": "pure-button pure-button-primary"})
|
||||||
removepassword_button = SubmitField('Remove password', render_kw={"class": "pure-button pure-button-primary"})
|
real_browser_save_screenshot = BooleanField('Save last screenshot when using Chrome?')
|
||||||
|
removepassword_button = SubmitField('Remove password', render_kw={"class": "pure-button pure-button-primary"})
|
||||||
|
|||||||
2
changedetectionio/static/js/jquery-3.6.0.min.js
vendored
Normal file
2
changedetectionio/static/js/jquery-3.6.0.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
53
changedetectionio/static/js/notifications.js
Normal file
53
changedetectionio/static/js/notifications.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
$(document).ready(function() {
|
||||||
|
|
||||||
|
$('#add-email-helper').click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
email = prompt("Destination email");
|
||||||
|
if(email) {
|
||||||
|
var n = $("#notification_urls");
|
||||||
|
var p=email_notification_prefix;
|
||||||
|
$(n).val( $.trim( $(n).val() )+"\n"+email_notification_prefix+email );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#send-test-notification').click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// this can be global
|
||||||
|
var csrftoken = $('input[name=csrf_token]').val();
|
||||||
|
$.ajaxSetup({
|
||||||
|
beforeSend: function(xhr, settings) {
|
||||||
|
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
|
||||||
|
xhr.setRequestHeader("X-CSRFToken", csrftoken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
data = {
|
||||||
|
window_url : window.location.href,
|
||||||
|
notification_urls : $('#notification_urls').val(),
|
||||||
|
notification_title : $('#notification_title').val(),
|
||||||
|
notification_body : $('#notification_body').val(),
|
||||||
|
notification_format : $('#notification_format').val(),
|
||||||
|
}
|
||||||
|
for (key in data) {
|
||||||
|
if (!data[key].length) {
|
||||||
|
alert(key+" is empty, cannot send test.")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: "POST",
|
||||||
|
url: notification_base_url,
|
||||||
|
data : data
|
||||||
|
}).done(function(data){
|
||||||
|
console.log(data);
|
||||||
|
alert('Sent');
|
||||||
|
}).fail(function(data){
|
||||||
|
console.log(data);
|
||||||
|
alert('Error: '+data.responseJSON.error);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
// Rewrite this is a plugin.. is all this JS really 'worth it?'
|
// Rewrite this is a plugin.. is all this JS really 'worth it?'
|
||||||
|
|
||||||
|
|
||||||
|
if(!window.location.hash) {
|
||||||
|
var tab=document.querySelectorAll("#default-tab a");
|
||||||
|
tab[0].click();
|
||||||
|
}
|
||||||
|
|
||||||
window.addEventListener('hashchange', function() {
|
window.addEventListener('hashchange', function() {
|
||||||
var tabs = document.getElementsByClassName('active');
|
var tabs = document.getElementsByClassName('active');
|
||||||
while (tabs[0]) {
|
while (tabs[0]) {
|
||||||
@@ -21,7 +26,6 @@ if (!has_errors.length) {
|
|||||||
focus_error_tab();
|
focus_error_tab();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function set_active_tab() {
|
function set_active_tab() {
|
||||||
var tab=document.querySelectorAll("a[href='"+location.hash+"']");
|
var tab=document.querySelectorAll("a[href='"+location.hash+"']");
|
||||||
if (tab.length) {
|
if (tab.length) {
|
||||||
|
|||||||
6
changedetectionio/static/js/watch-overview.js
Normal file
6
changedetectionio/static/js/watch-overview.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
$(function () {
|
||||||
|
// Remove unviewed status when normally clicked
|
||||||
|
$('.diff-link').click(function () {
|
||||||
|
$(this).closest('.unviewed').removeClass('unviewed');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
#diff-ui {
|
#diff-ui {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
padding: 2em;
|
padding: 2em;
|
||||||
margin: 1em;
|
margin-left: 1em;
|
||||||
|
margin-right: 1em;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
font-size: 11px; }
|
font-size: 11px; }
|
||||||
#diff-ui table {
|
#diff-ui table {
|
||||||
@@ -70,3 +71,8 @@ td#diff-col div {
|
|||||||
/* ignored and triggered? make it obvious error */
|
/* ignored and triggered? make it obvious error */
|
||||||
.ignored.triggered {
|
.ignored.triggered {
|
||||||
background-color: #ff0000; }
|
background-color: #ff0000; }
|
||||||
|
|
||||||
|
.tab-pane-inner#screenshot {
|
||||||
|
text-align: center; }
|
||||||
|
.tab-pane-inner#screenshot img {
|
||||||
|
max-width: 99%; }
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
background: #fff;
|
background: #fff;
|
||||||
padding: 2em;
|
padding: 2em;
|
||||||
margin: 1em;
|
margin-left: 1em;
|
||||||
|
margin-right: 1em;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
|
||||||
@@ -85,4 +86,11 @@ td#diff-col div {
|
|||||||
/* ignored and triggered? make it obvious error */
|
/* ignored and triggered? make it obvious error */
|
||||||
.ignored.triggered {
|
.ignored.triggered {
|
||||||
background-color: #ff0000;
|
background-color: #ff0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-pane-inner#screenshot {
|
||||||
|
text-align: center;
|
||||||
|
img {
|
||||||
|
max-width: 99%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -317,7 +317,7 @@ footer {
|
|||||||
right: auto; }
|
right: auto; }
|
||||||
section.content {
|
section.content {
|
||||||
padding-top: 110px; }
|
padding-top: 110px; }
|
||||||
div.tabs ul li {
|
div.tabs.collapsable ul li {
|
||||||
display: block;
|
display: block;
|
||||||
border-radius: 0px; }
|
border-radius: 0px; }
|
||||||
input[type='text'] {
|
input[type='text'] {
|
||||||
@@ -403,14 +403,15 @@ and also iPads specifically.
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-radius: 5px; }
|
border-radius: 5px; }
|
||||||
|
|
||||||
|
.tab-pane-inner {
|
||||||
|
padding: 0px; }
|
||||||
|
.tab-pane-inner:not(:target) {
|
||||||
|
display: none; }
|
||||||
|
.tab-pane-inner:target {
|
||||||
|
display: block; }
|
||||||
|
|
||||||
.edit-form {
|
.edit-form {
|
||||||
min-width: 70%; }
|
min-width: 70%; }
|
||||||
.edit-form .tab-pane-inner {
|
|
||||||
padding: 0px; }
|
|
||||||
.edit-form .tab-pane-inner:not(:target) {
|
|
||||||
display: none; }
|
|
||||||
.edit-form .tab-pane-inner:target {
|
|
||||||
display: block; }
|
|
||||||
.edit-form .box-wrap {
|
.edit-form .box-wrap {
|
||||||
position: relative; }
|
position: relative; }
|
||||||
.edit-form .inner {
|
.edit-form .inner {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ a.github-link {
|
|||||||
|
|
||||||
section.content {
|
section.content {
|
||||||
padding-top: 5em;
|
padding-top: 5em;
|
||||||
padding-bottom: 5em;
|
padding-bottom: 1em;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -437,7 +437,7 @@ footer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make the tabs easier to hit, they will be all nice and horizontal
|
// Make the tabs easier to hit, they will be all nice and horizontal
|
||||||
div.tabs ul li {
|
div.tabs.collapsable ul li {
|
||||||
display: block;
|
display: block;
|
||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
}
|
}
|
||||||
@@ -573,10 +573,7 @@ $form-edge-padding: 20px;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tab-pane-inner {
|
||||||
.edit-form {
|
|
||||||
min-width: 70%;
|
|
||||||
.tab-pane-inner {
|
|
||||||
&:not(:target) {
|
&:not(:target) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -585,7 +582,11 @@ $form-edge-padding: 20px;
|
|||||||
}
|
}
|
||||||
// doesnt need padding because theres another row of buttons/activity
|
// doesnt need padding because theres another row of buttons/activity
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.edit-form {
|
||||||
|
min-width: 70%;
|
||||||
|
|
||||||
.box-wrap {
|
.box-wrap {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ class ChangeDetectionStore:
|
|||||||
'notification_title': default_notification_title,
|
'notification_title': default_notification_title,
|
||||||
'notification_body': default_notification_body,
|
'notification_body': default_notification_body,
|
||||||
'notification_format': default_notification_format,
|
'notification_format': default_notification_format,
|
||||||
|
'real_browser_save_screenshot': True,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -381,6 +382,22 @@ class ChangeDetectionStore:
|
|||||||
|
|
||||||
return fname
|
return fname
|
||||||
|
|
||||||
|
def get_screenshot(self, watch_uuid):
|
||||||
|
output_path = "{}/{}".format(self.datastore_path, watch_uuid)
|
||||||
|
fname = "{}/last-screenshot.png".format(output_path)
|
||||||
|
if path.isfile(fname):
|
||||||
|
return fname
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Save as PNG, PNG is larger but better for doing visual diff in the future
|
||||||
|
def save_screenshot(self, watch_uuid, screenshot: bytes):
|
||||||
|
output_path = "{}/{}".format(self.datastore_path, watch_uuid)
|
||||||
|
fname = "{}/last-screenshot.png".format(output_path)
|
||||||
|
with open(fname, 'wb') as f:
|
||||||
|
f.write(screenshot)
|
||||||
|
f.close()
|
||||||
|
|
||||||
def sync_to_json(self):
|
def sync_to_json(self):
|
||||||
logging.info("Saving JSON..")
|
logging.info("Saving JSON..")
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
{% from '_helpers.jinja' import render_field %}
|
{% from '_helpers.jinja' import render_field %}
|
||||||
|
|
||||||
{% macro render_common_settings_form(form, current_base_url) %}
|
{% macro render_common_settings_form(form, current_base_url, emailprefix) %}
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
{{ render_field(form.notification_urls, rows=5, placeholder="Examples:
|
{{ render_field(form.notification_urls, rows=5, placeholder="Examples:
|
||||||
@@ -18,6 +18,12 @@
|
|||||||
<li>Go here for <a href="{{url_for('notification_logs')}}">Notification debug logs</a></li>
|
<li>Go here for <a href="{{url_for('notification_logs')}}">Notification debug logs</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<br/>
|
||||||
|
<a id="send-test-notification" class="pure-button button-secondary button-xsmall" style="font-size: 70%">Send test notification</a>
|
||||||
|
{% if emailprefix %}
|
||||||
|
<a id="add-email-helper" class="pure-button button-secondary button-xsmall" style="font-size: 70%">Add email</a>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div id="notification-customisation">
|
<div id="notification-customisation">
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
@@ -93,7 +99,4 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-control-group">
|
|
||||||
{{ render_field(form.trigger_check) }}
|
|
||||||
</div>
|
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
background-image: url({{url_for('static_content', group='images', filename='gradient-border.png')}});
|
background-image: url({{url_for('static_content', group='images', filename='gradient-border.png')}});
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='jquery-3.6.0.min.js')}}"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div id="settings">
|
<div id="settings">
|
||||||
<h1>Differences</h1>
|
<h1>Differences</h1>
|
||||||
<form class="pure-form " action="" method="GET">
|
<form class="pure-form " action="" method="GET">
|
||||||
@@ -35,21 +34,45 @@
|
|||||||
<div id="diff-jump">
|
<div id="diff-jump">
|
||||||
<a onclick="next_diff();">Jump</a>
|
<a onclick="next_diff();">Jump</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='tabs.js')}}" defer></script>
|
||||||
|
<div class="tabs">
|
||||||
|
<ul>
|
||||||
|
<li class="tab" id="default-tab"><a href="#text">Text</a></li>
|
||||||
|
{% if screenshot %}
|
||||||
|
<li class="tab"><a href="#screenshot">Screenshot</a></li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="diff-ui">
|
<div id="diff-ui">
|
||||||
<div class="tip">Pro-tip: Use <strong>show current snapshot</strong> tab to visualise what will be ignored.</div>
|
<div class="tab-pane-inner" id="text">
|
||||||
<table>
|
<div class="tip">Pro-tip: Use <strong>show current snapshot</strong> tab to visualise what will be ignored.
|
||||||
<tbody>
|
</div>
|
||||||
<tr>
|
<table>
|
||||||
<!-- just proof of concept copied straight from github.com/kpdecker/jsdiff -->
|
<tbody>
|
||||||
<td id="a" style="display: none;">{{previous}}</td>
|
<tr>
|
||||||
<td id="b" style="display: none;">{{newest}}</td>
|
<!-- just proof of concept copied straight from github.com/kpdecker/jsdiff -->
|
||||||
<td id="diff-col">
|
<td id="a" style="display: none;">{{previous}}</td>
|
||||||
<span id="result"></span>
|
<td id="b" style="display: none;">{{newest}}</td>
|
||||||
</td>
|
<td id="diff-col">
|
||||||
</tr>
|
<span id="result"></span>
|
||||||
</tbody>
|
</td>
|
||||||
</table>
|
</tr>
|
||||||
Diff algorithm from the amazing <a href="https://github.com/kpdecker/jsdiff">github.com/kpdecker/jsdiff</a>
|
</tbody>
|
||||||
|
</table>
|
||||||
|
Diff algorithm from the amazing <a href="https://github.com/kpdecker/jsdiff">github.com/kpdecker/jsdiff</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if screenshot %}
|
||||||
|
<div class="tab-pane-inner" id="screenshot">
|
||||||
|
<p>
|
||||||
|
<i>For now, only the most recent screenshot is saved and displayed.</i>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<img src="{{url_for('static_content', group='screenshot', filename=uuid)}}">
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,17 @@
|
|||||||
{% from '_helpers.jinja' import render_button %}
|
{% from '_helpers.jinja' import render_button %}
|
||||||
{% from '_common_fields.jinja' import render_common_settings_form %}
|
{% from '_common_fields.jinja' import render_common_settings_form %}
|
||||||
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='tabs.js')}}" defer></script>
|
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='tabs.js')}}" defer></script>
|
||||||
|
<script>
|
||||||
|
const notification_base_url="{{url_for('ajax_callback_send_notification_test')}}";
|
||||||
|
{% if emailprefix %}
|
||||||
|
const email_notification_prefix=JSON.parse('{{ emailprefix|tojson }}');
|
||||||
|
{% endif %}
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='notifications.js')}}" defer></script>
|
||||||
|
|
||||||
<div class="edit-form monospaced-textarea">
|
<div class="edit-form monospaced-textarea">
|
||||||
|
|
||||||
<div class="tabs">
|
<div class="tabs collapsable">
|
||||||
<ul>
|
<ul>
|
||||||
<li class="tab" id="default-tab"><a href="#general">General</a></li>
|
<li class="tab" id="default-tab"><a href="#general">General</a></li>
|
||||||
<li class="tab"><a href="#request">Request</a></li>
|
<li class="tab"><a href="#request">Request</a></li>
|
||||||
@@ -92,7 +99,7 @@ User-Agent: wonderbra 1.0") }}
|
|||||||
<strong>Note: <i>These settings override the global settings for this watch.</i></strong>
|
<strong>Note: <i>These settings override the global settings for this watch.</i></strong>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="field-group">
|
<div class="field-group">
|
||||||
{{ render_common_settings_form(form, current_base_url) }}
|
{{ render_common_settings_form(form, current_base_url, emailprefix) }}
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,18 +6,40 @@
|
|||||||
<h1>Current - {{watch.last_checked|format_timestamp_timeago}}</h1>
|
<h1>Current - {{watch.last_checked|format_timestamp_timeago}}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='tabs.js')}}" defer></script>
|
||||||
|
<div class="tabs">
|
||||||
|
<ul>
|
||||||
|
<li class="tab" id="default-tab"><a href="#text">Text</a></li>
|
||||||
|
{% if screenshot %}
|
||||||
|
<li class="tab"><a href="#screenshot">Screenshot</a></li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="diff-ui">
|
<div id="diff-ui">
|
||||||
<span class="ignored">Grey lines are ignored</span> <span class="triggered">Blue lines are triggers</span>
|
<div class="tab-pane-inner" id="text">
|
||||||
<table>
|
<span class="ignored">Grey lines are ignored</span> <span class="triggered">Blue lines are triggers</span>
|
||||||
<tbody>
|
<table>
|
||||||
<tr>
|
<tbody>
|
||||||
<td id="diff-col">
|
<tr>
|
||||||
|
<td id="diff-col">
|
||||||
{% for row in content %}
|
{% for row in content %}
|
||||||
<div class="{{row.classes}}">{{row.line}}</div>
|
<div class="{{row.classes}}">{{row.line}}</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if screenshot %}
|
||||||
|
<div class="tab-pane-inner" id="screenshot">
|
||||||
|
<p>
|
||||||
|
<i>For now, only the most recent screenshot is saved and displayed.</i>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<img src="{{url_for('static_content', group='screenshot', filename=uuid)}}">
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -3,12 +3,18 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
{% from '_helpers.jinja' import render_field, render_button %}
|
{% from '_helpers.jinja' import render_field, render_button %}
|
||||||
{% from '_common_fields.jinja' import render_common_settings_form %}
|
{% from '_common_fields.jinja' import render_common_settings_form %}
|
||||||
|
<script>
|
||||||
|
const notification_base_url="{{url_for('ajax_callback_send_notification_test')}}";
|
||||||
|
{% if emailprefix %}
|
||||||
|
const email_notification_prefix=JSON.parse('{{emailprefix|tojson}}');
|
||||||
|
{% endif %}
|
||||||
|
</script>
|
||||||
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='settings.js')}}" defer></script>
|
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='settings.js')}}" defer></script>
|
||||||
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='tabs.js')}}" defer></script>
|
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='tabs.js')}}" defer></script>
|
||||||
|
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='notifications.js')}}" defer></script>
|
||||||
|
|
||||||
<div class="edit-form">
|
<div class="edit-form">
|
||||||
<div class="tabs">
|
<div class="tabs collapsable">
|
||||||
<ul>
|
<ul>
|
||||||
<li class="tab" id="default-tab"><a href="#general">General</a></li>
|
<li class="tab" id="default-tab"><a href="#general">General</a></li>
|
||||||
<li class="tab"><a href="#notifications">Notifications</a></li>
|
<li class="tab"><a href="#notifications">Notifications</a></li>
|
||||||
@@ -50,13 +56,19 @@
|
|||||||
{{ render_field(form.extract_title_as_title) }}
|
{{ 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>
|
||||||
|
|
||||||
|
<div class="pure-control-group">
|
||||||
|
{{ render_field(form.real_browser_save_screenshot) }}
|
||||||
|
<span class="pure-form-message-inline">When using a Chrome browser, a screenshot from the last check will be available on the Diff page</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-pane-inner" id="notifications">
|
<div class="tab-pane-inner" id="notifications">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="field-group">
|
<div class="field-group">
|
||||||
{{ render_common_settings_form(form, current_base_url) }}
|
{{ render_common_settings_form(form, current_base_url, emailprefix) }}
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<a href="{{url_for('notification_logs')}}">Notification debug logs</a>
|
<a href="{{url_for('notification_logs')}}">Notification debug logs</a>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% from '_helpers.jinja' import render_simple_field %}
|
{% from '_helpers.jinja' import render_simple_field %}
|
||||||
|
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='jquery-3.6.0.min.js')}}"></script>
|
||||||
|
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='watch-overview.js')}}" defer></script>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
|
|
||||||
<form class="pure-form" action="{{ url_for('api_watch_add') }}" method="POST" id="new-watch-form">
|
<form class="pure-form" action="{{ url_for('api_watch_add') }}" method="POST" id="new-watch-form">
|
||||||
@@ -75,7 +76,7 @@
|
|||||||
class="pure-button button-small pure-button-primary">Recheck</a>
|
class="pure-button button-small pure-button-primary">Recheck</a>
|
||||||
<a href="{{ url_for('edit_page', uuid=watch.uuid)}}" class="pure-button button-small pure-button-primary">Edit</a>
|
<a href="{{ url_for('edit_page', uuid=watch.uuid)}}" class="pure-button button-small pure-button-primary">Edit</a>
|
||||||
{% if watch.history|length >= 2 %}
|
{% if watch.history|length >= 2 %}
|
||||||
<a href="{{ url_for('diff_history_page', uuid=watch.uuid) }}" target="{{watch.uuid}}" class="pure-button button-small pure-button-primary">Diff</a>
|
<a href="{{ url_for('diff_history_page', uuid=watch.uuid) }}" target="{{watch.uuid}}" class="pure-button button-small pure-button-primary diff-link">Diff</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if watch.history|length == 1 %}
|
{% if watch.history|length == 1 %}
|
||||||
<a href="{{ url_for('preview_page', uuid=watch.uuid)}}" target="{{watch.uuid}}" class="pure-button button-small pure-button-primary">Preview</a>
|
<a href="{{ url_for('preview_page', uuid=watch.uuid)}}" target="{{watch.uuid}}" class="pure-button button-small pure-button-primary">Preview</a>
|
||||||
|
|||||||
@@ -70,6 +70,11 @@ def test_check_basic_change_detection_functionality(client, live_server):
|
|||||||
res = client.get(url_for("rss"))
|
res = client.get(url_for("rss"))
|
||||||
expected_url = url_for('test_endpoint', _external=True)
|
expected_url = url_for('test_endpoint', _external=True)
|
||||||
assert b'<rss' in res.data
|
assert b'<rss' in res.data
|
||||||
|
|
||||||
|
# re #16 should have the diff in here too
|
||||||
|
assert b'(into ) which has this one new line' in res.data
|
||||||
|
assert b'CDATA' in res.data
|
||||||
|
|
||||||
assert expected_url.encode('utf-8') in res.data
|
assert expected_url.encode('utf-8') in res.data
|
||||||
|
|
||||||
# Following the 'diff' link, it should no longer display as 'unviewed' even after we recheck it a few times
|
# Following the 'diff' link, it should no longer display as 'unviewed' even after we recheck it a few times
|
||||||
|
|||||||
@@ -2,15 +2,17 @@ import os
|
|||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
from . util import set_original_response, set_modified_response, live_server_setup
|
from . util import set_original_response, set_modified_response, set_more_modified_response, live_server_setup
|
||||||
import logging
|
import logging
|
||||||
from changedetectionio.notification import default_notification_body, default_notification_title
|
from changedetectionio.notification import default_notification_body, default_notification_title
|
||||||
|
|
||||||
|
def test_setup(live_server):
|
||||||
|
live_server_setup(live_server)
|
||||||
|
|
||||||
# Hard to just add more live server URLs when one test is already running (I think)
|
# Hard to just add more live server URLs when one test is already running (I think)
|
||||||
# So we add our test here (was in a different file)
|
# So we add our test here (was in a different file)
|
||||||
def test_check_notification(client, live_server):
|
def test_check_notification(client, live_server):
|
||||||
|
|
||||||
live_server_setup(live_server)
|
|
||||||
set_original_response()
|
set_original_response()
|
||||||
|
|
||||||
# Give the endpoint time to spin up
|
# Give the endpoint time to spin up
|
||||||
@@ -49,84 +51,76 @@ def test_check_notification(client, live_server):
|
|||||||
notification_url = url.replace('http', 'json')
|
notification_url = url.replace('http', 'json')
|
||||||
|
|
||||||
print (">>>> Notification URL: "+notification_url)
|
print (">>>> Notification URL: "+notification_url)
|
||||||
|
|
||||||
|
notification_form_data = {"notification_urls": notification_url,
|
||||||
|
"notification_title": "New ChangeDetection.io Notification - {watch_url}",
|
||||||
|
"notification_body": "BASE URL: {base_url}\n"
|
||||||
|
"Watch URL: {watch_url}\n"
|
||||||
|
"Watch UUID: {watch_uuid}\n"
|
||||||
|
"Watch title: {watch_title}\n"
|
||||||
|
"Watch tag: {watch_tag}\n"
|
||||||
|
"Preview: {preview_url}\n"
|
||||||
|
"Diff URL: {diff_url}\n"
|
||||||
|
"Snapshot: {current_snapshot}\n"
|
||||||
|
"Diff: {diff}\n"
|
||||||
|
"Diff Full: {diff_full}\n"
|
||||||
|
":-)",
|
||||||
|
"notification_format": "Text"}
|
||||||
|
|
||||||
|
notification_form_data.update({
|
||||||
|
"url": test_url,
|
||||||
|
"tag": "my tag",
|
||||||
|
"title": "my title",
|
||||||
|
"headers": "",
|
||||||
|
"fetch_backend": "html_requests"})
|
||||||
|
|
||||||
res = client.post(
|
res = client.post(
|
||||||
url_for("edit_page", uuid="first"),
|
url_for("edit_page", uuid="first"),
|
||||||
data={"notification_urls": notification_url,
|
data=notification_form_data,
|
||||||
"notification_title": "New ChangeDetection.io Notification - {watch_url}",
|
|
||||||
"notification_body": "BASE URL: {base_url}\n"
|
|
||||||
"Watch URL: {watch_url}\n"
|
|
||||||
"Watch UUID: {watch_uuid}\n"
|
|
||||||
"Watch title: {watch_title}\n"
|
|
||||||
"Watch tag: {watch_tag}\n"
|
|
||||||
"Preview: {preview_url}\n"
|
|
||||||
"Diff URL: {diff_url}\n"
|
|
||||||
"Snapshot: {current_snapshot}\n"
|
|
||||||
"Diff: {diff}\n"
|
|
||||||
"Diff Full: {diff_full}\n"
|
|
||||||
":-)",
|
|
||||||
"notification_format": "Text",
|
|
||||||
"url": test_url,
|
|
||||||
"tag": "my tag",
|
|
||||||
"title": "my title",
|
|
||||||
"headers": "",
|
|
||||||
"fetch_backend": "html_requests",
|
|
||||||
"trigger_check": "y"},
|
|
||||||
follow_redirects=True
|
follow_redirects=True
|
||||||
)
|
)
|
||||||
assert b"Updated watch." in res.data
|
assert b"Updated watch." in res.data
|
||||||
assert b"Test notification queued" in res.data
|
|
||||||
|
|
||||||
# Hit the edit page, be sure that we saved it
|
# Hit the edit page, be sure that we saved it
|
||||||
|
# Re #242 - wasnt saving?
|
||||||
res = client.get(
|
res = client.get(
|
||||||
url_for("edit_page", uuid="first"))
|
url_for("edit_page", uuid="first"))
|
||||||
assert bytes(notification_url.encode('utf-8')) in res.data
|
assert bytes(notification_url.encode('utf-8')) in res.data
|
||||||
|
|
||||||
# Re #242 - wasnt saving?
|
|
||||||
assert bytes("New ChangeDetection.io Notification".encode('utf-8')) in res.data
|
assert bytes("New ChangeDetection.io Notification".encode('utf-8')) in res.data
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Because we hit 'send test notification on save'
|
## Now recheck, and it should have sent the notification
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
|
set_modified_response()
|
||||||
|
|
||||||
notification_submission = None
|
notification_submission = None
|
||||||
|
|
||||||
|
# Trigger a check
|
||||||
|
client.get(url_for("api_watch_checknow"), follow_redirects=True)
|
||||||
|
time.sleep(3)
|
||||||
# Verify what was sent as a notification, this file should exist
|
# Verify what was sent as a notification, this file should exist
|
||||||
with open("test-datastore/notification.txt", "r") as f:
|
with open("test-datastore/notification.txt", "r") as f:
|
||||||
notification_submission = f.read()
|
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")
|
os.unlink("test-datastore/notification.txt")
|
||||||
|
|
||||||
set_modified_response()
|
# Did we see the URL that had a change, in the notification?
|
||||||
|
|
||||||
# Trigger a check
|
|
||||||
client.get(url_for("api_watch_checknow"), follow_redirects=True)
|
|
||||||
|
|
||||||
# Give the thread time to pick it up
|
|
||||||
time.sleep(3)
|
|
||||||
|
|
||||||
# Did the front end see it?
|
|
||||||
res = client.get(
|
|
||||||
url_for("index"))
|
|
||||||
|
|
||||||
assert bytes("just now".encode('utf-8')) in res.data
|
|
||||||
|
|
||||||
notification_submission=None
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# Diff was correctly executed
|
# Diff was correctly executed
|
||||||
|
assert test_url in notification_submission
|
||||||
|
assert ':-)' in notification_submission
|
||||||
assert "Diff Full: Some initial text" in notification_submission
|
assert "Diff Full: Some initial text" in notification_submission
|
||||||
assert "Diff: (changed) Which is across multiple lines" in notification_submission
|
assert "Diff: (changed) Which is across multiple lines" in notification_submission
|
||||||
assert "(into ) which has this one new line" in notification_submission
|
assert "(into ) which has this one new line" in notification_submission
|
||||||
|
# Re #342 - check for accidental python byte encoding of non-utf8/string
|
||||||
|
assert "b'" not in notification_submission
|
||||||
|
assert re.search('Watch UUID: [0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}', notification_submission, re.IGNORECASE)
|
||||||
|
assert "Watch title: my title" in notification_submission
|
||||||
|
assert "Watch tag: my tag" in notification_submission
|
||||||
|
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
|
||||||
|
|
||||||
if env_base_url:
|
if env_base_url:
|
||||||
# Re #65 - did we see our BASE_URl ?
|
# Re #65 - did we see our BASE_URl ?
|
||||||
@@ -135,50 +129,17 @@ def test_check_notification(client, live_server):
|
|||||||
else:
|
else:
|
||||||
logging.debug(">>> Skipping BASE_URL check")
|
logging.debug(">>> Skipping BASE_URL check")
|
||||||
|
|
||||||
## Now configure something clever, we go into custom config (non-default) mode, this is returned by the endpoint
|
|
||||||
with open("test-datastore/endpoint-content.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_urls": "json://foobar.com", #Re #143 should not see that it sent without [test checkbox]
|
|
||||||
"minutes_between_check": 180,
|
|
||||||
"fetch_backend": "html_requests",
|
|
||||||
},
|
|
||||||
follow_redirects=True
|
|
||||||
)
|
|
||||||
assert b"Settings updated." in res.data
|
|
||||||
# Re #143 - should not see this if we didnt hit the test box
|
|
||||||
assert b"Test notification queued" not in res.data
|
|
||||||
|
|
||||||
# Trigger a check
|
# This should insert the {current_snapshot}
|
||||||
|
set_more_modified_response()
|
||||||
client.get(url_for("api_watch_checknow"), follow_redirects=True)
|
client.get(url_for("api_watch_checknow"), follow_redirects=True)
|
||||||
|
|
||||||
# Give the thread time to pick it up
|
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
|
# Verify what was sent as a notification, this file should exist
|
||||||
# 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:
|
with open("test-datastore/notification.txt", "r") as f:
|
||||||
notification_submission = f.read()
|
notification_submission = f.read()
|
||||||
print ("Notification submission was:", notification_submission)
|
assert "Ohh yeah awesome" in notification_submission
|
||||||
# Re #342 - check for accidental python byte encoding of non-utf8/string
|
|
||||||
assert "b'" not in notification_submission
|
|
||||||
|
|
||||||
assert re.search('Watch UUID: [0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}', notification_submission, re.IGNORECASE)
|
|
||||||
assert "Watch title: my title" in notification_submission
|
|
||||||
assert "Watch tag: my tag" in notification_submission
|
|
||||||
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
|
|
||||||
|
|
||||||
# Prove that "content constantly being marked as Changed with no Updating causes notification" is not a thing
|
# Prove that "content constantly being marked as Changed with no Updating causes notification" is not a thing
|
||||||
# https://github.com/dgtlmoon/changedetection.io/discussions/192
|
# https://github.com/dgtlmoon/changedetection.io/discussions/192
|
||||||
@@ -186,33 +147,39 @@ def test_check_notification(client, live_server):
|
|||||||
|
|
||||||
# Trigger a check
|
# Trigger a check
|
||||||
client.get(url_for("api_watch_checknow"), follow_redirects=True)
|
client.get(url_for("api_watch_checknow"), follow_redirects=True)
|
||||||
time.sleep(3)
|
time.sleep(1)
|
||||||
client.get(url_for("api_watch_checknow"), follow_redirects=True)
|
client.get(url_for("api_watch_checknow"), follow_redirects=True)
|
||||||
time.sleep(3)
|
time.sleep(1)
|
||||||
client.get(url_for("api_watch_checknow"), follow_redirects=True)
|
client.get(url_for("api_watch_checknow"), follow_redirects=True)
|
||||||
time.sleep(3)
|
time.sleep(1)
|
||||||
assert os.path.exists("test-datastore/notification.txt") == False
|
assert os.path.exists("test-datastore/notification.txt") == False
|
||||||
|
|
||||||
|
# cleanup for the next
|
||||||
# Now adding a wrong token should give us an error
|
client.get(
|
||||||
res = client.post(
|
url_for("api_delete", uuid="first"),
|
||||||
url_for("settings_page"),
|
|
||||||
data={"notification_title": "New ChangeDetection.io Notification - {watch_url}",
|
|
||||||
"notification_body": "Rubbish: {rubbish}\n",
|
|
||||||
"notification_format": "Text",
|
|
||||||
"notification_urls": "json://foobar.com",
|
|
||||||
"minutes_between_check": 180,
|
|
||||||
"fetch_backend": "html_requests"
|
|
||||||
},
|
|
||||||
follow_redirects=True
|
follow_redirects=True
|
||||||
)
|
)
|
||||||
|
|
||||||
assert bytes("is not a valid token".encode('utf-8')) in res.data
|
|
||||||
|
def test_notification_validation(client, live_server):
|
||||||
|
#live_server_setup(live_server)
|
||||||
|
time.sleep(3)
|
||||||
|
# re #242 - when you edited an existing new entry, it would not correctly show the notification settings
|
||||||
|
# Add our URL to the import page
|
||||||
|
test_url = url_for('test_endpoint', _external=True)
|
||||||
|
res = client.post(
|
||||||
|
url_for("api_watch_add"),
|
||||||
|
data={"url": test_url, "tag": 'nice one'},
|
||||||
|
follow_redirects=True
|
||||||
|
)
|
||||||
|
with open("xxx.bin", "wb") as f:
|
||||||
|
f.write(res.data)
|
||||||
|
assert b"Watch added" in res.data
|
||||||
|
|
||||||
# Re #360 some validation
|
# Re #360 some validation
|
||||||
res = client.post(
|
res = client.post(
|
||||||
url_for("edit_page", uuid="first"),
|
url_for("edit_page", uuid="first"),
|
||||||
data={"notification_urls": notification_url,
|
data={"notification_urls": 'json://localhost/foobar',
|
||||||
"notification_title": "",
|
"notification_title": "",
|
||||||
"notification_body": "",
|
"notification_body": "",
|
||||||
"notification_format": "Text",
|
"notification_format": "Text",
|
||||||
@@ -220,8 +187,28 @@ def test_check_notification(client, live_server):
|
|||||||
"tag": "my tag",
|
"tag": "my tag",
|
||||||
"title": "my title",
|
"title": "my title",
|
||||||
"headers": "",
|
"headers": "",
|
||||||
"fetch_backend": "html_requests",
|
"fetch_backend": "html_requests"},
|
||||||
"trigger_check": "y"},
|
|
||||||
follow_redirects=True
|
follow_redirects=True
|
||||||
)
|
)
|
||||||
assert b"Notification Body and Title is required when a Notification URL is used" in res.data
|
assert b"Notification Body and Title is required when a Notification URL is used" in res.data
|
||||||
|
|
||||||
|
# Now adding a wrong token should give us an error
|
||||||
|
res = client.post(
|
||||||
|
url_for("settings_page"),
|
||||||
|
data={"notification_title": "New ChangeDetection.io Notification - {watch_url}",
|
||||||
|
"notification_body": "Rubbish: {rubbish}\n",
|
||||||
|
"notification_format": "Text",
|
||||||
|
"notification_urls": "json://localhost/foobar",
|
||||||
|
"time_between_check": {'seconds': 180},
|
||||||
|
"fetch_backend": "html_requests"
|
||||||
|
},
|
||||||
|
follow_redirects=True
|
||||||
|
)
|
||||||
|
|
||||||
|
assert bytes("is not a valid token".encode('utf-8')) in res.data
|
||||||
|
|
||||||
|
# cleanup for the next
|
||||||
|
client.get(
|
||||||
|
url_for("api_delete", uuid="first"),
|
||||||
|
follow_redirects=True
|
||||||
|
)
|
||||||
|
|||||||
@@ -35,6 +35,24 @@ def set_modified_response():
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def set_more_modified_response():
|
||||||
|
test_return_data = """<html>
|
||||||
|
<head><title>modified head title</title></head>
|
||||||
|
<body>
|
||||||
|
Some initial text</br>
|
||||||
|
<p>which has this one new line</p>
|
||||||
|
</br>
|
||||||
|
So let's see what happens. </br>
|
||||||
|
Ohh yeah awesome<br/>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
with open("test-datastore/endpoint-content.txt", "w") as f:
|
||||||
|
f.write(test_return_data)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def live_server_setup(live_server):
|
def live_server_setup(live_server):
|
||||||
|
|
||||||
@@ -82,7 +100,7 @@ def live_server_setup(live_server):
|
|||||||
if data != None:
|
if data != None:
|
||||||
f.write(data)
|
f.write(data)
|
||||||
|
|
||||||
print("\n>> Test notification endpoint was hit.\n")
|
print("\n>> Test notification endpoint was hit.\n", data)
|
||||||
return "Text was set"
|
return "Text was set"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -38,11 +38,12 @@ class update_worker(threading.Thread):
|
|||||||
|
|
||||||
changed_detected = False
|
changed_detected = False
|
||||||
contents = ""
|
contents = ""
|
||||||
|
screenshot = False
|
||||||
update_obj= {}
|
update_obj= {}
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
changed_detected, update_obj, contents = update_handler.run(uuid)
|
changed_detected, update_obj, contents, screenshot = update_handler.run(uuid)
|
||||||
|
|
||||||
# Re #342
|
# Re #342
|
||||||
# In Python 3, all strings are sequences of Unicode characters. There is a bytes type that holds raw bytes.
|
# In Python 3, all strings are sequences of Unicode characters. There is a bytes type that holds raw bytes.
|
||||||
@@ -140,6 +141,9 @@ class update_worker(threading.Thread):
|
|||||||
# Always record that we atleast tried
|
# Always record that we atleast tried
|
||||||
self.datastore.update_watch(uuid=uuid, update_obj={'fetch_time': round(time.time() - now, 3),
|
self.datastore.update_watch(uuid=uuid, update_obj={'fetch_time': round(time.time() - now, 3),
|
||||||
'last_checked': round(time.time())})
|
'last_checked': round(time.time())})
|
||||||
|
# Always save the screenshot if it's available
|
||||||
|
if screenshot:
|
||||||
|
self.datastore.save_screenshot(watch_uuid=uuid, screenshot=screenshot)
|
||||||
|
|
||||||
self.current_uuid = None # Done
|
self.current_uuid = None # Done
|
||||||
self.q.task_done()
|
self.q.task_done()
|
||||||
|
|||||||
Reference in New Issue
Block a user