# Telegram Keyboard Action Blueprint # Creates interactive Telegram messages with inline keyboard buttons and # handles button callbacks. See README.md for detailed documentation. # # Author: Alexei Dolgolyov (dolgolyov.alexei@gmail.com) blueprint: name: "Custom: Telegram Keyboard Action" description: > Sends a Telegram message with inline keyboard buttons to multiple chat IDs. When a button is pressed, executes the corresponding callback action. Supports up to 4 buttons with individual callbacks. domain: automation input: # ------------------------------------------------------------------------- # Chat Configuration # ------------------------------------------------------------------------- chat_group: name: "Chats" collapsed: false input: chat_ids: name: Telegram Chat IDs description: > List of numeric chat IDs to send messages to. You can find your chat ID by messaging @userinfobot on Telegram. default: [] selector: text: multiple: true chat_entities: name: Telegram Notification Targets description: > Notify entities with chat ID in friendly name. Format: " ()" - e.g., "Alex (2132562465)" The number in parentheses will be extracted as chat ID. default: [] selector: entity: domain: notify multiple: true config_entry_id: name: Telegram Bot Config Entry ID description: > Required if you have multiple Telegram bots configured. Find this in Home Assistant: Settings → Devices & Services → Telegram Bot → Click on your bot → look at the URL, the ID is after /config_entry/ Example: "01JKXXXXXXXXXXXXXXXXXXX" Leave empty if you only have one Telegram bot. default: "" selector: text: # ------------------------------------------------------------------------- # Message Configuration # ------------------------------------------------------------------------- message_group: name: "Message" collapsed: false input: keyboard_id: name: Keyboard ID description: > Unique identifier for this keyboard. Used to distinguish button presses from different blueprints/automations. default: "keyboard" selector: text: message_text: name: Message Text description: The message displayed above the keyboard buttons default: "Hey, here's a new message" selector: text: multiline: true buttons: name: Button Labels description: > Text displayed on each button (up to 4 buttons). Buttons appear in a single row. default: - "✔" - "✖" selector: text: multiple: true answers: name: Answer Messages (optional) description: > Reply message for each button (same order as buttons). Leave empty to not send any reply when button is pressed. default: [] selector: text: multiple: true hide_keyboard_on_press: name: Hide Keyboard After Press description: Remove the keyboard buttons after any button is pressed default: true selector: boolean: hide_message_on_press: name: Delete Message After Press description: Delete the entire message after any button is pressed default: false selector: boolean: # ------------------------------------------------------------------------- # Button Callbacks # ------------------------------------------------------------------------- callbacks_group: name: "Callbacks" collapsed: false input: button_1_callback: name: Button 1 Callback description: Actions to run when first button is pressed default: [] selector: action: {} button_2_callback: name: Button 2 Callback description: Actions to run when second button is pressed default: [] selector: action: {} button_3_callback: name: Button 3 Callback description: Actions to run when third button is pressed default: [] selector: action: {} button_4_callback: name: Button 4 Callback description: Actions to run when fourth button is pressed default: [] selector: action: {} # ------------------------------------------------------------------------- # Debug # ------------------------------------------------------------------------- debug_group: name: "Debug" collapsed: true input: enable_debug: name: Enable Debug Notifications description: > Send persistent notifications for debugging automation behavior. Shows resolved chat IDs, callback data, and configuration. default: false selector: boolean: # Parallel mode allows multiple button presses to be processed simultaneously mode: parallel # ============================================================================= # Trigger # ============================================================================= trigger: # Listen for Telegram callback events (button presses) - platform: event event_type: telegram_callback id: "telegram_callback" # ============================================================================= # Variables # ============================================================================= variables: # Input references buttons: !input buttons keyboard_id: !input keyboard_id hide_keyboard_on_press: !input hide_keyboard_on_press hide_message_on_press: !input hide_message_on_press answers: !input answers config_entry_id: !input config_entry_id # Callback references (needed for condition checks) button_1_callback: !input button_1_callback button_2_callback: !input button_2_callback button_3_callback: !input button_3_callback button_4_callback: !input button_4_callback # Determine trigger type # When automation is called via service (action), trigger.platform is undefined is_manual_trigger: "{{ trigger.platform is not defined or trigger.platform is none }}" # Check if this callback event belongs to our keyboard # Callback data format: /_ is_our_callback: > {% if is_manual_trigger %} false {% else %} {% set callback_data = trigger.event.data.data | default('') %} {{ callback_data.startswith('/' ~ keyboard_id ~ '_') }} {% endif %} # Debug flag is_debug: !input enable_debug # ============================================================================= # Actions # ============================================================================= action: # --------------------------------------------------------------------------- # Debug Logging - Callback Events # --------------------------------------------------------------------------- - choose: - conditions: - condition: template value_template: "{{ is_debug and not is_manual_trigger }}" sequence: - service: persistent_notification.create data: title: "Telegram Keyboard Debug - Callback" message: > **Trigger Info:** - Platform: {{ trigger.platform | default('unknown') }} - Is our callback: {{ is_our_callback }} {% if is_our_callback %} **Callback Data:** - Raw data: {{ trigger.event.data.data | default('N/A') }} - Chat ID: {{ trigger.event.data.chat_id | default('N/A') }} - Message ID: {{ trigger.event.data.message.message_id | default('N/A') }} - Keyboard ID: {{ keyboard_id }} {% else %} **Ignored** (callback not for this keyboard) - Expected prefix: /{{ keyboard_id }}_ - Received: {{ trigger.event.data.data | default('N/A') }} {% endif %} **Config:** - Config Entry ID: {{ config_entry_id if config_entry_id | length > 0 else '(not set)' }} # --------------------------------------------------------------------------- # Handle Button Press (Callback Event) # --------------------------------------------------------------------------- - choose: - conditions: - condition: template value_template: "{{ is_our_callback }}" sequence: - variables: # Extract data from the callback event callback_data: "{{ trigger.event.data.data }}" chat_id: "{{ trigger.event.data.chat_id }}" message_id: "{{ trigger.event.data.message.message_id }}" # Extract button index from callback data (format: /_) # This handles multi-digit indices correctly button_index: > {% set prefix = '/' ~ keyboard_id ~ '_' %} {% set idx_str = callback_data[prefix | length:] %} {{ idx_str | int(-1) }} # Execute the appropriate button callback - choose: - conditions: "{{ button_index == 0 and button_1_callback | length > 0 }}" sequence: !input button_1_callback - conditions: "{{ button_index == 1 and button_2_callback | length > 0 }}" sequence: !input button_2_callback - conditions: "{{ button_index == 2 and button_3_callback | length > 0 }}" sequence: !input button_3_callback - conditions: "{{ button_index == 3 and button_4_callback | length > 0 }}" sequence: !input button_4_callback # Send answer message if configured for this button - choose: - conditions: > {{ button_index >= 0 and button_index < (answers | length) and (answers[button_index] | length) > 0 }} sequence: - service: telegram_bot.send_message data: target: "{{ chat_id }}" message: "{{ answers[button_index] }}" # Reply to original message unless we're deleting it reply_to_message_id: > {{ omit if hide_message_on_press else message_id }} config_entry_id: > {{ config_entry_id if config_entry_id | length > 0 else omit }} # Handle message/keyboard cleanup - choose: # Delete entire message - conditions: "{{ hide_message_on_press }}" sequence: - service: telegram_bot.delete_message data: chat_id: "{{ chat_id }}" message_id: "{{ message_id }}" config_entry_id: > {{ config_entry_id if config_entry_id | length > 0 else omit }} # Just remove keyboard (keep message) - conditions: "{{ hide_keyboard_on_press }}" sequence: - service: telegram_bot.edit_replymarkup data: chat_id: "{{ chat_id }}" message_id: "{{ message_id }}" inline_keyboard: [] config_entry_id: > {{ config_entry_id if config_entry_id | length > 0 else omit }} # --------------------------------------------------------------------------- # Send Initial Message (Manual Trigger / Service Call) # --------------------------------------------------------------------------- - choose: - conditions: - condition: template value_template: "{{ is_manual_trigger }}" sequence: - variables: # Get chat IDs from direct input chat_ids: !input chat_ids # Extract chat IDs from notify entity friendly names # Format: "Name (123456789)" or "Name (-123456789)" -> extracts the number # Supports both positive (user) and negative (group/channel) chat IDs chat_entities: !input chat_entities chat_ids_from_entities: > {% set ns = namespace(ids=[]) %} {% for entity in chat_entities %} {% set friendly_name = state_attr(entity, 'friendly_name') | default('') %} {% set match = friendly_name | regex_findall('\\((-?\\d+)\\)') %} {% if match | length > 0 %} {% set ns.ids = ns.ids + [match[0]] %} {% endif %} {% endfor %} {{ ns.ids }} # Combine all chat IDs result_chat_ids: "{{ (chat_ids + chat_ids_from_entities) | unique | list }}" # Build inline keyboard with callback data # Format: ["Button Text:/_", ...] message_text: !input message_text inline_keyboard: > {% set ns = namespace(buttons=[]) %} {% for i in range(buttons | length) %} {% set callback_data = '/' ~ keyboard_id ~ '_' ~ i %} {% set ns.buttons = ns.buttons + [(buttons[i] ~ ':' ~ callback_data)] %} {% endfor %} {{ ns.buttons }} # Debug logging for manual trigger - choose: - conditions: - condition: template value_template: "{{ is_debug }}" sequence: - service: persistent_notification.create data: title: "Telegram Keyboard Debug - Send Message" message: > **Chat ID Resolution:** - Direct chat IDs: {{ chat_ids | join(', ') if chat_ids | length > 0 else '(none)' }} - From entities: {{ chat_ids_from_entities | join(', ') if chat_ids_from_entities | length > 0 else '(none)' }} - **Resolved IDs: {{ result_chat_ids | join(', ') if result_chat_ids | length > 0 else '(none)' }}** **Entity Parsing:** {% for entity in chat_entities %} - {{ entity }}: "{{ state_attr(entity, 'friendly_name') | default('N/A') }}" {% else %} - (no entities configured) {% endfor %} **Message:** - Keyboard ID: {{ keyboard_id }} - Buttons: {{ buttons | join(', ') }} - Message: {{ message_text[:100] }}{{ '...' if message_text | length > 100 else '' }} **Config:** - Config Entry ID: {{ config_entry_id if config_entry_id | length > 0 else '(not set)' }} # Validate we have at least one chat ID - choose: - conditions: "{{ result_chat_ids | length == 0 }}" sequence: - stop: "No chat IDs resolved. Check chat_ids or chat_entities configuration." # Send the message with keyboard - service: telegram_bot.send_message data: target: "{{ result_chat_ids }}" message: "{{ message_text }}" inline_keyboard: "{{ inline_keyboard }}" config_entry_id: > {{ config_entry_id if config_entry_id | length > 0 else omit }}