Duplicati and Telegram

I was slow to understand that it only sends messages in case of failure, so I set it to send in all cases.
Thank you for your help!

1 Like

@bd8392 thanks for the instruction. Some additions:

  apprise:
    image: caronc/apprise:latest
    container_name: apprise
    ports:
      - 8000:8000
    volumes:
      - /var/lib/apprise/config:/config
    privileged: true

And you should grant the correct access rights for the config folder according to https://hub.docker.com/r/caronc/apprise

2 Likes

Welcome to the forum @aleksej.kuznecow and thanks for contributing the addition.
It’s wonderful to see the community helping to build what is truly a community effort.

1 Like

good morning everyone, after working all day to make the script better for my needs I would like to share it with you, I created a script called “telegram.sh” and inserted it inside /opt/scripts which is mounted inside /script in the docker container (volumes: - /opt/duplicati/scripts:/scripts) then I ran sudo chmod +x ./telegram.sh to give execution permissions. Then I modified my backup in the Duplicati GUI and in the advanced options I selected “run-script-after” and in the text box I inserted the path of the sh file or “/scripts/telegram.sh”. You can change the text to your liking for your language in my case I am Italian and I will share it with the text in Italian but feel free to modify it, whoever decides to improve it would be a good thing if they shared it as I did so that it can help other people like you did it with me. Attached I leave the terminal.sh script:

#!/bin/bash
#
# Script Configuration.
DEBUG_MODE="0"  # Impostalo su "1" per abilitare il debug
SCRIPT_FULLFN="$(basename "${0}")"
LOGFILE="/tmp/${SCRIPT_FULLFN%.*}.log"
LOG_MAX_LINES="1000"
SERVICE_NAME="Duplicati"
#
#
# -----------------
# --- Functions ---
# -----------------
logAdd ()
{
    TMP_DATETIME="$(date '+%Y-%m-%d [%H-%M-%S]')"
    TMP_LOGSTREAM="$(tail -n ${LOG_MAX_LINES} ${LOGFILE} 2>/dev/null)"
    echo "${TMP_LOGSTREAM}" > "$LOGFILE"
    if [ "$1" == "-q" ]; then
        #
        # Quiet mode.
        #
        echo "${TMP_DATETIME} ${@:2}" >> "${LOGFILE}"
    else
        #
        # Loud mode.
        #
        echo "${TMP_DATETIME} $*" | tee -a "${LOGFILE}"
    fi
    return
}
sendTelegramNotification ()
{
    #
    # Usage:            sendTelegramNotification "<PN_TEXT>"
    # Example:          sendTelegramNotification "Test push message"
    #
    # Returns:
    #
    # - "0" on FAILURE.
    # - "1" on SUCCESS.
    #
    # Consts.
    CURL_BIN="curl"
    CURL_TIMEOUT="10"
    # 
    # Telegram Bot
    STN_TELEGRAM_BOT_ID="TO_FILL"
    STN_TELEGRAM_BOT_APIKEY="TO_FILL"
    # 
    #  cf
    STN_TELEGRAM_CHAT_ID="TO_FILL"
    #
    # Variables.
    #
    STN_TEXT="$1"
    # Encode the text for URL
    STN_TEXT=$(echo -n "$STN_TEXT" | sed 's/ /%20/g; s/\n/%0A/g; s/<b>/%3Cb%3E/g; s/<\/b>/%3C%2Fb%3E/g; s/<i>/%3Ci%3E/g; s/<\/i>/%3C%2Fi%3E/g')

    #
    # Log message.
    #
    if [ "${DEBUG_MODE}" == "1" ]; then
        logAdd -q "${MY_SERVICE_NAME}::sendTelegramNotification() fired."
    fi

    CURL_COMMAND="${CURL_BIN} -s --max-time ${CURL_TIMEOUT} \"https://api.telegram.org/bot${STN_TELEGRAM_BOT_ID}:${STN_TELEGRAM_BOT_APIKEY}/sendMessage?chat_id=${STN_TELEGRAM_CHAT_ID}&parse_mode=HTML&text=${STN_TEXT}\""
    
    # Log the CURL command for debugging
    logAdd "[DEBUG] CURL command: ${CURL_COMMAND}"

    RESPONSE=$(eval ${CURL_COMMAND})
    logAdd "[DEBUG] CURL response: ${RESPONSE}"

    if echo "${RESPONSE}" | grep -Fiq "\"ok\":true"; then
        logAdd -q "${MY_SERVICE_NAME}::sendTelegramNotification: ... SUCCESS."
        echo -n "1"
    else
        logAdd -q "${MY_SERVICE_NAME}::sendTelegramNotification: ... FAILED."
        echo -n "0"
    fi
    return
}
# -----------------------------------------------------
# --------------- END OF FUNCTION BLOCK ---------------
# -----------------------------------------------------
#
# Script Main.
#
# Validate env vars we got from Duplicati.
if  [ -z "${DUPLICATI__backup_name}" ]; then
    export DUPLICATI__backup_name="API_ERR"
fi
#
if  [ -z "${DUPLICATI__PARSED_RESULT}" ]; then
    export DUPLICATI__PARSED_RESULT="API_ERR"
fi
#
if  [ -z "${DUPLICATI__EVENTNAME}" ]; then
    export DUPLICATI__EVENTNAME="API_ERR"
fi
#
if  [ -z "${DUPLICATI__OPERATIONNAME}" ]; then
    export DUPLICATI__OPERATIONNAME="API_ERR"
fi
#
if [ "${DEBUG_MODE}" == "1" ]; then
    env | grep "DUPLICATI__"
fi
#
# Prepare chat message.
if [ "${DUPLICATI__PARSED_RESULT}" == "Success" ]; then
    CHAT_MESSAGE="<b>${SERVICE_NAME}</b>%0A<i>💾 ${DUPLICATI__backup_name}</i>%0A%0A✅ Il backup è stato completato con successo alle ore $(date '+%H:%M')."
elif [ "${DUPLICATI__PARSED_RESULT}" == "Warning" ]; then
    CHAT_MESSAGE="<b>${SERVICE_NAME}</b>%0A<i>💾 ${DUPLICATI__backup_name}</i>%0A%0A⚠️ Attenzione: Il backup ha completato con avvisi alle ore $(date '+%H:%M'). Verifica i log per ulteriori dettagli."
elif [ "${DUPLICATI__PARSED_RESULT}" == "Error" ]; then
    CHAT_MESSAGE="<b>${SERVICE_NAME}</b>%0A<i>💾 ${DUPLICATI__backup_name}</i>%0A%0A❌ Errore: Il backup non è riuscito alle ore $(date '+%H:%M'). Controlla i log per identificare il problema."
elif [ "${DUPLICATI__PARSED_RESULT}" == "Fatal" ]; then
    CHAT_MESSAGE="<b>${SERVICE_NAME}</b>%0A<i>💾 ${DUPLICATI__backup_name}</i>%0A%0A💥 Errore grave: Il backup è fallito irreparabilmente alle ore $(date '+%H:%M'). Intervieni subito per risolvere il problema!"
elif [ "${DUPLICATI__PARSED_RESULT}" == "Unknown" ]; then
    CHAT_MESSAGE="<b>${SERVICE_NAME}</b>%0A<i>💾 ${DUPLICATI__backup_name}</i>%0A%0A🤔 Stato sconosciuto: Non è stato possibile determinare il risultato del backup alle ore $(date '+%H:%M'). Verifica manualmente."
fi
#
# Always send chat message.
logAdd "[INFO] Sending chat message [${CHAT_MESSAGE}] ..."
#
# Send chat message.
SEND_RESULT="$(sendTelegramNotification "${CHAT_MESSAGE}")"
if [ "${SEND_RESULT}" == "0" ]; then
    logAdd "[ERROR] Failed to send telegram notification."
    exit 99
fi
#
logAdd "[INFO] Successfully sent telegram notification."
exit 0
2 Likes

Great Work,Your script worked after some corrections.

For all those who are not fluent in Italian here is English version.

#!/bin/bash
#
# Script Configuration.
DEBUG_MODE="0"  # Set to "1" to enable debugging
SCRIPT_FULLFN="$(basename "${0}")"
LOGFILE="/tmp/${SCRIPT_FULLFN%.*}.log"
LOG_MAX_LINES="1000"
SERVICE_NAME="Duplicati"
#
# -----------------
# --- Functions ---
# -----------------
logAdd ()
{
    TMP_DATETIME="$(date '+%Y-%m-%d [%H-%M-%S]')"
    TMP_LOGSTREAM="$(tail -n ${LOG_MAX_LINES} ${LOGFILE} 2>/dev/null)"
    echo "${TMP_LOGSTREAM}" > "$LOGFILE"
    if [ "$1" == "-q" ]; then
        #
        # Quiet mode.
        #
        echo "${TMP_DATETIME} ${@:2}" >> "${LOGFILE}"
    else
        #
        # Loud mode.
        #
        echo "${TMP_DATETIME} $*" | tee -a "${LOGFILE}"
    fi
    return
}

sendTelegramNotification ()
{
    #
    # Usage:            sendTelegramNotification "<MESSAGE_TEXT>"
    # Example:          sendTelegramNotification "Test push message"
    #
    # Returns:
    #
    # - "0" on FAILURE.
    # - "1" on SUCCESS.
    #
    # Constants.
    CURL_BIN="curl"
    CURL_TIMEOUT="10"
    # 
    # Telegram Bot
    STN_TELEGRAM_BOT_ID="56834254534"  # Replace with your bot ID   Token Example - 56834254534:AAEBVSefdisfndi45345ifndis
    STN_TELEGRAM_BOT_APIKEY="AAEBVSefdisfndi45345ifndis"  # Replace with your bot API key
    # 
    # Chat ID
    STN_TELEGRAM_CHAT_ID="434346356"  # Replace with your chat ID
    #
    # Variables.
    #
    STN_TEXT="$1"
    # Encode the text for URL
    STN_TEXT=$(echo -n "$STN_TEXT" | sed 's/ /%20/g; s/\n/%0A/g; s/<b>/%3Cb%3E/g; s/<\/b>/%3C%2Fb%3E/g; s/<i>/%3Ci%3E/g; s/<\/i>/%3C%2Fi%3E/g')

    #
    # Log message.
    #
    if [ "${DEBUG_MODE}" == "1" ]; then
        logAdd -q "${SERVICE_NAME}::sendTelegramNotification() fired."
    fi

    CURL_COMMAND="${CURL_BIN} -s --max-time ${CURL_TIMEOUT} \"https://api.telegram.org/bot${STN_TELEGRAM_BOT_ID}:${STN_TELEGRAM_BOT_APIKEY}/sendMessage?chat_id=${STN_TELEGRAM_CHAT_ID}&parse_mode=HTML&text=${STN_TEXT}\""
    
    # Log the CURL command for debugging
    logAdd "[DEBUG] CURL command: ${CURL_COMMAND}"

    RESPONSE=$(eval ${CURL_COMMAND})
    logAdd "[DEBUG] CURL response: ${RESPONSE}"

    if echo "${RESPONSE}" | grep -Fiq "\"ok\":true"; then
        logAdd -q "${SERVICE_NAME}::sendTelegramNotification: ... SUCCESS."
        echo -n "1"
    else
        logAdd -q "${SERVICE_NAME}::sendTelegramNotification: ... FAILED."
        echo -n "0"
    fi
    return
}

# -----------------------------------------------------
# --------------- END OF FUNCTION BLOCK ---------------
# -----------------------------------------------------
#
# Script Main.
#
# Validate environment variables from Duplicati.
if  [ -z "${DUPLICATI__backup_name}" ]; then
    export DUPLICATI__backup_name="API_ERR"
fi
#
if  [ -z "${DUPLICATI__PARSED_RESULT}" ]; then
    export DUPLICATI__PARSED_RESULT="API_ERR"
fi
#
if  [ -z "${DUPLICATI__EVENTNAME}" ]; then
    export DUPLICATI__EVENTNAME="API_ERR"
fi
#
if  [ -z "${DUPLICATI__OPERATIONNAME}" ]; then
    export DUPLICATI__OPERATIONNAME="API_ERR"
fi
#
if [ "${DEBUG_MODE}" == "1" ]; then
    env | grep "DUPLICATI__"
fi
#
# Prepare chat message.
case "${DUPLICATI__PARSED_RESULT}" in
    "Success")
        CHAT_MESSAGE="<b>${SERVICE_NAME}</b>%0A<i>💾 ${DUPLICATI__backup_name}</i>%0A%0A✅ The backup completed successfully at $(date '+%H:%M')."
        ;;
    "Warning")
        CHAT_MESSAGE="<b>${SERVICE_NAME}</b>%0A<i>💾 ${DUPLICATI__backup_name}</i>%0A%0A⚠️ Warning: The backup completed with warnings at $(date '+%H:%M'). Check the logs for more details."
        ;;
    "Error")
        CHAT_MESSAGE="<b>${SERVICE_NAME}</b>%0A<i>💾 ${DUPLICATI__backup_name}</i>%0A%0A❌ Error: The backup failed at $(date '+%H:%M'). Check the logs to identify the problem."
        ;;
    "Fatal")
        CHAT_MESSAGE="<b>${SERVICE_NAME}</b>%0A<i>💾 ${DUPLICATI__backup_name}</i>%0A%0A💥 Fatal Error: The backup failed irreparably at $(date '+%H:%M'). Take immediate action to resolve the issue!"
        ;;
    "Unknown")
        CHAT_MESSAGE="<b>${SERVICE_NAME}</b>%0A<i>💾 ${DUPLICATI__backup_name}</i>%0A%0A🤔 Unknown Status: The result of the backup could not be determined at $(date '+%H:%M'). Check manually."
        ;;
    *)
        CHAT_MESSAGE="<b>${SERVICE_NAME}</b>%0A<i>💾 ${DUPLICATI__backup_name}</i>%0A%0A❓ Unknown result at $(date '+%H:%M')."
        ;;
esac

#
# Always send chat message.
logAdd "[INFO] Sending chat message [${CHAT_MESSAGE}] ..."
#
# Send chat message.
SEND_RESULT="$(sendTelegramNotification "${CHAT_MESSAGE}")"
if [ "${SEND_RESULT}" == "0" ]; then
    logAdd "[ERROR] Failed to send Telegram notification."
    exit 99
fi
#
logAdd "[INFO] Successfully sent Telegram notification."
exit 0

2 Likes

Based on the script here I have implemented a Telegram reporting module.

It does not do the prettified HTML formatting, but that could be added.

1 Like

Thanks @kenkendk for adding this! Working great.

For me there are two things that could be improved.

  1. Telegram Messages on failing run-script-before-required Scripts. I guess that would be part of this Pull Request, that is currently marked as draft: Changed logic so all modules are loaded before any are started #5245.

  2. Option to send Messages to dedicated threads/topics. I think this would be a rather simple addition as it is one optional parameter more. I unfortunately don’t have the time for a Pull-Request but it should be something like this in SendTelegramMessage.cs + some Command-Line Parameter Updates.

...

var p = new
    {
        chat_id = Uri.EscapeDataString(m_channelId),
+       message_thread_id = string.IsNullOrEmpty(topicId) ? null : Uri.EscapeDataString(topicId)
        parse_mode = "Markdown",
        text = Uri.EscapeDataString(totalParts > 1
            ? $"Part {partNumber}/{totalParts}:\n{message}"
            : message),
        botId = Uri.EscapeDataString(m_botid),
        apiKey = Uri.EscapeDataString(m_apikey)
    };

var url = $"https://api.telegram.org/bot{p.botId}:{p.apiKey}/sendMessage?chat_id={p.chat_id}&parse_mode={p.parse_mode}&text={p.text}";

+  // Append message_thread_id only if it is not null or empty
+  if (!string.IsNullOrEmpty(p.message_thread_id))
+  {
+      url += $"&message_thread_id={p.message_thread_id}";
+  }

...

Thanks for suggesting it.

I have created an issue requesting improved Telegram support.

Hi @BoThomas FYI: I’ve implemented the suggestion on this PR: Added support for Topic ID on Telegram by marceloduplicati · Pull Request #5939 · duplicati/duplicati · GitHub

2 Likes