Sistema di pre-risposta su Telegram

Contesto

Quando un bot deve eseguire un’elaborazione lunga prima di rispondere, la chat rimane muta. L’utente non sa se il bot ha ricevuto il comando, se sta lavorando o se si è fermato. Telegram offre tre strumenti API per gestire questo scenario:

  • sendChatAction — mostra un indicatore temporaneo (tip tapping, caricamento, etc.)
  • sendMessage — invia un messaggio nella chat
  • editMessageText — modifica il testo di un messaggio già inviato dallo stesso bot

Combinandoli si ottiene una “pre-risposta”: un messaggio provvisorio che viene progressivamente arricchito o sostituito dal risultato finale.

Funzionamento

Il flusso è semplice:

  1. si invia sendChatAction("typing") per segnalare attività
  2. si invia sendMessage("Sto elaborando...") e si salva il message_id restituito
  3. si esegue il lavoro
  4. si usa editMessageText su quel message_id per pubblicare il risultato

Il punto chiave: editMessageText sostituisce il contenuto del messaggio, non aggiunge in coda. Per simulare un accumulo progressivo, il client deve conservare il testo precedente e concatenarlo con il nuovo frammento prima di inviare la modifica.

Adattamento per Bash (curl + array)

Prerequisiti

  • Bash 4+
  • curl installato
  • sed (POSIX, presente su ogni Unix-like)
  • array BOT_TOKENS e CHAT_IDS già definiti

Stato persistente per coppia bot/chat

Ogni invio può avere più bot token e più chat. Serve uno stato separato per ogni combinazione BOT_TOKEN|CHAT_ID, altrimenti la modifica colpirebbe un messaggio sbagliato.

declare -gA TELEGRAM_MSG_ID=()
declare -gA TELEGRAM_MSG_TEXT=()

La chiave è una stringa concatenata con pipe:

_telegram_key() {
    local bot_token="$1"
    local chat_id="$2"
    printf '%s|%s' "$bot_token" "$chat_id"
}

telegram_msg

Invia il messaggio, estrae message_id dal JSON, e lo salva nell’associative array insieme al testo inviato.

telegram_msg() {
    local msg="$1"
    local response message_id key
 
    for BOT_TOKEN in "${BOT_TOKENS[@]}"; do
        for CHAT_ID in "${CHAT_IDS[@]}"; do
            response="$(curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
                -d chat_id="${CHAT_ID}" \
                --data-urlencode text="${msg}")" || true
 
            message_id="$(printf '%s' "$response" | sed -n 's/.*"message_id":\([0-9]\{1,\}\).*/\1/p')"
 
            if [[ -n "$message_id" ]]; then
                key="$(_telegram_key "$BOT_TOKEN" "$CHAT_ID")"
                TELEGRAM_MSG_ID["$key"]="$message_id"
                TELEGRAM_MSG_TEXT["$key"]="$msg"
            fi
        done
    done
}

Note:

  • --data-urlencode evita problemi con multilinea e caratteri speciali
  • sed estrae il solo message_id dal JSON di risposta (cerca il pattern "message_id":NUMERO)
  • se la chiamata fallisce, non viene salvato alcun id

telegram_edit

Legge lo stato della coppia bot/chat, costruisce il nuovo testo (append o replace), chiama editMessageText e aggiorna lo stato.

telegram_edit() {
    local new_chunk="$1"
    local mode="${2:-append}"   # append | replace
    local BOT_TOKEN CHAT_ID key current_text new_text message_id
 
    for BOT_TOKEN in "${BOT_TOKENS[@]}"; do
        for CHAT_ID in "${CHAT_IDS[@]}"; do
            key="$(_telegram_key "$BOT_TOKEN" "$CHAT_ID")"
            message_id="${TELEGRAM_MSG_ID[$key]}"
            current_text="${TELEGRAM_MSG_TEXT[$key]}"
 
            [[ -z "$message_id" ]] && continue
 
            if [[ "$mode" == "replace" ]]; then
                new_text="$new_chunk"
            else
                new_text="${current_text}${new_chunk}"
            fi
 
            curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/editMessageText" \
                -d chat_id="${CHAT_ID}" \
                -d message_id="${message_id}" \
                --data-urlencode text="${new_text}" >/dev/null || true
 
            TELEGRAM_MSG_TEXT["$key"]="$new_text"
        done
    done
}

telegram_typing

Invia sendChatAction("typing") a tutte le destinazioni. Non richiede message_id.

telegram_typing() {
    local BOT_TOKEN CHAT_ID
 
    for BOT_TOKEN in "${BOT_TOKENS[@]}"; do
        for CHAT_ID in "${CHAT_IDS[@]}"; do
            curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendChatAction" \
                -d chat_id="${CHAT_ID}" \
                -d action="typing" >/dev/null || true
        done
    done
}

Pattern di utilizzo

Base: typing + placeholder + replace finale

telegram_typing
telegram_msg "Elaborazione in corso..."
 
# lavoro lungo
sleep 5
 
telegram_edit "Risultato finale completo." replace

Progressivo: append a blocchi

telegram_typing
telegram_msg "Avvio procedura..."
 
ricevi_output_primo_passo
telegram_edit "\n- Step 1: completato"
 
ricevi_output_secondo_passo
telegram_edit "\n- Step 2: completato"
 
telegram_edit "\n\n**Fatto.**" replace

Limiti e note operative

AspettoDettaglio
Modificaun bot può modificare solo i propri messaggi
Appendnon esiste nel protocollo Telegram: è simulata via concatenazione lato client
Testo massimoeditMessageText ha lo stesso limite di sendMessage (4096 caratteri nella Bot API standard)
Troppe richiestela Bot API ha rate limit: non chiamare telegram_edit più di ~20 volte al minuto per chat
Messaggi già inviati via sendDocumentnon sono editabili; in quel caso tenere separato un messaggio testuale di stato
sendChatActionva ripetuto ogni ~5 secondi perché Telegram lo dismette dopo pochi secondi; non scrive nulla in chat

Estensioni possibili

  • aggiungere parse_mode=Markdown o HTML nei parametri di sendMessage e editMessageText
  • introdurre disable_web_page_preview=true per messaggi con link
  • errore handling esplicito (ritorno del codice HTTP o del campo ok del JSON Telegram)
  • funzione telegram_file() coordinata che invia un documento e un messaggio di stato separato