mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2025-12-19 22:45:52 +00:00
Compare commits
9 Commits
improve-lo
...
improve-no
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df2c58d1ff | ||
|
|
d29e0eea47 | ||
|
|
7e1e763989 | ||
|
|
327cc4af34 | ||
|
|
6008ff516e | ||
|
|
cdcf4b353f | ||
|
|
1ab70f8e86 | ||
|
|
8227c012a7 | ||
|
|
c113d5fb24 |
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -21,7 +21,7 @@ Steps to reproduce the behavior:
|
|||||||
3. Scroll down to '....'
|
3. Scroll down to '....'
|
||||||
4. See error
|
4. See error
|
||||||
|
|
||||||
! ALWAYS INCLUDE AN EXAMPLE URL WHERE IT IS POSSIBLE TO RE-CREATE THE ISSUE !
|
! ALWAYS INCLUDE AN EXAMPLE URL WHERE IT IS POSSIBLE TO RE-CREATE THE ISSUE - USE THE 'SHARE WATCH' FEATURE AND PASTE IN THE SHARE-LINK!
|
||||||
|
|
||||||
**Expected behavior**
|
**Expected behavior**
|
||||||
A clear and concise description of what you expected to happen.
|
A clear and concise description of what you expected to happen.
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ from copy import deepcopy
|
|||||||
from threading import Event
|
from threading import Event
|
||||||
|
|
||||||
import flask_login
|
import flask_login
|
||||||
|
import logging
|
||||||
import pytz
|
import pytz
|
||||||
import timeago
|
import timeago
|
||||||
from feedgen.feed import FeedGenerator
|
from feedgen.feed import FeedGenerator
|
||||||
@@ -351,9 +352,8 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
latest_fname = watch.history[dates[-1]]
|
latest_fname = watch.history[dates[-1]]
|
||||||
|
|
||||||
html_diff = diff.render_diff(prev_fname, latest_fname, include_equal=False, line_feed_sep="</br>")
|
html_diff = diff.render_diff(prev_fname, latest_fname, include_equal=False, line_feed_sep="</br>")
|
||||||
fe.description(description="<![CDATA["
|
fe.content(content="<html><body><h4>{}</h4>{}</body></html>".format(watch_title, html_diff),
|
||||||
"<html><body><h4>{}</h4>{}</body></html>"
|
type='CDATA')
|
||||||
"]]>".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))
|
||||||
@@ -458,6 +458,19 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
|
|
||||||
return 'OK'
|
return 'OK'
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/scrub/<string:uuid>", methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
def scrub_watch(uuid):
|
||||||
|
try:
|
||||||
|
datastore.scrub_watch(uuid)
|
||||||
|
except KeyError:
|
||||||
|
flash('Watch not found', 'error')
|
||||||
|
else:
|
||||||
|
flash("Scrubbed watch {}".format(uuid))
|
||||||
|
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
@app.route("/scrub", methods=['GET', 'POST'])
|
@app.route("/scrub", methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def scrub_page():
|
def scrub_page():
|
||||||
@@ -809,7 +822,13 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
|
|
||||||
screenshot_url = datastore.get_screenshot(uuid)
|
screenshot_url = datastore.get_screenshot(uuid)
|
||||||
|
|
||||||
output = render_template("diff.html", watch_a=watch,
|
system_uses_webdriver = datastore.data['settings']['application']['fetch_backend'] == 'html_webdriver'
|
||||||
|
|
||||||
|
is_html_webdriver = True if watch.get('fetch_backend') == 'html_webdriver' or (
|
||||||
|
watch.get('fetch_backend', None) is None and system_uses_webdriver) else False
|
||||||
|
|
||||||
|
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,
|
||||||
extra_stylesheets=extra_stylesheets,
|
extra_stylesheets=extra_stylesheets,
|
||||||
@@ -820,7 +839,8 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
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)
|
screenshot=screenshot_url,
|
||||||
|
is_html_webdriver=is_html_webdriver)
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
@@ -881,6 +901,11 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
content.append({'line': "No history found", 'classes': ''})
|
content.append({'line': "No history found", 'classes': ''})
|
||||||
|
|
||||||
screenshot_url = datastore.get_screenshot(uuid)
|
screenshot_url = datastore.get_screenshot(uuid)
|
||||||
|
system_uses_webdriver = datastore.data['settings']['application']['fetch_backend'] == 'html_webdriver'
|
||||||
|
|
||||||
|
is_html_webdriver = True if watch.get('fetch_backend') == 'html_webdriver' or (
|
||||||
|
watch.get('fetch_backend', None) is None and system_uses_webdriver) else False
|
||||||
|
|
||||||
output = render_template("preview.html",
|
output = render_template("preview.html",
|
||||||
content=content,
|
content=content,
|
||||||
extra_stylesheets=extra_stylesheets,
|
extra_stylesheets=extra_stylesheets,
|
||||||
@@ -889,8 +914,9 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
current_diff_url=watch['url'],
|
current_diff_url=watch['url'],
|
||||||
screenshot=screenshot_url,
|
screenshot=screenshot_url,
|
||||||
watch=watch,
|
watch=watch,
|
||||||
uuid=uuid)
|
uuid=uuid,
|
||||||
|
is_html_webdriver=is_html_webdriver)
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
@app.route("/settings/notification-logs", methods=['GET'])
|
@app.route("/settings/notification-logs", methods=['GET'])
|
||||||
@@ -898,7 +924,7 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
def notification_logs():
|
def notification_logs():
|
||||||
global notification_debug_log
|
global notification_debug_log
|
||||||
output = render_template("notification-log.html",
|
output = render_template("notification-log.html",
|
||||||
logs=notification_debug_log if len(notification_debug_log) else ["No errors or warnings detected"])
|
logs=notification_debug_log if len(notification_debug_log) else ["Notification logs are empty - no notifications sent yet."])
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
@@ -1169,7 +1195,8 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
flash("Could not share, something went wrong while communicating with the share server.", 'error')
|
logging.error("Error sharing -{}".format(str(e)))
|
||||||
|
flash("Could not share, something went wrong while communicating with the share server - {}".format(str(e)), 'error')
|
||||||
|
|
||||||
# https://changedetection.io/share/VrMv05wpXyQa
|
# https://changedetection.io/share/VrMv05wpXyQa
|
||||||
# in the browser - should give you a nice info page - wtf
|
# in the browser - should give you a nice info page - wtf
|
||||||
@@ -1217,6 +1244,9 @@ def check_for_new_version():
|
|||||||
|
|
||||||
def notification_runner():
|
def notification_runner():
|
||||||
global notification_debug_log
|
global notification_debug_log
|
||||||
|
from datetime import datetime
|
||||||
|
import json
|
||||||
|
|
||||||
while not app.config.exit.is_set():
|
while not app.config.exit.is_set():
|
||||||
try:
|
try:
|
||||||
# At the moment only one thread runs (single runner)
|
# At the moment only one thread runs (single runner)
|
||||||
@@ -1225,13 +1255,16 @@ def notification_runner():
|
|||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Process notifications
|
|
||||||
|
now = datetime.now()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from changedetectionio import notification
|
from changedetectionio import notification
|
||||||
|
|
||||||
notification.process_notification(n_object, datastore)
|
notification.process_notification(n_object, datastore)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Watch URL: {} Error {}".format(n_object['watch_url'], str(e)))
|
logging.error("Watch URL: {} Error {}".format(n_object['watch_url'], str(e)))
|
||||||
|
|
||||||
# UUID wont be present when we submit a 'test' from the global settings
|
# UUID wont be present when we submit a 'test' from the global settings
|
||||||
if 'uuid' in n_object:
|
if 'uuid' in n_object:
|
||||||
@@ -1241,9 +1274,10 @@ def notification_runner():
|
|||||||
log_lines = str(e).splitlines()
|
log_lines = str(e).splitlines()
|
||||||
notification_debug_log += log_lines
|
notification_debug_log += log_lines
|
||||||
|
|
||||||
# Trim the log length
|
# Process notifications
|
||||||
notification_debug_log = notification_debug_log[-100:]
|
notification_debug_log+= ["{} - SENDING {}".format(now.strftime("%Y/%m/%d %H:%M:%S,000"), json.dumps(n_object))]
|
||||||
|
# Trim the log length
|
||||||
|
notification_debug_log = notification_debug_log[-100:]
|
||||||
|
|
||||||
# Thread runner to check every minute, look for new watches to feed into the Queue.
|
# Thread runner to check every minute, look for new watches to feed into the Queue.
|
||||||
def ticker_thread_check_time_launch_checks():
|
def ticker_thread_check_time_launch_checks():
|
||||||
|
|||||||
@@ -97,6 +97,8 @@ def process_notification(n_object, datastore):
|
|||||||
if log_value and 'WARNING' in log_value or 'ERROR' in log_value:
|
if log_value and 'WARNING' in log_value or 'ERROR' in log_value:
|
||||||
raise Exception(log_value)
|
raise Exception(log_value)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Notification title + body content parameters get created here.
|
# Notification title + body content parameters get created here.
|
||||||
def create_notification_parameters(n_object, datastore):
|
def create_notification_parameters(n_object, datastore):
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|||||||
17
changedetectionio/static/js/diff-overview.js
Normal file
17
changedetectionio/static/js/diff-overview.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
$(document).ready(function () {
|
||||||
|
// Load it when the #screenshot tab is in use, so we dont give a slow experience when waiting for the text diff to load
|
||||||
|
window.addEventListener('hashchange', function (e) {
|
||||||
|
toggle(location.hash);
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
toggle(location.hash);
|
||||||
|
|
||||||
|
function toggle(hash_name) {
|
||||||
|
if (hash_name === '#screenshot') {
|
||||||
|
$("img#screenshot-img").attr('src', screenshot_url);
|
||||||
|
$("#settings").hide();
|
||||||
|
} else {
|
||||||
|
$("#settings").show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -254,12 +254,23 @@ class ChangeDetectionStore:
|
|||||||
def scrub_watch(self, uuid):
|
def scrub_watch(self, uuid):
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
self.__data['watching'][uuid].update({'history': {}, 'last_checked': 0, 'last_changed': 0, 'previous_md5': False})
|
self.__data['watching'][uuid].update(
|
||||||
self.needs_write_urgent = True
|
{'last_checked': 0,
|
||||||
|
'last_changed': 0,
|
||||||
|
'last_viewed': 0,
|
||||||
|
'previous_md5': False,
|
||||||
|
'last_notification_error': False,
|
||||||
|
'last_error': False})
|
||||||
|
|
||||||
for item in pathlib.Path(self.datastore_path).rglob(uuid+"/*.txt"):
|
# JSON Data, Screenshots, Textfiles (history index and snapshots), HTML in the future etc
|
||||||
|
for item in pathlib.Path(os.path.join(self.datastore_path, uuid)).rglob("*.*"):
|
||||||
unlink(item)
|
unlink(item)
|
||||||
|
|
||||||
|
# Force the attr to recalculate
|
||||||
|
bump = self.__data['watching'][uuid].history
|
||||||
|
|
||||||
|
self.needs_write_urgent = True
|
||||||
|
|
||||||
def add_watch(self, url, tag="", extras=None, write_to_disk_now=True):
|
def add_watch(self, url, tag="", extras=None, write_to_disk_now=True):
|
||||||
|
|
||||||
if extras is None:
|
if extras is None:
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<li>Use <a target=_new href="https://github.com/caronc/apprise">AppRise URLs</a> for notification to just about any service! <i><a target=_new href="https://github.com/dgtlmoon/changedetection.io/wiki/Notification-configuration-notes">Please read the notification services wiki here for important configuration notes</a></i>.</li>
|
<li>Use <a target=_new href="https://github.com/caronc/apprise">AppRise URLs</a> for notification to just about any service! <i><a target=_new href="https://github.com/dgtlmoon/changedetection.io/wiki/Notification-configuration-notes">Please read the notification services wiki here for important configuration notes</a></i>.</li>
|
||||||
<li><code>discord://</code> only supports a maximum <strong>2,000 characters</strong> of notification text, including the title.</li>
|
<li><code>discord://</code> only supports a maximum <strong>2,000 characters</strong> of notification text, including the title.</li>
|
||||||
<li><code>tgram://</code> bots cant send messages to other bots, so you should specify chat ID of non-bot user.</li>
|
<li><code>tgram://</code> bots cant send messages to other bots, so you should specify chat ID of non-bot user.</li>
|
||||||
<li>Go here for <a href="{{url_for('notification_logs')}}">notification debug logs</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
{% if emailprefix %}
|
{% if emailprefix %}
|
||||||
<a id="add-email-helper" class="pure-button button-secondary button-xsmall" style="font-size: 70%">Add email</a>
|
<a id="add-email-helper" class="pure-button button-secondary button-xsmall" style="font-size: 70%">Add email</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<a href="{{url_for('notification_logs')}}" class="pure-button button-secondary button-xsmall" style="font-size: 70%">Notification debug logs</a>
|
||||||
</div>
|
</div>
|
||||||
<div id="notification-customisation" class="pure-control-group">
|
<div id="notification-customisation" class="pure-control-group">
|
||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<script>
|
||||||
|
const screenshot_url="{{url_for('static_content', group='screenshot', filename=uuid)}}";
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='diff-overview.js')}}" defer></script>
|
||||||
|
|
||||||
<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">
|
||||||
@@ -39,6 +44,7 @@
|
|||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
<ul>
|
<ul>
|
||||||
<li class="tab" id="default-tab"><a href="#text">Text</a></li>
|
<li class="tab" id="default-tab"><a href="#text">Text</a></li>
|
||||||
|
<li class="tab" id="screenshot-tab"><a href="#screenshot">Screenshot</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -60,6 +66,21 @@
|
|||||||
</table>
|
</table>
|
||||||
Diff algorithm from the amazing <a href="https://github.com/kpdecker/jsdiff">github.com/kpdecker/jsdiff</a>
|
Diff algorithm from the amazing <a href="https://github.com/kpdecker/jsdiff">github.com/kpdecker/jsdiff</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tab-pane-inner" id="screenshot">
|
||||||
|
<div class="tip">
|
||||||
|
For now, Differences are performed on text, not graphically, only the latest screenshot is available.
|
||||||
|
</div>
|
||||||
|
</br>
|
||||||
|
{% if is_html_webdriver %}
|
||||||
|
{% if screenshot %}
|
||||||
|
<img style="max-width: 80%" id="screenshot-img" alt="Current screenshot from most recent request"/>
|
||||||
|
{% else %}
|
||||||
|
No screenshot available just yet! Try rechecking the page.
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<strong>Screenshot requires Playwright/WebDriver enabled</strong>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -259,6 +259,8 @@ nav
|
|||||||
|
|
||||||
<a href="{{url_for('form_delete', uuid=uuid)}}"
|
<a href="{{url_for('form_delete', uuid=uuid)}}"
|
||||||
class="pure-button button-small button-error ">Delete</a>
|
class="pure-button button-small button-error ">Delete</a>
|
||||||
|
<a href="{{url_for('scrub_watch', uuid=uuid)}}"
|
||||||
|
class="pure-button button-small button-error ">Scrub</a>
|
||||||
<a href="{{url_for('form_clone', uuid=uuid)}}"
|
<a href="{{url_for('form_clone', uuid=uuid)}}"
|
||||||
class="pure-button button-small ">Create Copy</a>
|
class="pure-button button-small ">Create Copy</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<div class="edit-form">
|
<div class="edit-form">
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
|
|
||||||
<h4 style="margin-top: 0px;">The following issues were detected when sending notifications</h4>
|
<h4 style="margin-top: 0px;">Notification debug log</h4>
|
||||||
<div id="notification-error-log">
|
<div id="notification-error-log">
|
||||||
<ul style="font-size: 80%; margin:0px; padding: 0 0 0 7px">
|
<ul style="font-size: 80%; margin:0px; padding: 0 0 0 7px">
|
||||||
{% for log in logs|reverse %}
|
{% for log in logs|reverse %}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<script>
|
||||||
|
const screenshot_url="{{url_for('static_content', group='screenshot', filename=uuid)}}";
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript" src="{{url_for('static_content', group='js', filename='diff-overview.js')}}" defer></script>
|
||||||
|
|
||||||
<div id="settings">
|
<div id="settings">
|
||||||
<h1>Current - {{watch.last_checked|format_timestamp_timeago}}</h1>
|
<h1>Current - {{watch.last_checked|format_timestamp_timeago}}</h1>
|
||||||
@@ -10,6 +14,7 @@
|
|||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
<ul>
|
<ul>
|
||||||
<li class="tab" id="default-tab"><a href="#text">Text</a></li>
|
<li class="tab" id="default-tab"><a href="#text">Text</a></li>
|
||||||
|
<li class="tab" id="screenshot-tab"><a href="#screenshot">Screenshot</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -28,5 +33,20 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tab-pane-inner" id="screenshot">
|
||||||
|
<div class="tip">
|
||||||
|
For now, Differences are performed on text, not graphically, only the latest screenshot is available.
|
||||||
|
</div>
|
||||||
|
</br>
|
||||||
|
{% if is_html_webdriver %}
|
||||||
|
{% if screenshot %}
|
||||||
|
<img style="max-width: 80%" id="screenshot-img" alt="Current screenshot from most recent request"/>
|
||||||
|
{% else %}
|
||||||
|
No screenshot available just yet! Try rechecking the page.
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<strong>Screenshot requires Playwright/WebDriver enabled</strong>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user