Telegram improvements; AppriseAsset refactoring

pull/8/head
Chris Caron 2018-03-03 17:34:10 -05:00
parent 44a053c443
commit 5b9be6bcc4
15 changed files with 432 additions and 57 deletions

View File

@ -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'&emsp;').\
replace(u' ', u'&nbsp;') if whitespace:
# Tidy up whitespace too
escaped = escaped\
.replace(u'\t', u'&emsp;')\
.replace(u' ', u'&nbsp;')
if convert_new_lines: if convert_new_lines:
return escaped.replace(u'\n', u'&lt;br/&gt;') return escaped.replace(u'\n', u'&lt;br/&gt;')
@ -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']

View File

@ -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 '

View File

@ -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):

View File

@ -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

View File

@ -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 '

View File

@ -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 '

View File

@ -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.')

View File

@ -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.')

View File

@ -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 ' % (

View File

@ -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 ' % (

View File

@ -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 (&nbsp;) and tabs (&emsp;) aren't supported
# HTML # See https://core.telegram.org/bots/api#html-style
payload['parse_mode'] = 'HTML' title = re.sub('&nbsp;?', ' ', title, re.I)
payload['text'] = '<b>%s</b>\r\n%s' % (title, body) body = re.sub('&nbsp;?', ' ', body, re.I)
# Tabs become 3 spaces
title = re.sub('&emsp;?', ' ', title, re.I)
body = re.sub('&emsp;?', ' ', 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

View File

@ -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 ' % (

View File

@ -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(

View File

@ -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 '

View File

@ -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)