Compare commits

...

10 Commits

11 changed files with 72 additions and 58 deletions

View File

@@ -38,7 +38,7 @@ from flask_paginate import Pagination, get_page_parameter
from changedetectionio import html_tools from changedetectionio import html_tools
from changedetectionio.api import api_v1 from changedetectionio.api import api_v1
__version__ = '0.45.8' __version__ = '0.45.8.1'
from changedetectionio.store import BASE_URL_NOT_SET_TEXT from changedetectionio.store import BASE_URL_NOT_SET_TEXT

View File

@@ -83,9 +83,12 @@ def apprise_custom_api_call_wrapper(body, title, notify_type, *args, **kwargs):
headers = {URLBase.unquote(x): URLBase.unquote(y) headers = {URLBase.unquote(x): URLBase.unquote(y)
for x, y in results['qsd+'].items()} for x, y in results['qsd+'].items()}
# Add our GET paramters in the event the user wants to pass these along # https://github.com/caronc/apprise/wiki/Notify_Custom_JSON#get-parameter-manipulation
params = {URLBase.unquote(x): URLBase.unquote(y) # In Apprise, it relies on prefixing each request arg with "-", because it uses say &method=update as a flag for apprise
for x, y in results['qsd-'].items()} # but here we are making straight requests, so we need todo convert this against apprise's logic
for k, v in results['qsd'].items():
if not k.strip('+-') in results['qsd+'].keys():
params[URLBase.unquote(k)] = URLBase.unquote(v)
# Determine Authentication # Determine Authentication
auth = '' auth = ''
@@ -102,10 +105,10 @@ def apprise_custom_api_call_wrapper(body, title, notify_type, *args, **kwargs):
pass pass
r(results.get('url'), r(results.get('url'),
headers=headers, auth=auth,
data=body, data=body,
params=params, headers=headers,
auth=auth params=params
) )

View File

@@ -52,6 +52,11 @@ class difference_detection_processor():
prefer_fetch_backend = 'base_html_playwright' prefer_fetch_backend = 'base_html_playwright'
browser_connection_url = connection[0].get('browser_connection_url') browser_connection_url = connection[0].get('browser_connection_url')
# PDF should be html_requests because playwright will serve it up (so far) in a embedded page
# @todo https://github.com/dgtlmoon/changedetection.io/issues/2019
# @todo needs test to or a fix
if self.watch.is_pdf:
prefer_fetch_backend = "html_requests"
# Grab the right kind of 'fetcher', (playwright, requests, etc) # Grab the right kind of 'fetcher', (playwright, requests, etc)
if hasattr(content_fetcher, prefer_fetch_backend): if hasattr(content_fetcher, prefer_fetch_backend):

View File

@@ -2,21 +2,28 @@ $(document).ready(function () {
// Lazy Hide/Show elements mechanism // Lazy Hide/Show elements mechanism
$('[data-visible-for]').hide(); $('[data-visible-for]').hide();
$(':radio').on('keyup keypress blur change click', function (e) { function show_related_elem(e) {
$('[data-visible-for]').hide(); var n = $(e).attr('name') + "=" + $(e).val();
$('.advanced-options').hide();
var n = $(this).attr('name') + "=" + $(this).val();
if (n === 'fetch_backend=system') { if (n === 'fetch_backend=system') {
n = "fetch_backend=" + default_system_fetch_backend; n = "fetch_backend=" + default_system_fetch_backend;
} }
$(`[data-visible-for~="${n}"]`).show(); $(`[data-visible-for~="${n}"]`).show();
}
$(':radio').on('keyup keypress blur change click', function (e) {
$(`[data-visible-for]`).hide();
$('.advanced-options').hide();
show_related_elem(this);
}); });
$(':radio:checked').change();
$(':radio:checked').each(function (e) {
show_related_elem(this);
})
// Show advanced // Show advanced
$('.show-advanced').click(function (e) { $('.show-advanced').click(function (e) {
$(this).closest('.tab-pane-inner').find('.advanced-options').toggle(); $(this).closest('.tab-pane-inner').find('.advanced-options').each(function (e) {
$(this).toggle();
})
}); });
}); });

View File

@@ -1,18 +1,4 @@
$(document).ready(function () { $(document).ready(function () {
// Lazy Hide/Show elements mechanism
$('[data-visible-for]').hide();
$(':radio').on('keyup keypress blur change click', function (e){
$('[data-visible-for]').hide();
var n = $(this).attr('name') + "=" + $(this).val();
if (n === 'fetch_backend=system') {
n = "fetch_backend=" + default_system_fetch_backend;
}
$(`[data-visible-for~="${n}"]`).show();
});
$(':radio:checked').change();
$('#notification-setting-reset-to-default').click(function (e) { $('#notification-setting-reset-to-default').click(function (e) {
$('#notification_title').val(''); $('#notification_title').val('');
$('#notification_body').val(''); $('#notification_body').val('');

View File

@@ -16,7 +16,7 @@
<li><code><a target=_new href="https://github.com/caronc/apprise/wiki/Notify_discord">discord://</a></code> (or <code>https://discord.com/api/webhooks...</code>)) only supports a maximum <strong>2,000 characters</strong> of notification text, including the title.</li> <li><code><a target=_new href="https://github.com/caronc/apprise/wiki/Notify_discord">discord://</a></code> (or <code>https://discord.com/api/webhooks...</code>)) only supports a maximum <strong>2,000 characters</strong> of notification text, including the title.</li>
<li><code><a target=_new href="https://github.com/caronc/apprise/wiki/Notify_telegram">tgram://</a></code> bots can't send messages to other bots, so you should specify chat ID of non-bot user.</li> <li><code><a target=_new href="https://github.com/caronc/apprise/wiki/Notify_telegram">tgram://</a></code> bots can't send messages to other bots, so you should specify chat ID of non-bot user.</li>
<li><code><a target=_new href="https://github.com/caronc/apprise/wiki/Notify_telegram">tgram://</a></code> only supports very limited HTML and can fail when extra tags are sent, <a href="https://core.telegram.org/bots/api#html-style">read more here</a> (or use plaintext/markdown format)</li> <li><code><a target=_new href="https://github.com/caronc/apprise/wiki/Notify_telegram">tgram://</a></code> only supports very limited HTML and can fail when extra tags are sent, <a href="https://core.telegram.org/bots/api#html-style">read more here</a> (or use plaintext/markdown format)</li>
<li><code>gets://</code>, <code>posts://</code>, <code>puts://</code>, <code>deletes://</code> for direct API calls (or omit the "<code>s</code>" for non-SSL ie <code>get://</code>)</li> <li><code>gets://</code>, <code>posts://</code>, <code>puts://</code>, <code>deletes://</code> for direct API calls (or omit the "<code>s</code>" for non-SSL ie <code>get://</code>) <a href="https://github.com/dgtlmoon/changedetection.io/wiki/Notification-configuration-notes#postposts">more help here</a></li>
<li>Accepts the <code>{{ '{{token}}' }}</code> placeholders listed below</li> <li>Accepts the <code>{{ '{{token}}' }}</code> placeholders listed below</li>
</ul> </ul>
</div> </div>

View File

@@ -127,7 +127,7 @@
{% endif %} {% endif %}
<!-- webdriver always --> <!-- webdriver always -->
<fieldset data-visible-for="fetch_backend=html_webdriver"> <fieldset data-visible-for="fetch_backend=html_webdriver" style="display: none;">
<div class="pure-control-group"> <div class="pure-control-group">
{{ render_field(form.webdriver_delay) }} {{ render_field(form.webdriver_delay) }}
<div class="pure-form-message-inline"> <div class="pure-form-message-inline">
@@ -153,15 +153,15 @@
</div> </div>
</fieldset> </fieldset>
<!-- html requests always --> <!-- html requests always -->
<fieldset data-visible-for="fetch_backend=html_requests" style="display: none;"> <fieldset data-visible-for="fetch_backend=html_requests">
<div class="pure-control-group"> <div class="pure-control-group">
<a class="pure-button button-secondary button-xsmall show-advanced">Show advanced options</a> <a class="pure-button button-secondary button-xsmall show-advanced">Show advanced options</a>
</div> </div>
<div class="advanced-options" style="display: none;"> <div class="advanced-options" style="display: none;">
<div class="pure-control-group advanced-options" id="request-method" style="display: none;"> <div class="pure-control-group" id="request-method">
{{ render_field(form.method) }} {{ render_field(form.method) }}
</div> </div>
<div class="advanced-options" id="request-body"> <div id="request-body">
{{ render_field(form.body, rows=5, placeholder="Example {{ render_field(form.body, rows=5, placeholder="Example
{ {
\"name\":\"John\", \"name\":\"John\",
@@ -187,8 +187,8 @@ User-Agent: wonderbra 1.0") }}
(Not supported by Selenium browser) (Not supported by Selenium browser)
</div> </div>
</div> </div>
<fieldset data-visible-for="fetch_backend=html_requests fetch_backend=html_webdriver" style="display: none;"> <fieldset data-visible-for="fetch_backend=html_requests fetch_backend=html_webdriver" >
<div class="pure-control-group inline-radio advanced-options" style="display: none;"> <div class="pure-control-group inline-radio advanced-options" style="display: none;">
{{ render_checkbox_field(form.ignore_status_codes) }} {{ render_checkbox_field(form.ignore_status_codes) }}
</div> </div>
</fieldset> </fieldset>

View File

@@ -236,8 +236,11 @@ nav
<span class="pure-form-message-inline">SOCKS5 proxies with authentication are only supported with 'plain requests' fetcher, for other fetchers you should whitelist the IP access instead</span> <span class="pure-form-message-inline">SOCKS5 proxies with authentication are only supported with 'plain requests' fetcher, for other fetchers you should whitelist the IP access instead</span>
</div> </div>
<div class="pure-control-group" id="extra-browsers-setting"> <div class="pure-control-group" id="extra-browsers-setting">
<span class="pure-form-message-inline"><i>Extra Browsers</i> allow changedetection.io to communicate with a different web-browser.</span><br> <p>
{{ render_field(form.requests.form.extra_browsers) }} <span class="pure-form-message-inline"><i>Extra Browsers</i> can be attached to further defeat CAPTCHA's on websites that are particularly hard to scrape.</span><br>
<span class="pure-form-message-inline">Simply paste the connection address into the box, <a href="https://changedetection.io/tutorial/using-bright-datas-scraping-browser-pass-captchas-and-other-protection-when-monitoring">More instructions and examples here</a> </span>
</p>
{{ render_field(form.requests.form.extra_browsers) }}
</div> </div>
</div> </div>
<div id="actions"> <div id="actions">

View File

@@ -13,22 +13,17 @@ global app
def cleanup(datastore_path): def cleanup(datastore_path):
import glob
# Unlink test output files # Unlink test output files
files = [
'count.txt', for g in ["*.txt", "*.json", "*.pdf"]:
'endpoint-content.txt' files = glob.glob(os.path.join(datastore_path, g))
'headers.txt', for f in files:
'headers-testtag.txt', if 'proxies.json' in f:
'notification.txt', # Usually mounted by docker container during test time
'secret.txt', continue
'url-watches.json', if os.path.isfile(f):
'output.txt', os.unlink(f)
]
for file in files:
try:
os.unlink("{}/{}".format(datastore_path, file))
except FileNotFoundError:
pass
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
def app(request): def app(request):

View File

@@ -281,7 +281,8 @@ def test_notification_custom_endpoint_and_jinja2(client, live_server):
# CUSTOM JSON BODY CHECK for POST:// # CUSTOM JSON BODY CHECK for POST://
set_original_response() set_original_response()
test_notification_url = url_for('test_notification_endpoint', _external=True).replace('http://', 'post://')+"?xxx={{ watch_url }}" # https://github.com/caronc/apprise/wiki/Notify_Custom_JSON#header-manipulation
test_notification_url = url_for('test_notification_endpoint', _external=True).replace('http://', 'post://')+"?xxx={{ watch_url }}&+custom-header=123"
res = client.post( res = client.post(
url_for("settings_page"), url_for("settings_page"),
@@ -297,10 +298,7 @@ def test_notification_custom_endpoint_and_jinja2(client, live_server):
follow_redirects=True follow_redirects=True
) )
assert b'Settings updated' in res.data assert b'Settings updated' in res.data
client.get(
url_for("form_delete", uuid="all"),
follow_redirects=True
)
# Add a watch and trigger a HTTP POST # Add a watch and trigger a HTTP POST
test_url = url_for('test_endpoint', _external=True) test_url = url_for('test_endpoint', _external=True)
res = client.post( res = client.post(
@@ -315,7 +313,9 @@ def test_notification_custom_endpoint_and_jinja2(client, live_server):
set_modified_response() set_modified_response()
client.get(url_for("form_watch_checknow"), follow_redirects=True) client.get(url_for("form_watch_checknow"), follow_redirects=True)
time.sleep(2) wait_for_all_checks(client)
time.sleep(2) # plus extra delay for notifications to fire
with open("test-datastore/notification.txt", 'r') as f: with open("test-datastore/notification.txt", 'r') as f:
x = f.read() x = f.read()
@@ -328,6 +328,13 @@ def test_notification_custom_endpoint_and_jinja2(client, live_server):
with open("test-datastore/notification-url.txt", 'r') as f: with open("test-datastore/notification-url.txt", 'r') as f:
notification_url = f.read() notification_url = f.read()
assert 'xxx=http' in notification_url assert 'xxx=http' in notification_url
# apprise style headers should be stripped
assert 'custom-header' not in notification_url
with open("test-datastore/notification-headers.txt", 'r') as f:
notification_headers = f.read()
assert 'custom-header: 123' in notification_headers.lower()
# Should always be automatically detected as JSON content type even when we set it as 'Text' (default) # Should always be automatically detected as JSON content type even when we set it as 'Text' (default)
assert os.path.isfile("test-datastore/notification-content-type.txt") assert os.path.isfile("test-datastore/notification-content-type.txt")
@@ -335,3 +342,8 @@ def test_notification_custom_endpoint_and_jinja2(client, live_server):
assert 'application/json' in f.read() assert 'application/json' in f.read()
os.unlink("test-datastore/notification-url.txt") os.unlink("test-datastore/notification-url.txt")
client.get(
url_for("form_delete", uuid="all"),
follow_redirects=True
)

View File

@@ -205,6 +205,9 @@ def live_server_setup(live_server):
with open("test-datastore/notification-url.txt", "w") as f: with open("test-datastore/notification-url.txt", "w") as f:
f.write(request.url) f.write(request.url)
with open("test-datastore/notification-headers.txt", "w") as f:
f.write(str(request.headers))
if request.content_type: if request.content_type:
with open("test-datastore/notification-content-type.txt", "w") as f: with open("test-datastore/notification-content-type.txt", "w") as f:
f.write(request.content_type) f.write(request.content_type)