Fix Telegram bot: IP caching, metrics efficiency, escaping, error messages

- Add get_cached_ip() with 5-minute TTL to bot service; replaces raw
  curl to api.ipify.org in tg_send(), /mp_link, /mp_add, /mp_rotate
- Escape server label with _esc() in tg_send() and tg_send_photo()
  headers so underscores/asterisks don't break Telegram Markdown
- Escape label with _esc() in all error message strings across handlers
- Refactor update_traffic() to fetch /metrics once and pass raw data
  to get_user_stats_tg() — eliminates one curl per user per interval
- get_user_stats_tg() accepts pre-fetched metrics as optional arg
- /mp_secrets fetches metrics once before iterating users
- Fix /mp_remove: check label existence first, then guard against
  removing the last secret with a clear dedicated error message
This commit is contained in:
SamNet-dev
2026-02-17 10:04:14 -06:00
parent 4844799f4b
commit 10ca34513c

View File

@@ -2626,7 +2626,7 @@ telegram_send_message() {
local label="${TELEGRAM_SERVER_LABEL:-MTProxyMax}" local label="${TELEGRAM_SERVER_LABEL:-MTProxyMax}"
local ip local ip
ip=$(curl -s --max-time 3 https://api.ipify.org 2>/dev/null || echo "") ip=$(get_public_ip)
local header local header
if [ -n "$ip" ]; then if [ -n "$ip" ]; then
header="[$(escape_md "$label") | ${ip}]" header="[$(escape_md "$label") | ${ip}]"
@@ -2908,13 +2908,29 @@ load_tg_settings() {
done < "$SETTINGS_FILE" done < "$SETTINGS_FILE"
} }
# IP cache (refreshed every 5 minutes)
_TG_IP_CACHE=""
_TG_IP_CACHE_AGE=0
get_cached_ip() {
local now; now=$(date +%s)
if [ -n "$_TG_IP_CACHE" ] && [ $(( now - _TG_IP_CACHE_AGE )) -lt 300 ]; then
echo "$_TG_IP_CACHE"; return 0
fi
local ip
ip=$(curl -s --max-time 3 https://api.ipify.org 2>/dev/null || echo "")
if [ -n "$ip" ]; then
_TG_IP_CACHE="$ip"
_TG_IP_CACHE_AGE=$now
fi
echo "$ip"
}
# Minimal Telegram send # Minimal Telegram send
tg_send() { tg_send() {
local msg="$1" local msg="$1"
local label="${TELEGRAM_SERVER_LABEL:-MTProxyMax}" local label="${TELEGRAM_SERVER_LABEL:-MTProxyMax}"
local _ip local _ip; _ip=$(get_cached_ip)
_ip=$(curl -s --max-time 3 https://api.ipify.org 2>/dev/null || echo "") [ -n "$_ip" ] && msg="[$(_esc "$label") | ${_ip}] ${msg}" || msg="[$(_esc "$label")] ${msg}"
[ -n "$_ip" ] && msg="[${label} | ${_ip}] ${msg}" || msg="[${label}] ${msg}"
local _cfg=$(mktemp /tmp/.mtproxymax-tg.XXXXXX) local _cfg=$(mktemp /tmp/.mtproxymax-tg.XXXXXX)
chmod 600 "$_cfg" chmod 600 "$_cfg"
printf 'url = "https://api.telegram.org/bot%s/sendMessage"\n' "$TELEGRAM_BOT_TOKEN" > "$_cfg" printf 'url = "https://api.telegram.org/bot%s/sendMessage"\n' "$TELEGRAM_BOT_TOKEN" > "$_cfg"
@@ -2933,7 +2949,7 @@ tg_send_photo() {
curl -s --max-time 15 -X POST -K "$_cfg" \ curl -s --max-time 15 -X POST -K "$_cfg" \
--data-urlencode "chat_id=${TELEGRAM_CHAT_ID}" \ --data-urlencode "chat_id=${TELEGRAM_CHAT_ID}" \
--data-urlencode "photo=${photo}" \ --data-urlencode "photo=${photo}" \
--data-urlencode "caption=[${TELEGRAM_SERVER_LABEL:-MTProxyMax}] ${caption}" \ --data-urlencode "caption=[$(_esc "${TELEGRAM_SERVER_LABEL:-MTProxyMax}")] ${caption}" \
--data-urlencode "parse_mode=Markdown" >/dev/null 2>&1 --data-urlencode "parse_mode=Markdown" >/dev/null 2>&1
rm -f "$_cfg" rm -f "$_cfg"
} }
@@ -2978,8 +2994,8 @@ get_uptime() {
} }
get_user_stats_tg() { get_user_stats_tg() {
local user="$1" local user="$1" m="${2:-}"
local m=$(curl -s --max-time 2 "http://127.0.0.1:${PROXY_METRICS_PORT:-9090}/metrics" 2>/dev/null) [ -z "$m" ] && m=$(curl -s --max-time 2 "http://127.0.0.1:${PROXY_METRICS_PORT:-9090}/metrics" 2>/dev/null)
[ -z "$m" ] && echo "0 0 0" && return [ -z "$m" ] && echo "0 0 0" && return
local i=$(echo "$m"|awk -v u="$user" '$0 ~ "^telemt_user_octets_to_client\\{.*user=\"" u "\"" {print $NF}') local i=$(echo "$m"|awk -v u="$user" '$0 ~ "^telemt_user_octets_to_client\\{.*user=\"" u "\"" {print $NF}')
local o=$(echo "$m"|awk -v u="$user" '$0 ~ "^telemt_user_octets_from_client\\{.*user=\"" u "\"" {print $NF}') local o=$(echo "$m"|awk -v u="$user" '$0 ~ "^telemt_user_octets_from_client\\{.*user=\"" u "\"" {print $NF}')
@@ -3034,9 +3050,15 @@ save_traffic() {
} }
update_traffic() { update_traffic() {
local stats=$(get_stats) # Fetch metrics once for both global and per-user stats
local cur_in=$(echo "$stats"|awk '{print $1}') local _metrics
local cur_out=$(echo "$stats"|awk '{print $2}') _metrics=$(curl -s --max-time 2 "http://127.0.0.1:${PROXY_METRICS_PORT:-9090}/metrics" 2>/dev/null)
local cur_in cur_out
if [ -n "$_metrics" ]; then
cur_in=$(echo "$_metrics"|awk '/^telemt_user_octets_to_client\{/{s+=$NF}END{printf "%.0f",s}')
cur_out=$(echo "$_metrics"|awk '/^telemt_user_octets_from_client\{/{s+=$NF}END{printf "%.0f",s}')
fi
cur_in=${cur_in:-0}; cur_out=${cur_out:-0}
# Compute deltas (torware pattern: detect container restart by negative delta) # Compute deltas (torware pattern: detect container restart by negative delta)
local delta_in=$((cur_in - _prev_total_in)) local delta_in=$((cur_in - _prev_total_in))
@@ -3048,11 +3070,11 @@ update_traffic() {
_prev_total_in=$cur_in _prev_total_in=$cur_in
_prev_total_out=$cur_out _prev_total_out=$cur_out
# Per-user delta tracking # Per-user delta tracking (reuse already-fetched metrics)
while IFS='|' read -r label secret created enabled _mc _mi _q _ex; do while IFS='|' read -r label secret created enabled _mc _mi _q _ex; do
[[ "$label" =~ ^# ]] && continue; [ -z "$secret" ] && continue [[ "$label" =~ ^# ]] && continue; [ -z "$secret" ] && continue
[ "$enabled" != "true" ] && continue [ "$enabled" != "true" ] && continue
local us=$(get_user_stats_tg "$label") local us=$(get_user_stats_tg "$label" "$_metrics")
local ui=$(echo "$us"|awk '{print $1}') local ui=$(echo "$us"|awk '{print $1}')
local uo=$(echo "$us"|awk '{print $2}') local uo=$(echo "$us"|awk '{print $2}')
local prev_ui=${_prev_user_in["$label"]:-0} local prev_ui=${_prev_user_in["$label"]:-0}
@@ -3142,11 +3164,13 @@ _process_cmd() {
load_tg_settings load_tg_settings
[ ! -f "$SECRETS_FILE" ] && tg_send "📋 No secrets configured." && return [ ! -f "$SECRETS_FILE" ] && tg_send "📋 No secrets configured." && return
local msg="📋 *Secrets*\n\n" local msg="📋 *Secrets*\n\n"
local _sec_metrics
_sec_metrics=$(curl -s --max-time 2 "http://127.0.0.1:${PROXY_METRICS_PORT:-9090}/metrics" 2>/dev/null)
while IFS='|' read -r label secret created enabled _mc _mi _q _ex; do while IFS='|' read -r label secret created enabled _mc _mi _q _ex; do
[[ "$label" =~ ^# ]] && continue [[ "$label" =~ ^# ]] && continue
[ -z "$secret" ] && continue [ -z "$secret" ] && continue
local icon="🟢"; [ "$enabled" != "true" ] && icon="🔴" local icon="🟢"; [ "$enabled" != "true" ] && icon="🔴"
local us=$(get_user_stats_tg "$label") local us=$(get_user_stats_tg "$label" "$_sec_metrics")
local uc=$(echo "$us"|awk '{print $3}') local uc=$(echo "$us"|awk '{print $3}')
local cum_u=$(get_cum_user_traffic "$label") local cum_u=$(get_cum_user_traffic "$label")
local cui=$(echo "$cum_u"|awk '{print $1}') local cui=$(echo "$cum_u"|awk '{print $1}')
@@ -3157,7 +3181,7 @@ _process_cmd() {
;; ;;
/mp_link|/mp_link@*) /mp_link|/mp_link@*)
load_tg_settings load_tg_settings
local ip=$(curl -s --max-time 3 https://api.ipify.org 2>/dev/null) local ip; ip=$(get_cached_ip)
[ -z "$ip" ] && tg_send "❌ Cannot detect server IP" && return [ -z "$ip" ] && tg_send "❌ Cannot detect server IP" && return
local msg="🔗 *Proxy Links*\n\n" local msg="🔗 *Proxy Links*\n\n"
while IFS='|' read -r label secret created enabled _mc _mi _q _ex; do while IFS='|' read -r label secret created enabled _mc _mi _q _ex; do
@@ -3187,24 +3211,34 @@ _process_cmd() {
"${INSTALL_DIR}/mtproxymax" secret add "$label" &>/dev/null "${INSTALL_DIR}/mtproxymax" secret add "$label" &>/dev/null
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
load_tg_settings load_tg_settings
local ip=$(curl -s --max-time 3 https://api.ipify.org 2>/dev/null) local ip; ip=$(get_cached_ip)
local ns=$(grep "^${label}|" "$SECRETS_FILE" 2>/dev/null | head -1 | cut -d'|' -f2) local ns=$(grep "^${label}|" "$SECRETS_FILE" 2>/dev/null | head -1 | cut -d'|' -f2)
local dh=$(domain_to_hex "${PROXY_DOMAIN:-cloudflare.com}") local dh=$(domain_to_hex "${PROXY_DOMAIN:-cloudflare.com}")
local fs="ee${ns}${dh}" local fs="ee${ns}${dh}"
tg_send "✅ Secret *$(_esc "$label")* created!\n\n🔗 \`tg://proxy?server=${ip}&port=${PROXY_PORT}&secret=${fs}\`\n\n🌐 https://t.me/proxy?server=${ip}&port=${PROXY_PORT}&secret=${fs}" tg_send "✅ Secret *$(_esc "$label")* created!\n\n🔗 \`tg://proxy?server=${ip}&port=${PROXY_PORT}&secret=${fs}\`\n\n🌐 https://t.me/proxy?server=${ip}&port=${PROXY_PORT}&secret=${fs}"
else else
tg_send "❌ Failed to add secret '${label}' (may already exist)" tg_send "❌ Failed to add secret '$(_esc "$label")' (may already exist)"
fi fi
;; ;;
/mp_remove\ *|/mp_remove@*\ *) /mp_remove\ *|/mp_remove@*\ *)
local label=$(echo "$text" | awk '{print $2}') local label=$(echo "$text" | awk '{print $2}')
[ -z "$label" ] && tg_send "❌ Usage: /mp\\_remove <label>" && return [ -z "$label" ] && tg_send "❌ Usage: /mp\\_remove <label>" && return
[[ "$label" =~ ^[a-zA-Z0-9_-]+$ ]] || { tg_send "❌ Invalid label"; return; } [[ "$label" =~ ^[a-zA-Z0-9_-]+$ ]] || { tg_send "❌ Invalid label"; return; }
if ! grep -q "^${label}|" "$SECRETS_FILE" 2>/dev/null; then
tg_send "❌ Secret '$(_esc "$label")' not found"
return
fi
local _scount
_scount=$(grep -v '^#' "$SECRETS_FILE" 2>/dev/null | grep -c '|' || echo 0)
if [ "${_scount:-0}" -le 1 ]; then
tg_send "❌ Cannot remove the last secret"
return
fi
"${INSTALL_DIR}/mtproxymax" secret remove "$label" &>/dev/null "${INSTALL_DIR}/mtproxymax" secret remove "$label" &>/dev/null
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
tg_send "✅ Secret *$(_esc "$label")* removed" tg_send "✅ Secret *$(_esc "$label")* removed"
else else
tg_send "❌ Secret '${label}' not found" tg_send "❌ Failed to remove secret '$(_esc "$label")'"
fi fi
;; ;;
/mp_rotate\ *|/mp_rotate@*\ *) /mp_rotate\ *|/mp_rotate@*\ *)
@@ -3214,14 +3248,14 @@ _process_cmd() {
"${INSTALL_DIR}/mtproxymax" secret rotate "$label" &>/dev/null "${INSTALL_DIR}/mtproxymax" secret rotate "$label" &>/dev/null
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
load_tg_settings load_tg_settings
local ip=$(curl -s --max-time 3 https://api.ipify.org 2>/dev/null) local ip; ip=$(get_cached_ip)
# Re-read the new secret from file # Re-read the new secret from file
local ns=$(grep "^${label}|" "$SECRETS_FILE" 2>/dev/null | head -1 | cut -d'|' -f2) local ns=$(grep "^${label}|" "$SECRETS_FILE" 2>/dev/null | head -1 | cut -d'|' -f2)
local dh=$(domain_to_hex "${PROXY_DOMAIN:-cloudflare.com}") local dh=$(domain_to_hex "${PROXY_DOMAIN:-cloudflare.com}")
local fs="ee${ns}${dh}" local fs="ee${ns}${dh}"
tg_send "🔄 Secret *$(_esc "$label")* rotated!\n\n🔗 New link:\n\`tg://proxy?server=${ip}&port=${PROXY_PORT}&secret=${fs}\`" tg_send "🔄 Secret *$(_esc "$label")* rotated!\n\n🔗 New link:\n\`tg://proxy?server=${ip}&port=${PROXY_PORT}&secret=${fs}\`"
else else
tg_send "❌ Secret '${label}' not found" tg_send "❌ Secret '$(_esc "$label")' not found"
fi fi
;; ;;
/mp_restart|/mp_restart@*) /mp_restart|/mp_restart@*)
@@ -3242,7 +3276,7 @@ _process_cmd() {
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
tg_send "✅ Secret *$(_esc "$label")* enabled" tg_send "✅ Secret *$(_esc "$label")* enabled"
else else
tg_send "❌ Secret '${label}' not found" tg_send "❌ Secret '$(_esc "$label")' not found"
fi fi
;; ;;
/mp_disable\ *|/mp_disable@*\ *) /mp_disable\ *|/mp_disable@*\ *)
@@ -3253,7 +3287,7 @@ _process_cmd() {
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
tg_send "✅ Secret *$(_esc "$label")* disabled" tg_send "✅ Secret *$(_esc "$label")* disabled"
else else
tg_send "❌ Secret '${label}' not found" tg_send "❌ Secret '$(_esc "$label")' not found"
fi fi
;; ;;
/mp_health|/mp_health@*) /mp_health|/mp_health@*)