mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2025-11-13 13:06:10 +00:00
Compare commits
14 Commits
ui-long-li
...
screenshot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
041b8800f6 | ||
|
|
9997ff9dae | ||
|
|
98d7d75a36 | ||
|
|
b1df222bc2 | ||
|
|
c2f724f1b1 | ||
|
|
21af71ad61 | ||
|
|
13676cd5a6 | ||
|
|
36c661c534 | ||
|
|
e58011068e | ||
|
|
8b26f13c5a | ||
|
|
568d00b818 | ||
|
|
752ffe24a9 | ||
|
|
2f9e03b900 | ||
|
|
addd9a38e5 |
10
Dockerfile
10
Dockerfile
@@ -9,6 +9,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
gcc \
|
gcc \
|
||||||
libc-dev \
|
libc-dev \
|
||||||
libffi-dev \
|
libffi-dev \
|
||||||
|
libjpeg-dev \
|
||||||
libssl-dev \
|
libssl-dev \
|
||||||
libxslt-dev \
|
libxslt-dev \
|
||||||
make \
|
make \
|
||||||
@@ -36,13 +37,14 @@ ARG CRYPTOGRAPHY_DONT_BUILD_RUST=1
|
|||||||
|
|
||||||
# Re #93, #73, excluding rustc (adds another 430Mb~)
|
# Re #93, #73, excluding rustc (adds another 430Mb~)
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
libssl-dev \
|
g++ \
|
||||||
libffi-dev \
|
|
||||||
gcc \
|
gcc \
|
||||||
libc-dev \
|
libc-dev \
|
||||||
|
libffi-dev \
|
||||||
|
libjpeg-dev \
|
||||||
|
libssl-dev \
|
||||||
libxslt-dev \
|
libxslt-dev \
|
||||||
zlib1g-dev \
|
zlib1g-dev
|
||||||
g++
|
|
||||||
|
|
||||||
# https://stackoverflow.com/questions/58701233/docker-logs-erroneously-appears-empty-until-container-stops
|
# https://stackoverflow.com/questions/58701233/docker-logs-erroneously-appears-empty-until-container-stops
|
||||||
ENV PYTHONUNBUFFERED=1
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ _Need an actual Chrome runner with Javascript support? We support fetching via W
|
|||||||
- Override Request Headers, Specify `POST` or `GET` and other methods
|
- Override Request Headers, Specify `POST` or `GET` and other methods
|
||||||
- Use the "Visual Selector" to help target specific elements
|
- Use the "Visual Selector" to help target specific elements
|
||||||
- Configurable [proxy per watch](https://github.com/dgtlmoon/changedetection.io/wiki/Proxy-configuration)
|
- Configurable [proxy per watch](https://github.com/dgtlmoon/changedetection.io/wiki/Proxy-configuration)
|
||||||
|
- Send a screenshot with the notification when a change is detected in the web page
|
||||||
|
|
||||||
We [recommend and use Bright Data](https://brightdata.grsm.io/n0r16zf7eivq) global proxy services, Bright Data will match any first deposit up to $100 using our signup link.
|
We [recommend and use Bright Data](https://brightdata.grsm.io/n0r16zf7eivq) global proxy services, Bright Data will match any first deposit up to $100 using our signup link.
|
||||||
|
|
||||||
|
|||||||
@@ -644,12 +644,18 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
jq_support = False
|
jq_support = False
|
||||||
|
|
||||||
|
watch = datastore.data['watching'].get(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("edit.html",
|
output = render_template("edit.html",
|
||||||
current_base_url=datastore.data['settings']['application']['base_url'],
|
current_base_url=datastore.data['settings']['application']['base_url'],
|
||||||
emailprefix=os.getenv('NOTIFICATION_MAIL_BUTTON_PREFIX', False),
|
emailprefix=os.getenv('NOTIFICATION_MAIL_BUTTON_PREFIX', False),
|
||||||
form=form,
|
form=form,
|
||||||
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,
|
||||||
|
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),
|
||||||
settings_application=datastore.data['settings']['application'],
|
settings_application=datastore.data['settings']['application'],
|
||||||
@@ -657,7 +663,7 @@ def changedetection_app(config=None, datastore_o=None):
|
|||||||
uuid=uuid,
|
uuid=uuid,
|
||||||
visualselector_data_is_ready=visualselector_data_is_ready,
|
visualselector_data_is_ready=visualselector_data_is_ready,
|
||||||
visualselector_enabled=visualselector_enabled,
|
visualselector_enabled=visualselector_enabled,
|
||||||
watch=datastore.data['watching'][uuid],
|
watch=watch
|
||||||
)
|
)
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|||||||
@@ -248,7 +248,28 @@ class model(dict):
|
|||||||
if os.path.isfile(fname):
|
if os.path.isfile(fname):
|
||||||
return fname
|
return fname
|
||||||
|
|
||||||
return False
|
# False is not an option for AppRise, must be type None
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_screenshot_as_jpeg(self):
|
||||||
|
"""Best used in notifications due to its smaller size"""
|
||||||
|
png_fname = os.path.join(self.watch_data_dir, "last-screenshot.png")
|
||||||
|
jpg_fname = os.path.join(self.watch_data_dir, "last-screenshot.jpg")
|
||||||
|
|
||||||
|
if os.path.isfile(jpg_fname):
|
||||||
|
return jpg_fname
|
||||||
|
|
||||||
|
if os.path.isfile(png_fname) and not os.path.isfile(jpg_fname):
|
||||||
|
# Doesnt exist, so create the JPEG from the PNG
|
||||||
|
from PIL import Image
|
||||||
|
im1 = Image.open(png_fname)
|
||||||
|
im1.convert('RGB').save(jpg_fname, quality=int(os.getenv("NOTIFICATION_SCREENSHOT_JPG_QUALITY", 75)))
|
||||||
|
return jpg_fname
|
||||||
|
|
||||||
|
|
||||||
|
# False is not an option for AppRise, must be type None
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def __get_file_ctime(self, filename):
|
def __get_file_ctime(self, filename):
|
||||||
fname = os.path.join(self.watch_data_dir, filename)
|
fname = os.path.join(self.watch_data_dir, filename)
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ def process_notification(n_object, datastore):
|
|||||||
body=n_body,
|
body=n_body,
|
||||||
body_format=n_format,
|
body_format=n_format,
|
||||||
# False is not an option for AppRise, must be type None
|
# False is not an option for AppRise, must be type None
|
||||||
attach=None if not n_object.get('screenshot') else n_object.get('screenshot')
|
attach=n_object.get('screenshot', None)
|
||||||
)
|
)
|
||||||
|
|
||||||
apobj.clear()
|
apobj.clear()
|
||||||
|
|||||||
@@ -141,9 +141,14 @@ User-Agent: wonderbra 1.0") }}
|
|||||||
<div class="pure-control-group inline-radio">
|
<div class="pure-control-group inline-radio">
|
||||||
{{ render_checkbox_field(form.notification_muted) }}
|
{{ render_checkbox_field(form.notification_muted) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-control-group inline-radio">
|
{% if is_html_webdriver %}
|
||||||
|
<span class="pure-control-group inline-radio">
|
||||||
{{ render_checkbox_field(form.notification_screenshot) }}
|
{{ render_checkbox_field(form.notification_screenshot) }}
|
||||||
|
<span class="pure-form-message-inline">
|
||||||
|
<strong>Use with caution!</strong> This will easily fill up your email storage quota or flood other storages.
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="field-group" id="notification-field-group">
|
<div class="field-group" id="notification-field-group">
|
||||||
{% if has_default_notification_urls %}
|
{% if has_default_notification_urls %}
|
||||||
<div class="inline-warning">
|
<div class="inline-warning">
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
@@ -20,7 +21,6 @@ def test_setup(live_server):
|
|||||||
# Hard to just add more live server URLs when one test is already running (I think)
|
# Hard to just add more live server URLs when one test is already running (I think)
|
||||||
# So we add our test here (was in a different file)
|
# So we add our test here (was in a different file)
|
||||||
def test_check_notification(client, live_server):
|
def test_check_notification(client, live_server):
|
||||||
|
|
||||||
set_original_response()
|
set_original_response()
|
||||||
|
|
||||||
# Give the endpoint time to spin up
|
# Give the endpoint time to spin up
|
||||||
@@ -70,13 +70,14 @@ def test_check_notification(client, live_server):
|
|||||||
# Give the thread time to pick up the first version
|
# Give the thread time to pick up the first version
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
|
|
||||||
testimage = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='
|
# We write the PNG to disk, but a JPEG should appear in the notification
|
||||||
|
testimage_png = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='
|
||||||
# Write the last screenshot png
|
# Write the last screenshot png
|
||||||
|
|
||||||
uuid = extract_UUID_from_client(client)
|
uuid = extract_UUID_from_client(client)
|
||||||
datastore = 'test-datastore'
|
datastore = 'test-datastore'
|
||||||
with open(os.path.join(datastore, str(uuid), 'last-screenshot.png'), 'wb') as f:
|
with open(os.path.join(datastore, str(uuid), 'last-screenshot.png'), 'wb') as f:
|
||||||
f.write(base64.b64decode(testimage))
|
f.write(base64.b64decode(testimage_png))
|
||||||
|
|
||||||
# Goto the edit page, add our ignore text
|
# Goto the edit page, add our ignore text
|
||||||
# Add our URL to the import page
|
# Add our URL to the import page
|
||||||
@@ -153,7 +154,19 @@ def test_check_notification(client, live_server):
|
|||||||
assert "preview/" in notification_submission
|
assert "preview/" in notification_submission
|
||||||
assert ":-)" in notification_submission
|
assert ":-)" in notification_submission
|
||||||
assert "New ChangeDetection.io Notification - {}".format(test_url) in notification_submission
|
assert "New ChangeDetection.io Notification - {}".format(test_url) in notification_submission
|
||||||
assert testimage in notification_submission
|
|
||||||
|
# Check the attachment was added, and that it is a JPEG from the original PNG
|
||||||
|
notification_submission_object = json.loads(notification_submission)
|
||||||
|
assert notification_submission_object['attachments'][0]['filename'] == 'last-screenshot.jpg'
|
||||||
|
assert len(notification_submission_object['attachments'][0]['base64'])
|
||||||
|
assert notification_submission_object['attachments'][0]['mimetype'] == 'image/jpeg'
|
||||||
|
jpeg_in_attachment = base64.b64decode(notification_submission_object['attachments'][0]['base64'])
|
||||||
|
assert b'JFIF' in jpeg_in_attachment
|
||||||
|
assert testimage_png not in notification_submission
|
||||||
|
# Assert that the JPEG is readable (didn't get chewed up somewhere)
|
||||||
|
from PIL import Image
|
||||||
|
import io
|
||||||
|
assert Image.open(io.BytesIO(jpeg_in_attachment))
|
||||||
|
|
||||||
if env_base_url:
|
if env_base_url:
|
||||||
# Re #65 - did we see our BASE_URl ?
|
# Re #65 - did we see our BASE_URl ?
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class update_worker(threading.Thread):
|
|||||||
n_object.update({
|
n_object.update({
|
||||||
'watch_url': watch['url'],
|
'watch_url': watch['url'],
|
||||||
'uuid': watch_uuid,
|
'uuid': watch_uuid,
|
||||||
'screenshot': watch.get_screenshot() if watch.get('notification_screenshot') else False,
|
'screenshot': watch.get_screenshot_as_jpeg() if watch.get('notification_screenshot') else None,
|
||||||
'current_snapshot': snapshot_contents.decode('utf-8'),
|
'current_snapshot': snapshot_contents.decode('utf-8'),
|
||||||
'diff': diff.render_diff(watch_history[dates[-2]], watch_history[dates[-1]], line_feed_sep=line_feed_sep),
|
'diff': diff.render_diff(watch_history[dates[-2]], watch_history[dates[-1]], line_feed_sep=line_feed_sep),
|
||||||
'diff_full': diff.render_diff(watch_history[dates[-2]], watch_history[dates[-1]], True, line_feed_sep=line_feed_sep)
|
'diff_full': diff.render_diff(watch_history[dates[-2]], watch_history[dates[-1]], True, line_feed_sep=line_feed_sep)
|
||||||
@@ -108,7 +108,7 @@ class update_worker(threading.Thread):
|
|||||||
n_object.update({
|
n_object.update({
|
||||||
'watch_url': watch['url'],
|
'watch_url': watch['url'],
|
||||||
'uuid': watch_uuid,
|
'uuid': watch_uuid,
|
||||||
'screenshot': False
|
'screenshot': None
|
||||||
})
|
})
|
||||||
self.notification_q.put(n_object)
|
self.notification_q.put(n_object)
|
||||||
print("Sent filter not found notification for {}".format(watch_uuid))
|
print("Sent filter not found notification for {}".format(watch_uuid))
|
||||||
|
|||||||
@@ -54,5 +54,7 @@ jinja2-time
|
|||||||
# https://github.com/dgtlmoon/changedetection.io/pull/1009
|
# https://github.com/dgtlmoon/changedetection.io/pull/1009
|
||||||
jq~=1.3 ;python_version >= "3.8" and sys_platform == "linux"
|
jq~=1.3 ;python_version >= "3.8" and sys_platform == "linux"
|
||||||
|
|
||||||
|
# Any current modern version, required so far for screenshot PNG->JPEG conversion but will be used more in the future
|
||||||
|
pillow
|
||||||
# playwright is installed at Dockerfile build time because it's not available on all platforms
|
# playwright is installed at Dockerfile build time because it's not available on all platforms
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user