Telegram actionable notifications help

I am trying to set up some telegram actionable notifications.

I have set up the “commands”, these work fine. And I can write the automation or appdaemon script to send messages that include the buttons, but nothing happens when I press them (the /removekeyboard one works fine).

A test automation looks like:

- alias: "test_actionable_notification" # test actionable notifications
  initial_state: True
  trigger:
    - platform: state
      from: 'off'
      to: 'on'
      entity_id: input_boolean.vacation_mode
  action:
    service: notify.home_aephir_bot
    # data:
    data_template:
      message: "Test Notification"
      data:
        inline_keyboard:
          - "Espresso on:/espresso_on, Espresso off:/espresso_off"
          - "Remove keyboard:/removekeyboard"

- id: 'telegram_espresso_on'
  alias: turn on espresso machine
  initial_state: True
  trigger:
    - platform: event
      event_type: telegram_command
      event_data:
        command: '/espresso_on'
  action:
    - service: switch.turn_on
      entity_id: switch.switch

- id: 'telegramremoveinline' # https://community.home-assistant.io/t/telegram-bot-notifications-and-communication/50247
  alias: 'Telegram callback to remove keyboard'
  hide_entity: true
  initial_state: True
  trigger:
    platform: event
    event_type: telegram_callback
    event_data:
      data: '/removekeyboard'
  action:
  - service: telegram_bot.answer_callback_query
    data_template:
      callback_query_id: '{{ trigger.event.data.id }}'
      message: 'OK'
  - service: telegram_bot.edit_replymarkup
    data_template:
      message_id: '{{ trigger.event.data.message.message_id }}'
      chat_id: '{{ trigger.event.data.user_id }}'
      inline_keyboard: []

I get the notification when I turn on the input_boolean:

But pressing either of the top buttons does nothing. However, just typing /espresso_on or espresso_off does exactly what I’d expect; this turns the the switch.switch on/off.

I’ve tried something similar with an appdaemon script, but same behavior. Message and buttons show up, but pressing them has not effect.

Any ideas?

I can’t help you with yaml automations, but this works for me with appdaemon

send the message with

                    kb = [[ ("Done", "/done") ]]
                    self.call_service('telegram_bot/send_message',
                            title = self.get_state(self.args["sensor"], attribute = "message") + " not out",
                            target = self.args["telegram_chat_id"],
                            message = warn_msg,
                            disable_notification = False,
                            inline_keyboard = kb)

listen for an event with

        self.listen_event(self.receive_telegram_callback, 'telegram_callback')

then the callback does

    def receive_telegram_callback(self, event_id, payload_event, *args):
        if payload_event['data'] == '/done':
            self.turn_off(self.args["todo"])

Hope I haven’t missed anything, but let me know if it doesn’t work (or maybe even if it does :grinning: )

1 Like

That’s actually better. I had some old yaml automations, but have been trying mo move all automations to AppDaemon. I only went back because when it didn’t work, I found more config examples in yaml.

Well, pure appademon works:

class TelegramBotEventListener(hass.Hass):
    """
    Event listener for Telegram callbacks.
    """

    def initialize(self):
        """
        Listen to Telegram events of interest.
        """
        # self.listen_event(self.receive_telegram_command, 'telegram_command')
        self.listen_event(self.receive_telegram_callback, 'telegram_callback')


    def receive_telegram_callback(self, event_id, payload_event, *args):
        """
        Match telegram commands with actions.
        """
        if payload_event['data'] == '/espresso_on':
            self.turn_on('switch.switch')
        if payload_event['data'] == '/espresso_off':
            self.turn_off('switch.switch')
        if payload_event['data'] == '/fountain_on':
            self.turn_on('switch.fountain')
        if payload_event['data'] == '/fountain_off':
            self.turn_off('switch.fountain')
        if payload_event['data'] == '/lights_off':
            self.turn_on('script.scene_all_lights_off')
        if payload_event['data'] == '/all_off':
            self.turn_on('script.everyone_left_turn_off_everything')
        if payload_event['data'] == '/set_alarm_away':
            self.turn_on('script.alarm_armed_away')
        if payload_event['data'] == '/set_alarm_all_off':
            self.turn_on('script.alarm_armed_away')
            self.turn_on('script.everyone_left_turn_off_everything')
        if payload_event['data'] == '/guest_mode_off':
            self.turn_off('input_boolean.guest_mode')

Together with this:

"""
Notify (actionable) me if the 'entity' is left 'on' for more than 'start_after' seconds.
Notify every 'time_between_notifications' seconds.
After 'end_after' seconds, turn off and notify.
Set variables in app config (apps.yaml)
"""

import appdaemon.plugins.hass.hassapi as hass
import datetime


class NotifyStatus(hass.Hass):

    def initialize(self):
        """
        Initialize the timers and set listen_state.
        """

        self.starttime = datetime.datetime.now()
        self.timer = None
        self.listen_state(self.StartTimer,self.args['entity'])


    def StartTimer(self, entity, attribute, old, new, kwargs):
        """
        Cancel timer if entity is turned 'off'.
        Otherwise note the time, and start the loop (SendNotification)
        """

        if new == 'off':
            self.cancel_timer(self.timer)

        elif new == 'on' and old == 'off':
            if self.timer != None:
                self.cancel_timer(self.timer)
            self.starttime = datetime.datetime.now()
            self.timer = self.run_in(self.SendNotification,self.args['start_after'])


    def SendNotification(self, kwargs):
        """
        Notify me about leaving the 'entity' on. Repeat every 'time_between_notifications' seconds.
        After 'end_after' seconds, if 'switch_off' is True, automatically turn off, and notify me.
        Remember to cancel timer before each recursive callback, as well as after last action ending the loop.
        """

        delta = datetime.datetime.now() - self.starttime
        seconds = int(datetime.timedelta.total_seconds(delta))
        minutes = round(seconds/60)

        keyboard_1 = [[(self.args['button_1'], self.args['command_1']),
                     (self.args['button_3'], self.args['command_3'])],
                    [(self.args['button_2'], self.args['command_2'])]]

        message_1 = str(self.friendly_name(self.args['entity'])) + " has been " + str(self.args["on_open"]) + " for " + str(minutes) + " minutes"

        keyboard_2 = [[(self.args['button_3'], self.args['command_3']),
                     (self.args['button_1'], self.args['command_1'])],
                    [(self.args['button_2'], self.args['command_2'])]]

        notify = self.args['notify']

        if seconds < self.args['end_after']:

            self.call_service(self.args['notify'],
                                title=self.args['title'],
                                target=self.args['user_id'],
                                message=message_1,
                                inline_keyboard=keyboard_1)

            # self.log(self.args['entity'] + " has been on for " + str(minutes) + " minutes") # for troubleshooting
            self.timer = self.run_in(self.SendNotification,self.args['time_between_notifications'])

        else:

            if self.args['switch_off']:
                self.turn_off(self.args['entity'])
                switchofftext = ", i turned it off."
                self.turn_off('switch.switch')

            else:
                switchofftext = "."

            message_2 = str(self.friendly_name(self.args['entity'])) + " has been " + str(self.args["on_open"]) + " for " + str(minutes) + " minutes" + switchofftext

            self.call_service(self.args['notify'],
                                title=self.args['title'],
                                target=self.args['user_id'],
                                message=message_2,
                                inline_keyboard=keyboard_2)

That said, do you have something in AppDaemon for substituting a removekeyboard automation? I’m having issues translating my automation (I’d want to include this so the keyboard is removed once a button is pressed):

- id: 'telegramremoveinline' # https://community.home-assistant.io/t/telegram-bot-notifications-and-communication/50247
  alias: 'Telegram callback to remove keyboard'
  hide_entity: true
  initial_state: True
  trigger:
    platform: event
    event_type: telegram_callback
    event_data:
      data: '/removekeyboard'
  action:
  - service: telegram_bot.answer_callback_query
    data_template:
      callback_query_id: '{{ trigger.event.data.id }}'
      message: 'OK'
  - service: telegram_bot.edit_replymarkup
    data_template:
      message_id: '{{ trigger.event.data.message.message_id }}'
      chat_id: '{{ trigger.event.data.user_id }}'
      inline_keyboard: []

I don’t have anything that does that, but it doesn’t look hard to translate. What issues are you having?

I guess that it’s just that I don’t know what the different variables are called.

The callback_query_id can (according to another post/appI found) be extracted by payload_event['id']. But I can’t figure out what the others should be. I tried:

class Test(hass.Hass):

    def initialize(self):

        self.listen_event(self.receive_telegram_callback, 'telegram_callback')

    def receive_telegram_callback(self, event_id, payload_event, *args):

        data_callback   = payload_event['data']
        callback_id     = payload_event['id']
        message_id      = payload_event['message_id']
        chat_id         = payload_event['user_id']

        if payload_event['data'] == '/removekeyboard':

            self.call_service(
                'telegram_bot/answer_callback_query',
                message='OK',
                callback_query_id=callback_id
                )

            self.call_service(
                'telegram_bot.edit_replymarkup',
                message_id=message_id,
                chat_id=user_id,
                inline_keyboard=[]
                )

But no luck. I mean, the automation works, I’d just like to move to appdaemon. So it’s not first priority, just a “nice-to-have”.

if you just self.log(payload_event) it should display all the values it contains.

I have to do some emergency traveling, so I’ll look into this later. Thanks for the suggestion!

Did you solve the problem with the yaml automation? I’m having the exact same problem as you.

I won’t be back home before the end of February. I’ll be sure to update here when I do have a chance to look into it (and feel free to do the same, if you figure it out).

OK, back home.

Logs show:

telegram_commands: {'user_id': REDACTED, 'from_first': 'READCTED', 'from_last': 'REDACTED', 'chat_id': REDACTED, 'data': '/removekeyboard', 'message': {'message_id': REDACTED, 'date': 1551767898, 'chat': {'id': REDACTED, 'type': 'private', 'first_name': 'REDACTED', 'last_name': 'REDACTED'}, 'text': 'Espresso machine was left on\nEspresso Machine has been on for 45 minutes', 'entities': [], 'caption_entities': [], 'photo': [], 'new_chat_members': [], 'new_chat_photo': [], 'delete_chat_photo': False, 'group_chat_created': False, 'supergroup_chat_created': False, 'channel_chat_created': False, 'from': {'id': REDACTED, 'first_name': 'REDACTED', 'is_bot': True, 'username': 'REDACTED'}}, 'chat_instance': 'REDACTED', 'id': 'REDACTED'}

There are two things I’m still unclear about.

  1. How do I extract nested dictionary values (e.g. how do I get message_id from something like this: telegram_commands: {'message': {'message_id': XXXX}})
  2. I’m not sure what the equivalent of “callback_id” is? I’m guessing perhaps the last 'id': in the above log?

So I’m guessing something like this, but I think I’m referencing the nested variables wrong (since it’s not working). That, or I’m referencing the wrong callback_id:

    def removeKeyboard(self, event_id, payload_event, *args):

        data_callback   = payload_event['data']
        callback_id     = payload_event['id']
        message_id      = payload_event['message']['message_id'] # How I though I was supposed to get nested dictionary values??
        chat_id         = payload_event['chat_id']