From 57803087ee515eccbb8b859d0350a2e4f435a1c5 Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Thu, 22 Jan 2026 02:56:46 +0300 Subject: [PATCH] [Claude] - Analyze `Telegram Questions.yaml` file designed to work as automation blueprint for Home Assistant OS. Refactor it improving overall code quality, fix obvious or critical bugs/mistakes, fix spelling if required and add comments that will make the code more easy to read and understand. Let's also change notification message, so it will include friendly name of an entity instead of entity id. --- Common/Telegram Question.yaml | 355 ++++++++++++++++++++++------------ 1 file changed, 227 insertions(+), 128 deletions(-) diff --git a/Common/Telegram Question.yaml b/Common/Telegram Question.yaml index 0222387..79863ac 100644 --- a/Common/Telegram Question.yaml +++ b/Common/Telegram Question.yaml @@ -1,136 +1,213 @@ +# ============================================================================= +# Telegram Keyboard Action Blueprint +# ============================================================================= +# This blueprint creates interactive Telegram messages with inline keyboard +# buttons. When a button is pressed, the corresponding callback action runs. +# +# How It Works: +# 1. Manual trigger (service call) -> Sends message with keyboard to chat(s) +# 2. Button press -> Triggers telegram_callback event +# 3. Blueprint matches callback data to keyboard_id +# 4. Executes the corresponding button's callback action +# 5. Optionally sends answer and/or hides keyboard/message +# +# Chat ID Resolution: +# Chat IDs can be provided in two ways: +# - Directly as text list (chat_ids input) +# - From notify entities with friendly names like "Alex (123456789)" +# The number in parentheses is extracted as the chat ID +# +# Callback Data Format: +# Each button sends callback data: /_ +# Example: /my_keyboard_0 for first button of keyboard "my_keyboard" +# ============================================================================= + blueprint: name: "Custom: Telegram Keyboard Action" description: > - Sends a Telegram message with inline keyboard buttons to multiple chat IDs - when executed manually, and reacts to button presses by performing - the corresponding action (one per button). + 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 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: "List of notification entities (entity friendly names must be in format ` ()`, for example `Alex (2132562465)` )" + 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 - + + # ------------------------------------------------------------------------- + # Message Configuration + # ------------------------------------------------------------------------- message_group: name: "Message" collapsed: false - input: + input: keyboard_id: name: Keyboard ID - description: Identifier of the keyboard. This identifier should be unique to distinguish keyboards. - default: 'keyboard' + 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: Text of the message - default: '✉︎ Hey, that a new message' + description: The message displayed above the keyboard buttons + default: "Hey, here's a new message" selector: text: - + multiline: true + buttons: - name: Buttons - description: List of buttons texts + 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: Answers - description: List of answers (optional) + 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 - + multiple: true + hide_keyboard_on_press: - name: Hide Keyboard On Any Button Press - description: Controls if keyboard must be hidden after any button 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: Hide Message On Any Button Press - description: Control if the message must be hidden after any button press + name: Delete Message After Press + description: Delete the entire message after any button is pressed default: false selector: - boolean: - + boolean: + + # ------------------------------------------------------------------------- + # Button Callbacks + # ------------------------------------------------------------------------- callbacks_group: name: "Callbacks" collapsed: false - input: + input: button_1_callback: name: Button 1 Callback + description: Actions to run when first button is pressed default: [] selector: - action: {} - + action: {} + button_2_callback: name: Button 2 Callback + description: Actions to run when second button is pressed default: [] selector: - action: {} - + action: {} + button_3_callback: name: Button 3 Callback + description: Actions to run when third button is pressed default: [] selector: - action: {} - + action: {} + button_4_callback: name: Button 4 Callback + description: Actions to run when fourth button is pressed default: [] selector: - action: {} + action: {} +# 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: - buttons: !input buttons + # 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 - - is_action: "{{ trigger.platform is none }}" - should_call_keyboard_callback: > - {% if trigger.platform is none%} + + # 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 button_id = trigger.event.data.data %} - {{ button_id.startswith('/' ~ keyboard_id) }} + {% set callback_data = trigger.event.data.data | default('') %} + {{ callback_data.startswith('/' ~ keyboard_id ~ '_') }} {% endif %} - is_debug: false - -trigger: - - platform: event - event_type: telegram_callback - -action: - # Debug info (log if required) + # Debug flag - set to true to enable persistent notifications for troubleshooting + is_debug: false + +# ============================================================================= +# Actions +# ============================================================================= +action: + # --------------------------------------------------------------------------- + # Debug Logging (optional) + # --------------------------------------------------------------------------- - choose: - conditions: - condition: template @@ -138,107 +215,129 @@ action: sequence: - service: persistent_notification.create data: - title: "Debug Info" + title: "Telegram Keyboard Debug" message: > - should_call_keyboard_callback = {{ should_call_keyboard_callback}} + Trigger platform: {{ trigger.platform | default('manual') }} + Is manual trigger: {{ is_manual_trigger }} + Is our callback: {{ is_our_callback }} + {% if not is_manual_trigger %} + Callback data: {{ trigger.event.data.data | default('N/A') }} + Chat ID: {{ trigger.event.data.chat_id | default('N/A') }} + {% endif %} + # --------------------------------------------------------------------------- + # Handle Button Press (Callback Event) + # --------------------------------------------------------------------------- - choose: - - conditions: - condition: template - value_template: "{{ should_call_keyboard_callback | bool }}" - sequence: + - 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 }}" - idx: "{{ callback_data[callback_data | length - 1] | int }}" + 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: "{{ idx == 0 and button_1_callback != [] }}" + - conditions: "{{ button_index == 0 and button_1_callback | length > 0 }}" sequence: !input button_1_callback - - conditions: "{{ idx == 1 and button_2_callback != [] }}" + - conditions: "{{ button_index == 1 and button_2_callback | length > 0 }}" sequence: !input button_2_callback - - conditions: "{{ idx == 2 and button_3_callback != [] }}" + - conditions: "{{ button_index == 2 and button_3_callback | length > 0 }}" sequence: !input button_3_callback - - conditions: "{{ idx == 3 and button_4_callback != [] }}" - sequence: !input button_4_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: "{{ idx != -1 and answers | length > idx and answers[idx] | length > 0 }}" + - conditions: > + {{ button_index >= 0 and button_index < (answers | length) + and (answers[button_index] | length) > 0 }} sequence: - - choose: - - conditions: "{{ hide_message_on_press }}" - sequence: - - service: telegram_bot.send_message - data: - target: "{{ chat_id }}" - message: "{{ answers[idx] }}" - - default: - - service: telegram_bot.send_message - data: - target: "{{ chat_id }}" - message: "{{ answers[idx] }}" - reply_to_message_id: "{{ trigger.event.data.message.message_id }}" - + - 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 }} + + # Handle message/keyboard cleanup - choose: - # Remove keyboard. + # Delete entire message - conditions: "{{ hide_message_on_press }}" - sequence: + sequence: - service: telegram_bot.delete_message data: - chat_id: "{{ trigger.event.data.chat_id }}" - message_id: "{{ trigger.event.data.message.message_id }}" - - # Remove keyboard. + chat_id: "{{ chat_id }}" + message_id: "{{ message_id }}" + + # Just remove keyboard (keep message) - conditions: "{{ hide_keyboard_on_press }}" - sequence: + sequence: - service: telegram_bot.edit_replymarkup data: - chat_id: "{{ trigger.event.data.chat_id }}" - message_id: "{{ trigger.event.data.message.message_id }}" - inline_keyboard: [] - - - conditions: - condition: template - value_template: "{{ is_action }}" - sequence: - - variables: + chat_id: "{{ chat_id }}" + message_id: "{{ message_id }}" + inline_keyboard: [] + + # --------------------------------------------------------------------------- + # 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)" -> extracts "123456789" chat_entities: !input chat_entities chat_ids_from_entities: > - {% set ns = namespace(numbers=[]) %} - {% for e in chat_entities %} - {% set friendly_name = state_attr(e, 'friendly_name') %} - {% set ns.numbers = ns.numbers + [ friendly_name | regex_findall_index('\((\d+)\)', 0) ] %} + {% 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.numbers }} - - chat_ids: !input chat_ids - result_chat_ids: "{{ chat_ids + chat_ids_from_entities }}" - + {{ 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 - keyboard_text: > - {% set ns = namespace(result = []) %} - {% for i in range(buttons|length) %} - {% set ns.result = ns.result + [(buttons[i] ~ ':/' ~ keyboard_id ~ i)] %} + 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.result }} - + {{ ns.buttons }} + + # Validate we have at least one chat ID - choose: - # Broadcast - - conditions: - condition: template - value_template: "{{ result_chat_ids | length == 0 }}" + - conditions: "{{ result_chat_ids | length == 0 }}" sequence: - - stop: "No chat ID(s) were resolved. No message will be sent." - - # Target - - conditions: - condition: template - value_template: "{{ result_chat_ids | length != 0 }}" - sequence: - - service: telegram_bot.send_message - data: - target: "{{ result_chat_ids }}" - message: "{{ message_text }}" - inline_keyboard: "{{ keyboard_text }}" - + - 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 }}"