mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2025-11-08 02:26:31 +00:00
Compare commits
2 Commits
exception-
...
update-eve
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
159ecb1404 | ||
|
|
ddfcb4b5ed |
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -4,10 +4,6 @@ updates:
|
|||||||
directory: /
|
directory: /
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
"caronc/apprise":
|
|
||||||
versioning-strategy: "increase"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
groups:
|
groups:
|
||||||
all:
|
all:
|
||||||
patterns:
|
patterns:
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
# Read more https://github.com/dgtlmoon/changedetection.io/wiki
|
# Read more https://github.com/dgtlmoon/changedetection.io/wiki
|
||||||
|
|
||||||
__version__ = '0.45.20'
|
__version__ = '0.45.17'
|
||||||
|
|
||||||
from changedetectionio.strtobool import strtobool
|
from distutils.util import strtobool
|
||||||
from json.decoder import JSONDecodeError
|
from json.decoder import JSONDecodeError
|
||||||
import os
|
import os
|
||||||
#os.environ['EVENTLET_NO_GREENDNS'] = 'yes'
|
#os.environ['EVENTLET_NO_GREENDNS'] = 'yes'
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
from changedetectionio.strtobool import strtobool
|
from distutils.util import strtobool
|
||||||
|
|
||||||
from flask_expects_json import expects_json
|
from flask_expects_json import expects_json
|
||||||
from changedetectionio import queuedWatchMetaData
|
from changedetectionio import queuedWatchMetaData
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from changedetectionio.strtobool import strtobool
|
from distutils.util import strtobool
|
||||||
from flask import Blueprint, request, make_response
|
from flask import Blueprint, request, make_response
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
from changedetectionio.strtobool import strtobool
|
from distutils.util import strtobool
|
||||||
from flask import Blueprint, flash, redirect, url_for
|
from flask import Blueprint, flash, redirect, url_for
|
||||||
from flask_login import login_required
|
from flask_login import login_required
|
||||||
from changedetectionio.store import ChangeDetectionStore
|
from changedetectionio.store import ChangeDetectionStore
|
||||||
|
|||||||
@@ -12,15 +12,9 @@ def construct_blueprint(datastore: ChangeDetectionStore):
|
|||||||
from .form import SingleTag
|
from .form import SingleTag
|
||||||
add_form = SingleTag(request.form)
|
add_form = SingleTag(request.form)
|
||||||
sorted_tags = sorted(datastore.data['settings']['application'].get('tags').items(), key=lambda x: x[1]['title'])
|
sorted_tags = sorted(datastore.data['settings']['application'].get('tags').items(), key=lambda x: x[1]['title'])
|
||||||
|
|
||||||
from collections import Counter
|
|
||||||
|
|
||||||
tag_count = Counter(tag for watch in datastore.data['watching'].values() if watch.get('tags') for tag in watch['tags'])
|
|
||||||
|
|
||||||
output = render_template("groups-overview.html",
|
output = render_template("groups-overview.html",
|
||||||
available_tags=sorted_tags,
|
|
||||||
form=add_form,
|
form=add_form,
|
||||||
tag_count=tag_count
|
available_tags=sorted_tags,
|
||||||
)
|
)
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
{% from '_helpers.jinja' import render_field, render_checkbox_field, render_button %}
|
{% from '_helpers.jinja' import render_field, render_checkbox_field, render_button %}
|
||||||
{% from '_common_fields.jinja' import render_common_settings_form %}
|
{% from '_common_fields.jinja' import render_common_settings_form %}
|
||||||
<script>
|
<script>
|
||||||
const notification_base_url="{{url_for('ajax_callback_send_notification_test', mode="group-settings")}}";
|
const notification_base_url="{{url_for('ajax_callback_send_notification_test', watch_uuid=uuid)}}";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="{{url_for('static_content', group='js', filename='tabs.js')}}" defer></script>
|
<script src="{{url_for('static_content', group='js', filename='tabs.js')}}" defer></script>
|
||||||
|
|||||||
@@ -27,7 +27,6 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th># Watches</th>
|
|
||||||
<th>Tag / Label name</th>
|
<th>Tag / Label name</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -46,8 +45,7 @@
|
|||||||
<td class="watch-controls">
|
<td class="watch-controls">
|
||||||
<a class="link-mute state-{{'on' if tag.notification_muted else 'off'}}" href="{{url_for('tags.mute', uuid=tag.uuid)}}"><img src="{{url_for('static_content', group='images', filename='bell-off.svg')}}" alt="Mute notifications" title="Mute notifications" class="icon icon-mute" ></a>
|
<a class="link-mute state-{{'on' if tag.notification_muted else 'off'}}" href="{{url_for('tags.mute', uuid=tag.uuid)}}"><img src="{{url_for('static_content', group='images', filename='bell-off.svg')}}" alt="Mute notifications" title="Mute notifications" class="icon icon-mute" ></a>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ "{:,}".format(tag_count[uuid]) if uuid in tag_count else 0 }}</td>
|
<td class="title-col inline">{{tag.title}}</td>
|
||||||
<td class="title-col inline"> <a href="{{url_for('index', tag=uuid) }}">{{ tag.title }}</a></td>
|
|
||||||
<td>
|
<td>
|
||||||
<a class="pure-button pure-button-primary" href="{{ url_for('tags.form_tag_edit', uuid=uuid) }}">Edit</a>
|
<a class="pure-button pure-button-primary" href="{{ url_for('tags.form_tag_edit', uuid=uuid) }}">Edit</a>
|
||||||
<a class="pure-button pure-button-primary" href="{{ url_for('tags.delete', uuid=uuid) }}" title="Deletes and removes tag">Delete</a>
|
<a class="pure-button pure-button-primary" href="{{ url_for('tags.delete', uuid=uuid) }}" title="Deletes and removes tag">Delete</a>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import sys
|
import sys
|
||||||
from changedetectionio.strtobool import strtobool
|
from distutils.util import strtobool
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from changedetectionio.content_fetchers.exceptions import BrowserStepsStepException
|
from changedetectionio.content_fetchers.exceptions import BrowserStepsStepException
|
||||||
import os
|
import os
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import queue
|
|||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from changedetectionio.strtobool import strtobool
|
from distutils.util import strtobool
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from threading import Event
|
from threading import Event
|
||||||
|
|
||||||
@@ -516,38 +516,21 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
|
|
||||||
watch = datastore.data['watching'].get(watch_uuid) if watch_uuid else None
|
watch = datastore.data['watching'].get(watch_uuid) if watch_uuid else None
|
||||||
|
|
||||||
notification_urls = request.form['notification_urls'].strip().splitlines()
|
# validate URLS
|
||||||
|
if not len(request.form['notification_urls'].strip()):
|
||||||
|
return make_response({'error': 'No Notification URLs set'}, 400)
|
||||||
|
|
||||||
if not notification_urls:
|
for server_url in request.form['notification_urls'].splitlines():
|
||||||
logger.debug("Test notification - Trying by group/tag in the edit form if available")
|
if len(server_url.strip()):
|
||||||
# On an edit page, we should also fire off to the tags if they have notifications
|
if not apobj.add(server_url):
|
||||||
if request.form.get('tags') and request.form['tags'].strip():
|
message = '{} is not a valid AppRise URL.'.format(server_url)
|
||||||
for k in request.form['tags'].split(','):
|
return make_response({'error': message}, 400)
|
||||||
tag = datastore.tag_exists_by_name(k.strip())
|
|
||||||
notification_urls = tag.get('notifications_urls') if tag and tag.get('notifications_urls') else None
|
|
||||||
|
|
||||||
is_global_settings_form = request.args.get('mode', '') == 'global-settings'
|
|
||||||
is_group_settings_form = request.args.get('mode', '') == 'group-settings'
|
|
||||||
if not notification_urls and not is_global_settings_form and not is_group_settings_form:
|
|
||||||
# In the global settings, use only what is typed currently in the text box
|
|
||||||
logger.debug("Test notification - Trying by global system settings notifications")
|
|
||||||
if datastore.data['settings']['application'].get('notification_urls'):
|
|
||||||
notification_urls = datastore.data['settings']['application']['notification_urls']
|
|
||||||
|
|
||||||
|
|
||||||
if not notification_urls:
|
|
||||||
return 'No Notification URLs set/found'
|
|
||||||
|
|
||||||
for n_url in notification_urls:
|
|
||||||
if len(n_url.strip()):
|
|
||||||
if not apobj.add(n_url):
|
|
||||||
return f'Error - {n_url} is not a valid AppRise URL.'
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# use the same as when it is triggered, but then override it with the form test values
|
# use the same as when it is triggered, but then override it with the form test values
|
||||||
n_object = {
|
n_object = {
|
||||||
'watch_url': request.form['window_url'],
|
'watch_url': request.form['window_url'],
|
||||||
'notification_urls': notification_urls
|
'notification_urls': request.form['notification_urls'].splitlines()
|
||||||
}
|
}
|
||||||
|
|
||||||
# Only use if present, if not set in n_object it should use the default system value
|
# Only use if present, if not set in n_object it should use the default system value
|
||||||
@@ -566,7 +549,7 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return make_response({'error': str(e)}, 400)
|
return make_response({'error': str(e)}, 400)
|
||||||
|
|
||||||
return 'OK - Sent test notifications'
|
return 'OK'
|
||||||
|
|
||||||
|
|
||||||
@app.route("/clear_history/<string:uuid>", methods=['GET'])
|
@app.route("/clear_history/<string:uuid>", methods=['GET'])
|
||||||
@@ -603,12 +586,6 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
output = render_template("clear_all_history.html")
|
output = render_template("clear_all_history.html")
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def _watch_has_tag_options_set(watch):
|
|
||||||
"""This should be fixed better so that Tag is some proper Model, a tag is just a Watch also"""
|
|
||||||
for tag_uuid, tag in datastore.data['settings']['application'].get('tags', {}).items():
|
|
||||||
if tag_uuid in watch.get('tags', []) and (tag.get('include_filters') or tag.get('subtractive_selectors')):
|
|
||||||
return True
|
|
||||||
|
|
||||||
@app.route("/edit/<string:uuid>", methods=['GET', 'POST'])
|
@app.route("/edit/<string:uuid>", methods=['GET', 'POST'])
|
||||||
@login_optionally_required
|
@login_optionally_required
|
||||||
# https://stackoverflow.com/questions/42984453/wtforms-populate-form-with-data-if-data-exists
|
# https://stackoverflow.com/questions/42984453/wtforms-populate-form-with-data-if-data-exists
|
||||||
@@ -779,7 +756,6 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
has_default_notification_urls=True if len(datastore.data['settings']['application']['notification_urls']) else False,
|
has_default_notification_urls=True if len(datastore.data['settings']['application']['notification_urls']) else False,
|
||||||
has_empty_checktime=using_default_check_time,
|
has_empty_checktime=using_default_check_time,
|
||||||
has_extra_headers_file=len(datastore.get_all_headers_in_textfile_for_watch(uuid=uuid)) > 0,
|
has_extra_headers_file=len(datastore.get_all_headers_in_textfile_for_watch(uuid=uuid)) > 0,
|
||||||
has_special_tag_options=_watch_has_tag_options_set(watch=watch),
|
|
||||||
is_html_webdriver=is_html_webdriver,
|
is_html_webdriver=is_html_webdriver,
|
||||||
jq_support=jq_support,
|
jq_support=jq_support,
|
||||||
playwright_enabled=os.getenv('PLAYWRIGHT_DRIVER_URL', False),
|
playwright_enabled=os.getenv('PLAYWRIGHT_DRIVER_URL', False),
|
||||||
@@ -1303,8 +1279,9 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
|
|
||||||
url = request.form.get('url').strip()
|
url = request.form.get('url').strip()
|
||||||
if datastore.url_exists(url):
|
if datastore.url_exists(url):
|
||||||
flash(f'Warning, URL {url} already exists', "notice")
|
flash('The URL {} already exists'.format(url), "error")
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
add_paused = request.form.get('edit_and_watch_submit_button') != None
|
add_paused = request.form.get('edit_and_watch_submit_button') != None
|
||||||
processor = request.form.get('processor', 'text_json_diff')
|
processor = request.form.get('processor', 'text_json_diff')
|
||||||
new_uuid = datastore.add_watch(url=url, tag=request.form.get('tags').strip(), extras={'paused': add_paused, 'processor': processor})
|
new_uuid = datastore.add_watch(url=url, tag=request.form.get('tags').strip(), extras={'paused': add_paused, 'processor': processor})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from changedetectionio.strtobool import strtobool
|
from distutils.util import strtobool
|
||||||
|
|
||||||
from wtforms import (
|
from wtforms import (
|
||||||
BooleanField,
|
BooleanField,
|
||||||
|
|||||||
@@ -169,14 +169,14 @@ def xpath1_filter(xpath_filter, html_content, append_pretty_line_formatting=Fals
|
|||||||
# And where the matched result doesn't include something that will cause Inscriptis to add a newline
|
# And where the matched result doesn't include something that will cause Inscriptis to add a newline
|
||||||
# (This way each 'match' reliably has a new-line in the diff)
|
# (This way each 'match' reliably has a new-line in the diff)
|
||||||
# Divs are converted to 4 whitespaces by inscriptis
|
# Divs are converted to 4 whitespaces by inscriptis
|
||||||
if append_pretty_line_formatting and len(html_block) and (not hasattr(element, 'tag') or not element.tag in (['br', 'hr', 'div', 'p'])):
|
if append_pretty_line_formatting and len(html_block) and (not hasattr( element, 'tag' ) or not element.tag in (['br', 'hr', 'div', 'p'])):
|
||||||
html_block += TEXT_FILTER_LIST_LINE_SUFFIX
|
html_block += TEXT_FILTER_LIST_LINE_SUFFIX
|
||||||
|
|
||||||
# Some kind of text, UTF-8 or other
|
if type(element) == etree._ElementStringResult:
|
||||||
if isinstance(element, (str, bytes)):
|
html_block += str(element)
|
||||||
html_block += element
|
elif type(element) == etree._ElementUnicodeResult:
|
||||||
|
html_block += str(element)
|
||||||
else:
|
else:
|
||||||
# Return the HTML which will get parsed as text
|
|
||||||
html_block += etree.tostring(element, pretty_print=True).decode('utf-8')
|
html_block += etree.tostring(element, pretty_print=True).decode('utf-8')
|
||||||
|
|
||||||
return html_block
|
return html_block
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from changedetectionio.strtobool import strtobool
|
from distutils.util import strtobool
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
@@ -362,7 +362,6 @@ class model(dict):
|
|||||||
# @todo bump static cache of the last timestamp so we dont need to examine the file to set a proper ''viewed'' status
|
# @todo bump static cache of the last timestamp so we dont need to examine the file to set a proper ''viewed'' status
|
||||||
return snapshot_fname
|
return snapshot_fname
|
||||||
|
|
||||||
@property
|
|
||||||
@property
|
@property
|
||||||
def has_empty_checktime(self):
|
def has_empty_checktime(self):
|
||||||
# using all() + dictionary comprehension
|
# using all() + dictionary comprehension
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import os
|
|||||||
import hashlib
|
import hashlib
|
||||||
import re
|
import re
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from changedetectionio.strtobool import strtobool
|
from distutils.util import strtobool
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
class difference_detection_processor():
|
class difference_detection_processor():
|
||||||
|
|||||||
@@ -28,11 +28,15 @@ $(document).ready(function() {
|
|||||||
notification_format: $('#notification_format').val(),
|
notification_format: $('#notification_format').val(),
|
||||||
notification_title: $('#notification_title').val(),
|
notification_title: $('#notification_title').val(),
|
||||||
notification_urls: $('.notification-urls').val(),
|
notification_urls: $('.notification-urls').val(),
|
||||||
tags: $('#tags').val(),
|
|
||||||
window_url: window.location.href,
|
window_url: window.location.href,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!data['notification_urls'].length) {
|
||||||
|
alert("Notification URL list is empty, cannot send test.")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "POST",
|
type: "POST",
|
||||||
url: notification_base_url,
|
url: notification_base_url,
|
||||||
@@ -45,7 +49,7 @@ $(document).ready(function() {
|
|||||||
}
|
}
|
||||||
}).done(function(data){
|
}).done(function(data){
|
||||||
console.log(data);
|
console.log(data);
|
||||||
alert(data);
|
alert('Sent');
|
||||||
}).fail(function(data){
|
}).fail(function(data){
|
||||||
console.log(data);
|
console.log(data);
|
||||||
alert('There was an error communicating with the server.');
|
alert('There was an error communicating with the server.');
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from changedetectionio.strtobool import strtobool
|
from distutils.util import strtobool
|
||||||
|
|
||||||
from flask import (
|
from flask import (
|
||||||
flash
|
flash
|
||||||
@@ -657,10 +657,7 @@ class ChangeDetectionStore:
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
def tag_exists_by_name(self, tag_name):
|
def tag_exists_by_name(self, tag_name):
|
||||||
# Check if any tag dictionary has a 'title' attribute matching the provided tag_name
|
return any(v.get('title', '').lower() == tag_name.lower() for k, v in self.__data['settings']['application']['tags'].items())
|
||||||
tags = self.__data['settings']['application']['tags'].values()
|
|
||||||
return next((v for v in tags if v.get('title', '').lower() == tag_name.lower()),
|
|
||||||
None)
|
|
||||||
|
|
||||||
def get_updates_available(self):
|
def get_updates_available(self):
|
||||||
import inspect
|
import inspect
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
# Because strtobool was removed in python 3.12 distutils
|
|
||||||
|
|
||||||
_MAP = {
|
|
||||||
'y': True,
|
|
||||||
'yes': True,
|
|
||||||
't': True,
|
|
||||||
'true': True,
|
|
||||||
'on': True,
|
|
||||||
'1': True,
|
|
||||||
'n': False,
|
|
||||||
'no': False,
|
|
||||||
'f': False,
|
|
||||||
'false': False,
|
|
||||||
'off': False,
|
|
||||||
'0': False
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def strtobool(value):
|
|
||||||
try:
|
|
||||||
return _MAP[str(value).lower()]
|
|
||||||
except KeyError:
|
|
||||||
raise ValueError('"{}" is not a valid bool value'.format(value))
|
|
||||||
@@ -7,8 +7,7 @@
|
|||||||
<script>
|
<script>
|
||||||
const browser_steps_available_screenshots=JSON.parse('{{ watch.get_browsersteps_available_screenshots|tojson }}');
|
const browser_steps_available_screenshots=JSON.parse('{{ watch.get_browsersteps_available_screenshots|tojson }}');
|
||||||
const browser_steps_config=JSON.parse('{{ browser_steps_config|tojson }}');
|
const browser_steps_config=JSON.parse('{{ browser_steps_config|tojson }}');
|
||||||
<!-- Should be _external so that firefox and others load it more reliably -->
|
const browser_steps_fetch_screenshot_image_url="{{url_for('browser_steps.browser_steps_fetch_screenshot_image', uuid=uuid)}}";
|
||||||
const browser_steps_fetch_screenshot_image_url="{{url_for('browser_steps.browser_steps_fetch_screenshot_image', uuid=uuid, _external=True)}}";
|
|
||||||
const browser_steps_last_error_step={{ watch.browser_steps_last_error_step|tojson }};
|
const browser_steps_last_error_step={{ watch.browser_steps_last_error_step|tojson }};
|
||||||
const browser_steps_start_url="{{url_for('browser_steps.browsersteps_start_session', uuid=uuid)}}";
|
const browser_steps_start_url="{{url_for('browser_steps.browsersteps_start_session', uuid=uuid)}}";
|
||||||
const browser_steps_sync_url="{{url_for('browser_steps.browsersteps_ui_update', uuid=uuid)}}";
|
const browser_steps_sync_url="{{url_for('browser_steps.browsersteps_ui_update', uuid=uuid)}}";
|
||||||
@@ -32,7 +31,6 @@
|
|||||||
<script src="{{url_for('static_content', group='js', filename='browser-steps.js')}}" defer></script>
|
<script src="{{url_for('static_content', group='js', filename='browser-steps.js')}}" defer></script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% set has_tag_filters_extra="WARNING: Watch has tag/groups set with special filters\n" if has_special_tag_options else '' %}
|
|
||||||
<script src="{{url_for('static_content', group='js', filename='recheck-proxy.js')}}" defer></script>
|
<script src="{{url_for('static_content', group='js', filename='recheck-proxy.js')}}" defer></script>
|
||||||
|
|
||||||
<div class="edit-form monospaced-textarea">
|
<div class="edit-form monospaced-textarea">
|
||||||
@@ -282,7 +280,7 @@ User-Agent: wonderbra 1.0") }}
|
|||||||
<div class="pure-control-group">
|
<div class="pure-control-group">
|
||||||
{% set field = render_field(form.include_filters,
|
{% set field = render_field(form.include_filters,
|
||||||
rows=5,
|
rows=5,
|
||||||
placeholder=has_tag_filters_extra+"#example
|
placeholder="#example
|
||||||
xpath://body/div/span[contains(@class, 'example-class')]",
|
xpath://body/div/span[contains(@class, 'example-class')]",
|
||||||
class="m-d")
|
class="m-d")
|
||||||
%}
|
%}
|
||||||
@@ -318,7 +316,7 @@ xpath://body/div/span[contains(@class, 'example-class')]",
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<fieldset class="pure-control-group">
|
<fieldset class="pure-control-group">
|
||||||
{{ render_field(form.subtractive_selectors, rows=5, placeholder=has_tag_filters_extra+"header
|
{{ render_field(form.subtractive_selectors, rows=5, placeholder="header
|
||||||
footer
|
footer
|
||||||
nav
|
nav
|
||||||
.stockticker") }}
|
.stockticker") }}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
{% from '_helpers.jinja' import render_field, render_checkbox_field, render_button %}
|
{% from '_helpers.jinja' import render_field, render_checkbox_field, render_button %}
|
||||||
{% from '_common_fields.jinja' import render_common_settings_form %}
|
{% from '_common_fields.jinja' import render_common_settings_form %}
|
||||||
<script>
|
<script>
|
||||||
const notification_base_url="{{url_for('ajax_callback_send_notification_test', mode="global-settings")}}";
|
const notification_base_url="{{url_for('ajax_callback_send_notification_test', watch_uuid=uuid)}}";
|
||||||
{% if emailprefix %}
|
{% if emailprefix %}
|
||||||
const email_notification_prefix=JSON.parse('{{emailprefix|tojson}}');
|
const email_notification_prefix=JSON.parse('{{emailprefix|tojson}}');
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -100,12 +100,6 @@ def test_setup_group_tag(client, live_server):
|
|||||||
assert b'Should be only this' in res.data
|
assert b'Should be only this' in res.data
|
||||||
assert b'And never this' not in res.data
|
assert b'And never this' not in res.data
|
||||||
|
|
||||||
res = client.get(
|
|
||||||
url_for("edit_page", uuid="first"),
|
|
||||||
follow_redirects=True
|
|
||||||
)
|
|
||||||
# 2307 the UI notice should appear in the placeholder
|
|
||||||
assert b'WARNING: Watch has tag/groups set with special filters' in res.data
|
|
||||||
|
|
||||||
# RSS Group tag filter
|
# RSS Group tag filter
|
||||||
# An extra one that should be excluded
|
# An extra one that should be excluded
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
#!/usr/bin/python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
@@ -255,69 +255,6 @@ def test_xpath23_prefix_validation(client, live_server):
|
|||||||
res = client.get(url_for("form_delete", uuid="all"), follow_redirects=True)
|
res = client.get(url_for("form_delete", uuid="all"), follow_redirects=True)
|
||||||
assert b'Deleted' in res.data
|
assert b'Deleted' in res.data
|
||||||
|
|
||||||
def test_xpath1_lxml(client, live_server):
|
|
||||||
#live_server_setup(live_server)
|
|
||||||
|
|
||||||
d = '''<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<rss xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
|
|
||||||
<channel>
|
|
||||||
<title>rpilocator.com</title>
|
|
||||||
<link>https://rpilocator.com</link>
|
|
||||||
<description>Find Raspberry Pi Computers in Stock</description>
|
|
||||||
<lastBuildDate>Thu, 19 May 2022 23:27:30 GMT</lastBuildDate>
|
|
||||||
<image>
|
|
||||||
<url>https://rpilocator.com/favicon.png</url>
|
|
||||||
<title>rpilocator.com</title>
|
|
||||||
<link>https://rpilocator.com/</link>
|
|
||||||
<width>32</width>
|
|
||||||
<height>32</height>
|
|
||||||
</image>
|
|
||||||
<item>
|
|
||||||
<title>Stock Alert (UK): RPi CM4</title>
|
|
||||||
<foo>something else unrelated</foo>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<title>Stock Alert (UK): Big monitorěěěě</title>
|
|
||||||
<foo>something else unrelated</foo>
|
|
||||||
</item>
|
|
||||||
</channel>
|
|
||||||
</rss>'''.encode('utf-8')
|
|
||||||
|
|
||||||
with open("test-datastore/endpoint-content.txt", "wb") as f:
|
|
||||||
f.write(d)
|
|
||||||
|
|
||||||
|
|
||||||
test_url = url_for('test_endpoint', _external=True)
|
|
||||||
res = client.post(
|
|
||||||
url_for("import_page"),
|
|
||||||
data={"urls": test_url},
|
|
||||||
follow_redirects=True
|
|
||||||
)
|
|
||||||
assert b"1 Imported" in res.data
|
|
||||||
wait_for_all_checks(client)
|
|
||||||
|
|
||||||
res = client.post(
|
|
||||||
url_for("edit_page", uuid="first"),
|
|
||||||
data={"include_filters": "xpath1://title/text()", "url": test_url, "tags": "", "headers": "",
|
|
||||||
'fetch_backend': "html_requests"},
|
|
||||||
follow_redirects=True
|
|
||||||
)
|
|
||||||
|
|
||||||
##### #2312
|
|
||||||
wait_for_all_checks(client)
|
|
||||||
res = client.get(url_for("index"))
|
|
||||||
assert b'_ElementStringResult' not in res.data # tested with 5.1.1 when it was removed and 5.1.0
|
|
||||||
assert b'Exception' not in res.data
|
|
||||||
res = client.get(
|
|
||||||
url_for("preview_page", uuid="first"),
|
|
||||||
follow_redirects=True
|
|
||||||
)
|
|
||||||
|
|
||||||
assert b"rpilocator.com" in res.data # in selector
|
|
||||||
assert "Stock Alert (UK): Big monitorěěěě".encode('utf-8') in res.data # not in selector
|
|
||||||
|
|
||||||
#####
|
|
||||||
|
|
||||||
|
|
||||||
def test_xpath1_validation(client, live_server):
|
def test_xpath1_validation(client, live_server):
|
||||||
# Add our URL to the import page
|
# Add our URL to the import page
|
||||||
|
|||||||
@@ -462,7 +462,7 @@ class update_worker(threading.Thread):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Exception reached processing watch UUID: {uuid}")
|
logger.error(f"Exception reached processing watch UUID: {uuid}")
|
||||||
logger.error(str(e))
|
logger.error(str(e))
|
||||||
self.datastore.update_watch(uuid=uuid, update_obj={'last_error': "Exception: " + str(e)})
|
self.datastore.update_watch(uuid=uuid, update_obj={'last_error': str(e)})
|
||||||
# Other serious error
|
# Other serious error
|
||||||
process_changedetection_results = False
|
process_changedetection_results = False
|
||||||
# import traceback
|
# import traceback
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
# Used by Pyppeteer
|
# Used by Pyppeteer
|
||||||
pyee
|
pyee
|
||||||
|
|
||||||
eventlet==0.33.3 # related to dnspython fixes
|
# eventlet 0.33.3 was related to dnspython fixes
|
||||||
|
# 0.34.1 - fixes python 3.12 "AttributeError: module 'ssl' has no attribute 'wrap_socket'"
|
||||||
|
eventlet==0.34.1
|
||||||
|
|
||||||
feedgen~=0.9
|
feedgen~=0.9
|
||||||
flask-compress
|
flask-compress
|
||||||
# 0.6.3 included compatibility fix for werkzeug 3.x (2.x had deprecation of url handlers)
|
# 0.6.3 included compatibility fix for werkzeug 3.x (2.x had deprecation of url handlers)
|
||||||
@@ -31,7 +34,7 @@ jsonpath-ng~=1.5.3
|
|||||||
|
|
||||||
# Pinned: module 'eventlet.green.select' has no attribute 'epoll'
|
# Pinned: module 'eventlet.green.select' has no attribute 'epoll'
|
||||||
# https://github.com/eventlet/eventlet/issues/805#issuecomment-1640463482
|
# https://github.com/eventlet/eventlet/issues/805#issuecomment-1640463482
|
||||||
dnspython==2.3.0 # related to eventlet fixes
|
dnspython==2.6.1 # related to eventlet fixes
|
||||||
|
|
||||||
# jq not available on Windows so must be installed manually
|
# jq not available on Windows so must be installed manually
|
||||||
|
|
||||||
@@ -52,7 +55,7 @@ cryptography~=3.4
|
|||||||
beautifulsoup4
|
beautifulsoup4
|
||||||
|
|
||||||
# XPath filtering, lxml is required by bs4 anyway, but put it here to be safe.
|
# XPath filtering, lxml is required by bs4 anyway, but put it here to be safe.
|
||||||
lxml >=4.8.0,<6
|
lxml
|
||||||
|
|
||||||
# XPath 2.0-3.1 support - 4.2.0 broke something?
|
# XPath 2.0-3.1 support - 4.2.0 broke something?
|
||||||
elementpath==4.1.5
|
elementpath==4.1.5
|
||||||
|
|||||||
Reference in New Issue
Block a user