mirror of https://github.com/caronc/apprise
Telegram improvements; AppriseAsset refactoring
parent
44a053c443
commit
5b9be6bcc4
|
@ -2,7 +2,7 @@
|
||||||
#
|
#
|
||||||
# Base Notify Wrapper
|
# Base Notify Wrapper
|
||||||
#
|
#
|
||||||
# Copyright (C) 2017 Chris Caron <lead2gold@gmail.com>
|
# Copyright (C) 2017-2018 Chris Caron <lead2gold@gmail.com>
|
||||||
#
|
#
|
||||||
# This file is part of apprise.
|
# This file is part of apprise.
|
||||||
#
|
#
|
||||||
|
@ -243,14 +243,18 @@ class NotifyBase(object):
|
||||||
return self.asset.app_url
|
return self.asset.app_url
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def escape_html(html, convert_new_lines=False):
|
def escape_html(html, convert_new_lines=False, whitespace=True):
|
||||||
"""
|
"""
|
||||||
Takes html text as input and escapes it so that it won't
|
Takes html text as input and escapes it so that it won't
|
||||||
conflict with any xml/html wrapping characters.
|
conflict with any xml/html wrapping characters.
|
||||||
"""
|
"""
|
||||||
escaped = _escape(html).\
|
escaped = _escape(html)
|
||||||
replace(u'\t', u' ').\
|
|
||||||
replace(u' ', u' ')
|
if whitespace:
|
||||||
|
# Tidy up whitespace too
|
||||||
|
escaped = escaped\
|
||||||
|
.replace(u'\t', u' ')\
|
||||||
|
.replace(u' ', u' ')
|
||||||
|
|
||||||
if convert_new_lines:
|
if convert_new_lines:
|
||||||
return escaped.replace(u'\n', u'<br/>')
|
return escaped.replace(u'\n', u'<br/>')
|
||||||
|
@ -335,7 +339,7 @@ class NotifyBase(object):
|
||||||
return is_hostname(hostname)
|
return is_hostname(hostname)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url, verify_host=True, default_format=NotifyFormat.TEXT):
|
def parse_url(url, verify_host=True):
|
||||||
"""
|
"""
|
||||||
Parses the URL and returns it broken apart into a dictionary.
|
Parses the URL and returns it broken apart into a dictionary.
|
||||||
|
|
||||||
|
@ -350,9 +354,6 @@ class NotifyBase(object):
|
||||||
# if our URL ends with an 's', then assueme our secure flag is set.
|
# if our URL ends with an 's', then assueme our secure flag is set.
|
||||||
results['secure'] = (results['schema'][-1] == 's')
|
results['secure'] = (results['schema'][-1] == 's')
|
||||||
|
|
||||||
# Our default notification format
|
|
||||||
results['notify_format'] = default_format
|
|
||||||
|
|
||||||
# Support SSL Certificate 'verify' keyword. Default to being enabled
|
# Support SSL Certificate 'verify' keyword. Default to being enabled
|
||||||
results['verify'] = verify_host
|
results['verify'] = verify_host
|
||||||
|
|
||||||
|
@ -360,6 +361,15 @@ class NotifyBase(object):
|
||||||
results['verify'] = parse_bool(
|
results['verify'] = parse_bool(
|
||||||
results['qsd'].get('verify', True))
|
results['qsd'].get('verify', True))
|
||||||
|
|
||||||
|
# Allow overriding the default format
|
||||||
|
if 'format' in results['qsd']:
|
||||||
|
results['format'] = results['qsd'].get('format')
|
||||||
|
if results['format'] not in NOTIFY_FORMATS:
|
||||||
|
NotifyBase.logger.warning(
|
||||||
|
'Unsupported format specified {}'.format(
|
||||||
|
results['format']))
|
||||||
|
del results['format']
|
||||||
|
|
||||||
# Password overrides
|
# Password overrides
|
||||||
if 'pass' in results['qsd']:
|
if 'pass' in results['qsd']:
|
||||||
results['password'] = results['qsd']['pass']
|
results['password'] = results['qsd']['pass']
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#
|
#
|
||||||
# Boxcar Notify Wrapper
|
# Boxcar Notify Wrapper
|
||||||
#
|
#
|
||||||
# Copyright (C) 2017 Chris Caron <lead2gold@gmail.com>
|
# Copyright (C) 2017-2018 Chris Caron <lead2gold@gmail.com>
|
||||||
#
|
#
|
||||||
# This file is part of apprise.
|
# This file is part of apprise.
|
||||||
#
|
#
|
||||||
|
@ -240,6 +240,9 @@ class NotifyBoxcar(NotifyBase):
|
||||||
# Return; we're done
|
# Return; we're done
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.logger.info('Sent Boxcar notification.')
|
||||||
|
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'A Connection error occured sending Boxcar '
|
'A Connection error occured sending Boxcar '
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#
|
#
|
||||||
# Email Notify Wrapper
|
# Email Notify Wrapper
|
||||||
#
|
#
|
||||||
# Copyright (C) 2017 Chris Caron <lead2gold@gmail.com>
|
# Copyright (C) 2017-2018 Chris Caron <lead2gold@gmail.com>
|
||||||
#
|
#
|
||||||
# This file is part of apprise.
|
# This file is part of apprise.
|
||||||
#
|
#
|
||||||
|
@ -157,7 +157,7 @@ class NotifyEmail(NotifyBase):
|
||||||
|
|
||||||
# Now we want to construct the To and From email
|
# Now we want to construct the To and From email
|
||||||
# addresses from the URL provided
|
# addresses from the URL provided
|
||||||
self.from_name = kwargs.get('name', NotifyBase.app_desc)
|
self.from_name = kwargs.get('name', self.app_desc)
|
||||||
self.from_addr = kwargs.get('from', None)
|
self.from_addr = kwargs.get('from', None)
|
||||||
|
|
||||||
if not NotifyBase.is_email(self.to_addr):
|
if not NotifyBase.is_email(self.to_addr):
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#
|
#
|
||||||
# Growl Notify Wrapper
|
# Growl Notify Wrapper
|
||||||
#
|
#
|
||||||
# Copyright (C) 2017 Chris Caron <lead2gold@gmail.com>
|
# Copyright (C) 2017-2018 Chris Caron <lead2gold@gmail.com>
|
||||||
#
|
#
|
||||||
# This file is part of apprise.
|
# This file is part of apprise.
|
||||||
#
|
#
|
||||||
|
@ -173,9 +173,7 @@ class NotifyGrowl(NotifyBase):
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.logger.debug(
|
self.logger.info('Sent Growl notification.')
|
||||||
'Growl notification sent successfully.'
|
|
||||||
)
|
|
||||||
|
|
||||||
except errors.BaseError as e:
|
except errors.BaseError as e:
|
||||||
# Since Growl servers listen for UDP broadcasts, it's possible
|
# Since Growl servers listen for UDP broadcasts, it's possible
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#
|
#
|
||||||
# JSON Notify Wrapper
|
# JSON Notify Wrapper
|
||||||
#
|
#
|
||||||
# Copyright (C) 2017 Chris Caron <lead2gold@gmail.com>
|
# Copyright (C) 2017-2018 Chris Caron <lead2gold@gmail.com>
|
||||||
#
|
#
|
||||||
# This file is part of apprise.
|
# This file is part of apprise.
|
||||||
#
|
#
|
||||||
|
@ -118,6 +118,9 @@ class NotifyJSON(NotifyBase):
|
||||||
# Return; we're done
|
# Return; we're done
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.logger.info('Sent JSON notification.')
|
||||||
|
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'A Connection error occured sending JSON '
|
'A Connection error occured sending JSON '
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#
|
#
|
||||||
# Join Notify Wrapper
|
# Join Notify Wrapper
|
||||||
#
|
#
|
||||||
# Copyright (C) 2017 Chris Caron <lead2gold@gmail.com>
|
# Copyright (C) 2017-2018 Chris Caron <lead2gold@gmail.com>
|
||||||
#
|
#
|
||||||
# This file is part of apprise.
|
# This file is part of apprise.
|
||||||
#
|
#
|
||||||
|
@ -196,6 +196,9 @@ class NotifyJoin(NotifyBase):
|
||||||
|
|
||||||
return_status = False
|
return_status = False
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.logger.info('Sent Join notification to %s.' % device)
|
||||||
|
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'A Connection error occured sending Join:%s '
|
'A Connection error occured sending Join:%s '
|
||||||
|
|
|
@ -155,6 +155,7 @@ class NotifyProwl(NotifyBase):
|
||||||
|
|
||||||
# Return; we're done
|
# Return; we're done
|
||||||
return False
|
return False
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.logger.info('Sent Prowl notification.')
|
self.logger.info('Sent Prowl notification.')
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#
|
#
|
||||||
# Pushjet Notify Wrapper
|
# Pushjet Notify Wrapper
|
||||||
#
|
#
|
||||||
# Copyright (C) 2017 Chris Caron <lead2gold@gmail.com>
|
# Copyright (C) 2017-2018 Chris Caron <lead2gold@gmail.com>
|
||||||
#
|
#
|
||||||
# This file is part of apprise.
|
# This file is part of apprise.
|
||||||
#
|
#
|
||||||
|
@ -67,6 +67,7 @@ class NotifyPushjet(NotifyBase):
|
||||||
service = api.Service(secret_key=self.host)
|
service = api.Service(secret_key=self.host)
|
||||||
|
|
||||||
service.send(body, title)
|
service.send(body, title)
|
||||||
|
self.logger.info('Sent Pushjet notification.')
|
||||||
|
|
||||||
except (errors.PushjetError, ValueError) as e:
|
except (errors.PushjetError, ValueError) as e:
|
||||||
self.logger.warning('Failed to send Pushjet notification.')
|
self.logger.warning('Failed to send Pushjet notification.')
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#
|
#
|
||||||
# Pushover Notify Wrapper
|
# Pushover Notify Wrapper
|
||||||
#
|
#
|
||||||
# Copyright (C) 2017 Chris Caron <lead2gold@gmail.com>
|
# Copyright (C) 2017-2018 Chris Caron <lead2gold@gmail.com>
|
||||||
#
|
#
|
||||||
# This file is part of apprise.
|
# This file is part of apprise.
|
||||||
#
|
#
|
||||||
|
@ -201,6 +201,10 @@ class NotifyPushover(NotifyBase):
|
||||||
# Return; we're done
|
# Return; we're done
|
||||||
has_error = True
|
has_error = True
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.logger.info(
|
||||||
|
'Sent Pushover notification to %s.' % device)
|
||||||
|
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'A Connection error occured sending Pushover:%s ' % (
|
'A Connection error occured sending Pushover:%s ' % (
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#
|
#
|
||||||
# Slack Notify Wrapper
|
# Slack Notify Wrapper
|
||||||
#
|
#
|
||||||
# Copyright (C) 2017 Chris Caron <lead2gold@gmail.com>
|
# Copyright (C) 2017-2018 Chris Caron <lead2gold@gmail.com>
|
||||||
#
|
#
|
||||||
# This file is part of apprise.
|
# This file is part of apprise.
|
||||||
#
|
#
|
||||||
|
@ -263,6 +263,9 @@ class NotifySlack(NotifyBase):
|
||||||
# Return; we're done
|
# Return; we're done
|
||||||
notify_okay = False
|
notify_okay = False
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.logger.info('Sent Slack notification.')
|
||||||
|
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'A Connection error occured sending Slack:%s ' % (
|
'A Connection error occured sending Slack:%s ' % (
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#
|
#
|
||||||
# Telegram Notify Wrapper
|
# Telegram Notify Wrapper
|
||||||
#
|
#
|
||||||
# Copyright (C) 2017 Chris Caron <lead2gold@gmail.com>
|
# Copyright (C) 2017-2018 Chris Caron <lead2gold@gmail.com>
|
||||||
#
|
#
|
||||||
# This file is part of apprise.
|
# This file is part of apprise.
|
||||||
#
|
#
|
||||||
|
@ -40,17 +40,24 @@
|
||||||
# For example, a url might look like this:
|
# For example, a url might look like this:
|
||||||
# https://api.telegram.org/bot123456789:alphanumeri_characters/getMe
|
# https://api.telegram.org/bot123456789:alphanumeri_characters/getMe
|
||||||
#
|
#
|
||||||
|
# Development API Reference::
|
||||||
|
# - https://core.telegram.org/bots/api
|
||||||
import requests
|
import requests
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from os.path import basename
|
||||||
|
|
||||||
from json import loads
|
from json import loads
|
||||||
from json import dumps
|
from json import dumps
|
||||||
|
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from .NotifyBase import HTTP_ERROR_MAP
|
from .NotifyBase import HTTP_ERROR_MAP
|
||||||
from ..common import NotifyFormat
|
|
||||||
from ..common import NotifyImageSize
|
from ..common import NotifyImageSize
|
||||||
|
from ..common import NotifyFormat
|
||||||
from ..utils import compat_is_basestring
|
from ..utils import compat_is_basestring
|
||||||
|
from ..utils import parse_bool
|
||||||
|
|
||||||
|
TELEGRAM_IMAGE_XY = NotifyImageSize.XY_256
|
||||||
|
|
||||||
# Token required as part of the API request
|
# Token required as part of the API request
|
||||||
# allow the word 'bot' infront
|
# allow the word 'bot' infront
|
||||||
|
@ -82,15 +89,15 @@ class NotifyTelegram(NotifyBase):
|
||||||
# Telegram uses the http protocol with JSON requests
|
# Telegram uses the http protocol with JSON requests
|
||||||
notify_url = 'https://api.telegram.org/bot'
|
notify_url = 'https://api.telegram.org/bot'
|
||||||
|
|
||||||
def __init__(self, bot_token, chat_ids, notify_format=NotifyFormat.HTML,
|
def __init__(self, bot_token, chat_ids, notify_format=NotifyFormat.TEXT,
|
||||||
**kwargs):
|
detect_bot_owner=True, include_image=True, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Telegram Object
|
Initialize Telegram Object
|
||||||
"""
|
"""
|
||||||
super(NotifyTelegram, self).__init__(
|
super(NotifyTelegram, self).__init__(
|
||||||
title_maxlen=250, body_maxlen=4096,
|
title_maxlen=250, body_maxlen=4096,
|
||||||
notify_format=notify_format,
|
notify_format=notify_format,
|
||||||
**kwargs)
|
image_size=TELEGRAM_IMAGE_XY, **kwargs)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.bot_token = bot_token.strip()
|
self.bot_token = bot_token.strip()
|
||||||
|
@ -124,10 +131,193 @@ class NotifyTelegram(NotifyBase):
|
||||||
# Treat this as a channel too
|
# Treat this as a channel too
|
||||||
self.chat_ids.append(self.user)
|
self.chat_ids.append(self.user)
|
||||||
|
|
||||||
|
if len(self.chat_ids) == 0 and detect_bot_owner:
|
||||||
|
_id = self.detect_bot_owner()
|
||||||
|
if _id:
|
||||||
|
# Store our id
|
||||||
|
self.chat_ids = [str(_id)]
|
||||||
|
|
||||||
if len(self.chat_ids) == 0:
|
if len(self.chat_ids) == 0:
|
||||||
self.logger.warning('No chat_id(s) were specified.')
|
self.logger.warning('No chat_id(s) were specified.')
|
||||||
raise TypeError('No chat_id(s) were specified.')
|
raise TypeError('No chat_id(s) were specified.')
|
||||||
|
|
||||||
|
# Track whether or not we want to send an image with our notification
|
||||||
|
# or not.
|
||||||
|
self.include_image = include_image
|
||||||
|
|
||||||
|
def send_image(self, chat_id, notify_type):
|
||||||
|
"""
|
||||||
|
Sends a sticker based on the specified notify type
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The URL; we do not set headers because the api doesn't seem to like
|
||||||
|
# when we set one.
|
||||||
|
url = '%s%s/%s' % (
|
||||||
|
self.notify_url,
|
||||||
|
self.bot_token,
|
||||||
|
'sendPhoto'
|
||||||
|
)
|
||||||
|
|
||||||
|
path = self.image_path(notify_type)
|
||||||
|
if not path:
|
||||||
|
# No image to send
|
||||||
|
self.logger.debug(
|
||||||
|
'Telegram Image does not exist for %s' % (
|
||||||
|
notify_type))
|
||||||
|
return None
|
||||||
|
|
||||||
|
files = {'photo': (basename(path), open(path), 'rb')}
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
'chat_id': chat_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.logger.debug(
|
||||||
|
'Telegram Image POST URL: %s (cert_verify=%r)' % (
|
||||||
|
url, self.verify_certificate))
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.post(
|
||||||
|
url,
|
||||||
|
files=files,
|
||||||
|
data=payload,
|
||||||
|
verify=self.verify_certificate,
|
||||||
|
)
|
||||||
|
|
||||||
|
if r.status_code != requests.codes.ok:
|
||||||
|
# We had a problem
|
||||||
|
try:
|
||||||
|
self.logger.warning(
|
||||||
|
'Failed to post Telegram Image: '
|
||||||
|
'%s (error=%s).' % (
|
||||||
|
HTTP_ERROR_MAP[r.status_code],
|
||||||
|
r.status_code))
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
self.logger.warning(
|
||||||
|
'Failed to detect Telegram Image. (error=%s).' % (
|
||||||
|
r.status_code))
|
||||||
|
|
||||||
|
# self.logger.debug('Response Details: %s' % r.raw.read())
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
self.logger.warning(
|
||||||
|
'A connection error occured posting Telegram Image.')
|
||||||
|
self.logger.debug('Socket Exception: %s' % str(e))
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def detect_bot_owner(self):
|
||||||
|
"""
|
||||||
|
Takes a bot and attempts to detect it's chat id from that
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'User-Agent': self.app_id,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
}
|
||||||
|
|
||||||
|
url = '%s%s/%s' % (
|
||||||
|
self.notify_url,
|
||||||
|
self.bot_token,
|
||||||
|
'getUpdates'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.logger.debug(
|
||||||
|
'Telegram User Detection POST URL: %s (cert_verify=%r)' % (
|
||||||
|
url, self.verify_certificate))
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.post(
|
||||||
|
url,
|
||||||
|
headers=headers,
|
||||||
|
verify=self.verify_certificate,
|
||||||
|
)
|
||||||
|
|
||||||
|
if r.status_code != requests.codes.ok:
|
||||||
|
# We had a problem
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Try to get the error message if we can:
|
||||||
|
error_msg = loads(r.content)['description']
|
||||||
|
|
||||||
|
except:
|
||||||
|
error_msg = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
if error_msg:
|
||||||
|
self.logger.warning(
|
||||||
|
'Failed to detect Telegram user: (%s) %s.' % (
|
||||||
|
r.status_code, error_msg))
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.logger.warning(
|
||||||
|
'Failed to detect Telegram user: '
|
||||||
|
'%s (error=%s).' % (
|
||||||
|
HTTP_ERROR_MAP[r.status_code],
|
||||||
|
r.status_code))
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
self.logger.warning(
|
||||||
|
'Failed to detect Telegram user. (error=%s).' % (
|
||||||
|
r.status_code))
|
||||||
|
|
||||||
|
# self.logger.debug('Response Details: %s' % r.raw.read())
|
||||||
|
return 0
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
self.logger.warning(
|
||||||
|
'A connection error occured detecting Telegram User.')
|
||||||
|
self.logger.debug('Socket Exception: %s' % str(e))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# A Response might look something like this:
|
||||||
|
# {
|
||||||
|
# "ok":true,
|
||||||
|
# "result":[{
|
||||||
|
# "update_id":645421321,
|
||||||
|
# "message":{
|
||||||
|
# "message_id":1,
|
||||||
|
# "from":{
|
||||||
|
# "id":532389719,
|
||||||
|
# "is_bot":false,
|
||||||
|
# "first_name":"Chris",
|
||||||
|
# "language_code":"en-US"
|
||||||
|
# },
|
||||||
|
# "chat":{
|
||||||
|
# "id":532389719,
|
||||||
|
# "first_name":"Chris",
|
||||||
|
# "type":"private"
|
||||||
|
# },
|
||||||
|
# "date":1519694394,
|
||||||
|
# "text":"/start",
|
||||||
|
# "entities":[{"offset":0,"length":6,"type":"bot_command"}]}}]
|
||||||
|
|
||||||
|
# Load our response and attempt to fetch our userid
|
||||||
|
response = loads(r.content)
|
||||||
|
if 'ok' in response and response['ok'] is True:
|
||||||
|
start = re.compile('^\s*\/start', re.I)
|
||||||
|
for _msg in iter(response['result']):
|
||||||
|
# Find /start
|
||||||
|
if not start.search(_msg['message']['text']):
|
||||||
|
continue
|
||||||
|
|
||||||
|
_id = _msg['message']['from'].get('id', 0)
|
||||||
|
_user = _msg['message']['from'].get('first_name')
|
||||||
|
self.logger.info('Detected telegram user %s (userid=%d)' % (
|
||||||
|
_user, _id))
|
||||||
|
# Return our detected userid
|
||||||
|
return _id
|
||||||
|
|
||||||
|
self.logger.warning(
|
||||||
|
'Could not detect bot owner. Is it running (/start)?')
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
def notify(self, title, body, notify_type, **kwargs):
|
def notify(self, title, body, notify_type, **kwargs):
|
||||||
"""
|
"""
|
||||||
Perform Telegram Notification
|
Perform Telegram Notification
|
||||||
|
@ -149,18 +339,23 @@ class NotifyTelegram(NotifyBase):
|
||||||
|
|
||||||
payload = {}
|
payload = {}
|
||||||
|
|
||||||
if self.notify_format == NotifyFormat.HTML:
|
# HTML Spaces ( ) and tabs ( ) aren't supported
|
||||||
# HTML
|
# See https://core.telegram.org/bots/api#html-style
|
||||||
payload['parse_mode'] = 'HTML'
|
title = re.sub(' ?', ' ', title, re.I)
|
||||||
payload['text'] = '<b>%s</b>\r\n%s' % (title, body)
|
body = re.sub(' ?', ' ', body, re.I)
|
||||||
|
# Tabs become 3 spaces
|
||||||
|
title = re.sub(' ?', ' ', title, re.I)
|
||||||
|
body = re.sub(' ?', ' ', body, re.I)
|
||||||
|
|
||||||
|
# HTML
|
||||||
|
title = NotifyBase.escape_html(title, whitespace=False)
|
||||||
|
body = NotifyBase.escape_html(body, whitespace=False)
|
||||||
|
|
||||||
else:
|
|
||||||
# Text
|
|
||||||
# payload['parse_mode'] = 'Markdown'
|
|
||||||
payload['parse_mode'] = 'HTML'
|
payload['parse_mode'] = 'HTML'
|
||||||
|
|
||||||
payload['text'] = '<b>%s</b>\r\n%s' % (
|
payload['text'] = '<b>%s</b>\r\n%s' % (
|
||||||
NotifyBase.escape_html(title),
|
title,
|
||||||
NotifyBase.escape_html(body),
|
body,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a copy of the chat_ids list
|
# Create a copy of the chat_ids list
|
||||||
|
@ -183,9 +378,17 @@ class NotifyTelegram(NotifyBase):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# ID
|
# ID
|
||||||
payload['chat_id'] = chat_id.group('idno')
|
payload['chat_id'] = int(chat_id.group('idno'))
|
||||||
|
|
||||||
|
if self.include_image is True:
|
||||||
|
# Send an image
|
||||||
|
if self.send_image(
|
||||||
|
payload['chat_id'], notify_type) is not None:
|
||||||
|
# We sent a post (whether we were successful or not)
|
||||||
|
# we still hit the remote server... just throttle
|
||||||
|
# before our next hit server query
|
||||||
|
self.throttle()
|
||||||
|
|
||||||
self.logger.debug('Telegram POST URL: %s' % url)
|
|
||||||
self.logger.debug('Telegram POST URL: %s (cert_verify=%r)' % (
|
self.logger.debug('Telegram POST URL: %s (cert_verify=%r)' % (
|
||||||
url, self.verify_certificate,
|
url, self.verify_certificate,
|
||||||
))
|
))
|
||||||
|
@ -204,7 +407,7 @@ class NotifyTelegram(NotifyBase):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Try to get the error message if we can:
|
# Try to get the error message if we can:
|
||||||
error_msg = loads(r.text)['description']
|
error_msg = loads(r.content)['description']
|
||||||
|
|
||||||
except:
|
except:
|
||||||
error_msg = None
|
error_msg = None
|
||||||
|
@ -236,9 +439,12 @@ class NotifyTelegram(NotifyBase):
|
||||||
# Flag our error
|
# Flag our error
|
||||||
has_error = True
|
has_error = True
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.logger.info('Sent Telegram notification.')
|
||||||
|
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'A Connection error occured sending Telegram:%s ' % (
|
'A connection error occured sending Telegram:%s ' % (
|
||||||
payload['chat_id']) + 'notification.'
|
payload['chat_id']) + 'notification.'
|
||||||
)
|
)
|
||||||
self.logger.debug('Socket Exception: %s' % str(e))
|
self.logger.debug('Socket Exception: %s' % str(e))
|
||||||
|
@ -322,4 +528,8 @@ class NotifyTelegram(NotifyBase):
|
||||||
# Store our chat ids
|
# Store our chat ids
|
||||||
results['chat_ids'] = chat_ids
|
results['chat_ids'] = chat_ids
|
||||||
|
|
||||||
|
# Include images with our message
|
||||||
|
results['include_image'] = \
|
||||||
|
parse_bool(results['qsd'].get('image', False))
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#
|
#
|
||||||
# (Super) Toasty Notify Wrapper
|
# (Super) Toasty Notify Wrapper
|
||||||
#
|
#
|
||||||
# Copyright (C) 2017 Chris Caron <lead2gold@gmail.com>
|
# Copyright (C) 2017-2018 Chris Caron <lead2gold@gmail.com>
|
||||||
#
|
#
|
||||||
# This file is part of apprise.
|
# This file is part of apprise.
|
||||||
#
|
#
|
||||||
|
@ -133,6 +133,10 @@ class NotifyToasty(NotifyBase):
|
||||||
# Return; we're done
|
# Return; we're done
|
||||||
has_error = True
|
has_error = True
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.logger.info(
|
||||||
|
'Sent Toasty notification to %s.' % device)
|
||||||
|
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'A Connection error occured sending Toasty:%s ' % (
|
'A Connection error occured sending Toasty:%s ' % (
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#
|
#
|
||||||
# Twitter Notify Wrapper
|
# Twitter Notify Wrapper
|
||||||
#
|
#
|
||||||
# Copyright (C) 2017 Chris Caron <lead2gold@gmail.com>
|
# Copyright (C) 2017-2018 Chris Caron <lead2gold@gmail.com>
|
||||||
#
|
#
|
||||||
# This file is part of apprise.
|
# This file is part of apprise.
|
||||||
#
|
#
|
||||||
|
@ -102,6 +102,7 @@ class NotifyTwitter(NotifyBase):
|
||||||
|
|
||||||
# Send our Direct Message
|
# Send our Direct Message
|
||||||
api.send_direct_message(self.user, text=text)
|
api.send_direct_message(self.user, text=text)
|
||||||
|
self.logger.info('Sent Twitter DM notification.')
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#
|
#
|
||||||
# XML Notify Wrapper
|
# XML Notify Wrapper
|
||||||
#
|
#
|
||||||
# Copyright (C) 2017 Chris Caron <lead2gold@gmail.com>
|
# Copyright (C) 2017-2018 Chris Caron <lead2gold@gmail.com>
|
||||||
#
|
#
|
||||||
# This file is part of apprise.
|
# This file is part of apprise.
|
||||||
#
|
#
|
||||||
|
@ -136,6 +136,9 @@ class NotifyXML(NotifyBase):
|
||||||
# Return; we're done
|
# Return; we're done
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.logger.info('Sent XML notification.')
|
||||||
|
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'A Connection error occured sending XML '
|
'A Connection error occured sending XML '
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#
|
#
|
||||||
# REST Based Plugins - Unit Tests
|
# REST Based Plugins - Unit Tests
|
||||||
#
|
#
|
||||||
# Copyright (C) 2017 Chris Caron <lead2gold@gmail.com>
|
# Copyright (C) 2017-2018 Chris Caron <lead2gold@gmail.com>
|
||||||
#
|
#
|
||||||
# This file is part of apprise.
|
# This file is part of apprise.
|
||||||
#
|
#
|
||||||
|
@ -985,19 +985,15 @@ TEST_URLS = (
|
||||||
('tgram://bottest@123456789:abcdefg_hijklmnop/lead2gold/', {
|
('tgram://bottest@123456789:abcdefg_hijklmnop/lead2gold/', {
|
||||||
'instance': plugins.NotifyTelegram,
|
'instance': plugins.NotifyTelegram,
|
||||||
}),
|
}),
|
||||||
# Testing valid format
|
# Testing image
|
||||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?format=text', {
|
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?image=Yes', {
|
||||||
'instance': plugins.NotifyTelegram,
|
'instance': plugins.NotifyTelegram,
|
||||||
}),
|
}),
|
||||||
# Testing valid format
|
# Testing invalid format (fall's back to html)
|
||||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?format=html', {
|
|
||||||
'instance': plugins.NotifyTelegram,
|
|
||||||
}),
|
|
||||||
# Testing invalid format (fall's back to text)
|
|
||||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?format=invalid', {
|
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?format=invalid', {
|
||||||
'instance': plugins.NotifyTelegram,
|
'instance': plugins.NotifyTelegram,
|
||||||
}),
|
}),
|
||||||
# Testing empty format (falls back to text)
|
# Testing empty format (falls back to html)
|
||||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?format=', {
|
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?format=', {
|
||||||
'instance': plugins.NotifyTelegram,
|
'instance': plugins.NotifyTelegram,
|
||||||
}),
|
}),
|
||||||
|
@ -1021,7 +1017,7 @@ TEST_URLS = (
|
||||||
'response': False,
|
'response': False,
|
||||||
'requests_response_code': requests.codes.internal_server_error,
|
'requests_response_code': requests.codes.internal_server_error,
|
||||||
}),
|
}),
|
||||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/', {
|
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?image=Yes', {
|
||||||
'instance': plugins.NotifyTelegram,
|
'instance': plugins.NotifyTelegram,
|
||||||
# force a failure without an image specified
|
# force a failure without an image specified
|
||||||
'include_image': False,
|
'include_image': False,
|
||||||
|
@ -1055,17 +1051,26 @@ TEST_URLS = (
|
||||||
'response': False,
|
'response': False,
|
||||||
'requests_response_code': 999,
|
'requests_response_code': 999,
|
||||||
}),
|
}),
|
||||||
|
# Test with image set
|
||||||
|
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?image=Yes', {
|
||||||
|
'instance': plugins.NotifyTelegram,
|
||||||
|
# throw a bizzare code forcing us to fail to look it up without
|
||||||
|
# having an image included
|
||||||
|
'include_image': True,
|
||||||
|
'response': False,
|
||||||
|
'requests_response_code': 999,
|
||||||
|
}),
|
||||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/', {
|
('tgram://123456789:abcdefg_hijklmnop/lead2gold/', {
|
||||||
'instance': plugins.NotifyTelegram,
|
'instance': plugins.NotifyTelegram,
|
||||||
# Throws a series of connection and transfer exceptions when this flag
|
# Throws a series of connection and transfer exceptions when this flag
|
||||||
# is set and tests that we gracfully handle them
|
# is set and tests that we gracfully handle them
|
||||||
'test_requests_exceptions': True,
|
'test_requests_exceptions': True,
|
||||||
}),
|
}),
|
||||||
('tgram://123456789:abcdefg_hijklmnop/lead2gold/', {
|
('tgram://123456789:abcdefg_hijklmnop/lead2gold/?image=Yes', {
|
||||||
'instance': plugins.NotifyTelegram,
|
'instance': plugins.NotifyTelegram,
|
||||||
# Throws a series of connection and transfer exceptions when this flag
|
# Throws a series of connection and transfer exceptions when this flag
|
||||||
# is set and tests that we gracfully handle them without images set
|
# is set and tests that we gracfully handle them without images set
|
||||||
'include_image': False,
|
'include_image': True,
|
||||||
'test_requests_exceptions': True,
|
'test_requests_exceptions': True,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
@ -1878,6 +1883,8 @@ def test_notify_telegram_plugin(mock_post, mock_get):
|
||||||
mock_post.return_value = requests.Request()
|
mock_post.return_value = requests.Request()
|
||||||
mock_post.return_value.status_code = requests.codes.ok
|
mock_post.return_value.status_code = requests.codes.ok
|
||||||
mock_get.return_value.status_code = requests.codes.ok
|
mock_get.return_value.status_code = requests.codes.ok
|
||||||
|
mock_get.return_value.content = '{}'
|
||||||
|
mock_post.return_value.content = '{}'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
obj = plugins.NotifyTelegram(bot_token=None, chat_ids=chat_ids)
|
obj = plugins.NotifyTelegram(bot_token=None, chat_ids=chat_ids)
|
||||||
|
@ -1960,3 +1967,127 @@ def test_notify_telegram_plugin(mock_post, mock_get):
|
||||||
title='title', body='body', notify_type=NotifyType.INFO) is False
|
title='title', body='body', notify_type=NotifyType.INFO) is False
|
||||||
assert nimg_obj.notify(
|
assert nimg_obj.notify(
|
||||||
title='title', body='body', notify_type=NotifyType.INFO) is False
|
title='title', body='body', notify_type=NotifyType.INFO) is False
|
||||||
|
|
||||||
|
# Bot Token Detection
|
||||||
|
# Just to make it clear to people reading this code and trying to learn
|
||||||
|
# what is going on. Apprise tries to detect the bot owner if you don't
|
||||||
|
# specify a user to message. The idea is to just default to messaging
|
||||||
|
# the bot owner himself (it makes it easier for people). So we're testing
|
||||||
|
# the creating of a Telegram Notification without providing a chat ID.
|
||||||
|
# We're testing the error handling of this bot detection section of the
|
||||||
|
# code
|
||||||
|
mock_post.return_value.content = dumps({
|
||||||
|
"ok": True,
|
||||||
|
"result": [{
|
||||||
|
"update_id": 645421321,
|
||||||
|
"message": {
|
||||||
|
"message_id": 1,
|
||||||
|
"from": {
|
||||||
|
"id": 532389719,
|
||||||
|
"is_bot": False,
|
||||||
|
"first_name": "Chris",
|
||||||
|
"language_code": "en-US"
|
||||||
|
},
|
||||||
|
"chat": {
|
||||||
|
"id": 532389719,
|
||||||
|
"first_name": "Chris",
|
||||||
|
"type": "private"
|
||||||
|
},
|
||||||
|
"date": 1519694394,
|
||||||
|
"text": "/start",
|
||||||
|
"entities": [{
|
||||||
|
"offset": 0,
|
||||||
|
"length": 6,
|
||||||
|
"type": "bot_command",
|
||||||
|
}],
|
||||||
|
}},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
mock_post.return_value.status_code = requests.codes.ok
|
||||||
|
|
||||||
|
obj = plugins.NotifyTelegram(bot_token=bot_token, chat_ids=None)
|
||||||
|
assert(len(obj.chat_ids) == 1)
|
||||||
|
assert(obj.chat_ids[0] == '532389719')
|
||||||
|
|
||||||
|
# Do the test again, but without the expected (parsed response)
|
||||||
|
mock_post.return_value.content = dumps({
|
||||||
|
"ok": True,
|
||||||
|
"result": [{
|
||||||
|
"message": {
|
||||||
|
"text": "/ignored.entry",
|
||||||
|
}},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
try:
|
||||||
|
obj = plugins.NotifyTelegram(bot_token=bot_token, chat_ids=None)
|
||||||
|
# No chat_ids specified
|
||||||
|
assert(False)
|
||||||
|
|
||||||
|
except TypeError:
|
||||||
|
# Exception should be thrown about the fact no token was specified
|
||||||
|
assert(True)
|
||||||
|
|
||||||
|
# Test our bot detection with a internal server error
|
||||||
|
mock_post.return_value.status_code = requests.codes.internal_server_error
|
||||||
|
try:
|
||||||
|
obj = plugins.NotifyTelegram(bot_token=bot_token, chat_ids=None)
|
||||||
|
# No chat_ids specified
|
||||||
|
assert(False)
|
||||||
|
|
||||||
|
except TypeError:
|
||||||
|
# Exception should be thrown about the fact no token was specified
|
||||||
|
assert(True)
|
||||||
|
|
||||||
|
# Test our bot detection with an unmappable html error
|
||||||
|
mock_post.return_value.status_code = 999
|
||||||
|
try:
|
||||||
|
obj = plugins.NotifyTelegram(bot_token=bot_token, chat_ids=None)
|
||||||
|
# No chat_ids specified
|
||||||
|
assert(False)
|
||||||
|
|
||||||
|
except TypeError:
|
||||||
|
# Exception should be thrown about the fact no token was specified
|
||||||
|
assert(True)
|
||||||
|
|
||||||
|
# Do it again but this time provide a failure message
|
||||||
|
mock_post.return_value.content = dumps({'description': 'Failure Message'})
|
||||||
|
try:
|
||||||
|
obj = plugins.NotifyTelegram(bot_token=bot_token, chat_ids=None)
|
||||||
|
# No chat_ids specified
|
||||||
|
assert(False)
|
||||||
|
|
||||||
|
except TypeError:
|
||||||
|
# Exception should be thrown about the fact no token was specified
|
||||||
|
assert(True)
|
||||||
|
|
||||||
|
# Do it again but this time provide a failure message and perform a
|
||||||
|
# notification without a bot detection by providing at least 1 chat id
|
||||||
|
obj = plugins.NotifyTelegram(bot_token=bot_token, chat_ids=['@abcd'])
|
||||||
|
assert nimg_obj.notify(
|
||||||
|
title='title', body='body', notify_type=NotifyType.INFO) is False
|
||||||
|
|
||||||
|
# Test our exception handling with bot detection
|
||||||
|
test_requests_exceptions = (
|
||||||
|
requests.ConnectionError(
|
||||||
|
0, 'requests.ConnectionError() not handled'),
|
||||||
|
requests.RequestException(
|
||||||
|
0, 'requests.RequestException() not handled'),
|
||||||
|
requests.HTTPError(
|
||||||
|
0, 'requests.HTTPError() not handled'),
|
||||||
|
requests.ReadTimeout(
|
||||||
|
0, 'requests.ReadTimeout() not handled'),
|
||||||
|
requests.TooManyRedirects(
|
||||||
|
0, 'requests.TooManyRedirects() not handled'),
|
||||||
|
)
|
||||||
|
|
||||||
|
# iterate over our exceptions and test them
|
||||||
|
for _exception in test_requests_exceptions:
|
||||||
|
mock_post.side_effect = _exception
|
||||||
|
try:
|
||||||
|
obj = plugins.NotifyTelegram(bot_token=bot_token, chat_ids=None)
|
||||||
|
# No chat_ids specified
|
||||||
|
assert(False)
|
||||||
|
|
||||||
|
except TypeError:
|
||||||
|
# Exception should be thrown about the fact no token was specified
|
||||||
|
assert(True)
|
||||||
|
|
Loading…
Reference in New Issue