# -*- coding: utf-8 -*-
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
# All rights reserved.
#
# This code is licensed under the MIT License.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions :
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import click
import logging
import platform
import sys
from os . path import isfile
from os . path import expanduser
from os . path import expandvars
from . import NotifyType
from . import Apprise
from . import AppriseAsset
from . import AppriseConfig
from . utils import parse_list
from . common import NOTIFY_TYPES
from . logger import logger
from . import __title__
from . import __version__
from . import __license__
from . import __copywrite__
# Defines our click context settings adding -h to the additional options that
# can be specified to get the help menu to come up
CONTEXT_SETTINGS = dict ( help_option_names = [ ' -h ' , ' --help ' ] )
# Define our default configuration we use if nothing is otherwise specified
DEFAULT_SEARCH_PATHS = (
' ~/.apprise ' ,
' ~/.apprise.yml ' ,
' ~/.config/apprise ' ,
' ~/.config/apprise.yml ' ,
)
# Detect Windows
if platform . system ( ) == ' Windows ' :
# Default Search Path for Windows Users
DEFAULT_SEARCH_PATHS = (
expandvars ( ' % APPDATA % /Apprise/apprise ' ) ,
expandvars ( ' % APPDATA % /Apprise/apprise.yml ' ) ,
expandvars ( ' % LOCALAPPDATA % /Apprise/apprise ' ) ,
expandvars ( ' % LOCALAPPDATA % /Apprise/apprise.yml ' ) ,
)
def print_help_msg ( command ) :
"""
Prints help message when - h or - - help is specified .
"""
with click . Context ( command ) as ctx :
click . echo ( command . get_help ( ctx ) )
def print_version_msg ( ) :
"""
Prints version message when - V or - - version is specified .
"""
result = list ( )
result . append ( ' {} v {} ' . format ( __title__ , __version__ ) )
result . append ( __copywrite__ )
result . append (
' This code is licensed under the {} License. ' . format ( __license__ ) )
click . echo ( ' \n ' . join ( result ) )
@click.command ( context_settings = CONTEXT_SETTINGS )
@click.option ( ' --body ' , ' -b ' , default = None , type = str ,
help = ' Specify the message body. If no body is specified then '
' content is read from <stdin>. ' )
@click.option ( ' --title ' , ' -t ' , default = None , type = str ,
help = ' Specify the message title. This field is complete '
' optional. ' )
@click.option ( ' --config ' , ' -c ' , default = None , type = str , multiple = True ,
metavar = ' CONFIG_URL ' ,
help = ' Specify one or more configuration locations. ' )
@click.option ( ' --attach ' , ' -a ' , default = None , type = str , multiple = True ,
metavar = ' ATTACHMENT_URL ' ,
help = ' Specify one or more configuration locations. ' )
@click.option ( ' --notification-type ' , ' -n ' , default = NotifyType . INFO , type = str ,
metavar = ' TYPE ' ,
help = ' Specify the message type (default=info). Possible values '
' are " {} " , and " {} " . ' . format (
' " , " ' . join ( NOTIFY_TYPES [ : - 1 ] ) , NOTIFY_TYPES [ - 1 ] ) )
@click.option ( ' --theme ' , ' -T ' , default = ' default ' , type = str , metavar = ' THEME ' ,
help = ' Specify the default theme. ' )
@click.option ( ' --tag ' , ' -g ' , default = None , type = str , multiple = True ,
metavar = ' TAG ' , help = ' Specify one or more tags to filter '
' which services to notify. Use multiple --tag (-g) entries to '
' " OR " the tags together and comma separated to " AND " them. '
' If no tags are specified then all services are notified. ' )
@click.option ( ' --dry-run ' , ' -d ' , is_flag = True ,
help = ' Perform a trial run but only prints the notification '
' services to-be triggered to stdout. Notifications are never '
' sent using this mode. ' )
@click.option ( ' --verbose ' , ' -v ' , count = True )
@click.option ( ' --version ' , ' -V ' , is_flag = True ,
help = ' Display the apprise version and exit. ' )
@click.argument ( ' urls ' , nargs = - 1 ,
metavar = ' SERVER_URL [SERVER_URL2 [SERVER_URL3]] ' , )
def main ( body , title , config , attach , urls , notification_type , theme , tag ,
dry_run , verbose , version ) :
"""
Send a notification to all of the specified servers identified by their
URLs the content provided within the title , body and notification - type .
For a list of all of the supported services and information on how to
use them , check out at https : / / github . com / caronc / apprise
"""
# Note: Click ignores the return values of functions it wraps, If you
# want to return a specific error code, you must call sys.exit()
# as you will see below.
# Logging
ch = logging . StreamHandler ( sys . stdout )
if verbose > 3 :
# -vvvv: Most Verbose Debug Logging
logger . setLevel ( logging . TRACE )
elif verbose > 2 :
# -vvv: Debug Logging
logger . setLevel ( logging . DEBUG )
elif verbose > 1 :
# -vv: INFO Messages
logger . setLevel ( logging . INFO )
elif verbose > 0 :
# -v: WARNING Messages
logger . setLevel ( logging . WARNING )
else :
# No verbosity means we display ERRORS only AND any deprecation
# warnings
logger . setLevel ( logging . ERROR )
# Format our logger
formatter = logging . Formatter ( ' %(asctime)s - %(levelname)s - %(message)s ' )
ch . setFormatter ( formatter )
logger . addHandler ( ch )
if version :
print_version_msg ( )
sys . exit ( 0 )
# Prepare our asset
asset = AppriseAsset ( theme = theme )
# Create our object
a = Apprise ( asset = asset )
# Load our configuration if no URLs or specified configuration was
# identified on the command line
a . add ( AppriseConfig (
paths = [ f for f in DEFAULT_SEARCH_PATHS if isfile ( expanduser ( f ) ) ]
if not ( config or urls ) else config ) , asset = asset )
# Load our inventory up
for url in urls :
a . add ( url )
if len ( a ) == 0 :
logger . error (
' You must specify at least one server URL or populated '
' configuration file. ' )
print_help_msg ( main )
sys . exit ( 1 )
# each --tag entry comprises of a comma separated 'and' list
# we or each of of the --tag and sets specified.
tags = None if not tag else [ parse_list ( t ) for t in tag ]
if not dry_run :
if body is None :
logger . trace ( ' No --body (-b) specified; reading from stdin ' )
# if no body was specified, then read from STDIN
body = click . get_text_stream ( ' stdin ' ) . read ( )
# now print it out
result = a . notify (
body = body , title = title , notify_type = notification_type , tag = tags ,
attach = attach )
else :
# Number of rows to assume in the terminal. In future, maybe this can
# be detected and made dynamic. The actual row count is 80, but 5
# characters are already reserved for the counter on the left
rows = 75
# Initialize our URL response; This is populated within the for/loop
# below; but plays a factor at the end when we need to determine if
# we iterated at least once in the loop.
url = None
for idx , server in enumerate ( a . find ( tag = tags ) ) :
url = server . url ( privacy = True )
click . echo ( " {: 3d} . {} " . format (
idx + 1 ,
url if len ( url ) < = rows else ' {} ... ' . format ( url [ : rows - 3 ] ) ) )
if server . tags :
click . echo ( " {} - {} " . format ( ' ' * 5 , ' , ' . join ( server . tags ) ) )
# Initialize a default response of nothing matched, otherwise
# if we matched at least one entry, we can return True
result = None if url is None else True
if result is None :
# There were no notifications set. This is a result of just having
# empty configuration files and/or being to restrictive when filtering
# by specific tag(s)
sys . exit ( 2 )
elif result is False :
# At least 1 notification service failed to send
sys . exit ( 1 )
# else: We're good!
sys . exit ( 0 )