From f98ab21b0670aea97b2801e3e944d7443126dcb7 Mon Sep 17 00:00:00 2001 From: azogue Date: Sun, 21 May 2017 11:58:39 +0200 Subject: [PATCH 1/6] bugfix for Telegram chat_ids - Negative `chat_id`s for groups. - Include `chat_id` in event data. - Handle KeyError when receiving other types of messages, as `new_chat_member` ones, and send them as text. --- homeassistant/components/notify/telegram.py | 2 +- .../components/telegram_bot/__init__.py | 30 ++++++++++++------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/notify/telegram.py b/homeassistant/components/notify/telegram.py index 1bc2baa632e6d2..39641bfbf3fc82 100644 --- a/homeassistant/components/notify/telegram.py +++ b/homeassistant/components/notify/telegram.py @@ -27,7 +27,7 @@ CONF_CHAT_ID = 'chat_id' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_CHAT_ID): cv.positive_int, + vol.Required(CONF_CHAT_ID): vol.Coerce(int), }) diff --git a/homeassistant/components/telegram_bot/__init__.py b/homeassistant/components/telegram_bot/__init__.py index 235217d1942ade..3177c26c61f6d3 100644 --- a/homeassistant/components/telegram_bot/__init__.py +++ b/homeassistant/components/telegram_bot/__init__.py @@ -76,7 +76,7 @@ vol.Required(CONF_PLATFORM): cv.string, vol.Required(CONF_API_KEY): cv.string, vol.Required(CONF_ALLOWED_CHAT_IDS): - vol.All(cv.ensure_list, [cv.positive_int]), + vol.All(cv.ensure_list, [vol.Coerce(int)]), vol.Optional(ATTR_PARSER, default=PARSER_MD): cv.string, vol.Optional(CONF_TRUSTED_NETWORKS, default=DEFAULT_TRUSTED_NETWORKS): vol.All(cv.ensure_list, [ip_network]) @@ -84,7 +84,7 @@ }, extra=vol.ALLOW_EXTRA) BASE_SERVICE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_TARGET): vol.All(cv.ensure_list, [cv.positive_int]), + vol.Optional(ATTR_TARGET): vol.All(cv.ensure_list, [vol.Coerce(int)]), vol.Optional(ATTR_PARSER): cv.string, vol.Optional(ATTR_DISABLE_NOTIF): cv.boolean, vol.Optional(ATTR_DISABLE_WEB_PREV): cv.boolean, @@ -113,19 +113,19 @@ SERVICE_EDIT_MESSAGE = 'edit_message' SERVICE_SCHEMA_EDIT_MESSAGE = SERVICE_SCHEMA_SEND_MESSAGE.extend({ vol.Required(ATTR_MESSAGEID): vol.Any(cv.positive_int, cv.string), - vol.Required(ATTR_CHAT_ID): cv.positive_int, + vol.Required(ATTR_CHAT_ID): vol.Coerce(int), }) SERVICE_EDIT_CAPTION = 'edit_caption' SERVICE_SCHEMA_EDIT_CAPTION = vol.Schema({ vol.Required(ATTR_MESSAGEID): vol.Any(cv.positive_int, cv.string), - vol.Required(ATTR_CHAT_ID): cv.positive_int, + vol.Required(ATTR_CHAT_ID): vol.Coerce(int), vol.Required(ATTR_CAPTION): cv.string, vol.Optional(ATTR_KEYBOARD_INLINE): cv.ensure_list, }, extra=vol.ALLOW_EXTRA) SERVICE_EDIT_REPLYMARKUP = 'edit_replymarkup' SERVICE_SCHEMA_EDIT_REPLYMARKUP = vol.Schema({ vol.Required(ATTR_MESSAGEID): vol.Any(cv.positive_int, cv.string), - vol.Required(ATTR_CHAT_ID): cv.positive_int, + vol.Required(ATTR_CHAT_ID): vol.Coerce(int), vol.Required(ATTR_KEYBOARD_INLINE): cv.ensure_list, }, extra=vol.ALLOW_EXTRA) SERVICE_ANSWER_CALLBACK_QUERY = 'answer_callback_query' @@ -480,7 +480,8 @@ def __init__(self, hass, allowed_chat_ids): def _get_message_data(self, msg_data): if (not msg_data or - ('text' not in msg_data and 'data' not in msg_data) or + ('text' not in msg_data and 'data' not in msg_data + and 'chat' not in msg_data) or 'from' not in msg_data or msg_data['from'].get('id') not in self.allowed_chat_ids): # Message is not correct. @@ -490,6 +491,7 @@ def _get_message_data(self, msg_data): return { ATTR_USER_ID: msg_data['from']['id'], + ATTR_CHAT_ID: msg_data['chat']['id'], ATTR_FROM_FIRST: msg_data['from']['first_name'], ATTR_FROM_LAST: msg_data['from']['last_name'] } @@ -503,12 +505,18 @@ def process_message(self, data): if event_data is None: return False - if data[ATTR_TEXT][0] == '/': - pieces = data[ATTR_TEXT].split(' ') - event_data[ATTR_COMMAND] = pieces[0] - event_data[ATTR_ARGS] = pieces[1:] + if 'text' in data: + if data['text'][0] == '/': + pieces = data['text'].split(' ') + event_data[ATTR_COMMAND] = pieces[0] + event_data[ATTR_ARGS] = pieces[1:] + else: + event_data[ATTR_TEXT] = data['text'] + event = EVENT_TELEGRAM_TEXT else: - event_data[ATTR_TEXT] = data[ATTR_TEXT] + # Some other thing... + _LOGGER.warning('SOME OTHER THING RECEIVED --> "%s"', data) + event_data[ATTR_TEXT] = str(data) event = EVENT_TELEGRAM_TEXT self.hass.bus.async_fire(event, event_data) From f07db135fd3b3c48e1ecb03050b9eb5b33da24be Mon Sep 17 00:00:00 2001 From: azogue Date: Sun, 21 May 2017 12:04:11 +0200 Subject: [PATCH 2/6] unused import --- homeassistant/components/notify/telegram.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/notify/telegram.py b/homeassistant/components/notify/telegram.py index 39641bfbf3fc82..fb453263dd8542 100644 --- a/homeassistant/components/notify/telegram.py +++ b/homeassistant/components/notify/telegram.py @@ -8,7 +8,6 @@ import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components.notify import ( ATTR_MESSAGE, ATTR_TITLE, ATTR_DATA, ATTR_TARGET, PLATFORM_SCHEMA, BaseNotificationService) From d3289f4e0de760adce3a774ce25669bb81097ff7 Mon Sep 17 00:00:00 2001 From: azogue Date: Sun, 21 May 2017 13:00:32 +0200 Subject: [PATCH 3/6] fix double quote style, fix boolean expr, change warning msg --- .../components/telegram_bot/__init__.py | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/telegram_bot/__init__.py b/homeassistant/components/telegram_bot/__init__.py index 3177c26c61f6d3..99a7625d67386e 100644 --- a/homeassistant/components/telegram_bot/__init__.py +++ b/homeassistant/components/telegram_bot/__init__.py @@ -198,7 +198,7 @@ def async_setup_platform(p_type, p_config=None, discovery_info=None): return except Exception: # pylint: disable=broad-except - _LOGGER.exception('Error setting up platform %s', p_type) + _LOGGER.exception("Error setting up platform %s", p_type) return notify_service = TelegramNotificationService( @@ -221,7 +221,7 @@ def _render_template_attr(data, attribute): kwargs = dict(service.data) _render_template_attr(kwargs, ATTR_MESSAGE) _render_template_attr(kwargs, ATTR_TITLE) - _LOGGER.debug('NEW telegram_message "%s": %s', msgtype, kwargs) + _LOGGER.debug("NEW telegram_message %s: %s", msgtype, kwargs) if msgtype == SERVICE_SEND_MESSAGE: yield from hass.async_add_job( @@ -300,7 +300,7 @@ def _get_target_chat_ids(self, target): if isinstance(target, int): if target in self.allowed_chat_ids: return [target] - _LOGGER.warning('BAD TARGET "%s", using default: %s', + _LOGGER.warning("BAD TARGET %s, using default: %s", target, self._default_user) else: try: @@ -308,9 +308,9 @@ def _get_target_chat_ids(self, target): if int(t) in self.allowed_chat_ids] if len(chat_ids) > 0: return chat_ids - _LOGGER.warning('ALL BAD TARGETS: "%s"', target) + _LOGGER.warning("ALL BAD TARGETS: %s", target) except (ValueError, TypeError): - _LOGGER.warning('BAD TARGET DATA "%s", using default: %s', + _LOGGER.warning("BAD TARGET DATA %s, using default: %s", target, self._default_user) return [self._default_user] @@ -378,10 +378,10 @@ def _send_msg(self, func_send, msg_error, *args_rep, **kwargs_rep): if not isinstance(out, bool) and hasattr(out, ATTR_MESSAGEID): chat_id = out.chat_id self._last_message_id[chat_id] = out[ATTR_MESSAGEID] - _LOGGER.debug('LAST MSG ID: %s (from chat_id %s)', + _LOGGER.debug("LAST MSG ID: %s (from chat_id %s)", self._last_message_id, chat_id) elif not isinstance(out, bool): - _LOGGER.warning('UPDATE LAST MSG??: out_type:%s, out=%s', + _LOGGER.warning("UPDATE LAST MSG??: out_type:%s, out=%s", type(out), out) return out except TelegramError: @@ -393,7 +393,7 @@ def send_message(self, message="", target=None, **kwargs): text = '{}\n{}'.format(title, message) if title else message params = self._get_msg_kwargs(kwargs) for chat_id in self._get_target_chat_ids(target): - _LOGGER.debug('send_message in chat_id %s with params: %s', + _LOGGER.debug("send_message in chat_id %s with params: %s", chat_id, params) self._send_msg(self.bot.sendMessage, "Error sending message", @@ -404,13 +404,13 @@ def edit_message(self, type_edit, chat_id=None, **kwargs): chat_id = self._get_target_chat_ids(chat_id)[0] message_id, inline_message_id = self._get_msg_ids(kwargs, chat_id) params = self._get_msg_kwargs(kwargs) - _LOGGER.debug('edit_message %s in chat_id %s with params: %s', + _LOGGER.debug("edit_message %s in chat_id %s with params: %s", message_id or inline_message_id, chat_id, params) if type_edit == SERVICE_EDIT_MESSAGE: message = kwargs.get(ATTR_MESSAGE) title = kwargs.get(ATTR_TITLE) text = '{}\n{}'.format(title, message) if title else message - _LOGGER.debug('editing message w/id %s.', + _LOGGER.debug("editing message w/id %s.", message_id or inline_message_id) return self._send_msg(self.bot.editMessageText, "Error editing text message", @@ -432,7 +432,7 @@ def answer_callback_query(self, message, callback_query_id, show_alert=False, **kwargs): """Answer a callback originated with a press in an inline keyboard.""" params = self._get_msg_kwargs(kwargs) - _LOGGER.debug('answer_callback_query w/callback_id %s: %s, alert: %s.', + _LOGGER.debug("answer_callback_query w/callback_id %s: %s, alert: %s.", callback_query_id, message, show_alert) self._send_msg(self.bot.answerCallbackQuery, "Error sending answer callback query", @@ -451,7 +451,7 @@ def send_file(self, is_photo=True, target=None, **kwargs): caption = kwargs.get(ATTR_CAPTION) func_send = self.bot.sendPhoto if is_photo else self.bot.sendDocument for chat_id in self._get_target_chat_ids(target): - _LOGGER.debug('send file %s to chat_id %s. Caption: %s.', + _LOGGER.debug("send file %s to chat_id %s. Caption: %s.", file, chat_id, caption) self._send_msg(func_send, "Error sending file", chat_id, file, caption=caption, **params) @@ -462,7 +462,7 @@ def send_location(self, latitude, longitude, target=None, **kwargs): longitude = float(longitude) params = self._get_msg_kwargs(kwargs) for chat_id in self._get_target_chat_ids(target): - _LOGGER.debug('send location %s/%s to chat_id %s.', + _LOGGER.debug("send location %s/%s to chat_id %s.", latitude, longitude, chat_id) self._send_msg(self.bot.sendLocation, "Error sending location", @@ -479,10 +479,13 @@ def __init__(self, hass, allowed_chat_ids): self.hass = hass def _get_message_data(self, msg_data): - if (not msg_data or - ('text' not in msg_data and 'data' not in msg_data - and 'chat' not in msg_data) or - 'from' not in msg_data or + if not msg_data: + return None + bad_fields = (not hasattr(msg_data, 'text') and + not hasattr(msg_data, 'data') and + not hasattr(msg_data, 'from') and + not hasattr(msg_data, 'chat')) + if (bad_fields or msg_data['from'].get('id') not in self.allowed_chat_ids): # Message is not correct. _LOGGER.error("Incoming message does not have required data (%s)", @@ -515,7 +518,7 @@ def process_message(self, data): event = EVENT_TELEGRAM_TEXT else: # Some other thing... - _LOGGER.warning('SOME OTHER THING RECEIVED --> "%s"', data) + _LOGGER.warning("Message without text data received: %s", data) event_data[ATTR_TEXT] = str(data) event = EVENT_TELEGRAM_TEXT @@ -537,5 +540,5 @@ def process_message(self, data): return True else: # Some other thing... - _LOGGER.warning('SOME OTHER THING RECEIVED --> "%s"', data) + _LOGGER.warning("SOME OTHER THING RECEIVED --> %s", data) return False From fe109971abe59a66de91cc773bb46d503ca3932f Mon Sep 17 00:00:00 2001 From: azogue Date: Sun, 21 May 2017 13:03:05 +0200 Subject: [PATCH 4/6] mistake --- homeassistant/components/telegram_bot/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/telegram_bot/__init__.py b/homeassistant/components/telegram_bot/__init__.py index 99a7625d67386e..c8528074ec5033 100644 --- a/homeassistant/components/telegram_bot/__init__.py +++ b/homeassistant/components/telegram_bot/__init__.py @@ -483,9 +483,8 @@ def _get_message_data(self, msg_data): return None bad_fields = (not hasattr(msg_data, 'text') and not hasattr(msg_data, 'data') and - not hasattr(msg_data, 'from') and not hasattr(msg_data, 'chat')) - if (bad_fields or + if (bad_fields or not hasattr(msg_data, 'from') or msg_data['from'].get('id') not in self.allowed_chat_ids): # Message is not correct. _LOGGER.error("Incoming message does not have required data (%s)", From 7fe756a13f171b1b16a0b6a83c8d919f6a79db5e Mon Sep 17 00:00:00 2001 From: azogue Date: Sun, 21 May 2017 17:23:18 +0200 Subject: [PATCH 5/6] some more fixes - fix if condition for msg bad fields. - return True for a correct but not allowed or not recognized message: if not, the message arrives continuously. - Allow to receive messages from unauthorized users if they come from authorized groups. --- .../components/telegram_bot/__init__.py | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/telegram_bot/__init__.py b/homeassistant/components/telegram_bot/__init__.py index c8528074ec5033..9141ac0eb10430 100644 --- a/homeassistant/components/telegram_bot/__init__.py +++ b/homeassistant/components/telegram_bot/__init__.py @@ -479,19 +479,24 @@ def __init__(self, hass, allowed_chat_ids): self.hass = hass def _get_message_data(self, msg_data): + """Return boolean msg_data_is_ok and dict msg_data.""" if not msg_data: - return None - bad_fields = (not hasattr(msg_data, 'text') and - not hasattr(msg_data, 'data') and - not hasattr(msg_data, 'chat')) - if (bad_fields or not hasattr(msg_data, 'from') or - msg_data['from'].get('id') not in self.allowed_chat_ids): + return False, None + bad_fields = ('text' not in msg_data and + 'data' not in msg_data and + 'chat' not in msg_data) + if bad_fields or 'from' not in msg_data: # Message is not correct. _LOGGER.error("Incoming message does not have required data (%s)", msg_data) - return None - - return { + return False, None + if msg_data['from'].get('id') not in self.allowed_chat_ids \ + or msg_data['chat'].get('id') not in self.allowed_chat_ids: + # Origin is not allowed. + _LOGGER.error("Incoming message is not allowed (%s)", msg_data) + return True, None + + return True, { ATTR_USER_ID: msg_data['from']['id'], ATTR_CHAT_ID: msg_data['chat']['id'], ATTR_FROM_FIRST: msg_data['from']['first_name'], @@ -503,9 +508,9 @@ def process_message(self, data): if ATTR_MSG in data: event = EVENT_TELEGRAM_COMMAND data = data.get(ATTR_MSG) - event_data = self._get_message_data(data) + message_ok, event_data = self._get_message_data(data) if event_data is None: - return False + return message_ok if 'text' in data: if data['text'][0] == '/': @@ -526,9 +531,9 @@ def process_message(self, data): elif ATTR_CALLBACK_QUERY in data: event = EVENT_TELEGRAM_CALLBACK data = data.get(ATTR_CALLBACK_QUERY) - event_data = self._get_message_data(data) + message_ok, event_data = self._get_message_data(data) if event_data is None: - return False + return message_ok event_data[ATTR_DATA] = data[ATTR_DATA] event_data[ATTR_MSG] = data[ATTR_MSG] @@ -540,4 +545,4 @@ def process_message(self, data): else: # Some other thing... _LOGGER.warning("SOME OTHER THING RECEIVED --> %s", data) - return False + return True From 311f2566c24a7493a480cbf315298d1d3a9efaea Mon Sep 17 00:00:00 2001 From: azogue Date: Sun, 21 May 2017 17:30:31 +0200 Subject: [PATCH 6/6] support for `edited_message`s - They come as normal messages, except for the 'edited_message' field instead of 'message'. --- homeassistant/components/telegram_bot/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/telegram_bot/__init__.py b/homeassistant/components/telegram_bot/__init__.py index 9141ac0eb10430..fdc9d16677cc21 100644 --- a/homeassistant/components/telegram_bot/__init__.py +++ b/homeassistant/components/telegram_bot/__init__.py @@ -38,6 +38,7 @@ ATTR_USER_ID = 'user_id' ATTR_ARGS = 'args' ATTR_MSG = 'message' +ATTR_EDITED_MSG = 'edited_message' ATTR_CHAT_INSTANCE = 'chat_instance' ATTR_CHAT_ID = 'chat_id' ATTR_MSGID = 'id' @@ -505,9 +506,12 @@ def _get_message_data(self, msg_data): def process_message(self, data): """Check for basic message rules and fire an event if message is ok.""" - if ATTR_MSG in data: + if ATTR_MSG in data or ATTR_EDITED_MSG in data: event = EVENT_TELEGRAM_COMMAND - data = data.get(ATTR_MSG) + if ATTR_MSG in data: + data = data.get(ATTR_MSG) + else: + data = data.get(ATTR_EDITED_MSG) message_ok, event_data = self._get_message_data(data) if event_data is None: return message_ok