Support multiple Telegram chat IDs for backups - #22
Conversation
Replace BACKUP_TELEGRAM_CHAT_ID with BACKUP_TELEGRAM_CHAT_IDS (comma-separated) and send backup documents/notifications to each chat ID. Add escape_markdown_v2 to safely escape MarkdownV2 characters and build a detailed upload summary (file list + extraction guide). Improve error handling and logging when sending documents and error logs. Auto-migrate existing .env key if present, and update prompts, view/edit/remove and backup_service to use the plural variable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
WalkthroughChangesTelegram backup delivery
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant BackupService
participant TelegramAPI
participant ChatRecipients
BackupService->>BackupService: Build escaped upload summary
BackupService->>TelegramAPI: Send each backup part with MarkdownV2 caption
TelegramAPI->>ChatRecipients: Deliver each part to configured chat IDs
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
lib/pasarguard-backup.sh (2)
163-167: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the
.envchat-ID migration into one shared helper.The
BACKUP_TELEGRAM_CHAT_ID→BACKUP_TELEGRAM_CHAT_IDSrename-in-place migration is copy-pasted verbatim in two functions; a single helper called from both keeps the migration logic in one place.
lib/pasarguard-backup.sh#L163-L167: replace this block with a call to a newmigrate_backup_telegram_chat_id_env()helper.lib/pasarguard-backup.sh#L435-L438: replace this block with the same helper call.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/pasarguard-backup.sh` around lines 163 - 167, The BACKUP_TELEGRAM_CHAT_ID to BACKUP_TELEGRAM_CHAT_IDS migration is duplicated across two functions. Add a shared migrate_backup_telegram_chat_id_env() helper containing the existing .env existence check and sed rename, then replace the duplicated blocks in lib/pasarguard-backup.sh lines 163-167 and 435-438 with calls to that helper.
325-341: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract chat-id CSV parsing/trimming into one shared helper.
The
IFS=',' read -ra chat_ids <<< "$BACKUP_TELEGRAM_CHAT_IDS"split plus per-itemxargstrim is duplicated between the upload and error-notification paths. A sharedget_backup_chat_ids()returning a clean array would remove the duplication and is a natural place to warn if zero valid IDs remain after trimming (e.g. value","), which currently passes silently.
lib/pasarguard-backup.sh#L325-L341: replace inline split/trim with a call to the shared helper before thefor part in "${backup_paths[@]}"loop.lib/pasarguard-backup.sh#L403-L406: replace inline split/trim with the same helper call.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/pasarguard-backup.sh` around lines 325 - 341, Add a shared get_backup_chat_ids() helper that parses BACKUP_TELEGRAM_CHAT_IDS into a clean array, trims each entry, excludes empty IDs, and warns when no valid IDs remain. In lib/pasarguard-backup.sh lines 325-341 and 403-406, replace the duplicated IFS/read parsing and per-item xargs trimming with calls to this helper before iterating over chat IDs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/pasarguard-backup.sh`:
- Around line 343-347: Add explicit connection and total-operation timeouts to
the curl invocation in the Telegram document upload within the backup function,
matching the existing timeout pattern used by the nearby ifconfig.me lookup.
Keep the current upload fields, response capture, and Telegram endpoint
unchanged.
- Around line 414-431: Update the Telegram sendDocument curl call in the
log-file branch to include the file’s established --max-time timeout option.
Declare response and http_code as local variables in this function, preserving
the existing response parsing and success/failure handling.
- Around line 408-412: Update the Telegram text notification flow around the
curl POST to include the same --max-time safeguard as the document-upload call
and capture the response body instead of discarding it. Report success only when
the response confirms "ok":true; otherwise use the existing failure message,
including curl failures, timeouts, invalid chat IDs, and Telegram-side
rejections.
- Around line 300-337: Update the backup summary flow around the info_message
construction and per-part sendDocument logic: send the escaped verbose summary
once per chat via sendMessage, before iterating over backup_paths, then remove
it from each document caption. Keep each per-document caption limited to the
server IP, filename, and backup time so it remains within Telegram’s caption
limit.
---
Nitpick comments:
In `@lib/pasarguard-backup.sh`:
- Around line 163-167: The BACKUP_TELEGRAM_CHAT_ID to BACKUP_TELEGRAM_CHAT_IDS
migration is duplicated across two functions. Add a shared
migrate_backup_telegram_chat_id_env() helper containing the existing .env
existence check and sed rename, then replace the duplicated blocks in
lib/pasarguard-backup.sh lines 163-167 and 435-438 with calls to that helper.
- Around line 325-341: Add a shared get_backup_chat_ids() helper that parses
BACKUP_TELEGRAM_CHAT_IDS into a clean array, trims each entry, excludes empty
IDs, and warns when no valid IDs remain. In lib/pasarguard-backup.sh lines
325-341 and 403-406, replace the duplicated IFS/read parsing and per-item xargs
trimming with calls to this helper before iterating over chat IDs.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 4c710570-f389-4511-87a1-ff5b407ae319
📒 Files selected for processing (1)
lib/pasarguard-backup.sh
| local info_message=$'📤 Backup Upload Summary\n' | ||
| info_message+=$'──────────────────────\n' | ||
| info_message+="⏳ Time: $backup_time"$'\n' | ||
| info_message+=$'\n✅ Files Uploaded:\n' | ||
| info_message+="$files_list"$'\n' | ||
| info_message+=$'\n📂 Extraction Guide:\n' | ||
| if [ "$extraction_mode" = "part_zip" ]; then | ||
| info_message+=$'🪟 Windows: Rebuild the archive first, e.g. copy /b backup_xxx.part01.zip+backup_xxx.part02.zip+... backup_xxx.zip, then extract backup_xxx.zip with 7-Zip.\n' | ||
| info_message+=$'🐧 Linux: Run cat backup_xxx.part*.zip > backup_xxx.zip && unzip backup_xxx.zip.\n' | ||
| info_message+=$'🍎 macOS: Run cat backup_xxx.part*.zip > backup_xxx.zip && unzip backup_xxx.zip.\n' | ||
| info_message+=$'⚠️ Always download every .partNN.zip file before rebuilding the archive.' | ||
| elif [ "$extraction_mode" = "split_zip" ]; then | ||
| info_message+=$'🪟 Windows: Install and use 7-Zip. Place the .zip and every .zXX part together, then start extraction from the .zip file.\n' | ||
| info_message+=$'🐧 Linux: Run unzip (e.g., unzip backup_xxx.zip) with all .zXX parts in the same directory.\n' | ||
| info_message+=$'🍎 macOS: Use Archive Utility or run unzip backup_xxx.zip from Terminal with the .zXX parts beside the .zip file.\n' | ||
| info_message+=$'⚠️ Always download the .zip and every .zXX part before extracting.' | ||
| else | ||
| info_message+=$'🪟 Windows: Extract the uploaded archive with 7-Zip.\n' | ||
| info_message+=$'🐧 Linux: Run unzip backup_xxx.zip.\n' | ||
| info_message+=$'🍎 macOS: Open the archive with Archive Utility or run unzip backup_xxx.zip.\n' | ||
| info_message+=$'⚠️ Download the complete archive before extracting.' | ||
| fi | ||
|
|
||
| local escaped_info_message=$(escape_markdown_v2 "$info_message") | ||
|
|
||
| IFS=',' read -ra chat_ids <<< "$BACKUP_TELEGRAM_CHAT_IDS" | ||
| for part in "${backup_paths[@]}"; do | ||
| local part_name=$(basename "$part") | ||
| local custom_filename="$part_name" | ||
|
|
||
| local escaped_server_ip=$(printf '%s' "$server_ip" | sed 's/[_*\[\]()~`>#+\-=|{}!.]/\\&/g') | ||
| local escaped_filename=$(printf '%s' "$custom_filename" | sed 's/[_*\[\]()~`>#+\-=|{}!.]/\\&/g') | ||
| local escaped_time=$(printf '%s' "$backup_time" | sed 's/[_*\[\]()~`>#+\-=|{}!.]/\\&/g') | ||
| local caption="📦 *Backup Information*\n🌐 *Server IP*: \`$escaped_server_ip\`\n📁 *Backup File*: \`$escaped_filename\`\n⏰ *Backup Time*: \`$escaped_time\`" | ||
|
|
||
| local response=$(curl "${curl_proxy_args[@]}" -s -w "\n%{http_code}" -F chat_id="$BACKUP_TELEGRAM_CHAT_ID" \ | ||
| -F document=@"$part;filename=$custom_filename" \ | ||
| -F caption="$(printf '%b' "$caption")" \ | ||
| -F parse_mode="MarkdownV2" \ | ||
| "https://api.telegram.org/bot$BACKUP_TELEGRAM_BOT_KEY/sendDocument" 2>&1) | ||
|
|
||
| local http_code=$(echo "$response" | tail -n1) | ||
| local response_body=$(echo "$response" | sed '$d') | ||
|
|
||
| if [ "$http_code" == "200" ]; then | ||
| # Check if response contains "ok":true | ||
| if echo "$response_body" | grep -q '"ok":true'; then | ||
| uploaded_files+=("$custom_filename") | ||
| colorized_echo green "Backup part $custom_filename successfully sent to Telegram." | ||
| local escaped_server_ip=$(escape_markdown_v2 "$server_ip") | ||
| local escaped_filename=$(escape_markdown_v2 "$custom_filename") | ||
| local escaped_time=$(escape_markdown_v2 "$backup_time") | ||
| local caption=$'📦 *Backup Information*\n' | ||
| caption+="🌐 *Server IP*: \`$escaped_server_ip\`"$'\n' | ||
| caption+="📁 *Backup File*: \`$escaped_filename\`"$'\n' | ||
| caption+="⏰ *Backup Time*: \`$escaped_time\`"$'\n\n' | ||
| caption+="$escaped_info_message" |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Repository files matching backup script:"
fd -a 'pasarguard-backup\.sh$' . || true
script="$(fd 'pasarguard-backup\.sh$' . | head -n1 || true)"
if [ -n "$script" ]; then
echo
echo "Line count:"
wc -l "$script"
echo
echo "Relevant section lines 250-370:"
sed -n '250,370p' "$script" | cat -n -v
echo
echo "Definitions/usages around files_list and backup_paths:"
rg -n "files_list|backup_paths|part_zip|split_zip|escape_markdown_v2|capture_errors|Telegram|sendDocument|send.*Files|caption" "$script"
fi
echo
echo "Telegram API references in repo:"
rg -n "api\.telegram|sendDocument|1024|400 Bad Request" .Repository: PasarGuard/scripts
Length of output: 15023
🌐 Web query:
Telegram Bot API sendDocument caption max 1024 characters MarkdownV2 caption limit
💡 Result:
The caption for a Telegram Bot API sendDocument request is strictly limited to a maximum of 1024 characters [1][2]. This limit applies to the caption text after any entities have been parsed [1][3][2]. Key considerations regarding this limit: - Parse Mode Impact: When using MarkdownV2 (or HTML), the 1024-character limit applies to the final, rendered text that appears in the message, not the raw input string containing the markup syntax [1][3][2]. - Exceeding the Limit: If the provided caption exceeds 1024 characters, the API will return a 400 Bad Request error [1]. - Comparison to Standard Messages: This limit is specific to media captions; standard text messages sent via the sendMessage method have a higher limit of 4096 characters [4]. - Workarounds: If your content exceeds 1024 characters, the recommended practice is to send the document first and then follow up with a separate text message containing the remaining content [1].
Citations:
- 1: https://gramio.dev/telegram/methods/sendDocument
- 2: https://tg-bot-sdk.website/api/methods/edit-message-caption/
- 3: https://hackage.haskell.org/package/telegram-bot-api-7.4.4/docs/Telegram-Bot-API-Methods-SendDocument.html
- 4: https://bugs.telegram.org/c/1022
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import re
text = open("lib/pasarguard-backup.sh", errors="utf-8-sig").read()
caption_block = text[text.index("""caption=$'""");text.index('caption+="', text.index("caption=$'"))]
# Extract literal-ish assignment from raw echo, but easier: locate source assignment as string.
m = re.search(r"local caption=.+?(?=local escaped_server_ip)", text, re.S)
print("caption source presence:", bool(m))
print(m.group(0)[:500])
# Build same source text without interpreting markdown as entities, only emoji/lines/file lines,
# to approximate raw char count.
# Use the visible snippet from sed as approximate bytes.
PY
python3 - <<'PY'
import textwrap
sample_names = [f"backup_xxx-{i:03d}.zip" for i in range(1, 21)]
files_list = "\n".join("- " + name for name in sample_names)
backup_time = "2026-07-01 12:00:00 UTC"
excerpt = f"- Backup Upload Summary\n──────────────────────\nTime: {backup_time}\n\n✅ Files Uploaded:\n{files_list}\n\n📂 Extraction Guide:\n🪟 Windows: Rebuild the archive first, e.g. copy /b backup_xxx.part01.zip+backup_xxx.part02.zip+... backup_xxx.zip, then extract backup_xxx.zip with 7-Zip.\n🐧 Linux: Run cat backup_xxx.part*.zip > backup_xxx.zip && unzip backup_xxx.zip.\n🍎 macOS: Run cat backup_xxx.part*.zip > backup_xxx.zip && unzip backup_xxx.zip.\n⚠️ Always download every .partNN.zip file before rebuilding the archive."
caption = "📦 *Backup Information*\n🌐 *Server IP*: `192.0.2.1`\n📁 *Backup File*: `backup_zzz.zip`\n⏰ *Backup Time*: `{backup_time}`\n\n{excerpt}"
# We need rendered count: markdown code ticks may render as code blocks but plain ASCII mostly rendered same; emojis count 1 char after rendering.
count = len(caption)
print(f"raw_len={count}")
print(f"exceeds_1024={count > 1024}")
print(f"excerpt_lines={caption.count(chr(10)) + 1}; file_lines={caption.count(chr(10))}")
PYRepository: PasarGuard/scripts
Length of output: 370
🌐 Web query:
Telegram Bot API sendDocument caption limit 1024 rendered entities not raw characters
💡 Result:
The caption length limit for the sendDocument method (and other media methods) in the Telegram Bot API is 1024 characters [1][2][3]. Crucially, this limit is defined based on the text after entity parsing or, more specifically, the number of UTF-16 code units [4]. According to the official Telegram Bot API documentation, the length is evaluated "after entities parsing" [1][2][3]. Key technical considerations regarding this limit include: 1. Measurement unit: Telegram computes the length of strings and entities using UTF-16 code units, not raw characters or bytes [4]. This means characters outside the Basic Multilingual Plane (such as certain emojis or special symbols) count as two UTF-16 code units [4]. 2. Parsing and Serialization: Because the limit applies after parsing, developers must be aware that using Markdown or HTML formatting (e.g., HTML entities like < or serialization) can cause a string that appears to be under 1024 characters in its raw form to exceed the limit once rendered by the Telegram server [5]. 3. Best Practice: If your intended caption exceeds the 1024-character limit after formatting, the recommended approach is to send the document first and then send the remaining text as a separate text message [1]. You should ensure your application calculates the length of the string based on UTF-16 code units and accounts for any expansion that occurs during the parsing process if using parse_mode (HTML or MarkdownV2) [4][5].
Citations:
- 1: https://gramio.dev/telegram/methods/sendDocument
- 2: https://tg-bot-sdk.website/api/methods/send-document/
- 3: https://docs.aiogram.dev/en/v3.13.1/api/methods/send_document.html
- 4: https://core.telegram.org/api/entities
- 5: bug(telegram): media captions can exceed 1024 after HTML rendering openclaw/openclaw#83815
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "MarkdownV2 escape implementation:"
sed -n '130,185p' lib/pasarguard-backup.sh | cat -n -v
echo
echo "Build approximation of caption length by echo:"
cat >/tmp/telegram_caption_helper.sh <<'BASH'
#!/bin/bash
extraction_mode="part_zip"
backup_paths=(
"/srv/parts/backup.part01.zip"
"/srv/parts/backup.part02.zip"
"/srv/parts/backup.part03.zip"
"/srv/parts/backup.part04.zip"
"/srv/parts/backup.part05.zip"
"/srv/parts/backup.part06.zip"
"/srv/parts/backup.part07.zip"
"/srv/parts/backup.part08.zip"
"/srv/parts/backup.part09.zip"
"/srv/parts/backup.part10.zip"
"/srv/parts/backup.part11.zip"
"/srv/parts/backup.part12.zip"
"/srv/parts/backup.part13.zip"
"/srv/parts/backup.part14.zip"
"/srv/parts/backup.part15.zip"
"/srv/parts/backup.part16.zip"
"/srv/parts/backup.part17.zip"
"/srv/parts/backup.part18.zip"
"/srv/parts/backup.part19.zip"
"/srv/parts/backup.part20.zip"
)
escape_markdown_v2() {
sed -e 's#_#\\_#g' -e 's#\*#\\*`#g`' -e 's#\[#\\[`#g`' -e 's#\]#\\]`#g`' -e 's#~#\\~`#g`' -e 's#>#\\>`#g`' -e 's#<#\\<`#g`' -e 's#`#\\`#g' -e 's#!#\\!`#g`' -e 's#|#\\|`#g`' \
-e ':a; s#\\\(#\\(\\)`#g`; s#\\\)([^]|[:space:]]|[[:space:]])#$#; s#\\\)#\(\)`#g`' "$1"
}
files_list=""
for file_path in "${backup_paths[@]}"; do
files_list+="- $(basename "$file_path")"$'\n'
done
files_list="${files_list%$'\n'}"
backup_time="2026-07-01 12:00:00 UTC"
info_message=$'📤 Backup Upload Summary\n'
info_message+=$'──────────────────────\n'
info_message+="⏳ Time: $backup_time"$'\n'
info_message+=$'\n✅ Files Uploaded:\n'
info_message+="$files_list"$'\n'
info_message+=$'\n📂 Extraction Guide:\n'
if [ "$extraction_mode" = "part_zip" ]; then
info_message+=$'🪟 Windows: Rebuild the archive first, e.g. copy /b backup_xxx.part01.zip+backup_xxx.part02.zip+... backup_xxx.zip, then extract backup_xxx.zip with 7-Zip.\n'
info_message+=$'🐧 Linux: Run cat backup_xxx.part*.zip > backup_xxx.zip && unzip backup_xxx.zip.\n'
info_message+=$'🍎 macOS: Run cat backup_xxx.part*.zip > backup_xxx.zip && unzip backup_xxx.zip.\n'
info_message+=$'⚠️ Always download every .partNN.zip file before rebuilding the archive.'
elif [ "$extraction_mode" = "split_zip" ]; then
info_message+=$'🪟 Windows: Install and use 7-Zip. Place the .zip and every .zXX part together, then start extraction from the .zip file.\n'
info_message+=$'🐧 Linux: Run unzip (e.g., unzip backup_xxx.zip) with all .zXX parts in the same directory.\n'
info_message+=$'🍎 macOS: Use Archive Utility or run unzip backup_xxx.zip from Terminal with the .zXX parts beside the .zip file.\n'
info_message+=$'⚠️ Always download the .zip and every .zXX part before extracting.'
else
info_message+=$'🪟 Windows: Extract the uploaded archive with 7-Zip.\n'
info_message+=$'🐧 Linux: Run unzip backup_xxx.zip.\n'
info_message+=$'🍎 macOS: Open the archive with Archive Utility or run unzip backup_xxx.zip.\n'
info_message+=$'⚠️ Download the complete archive before extracting.'
fi
escaped_info_message=$(escape_markdown_v2 "$info_message")
escaped_server_ip=$(escape_markdown_v2 "192.0.2.1")
custom_filename="backup.part20.zip"
escaped_filename=$(escape_markdown_v2 "$custom_filename")
escaped_time=$(escape_markdown_v2 "$backup_time")
caption=$'📦 *Backup Information*\n'
caption+="🌐 *Server IP*: \`$escaped_server_ip\`"$'\n'
caption+="📁 *Backup File*: \`$escaped_filename\`"$'\n'
caption+="⏰ *Backup Time*: \`$escaped_time\`"$'\n\n'
caption+="$escaped_info_message"
tmpfile="/tmp/pasarguard_caption_temp"
echo -n "$caption" > "$tmpfile"
python3 - "$tmpfile" <<'PY'
import sys
p=sys.argv[1]
raw=open(p,'rb').read()
print("file", p)
print("file_size_bytes", len(raw))
print("chars", len(raw.decode("utf-8")))
print("utf16_code_units", len(raw.decode("utf-8").encode("utf-16-le")) // 2)
PY
BASH
bash /tmp/telegram_caption_helper.shRepository: PasarGuard/scripts
Length of output: 2810
🌐 Web query:
Telegram Bot API sendDocument caption limit 1024 characters documented
💡 Result:
The Telegram Bot API documentation for the sendDocument method specifies that the caption parameter has a limit of 1024 characters [1][2][3]. While there have been community requests and discussions regarding increasing this limit to match the 4096-character limit used for standard text messages, the documented limit for media captions remains 1024 characters [4][5]. Exceeding this limit when calling the API typically results in a 400 Bad Request error [1][5].
Citations:
- 1: https://gramio.dev/telegram/methods/sendDocument
- 2: https://tg-bot-sdk.website/api/methods/send-document/
- 3: https://docs.python-telegram-bot.org/en/v13.6/telegram.bot.html
- 4: https://bugs.telegram.org/c/1022
- 5: [BUG] Max Caption Length python-telegram-bot/python-telegram-bot#3001
🌐 Web query:
Telegram Bot API sendMediaGroup sent as separate text messages caption workaround
💡 Result:
In the Telegram Bot API, the sendMediaGroup method does not support a separate text message for captions; it only supports captions attached directly to the media items within the group [1][2]. If your goal is to have a "global" caption that appears below the entire album, the established workaround is to set the caption on exactly one of the media items in the group (typically the first one) [3][4][5]. Key points regarding this behavior: - Single Caption Placement: When you provide a caption for only one item in the media array, the Telegram client interprets this as the "global" caption for the entire album and displays it beneath the media group [3][6]. - Multiple Captions: If you assign a caption to more than one media item, the Telegram client will not display a global caption [3][7]. Instead, each individual media item will retain its own caption, which is typically only visible when a user opens that specific media item in full screen [3][6]. - No Separate Message Workaround: The Telegram Bot API does not provide a native way to send a media group and a separate text message as a single atomic unit [1][2]. If you require a separate text message, you must send it as a distinct API call (e.g., using sendMessage) either before or after sending the media group, though this will result in two separate message bubbles in the chat [3]. In summary, there is no workaround to force a "separate text message" to appear as a linked caption for a media group. The only way to display a caption "below" the group is to include it within the metadata of one of the media items in the sendMediaGroup request [6][8].
Citations:
- 1: https://core.telegram.org/bots/api
- 2: https://docs.aiogram.dev/en/v3.28.2/api/methods/send_media_group.html
- 3: https://stackoverflow.com/questions/58893142/how-to-send-telegram-mediagroup-with-caption-text
- 4: sendMediaGroup caption TelegramBot/Api#188
- 5: https://telega.hexdocs.pm/telega/media_group.html
- 6: https://docs.rs/teloxide-ng/latest/teloxide_ng/payloads/struct.SendMediaGroup.html
- 7: Feature Request: explicit and intuitive
SendMediaGroupcaption support teloxide/teloxide#987 - 8: https://github.com/mtcute/mtcute/blob/0d7700740bb0d76387eac3471398dea72eab6e57/packages/core/src/highlevel/methods/messages/send-media-group.ts
Keep the per-document caption under Telegram’s media caption limit.
sendDocument captions are limited to 1024 UTF-16 code units after entity parsing. With 20+ part_zip/split_zip files, the metadata plus files list and extraction guide can exceed that limit, causing the document upload API calls to fail while iterating over parts and chat IDs.
Send the verbose summary once per chat as a separate sendMessage, then keep the per-document caption short (server IP, filename, time only).
🧰 Tools
🪛 Shellcheck (0.11.0)
[warning] 323-323: Declare and assign separately to avoid masking return values.
(SC2155)
[warning] 327-327: Declare and assign separately to avoid masking return values.
(SC2155)
[warning] 330-330: Declare and assign separately to avoid masking return values.
(SC2155)
[warning] 331-331: Declare and assign separately to avoid masking return values.
(SC2155)
[warning] 332-332: Declare and assign separately to avoid masking return values.
(SC2155)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/pasarguard-backup.sh` around lines 300 - 337, Update the backup summary
flow around the info_message construction and per-part sendDocument logic: send
the escaped verbose summary once per chat via sendMessage, before iterating over
backup_paths, then remove it from each document caption. Keep each per-document
caption limited to the server IP, filename, and backup time so it remains within
Telegram’s caption limit.
| local response=$(curl "${curl_proxy_args[@]}" -s -w "\n%{http_code}" -F chat_id="$chat_id" \ | ||
| -F document=@"$part;filename=$custom_filename" \ | ||
| -F caption="$caption" \ | ||
| -F parse_mode="MarkdownV2" \ | ||
| "https://api.telegram.org/bot$BACKUP_TELEGRAM_BOT_KEY/sendDocument" 2>&1) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
No timeout on the Telegram document upload.
Unlike the ifconfig.me lookup a few lines above (--max-time 5), this sendDocument call has no --max-time/--connect-timeout. If Telegram is unreachable or slow, this can hang indefinitely inside a cron-triggered backup run.
🧰 Tools
🪛 Shellcheck (0.11.0)
[warning] 343-343: Declare and assign separately to avoid masking return values.
(SC2155)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/pasarguard-backup.sh` around lines 343 - 347, Add explicit connection and
total-operation timeouts to the curl invocation in the Telegram document upload
within the backup function, matching the existing timeout pattern used by the
nearby ifconfig.me lookup. Keep the current upload fields, response capture, and
Telegram endpoint unchanged.
| curl "${curl_proxy_args[@]}" -s -X POST "https://api.telegram.org/bot$BACKUP_TELEGRAM_BOT_KEY/sendMessage" \ | ||
| -d chat_id="$chat_id" \ | ||
| -d text="$message" >/dev/null 2>&1 && | ||
| colorized_echo green "Backup error notification sent to Telegram chat $chat_id." || | ||
| colorized_echo red "Failed to send error notification to Telegram chat $chat_id." |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Error notification success is not actually verified.
Success here is only "curl exited 0," not "Telegram accepted the message." A few lines below, the document-upload path correctly checks the response body for "ok":true before reporting success — this text message doesn't, so an invalid chat_id or a Telegram-side rejection will still print "sent successfully," defeating the purpose of the error-alerting path. Also missing --max-time like the sibling call at Line 343.
🔧 Suggested fix
- curl "${curl_proxy_args[@]}" -s -X POST "https://api.telegram.org/bot$BACKUP_TELEGRAM_BOT_KEY/sendMessage" \
- -d chat_id="$chat_id" \
- -d text="$message" >/dev/null 2>&1 &&
- colorized_echo green "Backup error notification sent to Telegram chat $chat_id." ||
- colorized_echo red "Failed to send error notification to Telegram chat $chat_id."
+ local msg_response
+ msg_response=$(curl "${curl_proxy_args[@]}" -s --max-time 15 -X POST "https://api.telegram.org/bot$BACKUP_TELEGRAM_BOT_KEY/sendMessage" \
+ -d chat_id="$chat_id" -d text="$message" 2>&1)
+ if echo "$msg_response" | grep -q '"ok":true'; then
+ colorized_echo green "Backup error notification sent to Telegram chat $chat_id."
+ else
+ colorized_echo red "Failed to send error notification to Telegram chat $chat_id: $msg_response"
+ fi📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| curl "${curl_proxy_args[@]}" -s -X POST "https://api.telegram.org/bot$BACKUP_TELEGRAM_BOT_KEY/sendMessage" \ | |
| -d chat_id="$chat_id" \ | |
| -d text="$message" >/dev/null 2>&1 && | |
| colorized_echo green "Backup error notification sent to Telegram chat $chat_id." || | |
| colorized_echo red "Failed to send error notification to Telegram chat $chat_id." | |
| local msg_response | |
| msg_response=$(curl "${curl_proxy_args[@]}" -s --max-time 15 -X POST "https://api.telegram.org/bot$BACKUP_TELEGRAM_BOT_KEY/sendMessage" \ | |
| -d chat_id="$chat_id" -d text="$message" 2>&1) | |
| if echo "$msg_response" | grep -q '"ok":true'; then | |
| colorized_echo green "Backup error notification sent to Telegram chat $chat_id." | |
| else | |
| colorized_echo red "Failed to send error notification to Telegram chat $chat_id: $msg_response" | |
| fi |
🧰 Tools
🪛 Shellcheck (0.11.0)
[info] 410-410: Note that A && B || C is not if-then-else. C may run when A is true.
(SC2015)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/pasarguard-backup.sh` around lines 408 - 412, Update the Telegram text
notification flow around the curl POST to include the same --max-time safeguard
as the document-upload call and capture the response body instead of discarding
it. Report success only when the response confirms "ok":true; otherwise use the
existing failure message, including curl failures, timeouts, invalid chat IDs,
and Telegram-side rejections.
| if [ -f "$log_file" ]; then | ||
|
|
||
| response=$(curl "${curl_proxy_args[@]}" -s -w "%{http_code}" -o /tmp/tg_response.json \ | ||
| -F chat_id="$chat_id" \ | ||
| -F document=@"$log_file;filename=backup_error.log" \ | ||
| -F caption="📜 Backup Error Log - $error_time" \ | ||
| "https://api.telegram.org/bot$BACKUP_TELEGRAM_BOT_KEY/sendDocument") | ||
|
|
||
| http_code="${response:(-3)}" | ||
| if [ "$http_code" -eq 200 ]; then | ||
| colorized_echo green "Backup error log sent to Telegram chat $chat_id." | ||
| else | ||
| colorized_echo red "Failed to send backup error log to Telegram chat $chat_id. HTTP code: $http_code" | ||
| cat /tmp/tg_response.json | ||
| fi | ||
| else | ||
| colorized_echo red "Failed to send backup error log to Telegram. HTTP code: $http_code" | ||
| cat /tmp/tg_response.json | ||
| colorized_echo red "Log file not found: $log_file" | ||
| fi |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Missing --max-time and missing local.
Same missing-timeout gap as the other Telegram calls in this file. Additionally, response and http_code here aren't declared local (unlike local response=... at Line 343), so they leak into global scope — inconsistent with the rest of the file and a latent source of accidental cross-function clobbering.
🔧 Suggested fix
- response=$(curl "${curl_proxy_args[@]}" -s -w "%{http_code}" -o /tmp/tg_response.json \
+ local response http_code
+ response=$(curl "${curl_proxy_args[@]}" -s --max-time 30 -w "%{http_code}" -o /tmp/tg_response.json \
-F chat_id="$chat_id" \
-F document=@"$log_file;filename=backup_error.log" \
-F caption="📜 Backup Error Log - $error_time" \
"https://api.telegram.org/bot$BACKUP_TELEGRAM_BOT_KEY/sendDocument")
- http_code="${response:(-3)}"
+ http_code="${response:(-3)}"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if [ -f "$log_file" ]; then | |
| response=$(curl "${curl_proxy_args[@]}" -s -w "%{http_code}" -o /tmp/tg_response.json \ | |
| -F chat_id="$chat_id" \ | |
| -F document=@"$log_file;filename=backup_error.log" \ | |
| -F caption="📜 Backup Error Log - $error_time" \ | |
| "https://api.telegram.org/bot$BACKUP_TELEGRAM_BOT_KEY/sendDocument") | |
| http_code="${response:(-3)}" | |
| if [ "$http_code" -eq 200 ]; then | |
| colorized_echo green "Backup error log sent to Telegram chat $chat_id." | |
| else | |
| colorized_echo red "Failed to send backup error log to Telegram chat $chat_id. HTTP code: $http_code" | |
| cat /tmp/tg_response.json | |
| fi | |
| else | |
| colorized_echo red "Failed to send backup error log to Telegram. HTTP code: $http_code" | |
| cat /tmp/tg_response.json | |
| colorized_echo red "Log file not found: $log_file" | |
| fi | |
| if [ -f "$log_file" ]; then | |
| local response http_code | |
| response=$(curl "${curl_proxy_args[@]}" -s --max-time 30 -w "%{http_code}" -o /tmp/tg_response.json \ | |
| -F chat_id="$chat_id" \ | |
| -F document=@"$log_file;filename=backup_error.log" \ | |
| -F caption="📜 Backup Error Log - $error_time" \ | |
| "https://api.telegram.org/bot$BACKUP_TELEGRAM_BOT_KEY/sendDocument") | |
| http_code="${response:(-3)}" | |
| if [ "$http_code" -eq 200 ]; then | |
| colorized_echo green "Backup error log sent to Telegram chat $chat_id." | |
| else | |
| colorized_echo red "Failed to send backup error log to Telegram chat $chat_id. HTTP code: $http_code" | |
| cat /tmp/tg_response.json | |
| fi | |
| else | |
| colorized_echo red "Log file not found: $log_file" | |
| fi |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/pasarguard-backup.sh` around lines 414 - 431, Update the Telegram
sendDocument curl call in the log-file branch to include the file’s established
--max-time timeout option. Declare response and http_code as local variables in
this function, preserving the existing response parsing and success/failure
handling.
Replace BACKUP_TELEGRAM_CHAT_ID with BACKUP_TELEGRAM_CHAT_IDS (comma-separated) and send backup documents/notifications to each chat ID. Add escape_markdown_v2 to safely escape MarkdownV2 characters and build a detailed upload summary (file list + extraction guide). Improve error handling and logging when sending documents and error logs. Auto-migrate existing .env key if present, and update prompts, view/edit/remove and backup_service to use the plural variable.
Summary by CodeRabbit
New Features
Improvements