diff --git a/changedetectionio/apprise_plugin/__init__.py b/changedetectionio/apprise_plugin/__init__.py
new file mode 100644
index 00000000..93c382fa
--- /dev/null
+++ b/changedetectionio/apprise_plugin/__init__.py
@@ -0,0 +1,78 @@
+# include the decorator
+from apprise.decorators import notify
+
+@notify(on="delete")
+@notify(on="deletes")
+@notify(on="get")
+@notify(on="gets")
+@notify(on="post")
+@notify(on="posts")
+@notify(on="put")
+@notify(on="puts")
+def apprise_custom_api_call_wrapper(body, title, notify_type, *args, **kwargs):
+ import requests
+ import json
+ from apprise.utils import parse_url as apprise_parse_url
+ from apprise import URLBase
+
+ url = kwargs['meta'].get('url')
+
+ if url.startswith('post'):
+ r = requests.post
+ elif url.startswith('get'):
+ r = requests.get
+ elif url.startswith('put'):
+ r = requests.put
+ elif url.startswith('delete'):
+ r = requests.delete
+
+ url = url.replace('post://', 'http://')
+ url = url.replace('posts://', 'https://')
+ url = url.replace('put://', 'http://')
+ url = url.replace('puts://', 'https://')
+ url = url.replace('get://', 'http://')
+ url = url.replace('gets://', 'https://')
+ url = url.replace('put://', 'http://')
+ url = url.replace('puts://', 'https://')
+ url = url.replace('delete://', 'http://')
+ url = url.replace('deletes://', 'https://')
+
+ headers = {}
+ params = {}
+ auth = None
+
+ # Convert /foobar?+some-header=hello to proper header dictionary
+ results = apprise_parse_url(url)
+ if results:
+ # Add our headers that the user can potentially over-ride if they wish
+ # to to our returned result set and tidy entries by unquoting them
+ headers = {URLBase.unquote(x): URLBase.unquote(y)
+ for x, y in results['qsd+'].items()}
+
+ # https://github.com/caronc/apprise/wiki/Notify_Custom_JSON#get-parameter-manipulation
+ # In Apprise, it relies on prefixing each request arg with "-", because it uses say &method=update as a flag for apprise
+ # 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
+ auth = ''
+ if results.get('user') and results.get('password'):
+ auth = (URLBase.unquote(results.get('user')), URLBase.unquote(results.get('user')))
+ elif results.get('user'):
+ auth = (URLBase.unquote(results.get('user')))
+
+ # Try to auto-guess if it's JSON
+ try:
+ json.loads(body)
+ headers['Content-Type'] = 'application/json; charset=utf-8'
+ except ValueError as e:
+ pass
+
+ r(results.get('url'),
+ auth=auth,
+ data=body.encode('utf-8') if type(body) is str else body,
+ headers=headers,
+ params=params
+ )
\ No newline at end of file
diff --git a/changedetectionio/blueprint/browser_steps/browser_steps.py b/changedetectionio/blueprint/browser_steps/browser_steps.py
index 6f38be2e..b9765bac 100644
--- a/changedetectionio/blueprint/browser_steps/browser_steps.py
+++ b/changedetectionio/blueprint/browser_steps/browser_steps.py
@@ -25,6 +25,7 @@ browser_step_ui_config = {'Choose one': '0 0',
'Click element if exists': '1 0',
'Click element': '1 0',
'Click element containing text': '0 1',
+ 'Click element containing text if exists': '0 1',
'Enter text in field': '1 1',
'Execute JS': '0 1',
# 'Extract text and use as filter': '1 0',
@@ -96,12 +97,24 @@ class steppable_browser_interface():
return self.action_goto_url(value=self.start_url)
def action_click_element_containing_text(self, selector=None, value=''):
+ logger.debug("Clicking element containing text")
if not len(value.strip()):
return
elem = self.page.get_by_text(value)
if elem.count():
elem.first.click(delay=randint(200, 500), timeout=3000)
+ def action_click_element_containing_text_if_exists(self, selector=None, value=''):
+ logger.debug("Clicking element containing text if exists")
+ if not len(value.strip()):
+ return
+ elem = self.page.get_by_text(value)
+ logger.debug(f"Clicking element containing text - {elem.count()} elements found")
+ if elem.count():
+ elem.first.click(delay=randint(200, 500), timeout=3000)
+ else:
+ return
+
def action_enter_text_in_field(self, selector, value):
if not len(selector.strip()):
return
diff --git a/changedetectionio/blueprint/tags/templates/edit-tag.html b/changedetectionio/blueprint/tags/templates/edit-tag.html
index 2ccc68a0..a713cf6a 100644
--- a/changedetectionio/blueprint/tags/templates/edit-tag.html
+++ b/changedetectionio/blueprint/tags/templates/edit-tag.html
@@ -58,9 +58,9 @@ xpath://body/div/span[contains(@class, 'example-class')]",
{% if '/text()' in field %}
{% endif %}
-
*/ + overflow-wrap: break-word; /* Allows long words to break and wrap to the next line */ + } +} + +#activate-text-preview { + right: 0; + position: absolute; + z-index: 0; + box-shadow: 1px 1px 4px var(--color-shadow-jump); +} diff --git a/changedetectionio/static/styles/scss/styles.scss b/changedetectionio/static/styles/scss/styles.scss index b720b6d4..0c3fd6cd 100644 --- a/changedetectionio/static/styles/scss/styles.scss +++ b/changedetectionio/static/styles/scss/styles.scss @@ -12,6 +12,7 @@ @import "parts/_darkmode"; @import "parts/_menu"; @import "parts/_love"; +@import "parts/preview_text_filter"; body { color: var(--color-text); diff --git a/changedetectionio/static/styles/styles.css b/changedetectionio/static/styles/styles.css index 4f3fec10..7bc04082 100644 --- a/changedetectionio/static/styles/styles.css +++ b/changedetectionio/static/styles/styles.css @@ -46,14 +46,31 @@ #browser_steps li > label { display: none; } -#browser-steps-fieldlist { - height: 100%; - overflow-y: scroll; } - #browser-steps .flex-wrapper { display: flex; flex-flow: row; - height: 70vh; } + height: 70vh; + font-size: 80%; } + #browser-steps .flex-wrapper #browser-steps-ui { + flex-grow: 1; + /* Allow it to grow and fill the available space */ + flex-shrink: 1; + /* Allow it to shrink if needed */ + flex-basis: 0; + /* Start with 0 base width so it stretches as much as possible */ + background-color: #eee; + border-radius: 5px; } + #browser-steps .flex-wrapper #browser-steps-fieldlist { + flex-grow: 0; + /* Don't allow it to grow */ + flex-shrink: 0; + /* Don't allow it to shrink */ + flex-basis: auto; + /* Base width is determined by the content */ + max-width: 400px; + /* Set a max width to prevent overflow */ + padding-left: 1rem; + overflow-y: scroll; } /* this is duplicate :( */ #browsersteps-selector-wrapper { @@ -411,6 +428,47 @@ html[data-darkmode="true"] #toggle-light-mode .icon-dark { fill: #ff0000 !important; transition: all ease 0.3s !important; } +body.preview-text-enabled { + /* layout of the page */ + /* actual preview area */ } + body.preview-text-enabled #filters-and-triggers > div { + display: flex; + /* Establishes Flexbox layout */ + gap: 20px; + /* Adds space between the columns */ + position: relative; + /* Ensures the sticky positioning is relative to this parent */ } + body.preview-text-enabled #edit-text-filter, body.preview-text-enabled #text-preview { + flex: 1; + /* Each column takes an equal amount of available space */ + align-self: flex-start; + /* Aligns the right column to the start, allowing it to maintain its content height */ } + body.preview-text-enabled #edit-text-filter #pro-tips { + display: none; } + body.preview-text-enabled #text-preview { + position: sticky; + top: 25px; + display: block !important; } + body.preview-text-enabled #text-preview-inner { + background: var(--color-grey-900); + border: 1px solid var(--color-grey-600); + padding: 1rem; + color: #333; + font-family: "Courier New", Courier, monospace; + /* Sets the font to a monospace type */ + font-size: 12px; + overflow-x: scroll; + white-space: pre-wrap; + /* Preserves whitespace and line breaks like*/ + overflow-wrap: break-word; + /* Allows long words to break and wrap to the next line */ } + +#activate-text-preview { + right: 0; + position: absolute; + z-index: 0; + box-shadow: 1px 1px 4px var(--color-shadow-jump); } + body { color: var(--color-text); background: var(--color-background-page); @@ -1194,11 +1252,9 @@ ul { color: #fff; opacity: 0.7; } - .restock-label svg { vertical-align: middle; } - #chrome-extension-link { padding: 9px; border: 1px solid var(--color-grey-800); diff --git a/changedetectionio/store.py b/changedetectionio/store.py index e5999b9a..42691a9b 100644 --- a/changedetectionio/store.py +++ b/changedetectionio/store.py @@ -13,7 +13,6 @@ from threading import Lock import json import os import re -import requests import secrets import threading import time @@ -281,6 +280,7 @@ class ChangeDetectionStore: self.needs_write_urgent = True def add_watch(self, url, tag='', extras=None, tag_uuids=None, write_to_disk_now=True): + import requests if extras is None: extras = {} diff --git a/changedetectionio/templates/_common_fields.html b/changedetectionio/templates/_common_fields.html index 14fa9147..9447f903 100644 --- a/changedetectionio/templates/_common_fields.html +++ b/changedetectionio/templates/_common_fields.html @@ -11,8 +11,11 @@ class="notification-urls" ) }}-Show token/placeholders+