@ -1,4 +1,5 @@
import apprise
import time
from jinja2 import Environment , BaseLoader
from apprise import NotifyFormat
import json
@ -119,8 +120,8 @@ def process_notification(n_object, datastore):
# Get the notification body from datastore
jinja2_env = Environment ( loader = BaseLoader )
n_body = jinja2_env . from_string ( n_object . get ( ' notification_body ' , default_notification_body ) ) . render ( * * notification_parameters )
n_title = jinja2_env . from_string ( n_object . get ( ' notification_title ' , default_notification_title ) ) . render ( * * notification_parameters )
n_body = jinja2_env . from_string ( n_object . get ( ' notification_body ' , ' ' ) ) . render ( * * notification_parameters )
n_title = jinja2_env . from_string ( n_object . get ( ' notification_title ' , ' ' ) ) . render ( * * notification_parameters )
n_format = valid_notification_formats . get (
n_object . get ( ' notification_format ' , default_notification_format ) ,
valid_notification_formats [ default_notification_format ] ,
@ -131,103 +132,108 @@ def process_notification(n_object, datastore):
# Initially text or whatever
n_format = datastore . data [ ' settings ' ] [ ' application ' ] . get ( ' notification_format ' , valid_notification_formats [ default_notification_format ] )
# https://github.com/caronc/apprise/wiki/Development_LogCapture
# Anything higher than or equal to WARNING (which covers things like Connection errors)
# raise it as an exception
apobjs = [ ]
sent_objs = [ ]
sent_objs = [ ]
from . apprise_asset import asset
for url in n_object [ ' notification_urls ' ] :
url = jinja2_env . from_string ( url ) . render ( * * notification_parameters )
apobj = apprise . Apprise ( debug = True , asset = asset )
url = url . strip ( )
if len ( url ) :
apobj = apprise . Apprise ( debug = True , asset = asset )
if not n_object . get ( ' notification_urls ' ) :
return None
with apprise . LogCapture ( level = apprise . logging . DEBUG ) as logs :
for url in n_object [ ' notification_urls ' ] :
url = url . strip ( )
print ( " >> Process Notification: AppRise notifying {} " . format ( url ) )
with apprise . LogCapture ( level = apprise . logging . DEBUG ) as logs :
# Re 323 - Limit discord length to their 2000 char limit total or it wont send.
# Because different notifications may require different pre-processing, run each sequentially :(
# 2000 bytes minus -
# 200 bytes for the overhead of the _entire_ json payload, 200 bytes for {tts, wait, content} etc headers
# Length of URL - Incase they specify a longer custom avatar_url
# So if no avatar_url is specified, add one so it can be correctly calculated into the total payload
k = ' ? ' if not ' ? ' in url else ' & '
if not ' avatar_url ' in url \
and not url . startswith ( ' mail ' ) \
and not url . startswith ( ' post ' ) \
and not url . startswith ( ' get ' ) \
and not url . startswith ( ' delete ' ) \
and not url . startswith ( ' put ' ) :
url + = k + ' avatar_url=https://raw.githubusercontent.com/dgtlmoon/changedetection.io/master/changedetectionio/static/images/avatar-256x256.png '
if url . startswith ( ' tgram:// ' ) :
# Telegram only supports a limit subset of HTML, remove the '<br>' we place in.
# re https://github.com/dgtlmoon/changedetection.io/issues/555
# @todo re-use an existing library we have already imported to strip all non-allowed tags
n_body = n_body . replace ( ' <br> ' , ' \n ' )
n_body = n_body . replace ( ' </br> ' , ' \n ' )
# real limit is 4096, but minus some for extra metadata
payload_max_size = 3600
body_limit = max ( 0 , payload_max_size - len ( n_title ) )
n_title = n_title [ 0 : payload_max_size ]
n_body = n_body [ 0 : body_limit ]
elif url . startswith ( ' discord:// ' ) or url . startswith ( ' https://discordapp.com/api/webhooks ' ) or url . startswith ( ' https://discord.com/api ' ) :
# real limit is 2000, but minus some for extra metadata
payload_max_size = 1700
body_limit = max ( 0 , payload_max_size - len ( n_title ) )
n_title = n_title [ 0 : payload_max_size ]
n_body = n_body [ 0 : body_limit ]
elif url . startswith ( ' mailto ' ) :
# Apprise will default to HTML, so we need to override it
# So that whats' generated in n_body is in line with what is going to be sent.
# https://github.com/caronc/apprise/issues/633#issuecomment-1191449321
if not ' format= ' in url and ( n_format == ' Text ' or n_format == ' Markdown ' ) :
prefix = ' ? ' if not ' ? ' in url else ' & '
# Apprise format is lowercase text https://github.com/caronc/apprise/issues/633
n_format = n_format . tolower ( )
url = " {} {} format= {} " . format ( url , prefix , n_format )
# If n_format == HTML, then apprise email should default to text/html and we should be sending HTML only
apobj . add ( url )
apobj . notify (
title = n_title ,
body = n_body ,
body_format = n_format ,
# False is not an option for AppRise, must be type None
attach = n_object . get ( ' screenshot ' , None )
)
apobj . clear ( )
# Incase it needs to exist in memory for a while after to process(?)
apobjs . append ( apobj )
# Returns empty string if nothing found, multi-line string otherwise
log_value = logs . getvalue ( )
if log_value and ' WARNING ' in log_value or ' ERROR ' in log_value :
raise Exception ( log_value )
sent_objs . append ( { ' title ' : n_title ,
' body ' : n_body ,
' url ' : url ,
' body_format ' : n_format } )
url = jinja2_env . from_string ( url ) . render ( * * notification_parameters )
# Re 323 - Limit discord length to their 2000 char limit total or it wont send.
# Because different notifications may require different pre-processing, run each sequentially :(
# 2000 bytes minus -
# 200 bytes for the overhead of the _entire_ json payload, 200 bytes for {tts, wait, content} etc headers
# Length of URL - Incase they specify a longer custom avatar_url
# So if no avatar_url is specified, add one so it can be correctly calculated into the total payload
k = ' ? ' if not ' ? ' in url else ' & '
if not ' avatar_url ' in url \
and not url . startswith ( ' mail ' ) \
and not url . startswith ( ' post ' ) \
and not url . startswith ( ' get ' ) \
and not url . startswith ( ' delete ' ) \
and not url . startswith ( ' put ' ) :
url + = k + ' avatar_url=https://raw.githubusercontent.com/dgtlmoon/changedetection.io/master/changedetectionio/static/images/avatar-256x256.png '
if url . startswith ( ' tgram:// ' ) :
# Telegram only supports a limit subset of HTML, remove the '<br>' we place in.
# re https://github.com/dgtlmoon/changedetection.io/issues/555
# @todo re-use an existing library we have already imported to strip all non-allowed tags
n_body = n_body . replace ( ' <br> ' , ' \n ' )
n_body = n_body . replace ( ' </br> ' , ' \n ' )
# real limit is 4096, but minus some for extra metadata
payload_max_size = 3600
body_limit = max ( 0 , payload_max_size - len ( n_title ) )
n_title = n_title [ 0 : payload_max_size ]
n_body = n_body [ 0 : body_limit ]
elif url . startswith ( ' discord:// ' ) or url . startswith ( ' https://discordapp.com/api/webhooks ' ) or url . startswith (
' https://discord.com/api ' ) :
# real limit is 2000, but minus some for extra metadata
payload_max_size = 1700
body_limit = max ( 0 , payload_max_size - len ( n_title ) )
n_title = n_title [ 0 : payload_max_size ]
n_body = n_body [ 0 : body_limit ]
elif url . startswith ( ' mailto ' ) :
# Apprise will default to HTML, so we need to override it
# So that whats' generated in n_body is in line with what is going to be sent.
# https://github.com/caronc/apprise/issues/633#issuecomment-1191449321
if not ' format= ' in url and ( n_format == ' Text ' or n_format == ' Markdown ' ) :
prefix = ' ? ' if not ' ? ' in url else ' & '
# Apprise format is lowercase text https://github.com/caronc/apprise/issues/633
n_format = n_format . lower ( )
url = f " { url } { prefix } format= { n_format } "
# If n_format == HTML, then apprise email should default to text/html and we should be sending HTML only
apobj . add ( url )
sent_objs . append ( { ' title ' : n_title ,
' body ' : n_body ,
' url ' : url ,
' body_format ' : n_format } )
# Blast off the notifications tht are set in .add()
apobj . notify (
title = n_title ,
body = n_body ,
body_format = n_format ,
# False is not an option for AppRise, must be type None
attach = n_object . get ( ' screenshot ' , None )
)
# Give apprise time to register an error
time . sleep ( 3 )
# Returns empty string if nothing found, multi-line string otherwise
log_value = logs . getvalue ( )
if log_value and ' WARNING ' in log_value or ' ERROR ' in log_value :
raise Exception ( log_value )
# Return what was sent for better logging - after the for loop
return sent_objs
# Notification title + body content parameters get created here.
# ( Where we prepare the tokens in the notification to be replaced with actual values )
def create_notification_parameters ( n_object , datastore ) :
from copy import deepcopy
# in the case we send a test notification from the main settings, there is no UUID.
uuid = n_object [ ' uuid ' ] if ' uuid ' in n_object else ' '
if uuid != ' ' :
if uuid :
watch_title = datastore . data [ ' watching ' ] [ uuid ] . get ( ' title ' , ' ' )
tag_list = [ ]
tags = datastore . get_all_tags_for_watch ( uuid )
@ -255,7 +261,7 @@ def create_notification_parameters(n_object, datastore):
tokens . update (
{
' base_url ' : base_url ,
' current_snapshot ' : n_object [ ' current_snapshot ' ] if ' current_snapshot ' in n_object else ' ' ,
' current_snapshot ' : n_object . get ( ' current_snapshot ' , ' ' ) ,
' diff ' : n_object . get ( ' diff ' , ' ' ) , # Null default in the case we use a test
' diff_added ' : n_object . get ( ' diff_added ' , ' ' ) , # Null default in the case we use a test
' diff_full ' : n_object . get ( ' diff_full ' , ' ' ) , # Null default in the case we use a test