mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2025-11-14 21:46:14 +00:00
Compare commits
14 Commits
drop-arm-v
...
post-reque
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3f6b92670 | ||
|
|
1813977133 | ||
|
|
d7e85ffe8f | ||
|
|
d23a301826 | ||
|
|
3ce6096fdb | ||
|
|
8acdcdd861 | ||
|
|
755cba33de | ||
|
|
8aae7dfae0 | ||
|
|
ed00f67a80 | ||
|
|
44e7e142f8 | ||
|
|
fe704e05a3 | ||
|
|
e756e0af5e | ||
|
|
c0b6c8581e | ||
|
|
de558f208f |
4
.github/workflows/containers.yml
vendored
4
.github/workflows/containers.yml
vendored
@@ -95,7 +95,7 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
${{ secrets.DOCKER_HUB_USERNAME }}/changedetection.io:dev,ghcr.io/${{ github.repository }}:dev
|
${{ secrets.DOCKER_HUB_USERNAME }}/changedetection.io:dev,ghcr.io/${{ github.repository }}:dev
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v6,linux/arm/v7,linux/arm/v8
|
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v8,linux/arm64/v8
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ jobs:
|
|||||||
ghcr.io/dgtlmoon/changedetection.io:${{ github.event.release.tag_name }}
|
ghcr.io/dgtlmoon/changedetection.io:${{ github.event.release.tag_name }}
|
||||||
${{ secrets.DOCKER_HUB_USERNAME }}/changedetection.io:latest
|
${{ secrets.DOCKER_HUB_USERNAME }}/changedetection.io:latest
|
||||||
ghcr.io/dgtlmoon/changedetection.io:latest
|
ghcr.io/dgtlmoon/changedetection.io:latest
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v6,linux/arm/v7,linux/arm/v8
|
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v8,linux/arm64/v8
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
# Looks like this was disabled
|
# Looks like this was disabled
|
||||||
|
|||||||
2
.github/workflows/test-container-build.yml
vendored
2
.github/workflows/test-container-build.yml
vendored
@@ -64,7 +64,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: ./
|
context: ./
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v6,linux/arm/v7,linux/arm/v8
|
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v8,linux/arm64/v8
|
||||||
cache-from: type=local,src=/tmp/.buildx-cache
|
cache-from: type=local,src=/tmp/.buildx-cache
|
||||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ Requires Playwright to be enabled.
|
|||||||
|
|
||||||
### Awesome restock and price change notifications
|
### Awesome restock and price change notifications
|
||||||
|
|
||||||
Enable the _"Re-stock & Price detection for single product pages"_ option to activate the best way to monitor product pricing.
|
Enable the _"Re-stock & Price detection for single product pages"_ option to activate the best way to monitor product pricing, this will extract any meta-data in the HTML page and give you many options to follow the pricing of the product.
|
||||||
|
|
||||||
Easily organise and monitor prices for products from the dashboard, get alerts and notifications when the price of a product changes or comes back in stock again!
|
Easily organise and monitor prices for products from the dashboard, get alerts and notifications when the price of a product changes or comes back in stock again!
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Only exists for direct CLI usage
|
# Only exists for direct CLI usage
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Read more https://github.com/dgtlmoon/changedetection.io/wiki
|
# Read more https://github.com/dgtlmoon/changedetection.io/wiki
|
||||||
|
|
||||||
__version__ = '0.45.26'
|
__version__ = '0.46.01'
|
||||||
|
|
||||||
from changedetectionio.strtobool import strtobool
|
from changedetectionio.strtobool import strtobool
|
||||||
from json.decoder import JSONDecodeError
|
from json.decoder import JSONDecodeError
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import os
|
|||||||
import chardet
|
import chardet
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
from changedetectionio import strtobool
|
||||||
from changedetectionio.content_fetchers.exceptions import BrowserStepsInUnsupportedFetcher, EmptyReply, Non200ErrorCodeReceived
|
from changedetectionio.content_fetchers.exceptions import BrowserStepsInUnsupportedFetcher, EmptyReply, Non200ErrorCodeReceived
|
||||||
from changedetectionio.content_fetchers.base import Fetcher
|
from changedetectionio.content_fetchers.base import Fetcher
|
||||||
|
|
||||||
@@ -45,13 +46,19 @@ class fetcher(Fetcher):
|
|||||||
if self.system_https_proxy:
|
if self.system_https_proxy:
|
||||||
proxies['https'] = self.system_https_proxy
|
proxies['https'] = self.system_https_proxy
|
||||||
|
|
||||||
r = requests.request(method=request_method,
|
session = requests.Session()
|
||||||
data=request_body,
|
|
||||||
url=url,
|
if strtobool(os.getenv('ALLOW_FILE_URI', 'false')) and url.startswith('file://'):
|
||||||
headers=request_headers,
|
from requests_file import FileAdapter
|
||||||
timeout=timeout,
|
session.mount('file://', FileAdapter())
|
||||||
proxies=proxies,
|
|
||||||
verify=False)
|
r = session.request(method=request_method,
|
||||||
|
data=request_body.encode('utf-8') if type(request_body) is str else request_body,
|
||||||
|
url=url,
|
||||||
|
headers=request_headers,
|
||||||
|
timeout=timeout,
|
||||||
|
proxies=proxies,
|
||||||
|
verify=False)
|
||||||
|
|
||||||
# If the response did not tell us what encoding format to expect, Then use chardet to override what `requests` thinks.
|
# If the response did not tell us what encoding format to expect, Then use chardet to override what `requests` thinks.
|
||||||
# For example - some sites don't tell us it's utf-8, but return utf-8 content
|
# For example - some sites don't tell us it's utf-8, but return utf-8 content
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ if (include_filters.length) {
|
|||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (results.length) {
|
if (results != null && results.length) {
|
||||||
|
|
||||||
// Iterate over the results
|
// Iterate over the results
|
||||||
results.forEach(node => {
|
results.forEach(node => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import flask_login
|
import flask_login
|
||||||
@@ -532,12 +532,21 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
@login_optionally_required
|
@login_optionally_required
|
||||||
def ajax_callback_send_notification_test(watch_uuid=None):
|
def ajax_callback_send_notification_test(watch_uuid=None):
|
||||||
|
|
||||||
# Watch_uuid could be unsuet in the case its used in tag editor, global setings
|
# Watch_uuid could be unset in the case its used in tag editor, global setings
|
||||||
import apprise
|
import apprise
|
||||||
|
import random
|
||||||
from .apprise_asset import asset
|
from .apprise_asset import asset
|
||||||
apobj = apprise.Apprise(asset=asset)
|
apobj = apprise.Apprise(asset=asset)
|
||||||
|
|
||||||
watch = datastore.data['watching'].get(watch_uuid) if watch_uuid else None
|
is_global_settings_form = request.args.get('mode', '') == 'global-settings'
|
||||||
|
is_group_settings_form = request.args.get('mode', '') == 'group-settings'
|
||||||
|
|
||||||
|
# Use an existing random one on the global/main settings form
|
||||||
|
if not watch_uuid and (is_global_settings_form or is_group_settings_form):
|
||||||
|
logger.debug(f"Send test notification - Choosing random Watch {watch_uuid}")
|
||||||
|
watch_uuid = random.choice(list(datastore.data['watching'].keys()))
|
||||||
|
|
||||||
|
watch = datastore.data['watching'].get(watch_uuid)
|
||||||
|
|
||||||
notification_urls = request.form['notification_urls'].strip().splitlines()
|
notification_urls = request.form['notification_urls'].strip().splitlines()
|
||||||
|
|
||||||
@@ -549,8 +558,6 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
tag = datastore.tag_exists_by_name(k.strip())
|
tag = datastore.tag_exists_by_name(k.strip())
|
||||||
notification_urls = tag.get('notifications_urls') if tag and tag.get('notifications_urls') else None
|
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:
|
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
|
# In the global settings, use only what is typed currently in the text box
|
||||||
logger.debug("Test notification - Trying by global system settings notifications")
|
logger.debug("Test notification - Trying by global system settings notifications")
|
||||||
@@ -569,7 +576,7 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
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.get('window_url', "https://changedetection.io"),
|
||||||
'notification_urls': notification_urls
|
'notification_urls': notification_urls
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1362,6 +1369,30 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
|
@app.route("/edit/<string:uuid>/get-html", methods=['GET'])
|
||||||
|
@login_optionally_required
|
||||||
|
def watch_get_latest_html(uuid):
|
||||||
|
from io import BytesIO
|
||||||
|
from flask import send_file
|
||||||
|
import brotli
|
||||||
|
|
||||||
|
watch = datastore.data['watching'].get(uuid)
|
||||||
|
if watch and os.path.isdir(watch.watch_data_dir):
|
||||||
|
latest_filename = list(watch.history.keys())[0]
|
||||||
|
html_fname = os.path.join(watch.watch_data_dir, f"{latest_filename}.html.br")
|
||||||
|
if html_fname.endswith('.br'):
|
||||||
|
# Read and decompress the Brotli file
|
||||||
|
with open(html_fname, 'rb') as f:
|
||||||
|
decompressed_data = brotli.decompress(f.read())
|
||||||
|
|
||||||
|
buffer = BytesIO(decompressed_data)
|
||||||
|
|
||||||
|
return send_file(buffer, as_attachment=True, download_name=f"{latest_filename}.html", mimetype='text/html')
|
||||||
|
|
||||||
|
|
||||||
|
# Return a 500 error
|
||||||
|
abort(500)
|
||||||
|
|
||||||
@app.route("/form/add/quickwatch", methods=['POST'])
|
@app.route("/form/add/quickwatch", methods=['POST'])
|
||||||
@login_optionally_required
|
@login_optionally_required
|
||||||
def form_quick_watch_add():
|
def form_quick_watch_add():
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ def apprise_custom_api_call_wrapper(body, title, notify_type, *args, **kwargs):
|
|||||||
|
|
||||||
r(results.get('url'),
|
r(results.get('url'),
|
||||||
auth=auth,
|
auth=auth,
|
||||||
data=body,
|
data=body.encode('utf-8') if type(body) is str else body,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
params=params
|
params=params
|
||||||
)
|
)
|
||||||
@@ -157,7 +157,7 @@ def process_notification(n_object, datastore):
|
|||||||
logger.warning(f"Process Notification: skipping empty notification URL.")
|
logger.warning(f"Process Notification: skipping empty notification URL.")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
logger.info(">> Process Notification: AppRise notifying {}".format(url))
|
logger.info(f">> Process Notification: AppRise notifying {url}")
|
||||||
url = jinja_render(template_str=url, **notification_parameters)
|
url = jinja_render(template_str=url, **notification_parameters)
|
||||||
|
|
||||||
# Re 323 - Limit discord length to their 2000 char limit total or it wont send.
|
# Re 323 - Limit discord length to their 2000 char limit total or it wont send.
|
||||||
@@ -230,6 +230,7 @@ def process_notification(n_object, datastore):
|
|||||||
log_value = logs.getvalue()
|
log_value = logs.getvalue()
|
||||||
|
|
||||||
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:
|
||||||
|
logger.critical(log_value)
|
||||||
raise Exception(log_value)
|
raise Exception(log_value)
|
||||||
|
|
||||||
# Return what was sent for better logging - after the for loop
|
# Return what was sent for better logging - after the for loop
|
||||||
|
|||||||
@@ -45,13 +45,10 @@ class Restock(dict):
|
|||||||
|
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
# Custom logic to handle setting price and original_price
|
# Custom logic to handle setting price and original_price
|
||||||
if key == 'price':
|
if key == 'price' or key == 'original_price':
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
value = self.parse_currency(raw_value=value)
|
value = self.parse_currency(raw_value=value)
|
||||||
|
|
||||||
if value and not self.get('original_price'):
|
|
||||||
self['original_price'] = value
|
|
||||||
|
|
||||||
super().__setitem__(key, value)
|
super().__setitem__(key, value)
|
||||||
|
|
||||||
class Watch(BaseWatch):
|
class Watch(BaseWatch):
|
||||||
|
|||||||
@@ -177,6 +177,11 @@ class perform_site_check(difference_detection_processor):
|
|||||||
# Main detection method
|
# Main detection method
|
||||||
fetched_md5 = None
|
fetched_md5 = None
|
||||||
|
|
||||||
|
# store original price if not set
|
||||||
|
if itemprop_availability and itemprop_availability.get('price') and not itemprop_availability.get('original_price'):
|
||||||
|
itemprop_availability['original_price'] = itemprop_availability.get('price')
|
||||||
|
update_obj['restock']["original_price"] = itemprop_availability.get('price')
|
||||||
|
|
||||||
if not self.fetcher.instock_data and not itemprop_availability.get('availability'):
|
if not self.fetcher.instock_data and not itemprop_availability.get('availability'):
|
||||||
raise ProcessorException(
|
raise ProcessorException(
|
||||||
message=f"Unable to extract restock data for this page unfortunately. (Got code {self.fetcher.get_last_status_code()} from server), no embedded stock information was found and nothing interesting in the text, try using this watch with Chrome.",
|
message=f"Unable to extract restock data for this page unfortunately. (Got code {self.fetcher.get_last_status_code()} from server), no embedded stock information was found and nothing interesting in the text, try using this watch with Chrome.",
|
||||||
@@ -195,7 +200,7 @@ class perform_site_check(difference_detection_processor):
|
|||||||
|
|
||||||
# What we store in the snapshot
|
# What we store in the snapshot
|
||||||
price = update_obj.get('restock').get('price') if update_obj.get('restock').get('price') else ""
|
price = update_obj.get('restock').get('price') if update_obj.get('restock').get('price') else ""
|
||||||
snapshot_content = f"{update_obj.get('restock').get('in_stock')} - {price}"
|
snapshot_content = f"In Stock: {update_obj.get('restock').get('in_stock')} - Price: {price}"
|
||||||
|
|
||||||
# Main detection method
|
# Main detection method
|
||||||
fetched_md5 = hashlib.md5(snapshot_content.encode('utf-8')).hexdigest()
|
fetched_md5 = hashlib.md5(snapshot_content.encode('utf-8')).hexdigest()
|
||||||
|
|||||||
@@ -35,4 +35,8 @@ pytest tests/test_access_control.py
|
|||||||
pytest tests/test_notification.py
|
pytest tests/test_notification.py
|
||||||
pytest tests/test_backend.py
|
pytest tests/test_backend.py
|
||||||
pytest tests/test_rss.py
|
pytest tests/test_rss.py
|
||||||
pytest tests/test_unique_lines.py
|
pytest tests/test_unique_lines.py
|
||||||
|
|
||||||
|
# Check file:// will pickup a file when enabled
|
||||||
|
echo "Hello world" > /tmp/test-file.txt
|
||||||
|
ALLOW_FILE_URI=yes pytest tests/test_security.py
|
||||||
|
|||||||
@@ -479,6 +479,12 @@ Unavailable") }}
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
{% if watch.history_n %}
|
||||||
|
<p>
|
||||||
|
<a href="{{url_for('watch_get_latest_html', uuid=uuid)}}" class="pure-button button-small">Download latest HTML snapshot</a>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="actions">
|
<div id="actions">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
import resource
|
import resource
|
||||||
import time
|
import time
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# !/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from .. import conftest
|
from .. import conftest
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from .. import conftest
|
from .. import conftest
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from .. import conftest
|
from .. import conftest
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
import asyncio
|
import asyncio
|
||||||
from aiosmtpd.controller import Controller
|
from aiosmtpd.controller import Controller
|
||||||
from aiosmtpd.smtp import SMTP
|
from aiosmtpd.smtp import SMTP
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
import os.path
|
import os.path
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
@@ -150,6 +150,11 @@ def test_check_basic_change_detection_functionality(client, live_server, measure
|
|||||||
res = client.get(url_for("index"))
|
res = client.get(url_for("index"))
|
||||||
assert b'preview/' in res.data
|
assert b'preview/' in res.data
|
||||||
|
|
||||||
|
|
||||||
|
# Check the 'get latest snapshot works'
|
||||||
|
res = client.get(url_for("watch_get_latest_html", uuid=uuid))
|
||||||
|
assert b'<head><title>head title</title></head>' in res.data
|
||||||
|
|
||||||
#
|
#
|
||||||
# Cleanup everything
|
# Cleanup everything
|
||||||
res = client.get(url_for("form_delete", uuid="all"), follow_redirects=True)
|
res = client.get(url_for("form_delete", uuid="all"), follow_redirects=True)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from .util import set_original_response, live_server_setup, wait_for_all_checks
|
from .util import set_original_response, live_server_setup, wait_for_all_checks
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# https://www.reddit.com/r/selfhosted/comments/wa89kp/comment/ii3a4g7/?context=3
|
# https://www.reddit.com/r/selfhosted/comments/wa89kp/comment/ii3a4g7/?context=3
|
||||||
import os
|
import os
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
"""Test suite for the method to extract text from an html string"""
|
"""Test suite for the method to extract text from an html string"""
|
||||||
from ..html_tools import html_to_text
|
from ..html_tools import html_to_text
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from . util import live_server_setup
|
from . util import live_server_setup
|
||||||
from changedetectionio import html_tools
|
from changedetectionio import html_tools
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
"""Test suite for the render/not render anchor tag content functionality"""
|
"""Test suite for the render/not render anchor tag content functionality"""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import os
|
|||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from .util import set_original_response, set_modified_response, set_more_modified_response, live_server_setup, wait_for_all_checks, \
|
from .util import set_original_response, set_modified_response, set_more_modified_response, live_server_setup, wait_for_all_checks, \
|
||||||
set_longer_modified_response
|
set_longer_modified_response
|
||||||
from . util import extract_UUID_from_client
|
from . util import extract_UUID_from_client
|
||||||
@@ -347,3 +349,81 @@ def test_notification_custom_endpoint_and_jinja2(client, live_server, measure_me
|
|||||||
url_for("form_delete", uuid="all"),
|
url_for("form_delete", uuid="all"),
|
||||||
follow_redirects=True
|
follow_redirects=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
#2510
|
||||||
|
def test_global_send_test_notification(client, live_server, measure_memory_usage):
|
||||||
|
|
||||||
|
|
||||||
|
#live_server_setup(live_server)
|
||||||
|
set_original_response()
|
||||||
|
|
||||||
|
# otherwise other settings would have already existed from previous tests in this file
|
||||||
|
res = client.post(
|
||||||
|
url_for("settings_page"),
|
||||||
|
data={
|
||||||
|
"application-fetch_backend": "html_requests",
|
||||||
|
"application-minutes_between_check": 180,
|
||||||
|
"application-notification_body": 'change detection is cool',
|
||||||
|
"application-notification_format": default_notification_format,
|
||||||
|
"application-notification_urls": "",
|
||||||
|
"application-notification_title": "New ChangeDetection.io Notification - {{ watch_url }}",
|
||||||
|
},
|
||||||
|
follow_redirects=True
|
||||||
|
)
|
||||||
|
assert b'Settings updated' in res.data
|
||||||
|
|
||||||
|
test_url = url_for('test_endpoint', _external=True)
|
||||||
|
res = client.post(
|
||||||
|
url_for("form_quick_watch_add"),
|
||||||
|
data={"url": test_url, "tags": 'nice one'},
|
||||||
|
follow_redirects=True
|
||||||
|
)
|
||||||
|
|
||||||
|
assert b"Watch added" in res.data
|
||||||
|
|
||||||
|
test_notification_url = url_for('test_notification_endpoint', _external=True).replace('http://', 'post://')+"?xxx={{ watch_url }}&+custom-header=123"
|
||||||
|
|
||||||
|
######### Test global/system settings
|
||||||
|
res = client.post(
|
||||||
|
url_for("ajax_callback_send_notification_test")+"?mode=global-settings",
|
||||||
|
data={"notification_urls": test_notification_url},
|
||||||
|
follow_redirects=True
|
||||||
|
)
|
||||||
|
|
||||||
|
assert res.status_code != 400
|
||||||
|
assert res.status_code != 500
|
||||||
|
|
||||||
|
# Give apprise time to fire
|
||||||
|
time.sleep(4)
|
||||||
|
|
||||||
|
with open("test-datastore/notification.txt", 'r') as f:
|
||||||
|
x = f.read()
|
||||||
|
assert 'change detection is coo' in x
|
||||||
|
|
||||||
|
|
||||||
|
os.unlink("test-datastore/notification.txt")
|
||||||
|
|
||||||
|
######### Test group/tag settings
|
||||||
|
res = client.post(
|
||||||
|
url_for("ajax_callback_send_notification_test")+"?mode=group-settings",
|
||||||
|
data={"notification_urls": test_notification_url},
|
||||||
|
follow_redirects=True
|
||||||
|
)
|
||||||
|
|
||||||
|
assert res.status_code != 400
|
||||||
|
assert res.status_code != 500
|
||||||
|
|
||||||
|
# Give apprise time to fire
|
||||||
|
time.sleep(4)
|
||||||
|
|
||||||
|
with open("test-datastore/notification.txt", 'r') as f:
|
||||||
|
x = f.read()
|
||||||
|
# Should come from notification.py default handler when there is no notification body to pull from
|
||||||
|
assert 'change detection is coo' in x
|
||||||
|
|
||||||
|
client.get(
|
||||||
|
url_for("form_delete", uuid="all"),
|
||||||
|
follow_redirects=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
from .util import set_original_response, set_modified_response, live_server_setup, wait_for_all_checks
|
from .util import set_original_response, set_modified_response, live_server_setup, wait_for_all_checks
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from .. import strtobool
|
||||||
|
|
||||||
|
|
||||||
def test_setup(client, live_server, measure_memory_usage):
|
def test_setup(client, live_server, measure_memory_usage):
|
||||||
live_server_setup(live_server)
|
live_server_setup(live_server)
|
||||||
|
|
||||||
@@ -55,17 +60,33 @@ def test_bad_access(client, live_server, measure_memory_usage):
|
|||||||
|
|
||||||
assert b'Watch protocol is not permitted by SAFE_PROTOCOL_REGEX' in res.data
|
assert b'Watch protocol is not permitted by SAFE_PROTOCOL_REGEX' in res.data
|
||||||
|
|
||||||
# file:// is permitted by default, but it will be caught by ALLOW_FILE_URI
|
|
||||||
|
|
||||||
|
def test_file_access(client, live_server, measure_memory_usage):
|
||||||
|
#live_server_setup(live_server)
|
||||||
|
|
||||||
|
test_file_path = "/tmp/test-file.txt"
|
||||||
|
|
||||||
|
# file:// is permitted by default, but it will be caught by ALLOW_FILE_URI
|
||||||
client.post(
|
client.post(
|
||||||
url_for("form_quick_watch_add"),
|
url_for("form_quick_watch_add"),
|
||||||
data={"url": 'file:///tasty/disk/drive', "tags": ''},
|
data={"url": f"file://{test_file_path}", "tags": ''},
|
||||||
follow_redirects=True
|
follow_redirects=True
|
||||||
)
|
)
|
||||||
wait_for_all_checks(client)
|
wait_for_all_checks(client)
|
||||||
res = client.get(url_for("index"))
|
res = client.get(url_for("index"))
|
||||||
|
|
||||||
assert b'file:// type access is denied for security reasons.' in res.data
|
# If it is enabled at test time
|
||||||
|
if strtobool(os.getenv('ALLOW_FILE_URI', 'false')):
|
||||||
|
res = client.get(
|
||||||
|
url_for("preview_page", uuid="first"),
|
||||||
|
follow_redirects=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Should see something (this file added by run_basic_tests.sh)
|
||||||
|
assert b"Hello world" in res.data
|
||||||
|
else:
|
||||||
|
# Default should be here
|
||||||
|
assert b'file:// type access is denied for security reasons.' in res.data
|
||||||
|
|
||||||
def test_xss(client, live_server, measure_memory_usage):
|
def test_xss(client, live_server, measure_memory_usage):
|
||||||
#live_server_setup(live_server)
|
#live_server_setup(live_server)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# run from dir above changedetectionio/ dir
|
# run from dir above changedetectionio/ dir
|
||||||
# python3 -m unittest changedetectionio.tests.unit.test_jinja2_security
|
# python3 -m unittest changedetectionio.tests.unit.test_jinja2_security
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# run from dir above changedetectionio/ dir
|
# run from dir above changedetectionio/ dir
|
||||||
# python3 -m unittest changedetectionio.tests.unit.test_notification_diff
|
# python3 -m unittest changedetectionio.tests.unit.test_notification_diff
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# run from dir above changedetectionio/ dir
|
# run from dir above changedetectionio/ dir
|
||||||
# python3 -m unittest changedetectionio.tests.unit.test_restock_logic
|
# python3 -m unittest changedetectionio.tests.unit.test_restock_logic
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# run from dir above changedetectionio/ dir
|
# run from dir above changedetectionio/ dir
|
||||||
# python3 -m unittest changedetectionio.tests.unit.test_notification_diff
|
# python3 -m unittest changedetectionio.tests.unit.test_notification_diff
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from flask import make_response, request
|
from flask import make_response, request
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from .. import conftest
|
from .. import conftest
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ validators~=0.21
|
|||||||
# >= 2.26 also adds Brotli support if brotli is installed
|
# >= 2.26 also adds Brotli support if brotli is installed
|
||||||
brotli~=1.0
|
brotli~=1.0
|
||||||
requests[socks]
|
requests[socks]
|
||||||
|
requests-file
|
||||||
|
|
||||||
urllib3==1.26.19
|
urllib3==1.26.19
|
||||||
chardet>2.3.0
|
chardet>2.3.0
|
||||||
|
|||||||
Reference in New Issue
Block a user