P9(5): restore upstream Telegram bot (settings, backup, notifications)

Per user decision 2026-10-21 (reverses the 2026-10-20 drop-Telegram
call): port the upstream 3x-ui v3.7.0-generation Telegram bot wholesale
instead of rewriting a minimal set, adapted to this fork.

- new internal/web/service/tgbot package (telego): bot lifecycle,
  commands, reports, callback router, send helpers; token validation via
  telego; custom API server URL passes the fork's SSRF sanitizer
  (loopback/private bases fall back to the default endpoint, logged)
- settings: full upstream key set (tgBotEnable/Token/Proxy/APIServer/
  ChatId, tgRunTime, tgBotBackup, tgCpu/tgMemory, tgLang,
  tgEnabledEvents) with getters/setters; settings page gains a Telegram
  tab + sidebar entry; token shown masked, clearTgBotToken honored
- wiring (web.go): stats-notify cron registered from tgRunTime when the
  bot is enabled; CheckHashStorage job; CPU/memory alarm jobs gated on
  tgEnabledEvents + thresholds; login notifications via event bus;
  POST /panel/api/setting/testTgBot and /panel/api/backuptotgbot
- lite adaptation: StatsNotifyJob no longer gates on a local Xray core
  (this panel never runs one) so the periodic report and the scheduled
  DB backup (sendDocument) can actually fire
- translations: upstream TG keys restored across all locales

End-to-end smoke (real binary + fake Telegram API over HTTPS at a
public-looking address, browser form wire format): bot starts on
settings change (getMe), testTgBot delivers, panel restart re-arms the
bot, tgRunTime='@every 1m' cron really fires sendDocument carrying the
panel SQLite DB (magic verified) plus the report sendMessage, login
notification delivered, dead-endpoint failure is logged not silent.
13/13 PASS — see P9-REPORT.md.
This commit is contained in:
Muse
2026-10-21 17:16:32 +00:00
parent 5031a70ccb
commit ceca96571d
49 changed files with 8351 additions and 50 deletions
+216 -3
View File
@@ -88,7 +88,8 @@
"noExpiry": "Không hết hạn",
"copyAllConfigs": "Sao chép tất cả cấu hình",
"copyAllConfigsCopied": "Đã sao chép tất cả cấu hình",
"email": "Email"
"email": "Email",
"individualLinks": "Liên kết riêng lẻ"
},
"menu": {
"theme": "Chủ đề",
@@ -1168,7 +1169,44 @@
"httpMethodDesc": "Phương thức dùng cho yêu cầu probe.",
"note": "Các bộ cân bằng leastPing/leastLoad luôn mang một burstObservatory. Công tắc này tùy chỉnh các tham số probe — tắt nó để dùng mặc định tích hợp. Các thay đổi áp dụng sau khi khởi động lại bảng điều khiển."
}
}
},
"TGBotSettings": "Bot Telegram",
"notifications": "Thông báo",
"notifyTime": {
"custom": "Tùy chỉnh (crontab)",
"daily": "@daily — mỗi ngày lúc 00:00",
"every": "@every — lặp lại theo khoảng thời gian",
"hourly": "@hourly — mỗi giờ",
"hours": "Giờ",
"interval": "Khoảng thời gian",
"minutes": "Phút",
"monthly": "@monthly — mỗi tháng",
"seconds": "Giây",
"unit": "Đơn vị",
"weekly": "@weekly — mỗi tuần"
},
"telegramAPIServer": "Máy chủ API Telegram",
"telegramAPIServerDesc": "Máy chủ API Telegram để sử dụng. Để trống để sử dụng máy chủ mặc định.",
"telegramBotEnable": "Bật Bot Telegram",
"telegramBotEnableDesc": "Kết nối với các tính năng của bảng điều khiển này thông qua bot Telegram",
"telegramBotLanguage": "Ngôn ngữ của Bot Telegram",
"telegramChatId": "Chat ID Telegram của quản trị viên",
"telegramChatIdDesc": "Nhiều Chat ID phân tách bằng dấu phẩy. Sử dụng {'@'}userinfobot hoặc sử dụng lệnh '/id' trong bot để lấy Chat ID của bạn.",
"telegramNotifyTime": "Thời gian thông báo của bot Telegram",
"telegramNotifyTimeDesc": "Tần suất bot Telegram gửi báo cáo định kỳ. Chọn một khoảng thời gian có sẵn, hoặc chọn Tùy chỉnh để nhập biểu thức crontab.",
"telegramToken": "Token Telegram",
"telegramTokenConfigured": "Đã cấu hình; để trống để giữ token hiện tại.",
"telegramTokenDesc": "Bạn phải nhận token từ quản lý bot Telegram {'@'}botfather",
"telegramTokenPlaceholder": "Đã cấu hình - nhập token mới để thay thế",
"testTgBot": "Gửi tin nhắn thử nghiệm",
"tgBotNotEnabled": "Bot Telegram chưa được bật",
"tgBotNotRunning": "Bot Telegram không hoạt động",
"tgEventBusNotify": "Thông báo sự kiện qua Telegram",
"tgEventBusNotifyDesc": "Chọn những sự kiện nào sẽ kích hoạt thông báo qua Telegram",
"tgNotifyBackup": "Sao lưu Cơ sở dữ liệu",
"tgNotifyBackupDesc": "Bao gồm tệp sao lưu cơ sở dữ liệu với thông báo báo cáo.",
"tgTestFailed": "Thử nghiệm Telegram thất bại",
"tgTestSuccess": "Đã gửi tin nhắn thử nghiệm tới Telegram"
},
"xray": {
"importRules": "Nhập quy tắc",
@@ -1657,7 +1695,182 @@
}
},
"tgbot": {
"noIpRecord": "❗ Không có bản ghi IP!"
"noIpRecord": "❗ Không có bản ghi IP!",
"keyboardClosed": "❌ Bàn phím đã đóng!",
"noResult": "❗ Không có kết quả!",
"noQuery": "❌ Không tìm thấy truy vấn! Vui lòng sử dụng lại lệnh!",
"wentWrong": "❌ Đã xảy ra lỗi!",
"noInbounds": "❗ Không tìm thấy inbound!",
"unlimited": "♾ Không giới hạn (Đặt lại)",
"add": "Thêm",
"month": "Tháng",
"months": "Tháng",
"days": "Ngày",
"hours": "Giờ",
"minutes": "Phút",
"unknown": "Không xác định",
"inbounds": "Inbound",
"clients": "Client",
"offline": "🔴 Ngoại tuyến",
"online": "🟢 Trực tuyến",
"commands": {
"unknown": "❗ Lệnh không rõ",
"pleaseChoose": "👇 Vui lòng chọn:\r\n",
"help": "🤖 Chào mừng bạn đến với bot này! Bot được thiết kế để cung cấp cho bạn dữ liệu cụ thể từ máy chủ và cho phép bạn thực hiện các thay đổi cần thiết.\r\n\r\n",
"start": "👋 Xin chào <i>{{ .Firstname }}</i>.\r\n",
"welcome": "🤖 Chào mừng đến với bot quản lý của <b>{{ .Hostname }}</b>.\r\n",
"status": "✅ Bot hoạt động bình thường!",
"usage": "❗ Vui lòng cung cấp văn bản để tìm kiếm!",
"getID": "🆔 ID của bạn: <code>{{ .ID }}</code>",
"helpAdminCommands": "Để khởi động lại Xray Core:\r\n<code>/restart</code>\r\n\r\nĐể tìm kiếm email của khách hàng:\r\n<code>/usage [Email]</code>\r\n\r\nĐể tìm kiếm các nhập (với số liệu thống kê của khách hàng):\r\n<code>/inbound [Ghi chú]</code>\r\n\r\nID Trò chuyện Telegram:\r\n<code>/id</code>",
"helpClientCommands": "Để tìm kiếm thống kê, sử dụng lệnh sau:\r\n<code>/usage [Email]</code>\r\n\r\nID Trò chuyện Telegram:\r\n<code>/id</code>",
"restartUsage": "\r\n\r\n<code>/restart</code>",
"restartSuccess": "✅ Hoạt động thành công!",
"restartFailed": "❗ Lỗi trong quá trình hoạt động.\r\n\r\n<code>Lỗi: {{ .Error }}</code>.",
"xrayNotRunning": "❗ Xray Core không chạy.",
"startDesc": "Hiển thị menu chính",
"helpDesc": "Trợ giúp bot",
"statusDesc": "Kiểm tra trạng thái bot",
"idDesc": "Hiển thị ID Telegram của bạn",
"usageDesc": "Xem mức dùng của khách: /usage email",
"inboundDesc": "Tìm inbound: /inbound tên (quản trị)",
"restartDesc": "Khởi động lại lõi Xray (quản trị)",
"clearallDesc": "Đặt lại lưu lượng mọi khách hàng (quản trị)"
},
"messages": {
"cpuThreshold": "Sử dụng CPU {{ .Percent }}% vượt quá ngưỡng {{ .Threshold }}%",
"selectUserFailed": "❌ Lỗi khi chọn người dùng!",
"userSaved": "✅ Người dùng Telegram đã được lưu.",
"loginSuccess": "✅ Đăng nhập thành công vào bảng điều khiển.\r\n",
"loginFailed": "❗️ Đăng nhập vào bảng điều khiển thất bại.\r\n",
"report": "🕰 Báo cáo định kỳ: {{ .RunTime }}\r\n",
"datetime": "⏰ Ngày-Giờ: {{ .DateTime }}\r\n",
"hostname": "💻 Host: {{ .Hostname }}\r\n",
"version": "🚀 Phiên bản X-UI: {{ .Version }}\r\n",
"xrayVersion": "📡 Phiên bản Xray: {{ .XrayVersion }}\r\n",
"ipv6": "🌐 IPv6: {{ .IPv6 }}\r\n",
"ipv4": "🌐 IPv4: {{ .IPv4 }}\r\n",
"ip": "🌐 IP: {{ .IP }}\r\n",
"ips": "🔢 IPs:\r\n{{ .IPs }}\r\n",
"serverUpTime": "⏳ Thời gian hoạt động của máy chủ: {{ .UpTime }} {{ .Unit }}\r\n",
"serverLoad": "📈 Tải máy chủ: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n",
"serverMemory": "📋 RAM: {{ .Current }}/{{ .Total }}\r\n",
"tcpCount": "🔹 TCP: {{ .Count }}\r\n",
"udpCount": "🔸 UDP: {{ .Count }}\r\n",
"traffic": "🚦 Lưu lượng: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n",
"xrayStatus": "ℹ️ Trạng thái: {{ .State }}\r\n",
"username": "👤 Tên người dùng: {{ .Username }}\r\n",
"reason": "❗️ Lý do: {{ .Reason }}\r\n",
"time": "⏰ Thời gian: {{ .Time }}\r\n",
"inbound": "📍 Inbound: {{ .Remark }}\r\n",
"port": "🔌 Cổng: {{ .Port }}\r\n",
"expire": "📅 Ngày hết hạn: {{ .Time }}\r\n",
"expireIn": "📅 Hết hạn sau: {{ .Time }}\r\n",
"active": "💡 Đang hoạt động: {{ .Enable }}\r\n",
"enabled": "🚨 Đã bật: {{ .Enable }}\r\n",
"online": "🌐 Trạng thái kết nối: {{ .Status }}\r\n",
"lastOnline": "🔙 Lần online gần nhất: {{ .Time }}\r\n",
"email": "📧 Email: {{ .Email }}\r\n",
"upload": "🔼 Tải lên: ↑{{ .Upload }}\r\n",
"download": "🔽 Tải xuống: ↓{{ .Download }}\r\n",
"total": "📊 Tổng: ↑↓{{ .UpDown }} / {{ .Total }}\r\n",
"TGUser": "👤 Người dùng Telegram: {{ .TelegramID }}\r\n",
"exhaustedCount": "🚨 Số lần cạn kiệt {{ .Type }}:\r\n",
"onlinesCount": "🌐 Khách hàng trực tuyến: {{ .Count }}\r\n",
"disabled": "🛑 Vô hiệu hóa: {{ .Disabled }}\r\n",
"depleteSoon": "🔜 Sắp cạn kiệt: {{ .Deplete }}\r\n\r\n",
"backupTime": "🗄 Thời gian sao lưu: {{ .Time }}\r\n",
"refreshedOn": "\r\n📋🔄 Đã cập nhật lần cuối vào: {{ .Time }}\r\n\r\n",
"yes": "✅ Có",
"no": "❌ Không",
"received_email": "📧📥 Email đã được cập nhật.",
"received_comment": "💬📥 Bình luận đã được cập nhật.",
"email_prompt": "📧 Email mặc định: {{ .ClientEmail }}\n\nVui lòng nhập email của bạn.",
"comment_prompt": "💬 Bình luận mặc định: {{ .ClientComment }}\n\nVui lòng nhập bình luận của bạn.",
"cancel": "❌ Quá trình đã bị hủy! \n\nBạn có thể bắt đầu lại bất cứ lúc nào bằng cách nhập /start. 🔄",
"error_add_client": "⚠️ Lỗi:\n\n {{ .error }}",
"using_default_value": "Được rồi, tôi sẽ sử dụng giá trị mặc định. 😊",
"incorrect_input": "Dữ liệu bạn nhập không hợp lệ.\nCác chuỗi phải liền mạch và không có dấu cách.\nVí dụ đúng: aaaaaa\nVí dụ sai: aaa aaa 🚫",
"AreYouSure": "Bạn có chắc không? 🤔",
"SuccessResetTraffic": "📧 Email: {{ .ClientEmail }}\n🏁 Kết quả: ✅ Thành công",
"FailedResetTraffic": "📧 Email: {{ .ClientEmail }}\n🏁 Kết quả: ❌ Thất bại \n\n🛠️ Lỗi: [ {{ .ErrorMessage }} ]",
"FinishProcess": "🔚 Quá trình đặt lại lưu lượng đã hoàn tất cho tất cả khách hàng.",
"eventOutboundDown": "Outbound {{ .Tag }} đã NGỪNG HOẠT ĐỘNG",
"eventOutboundUp": "Outbound {{ .Tag }} đã HOẠT ĐỘNG",
"eventErrorDetail": "Lỗi: {{ .Error }}",
"eventDelayDetail": "Độ trễ: {{ .Delay }}ms",
"eventXrayCrash": "Xray GẶP SỰ CỐ",
"eventXrayCrashError": "Lỗi: {{ .Error }}",
"eventNodeDown": "Node {{ .Name }} đã NGỪNG HOẠT ĐỘNG",
"eventNodeUp": "Node {{ .Name }} đã HOẠT ĐỘNG",
"eventLoginFallback": "Đăng nhập thất bại từ {{ .Source }}",
"memoryThreshold": "Sử dụng bộ nhớ {{ .Percent }}% vượt quá ngưỡng {{ .Threshold }}%"
},
"buttons": {
"closeKeyboard": "❌ Đóng Bàn Phím",
"cancel": "❌ Hủy",
"cancelReset": "❌ Hủy Đặt Lại",
"cancelIpLimit": "❌ Hủy Giới Hạn IP",
"confirmResetTraffic": "✅ Xác Nhận Đặt Lại Lưu Lượng?",
"confirmClearIps": "✅ Xác Nhận Xóa Các IP?",
"confirmRemoveTGUser": "✅ Xác Nhận Xóa Người Dùng Telegram?",
"confirmToggle": "✅ Xác nhận Bật/Tắt người dùng?",
"dbBackup": "Tải bản sao lưu cơ sở dữ liệu",
"serverUsage": "Sử Dụng Máy Chủ",
"getInbounds": "Lấy cổng vào",
"depleteSoon": "Depleted Soon",
"clientUsage": "Lấy Sử Dụng",
"onlines": "Khách hàng trực tuyến",
"commands": "Lệnh",
"refresh": "🔄 Cập Nhật",
"clearIPs": "❌ Xóa IP",
"removeTGUser": "❌ Xóa Người Dùng Telegram",
"selectTGUser": "👤 Chọn Người Dùng Telegram",
"selectOneTGUser": "👤 Chọn một người dùng telegram:",
"resetTraffic": "📈 Đặt Lại Lưu Lượng",
"resetExpire": "📅 Thay đổi ngày hết hạn",
"ipLog": "🔢 Nhật ký địa chỉ IP",
"ipLimit": "🔢 Giới Hạn địa chỉ IP",
"setTGUser": "👤 Đặt Người Dùng Telegram",
"toggle": "🔘 Bật / Tắt",
"custom": "🔢 Tùy chỉnh",
"confirmNumber": "✅ Xác nhận: {{ .Num }}",
"confirmNumberAdd": "✅ Xác nhận thêm: {{ .Num }}",
"limitTraffic": "🚧 Giới hạn lưu lượng",
"getBanLogs": "Cấm nhật ký",
"allClients": "Tất cả Khách hàng",
"addClient": "Thêm Khách Hàng",
"submitDisable": "Gửi Dưới Dạng Vô Hiệu ☑️",
"submitEnable": "Gửi Dưới Dạng Kích Hoạt ✅",
"use_default": "🏷️ Sử Dụng Mặc Định",
"change_email": "⚙️📧 Email",
"change_comment": "⚙️💬 Bình Luận",
"ResetAllTraffics": "Đặt lại tất cả lưu lượng",
"SortedTrafficUsageReport": "Báo cáo sử dụng lưu lượng đã sắp xếp"
},
"answers": {
"successfulOperation": "✅ Thành công!",
"errorOperation": "❗ Lỗi Trong Quá Trình Thực Hiện.",
"getInboundsFailed": "❌ Không Thể Lấy Được Inbounds",
"getClientsFailed": "❌ Không thể lấy khách hàng.",
"canceled": "❌ {{ .Email }} : Thao Tác Đã Bị Hủy.",
"clientRefreshSuccess": "✅ {{ .Email }} : Cập Nhật Thành Công Cho Khách Hàng.",
"IpRefreshSuccess": "✅ {{ .Email }} : Cập Nhật Thành Công Cho IPs.",
"TGIdRefreshSuccess": "✅ {{ .Email }} : Cập Nhật Thành Công Cho Người Dùng Telegram.",
"resetTrafficSuccess": "✅ {{ .Email }} : Đặt Lại Lưu Lượng Thành Công.",
"setTrafficLimitSuccess": "✅ {{ .Email }} : Đã lưu thành công giới hạn lưu lượng.",
"expireResetSuccess": "✅ {{ .Email }} : Đặt Lại Ngày Hết Hạn Thành Công.",
"resetIpSuccess": "✅ {{ .Email }} : Giới Hạn IP {{ .Count }} Đã Được Lưu Thành Công.",
"clearIpSuccess": "✅ {{ .Email }} : IP Đã Được Xóa Thành Công.",
"getIpLog": "✅ {{ .Email }} : Lấy nhật ký IP Thành Công.",
"getUserInfo": "✅ {{ .Email }} : Lấy Thông Tin Người Dùng Telegram Thành Công.",
"removedTGUserSuccess": "✅ {{ .Email }} : Người Dùng Telegram Đã Được Xóa Thành Công.",
"enableSuccess": "✅ {{ .Email }} : Đã Bật Thành Công.",
"disableSuccess": "✅ {{ .Email }} : Đã Tắt Thành Công.",
"askToAddUserId": "Cấu hình của bạn không được tìm thấy!\r\nVui lòng yêu cầu Quản trị viên sử dụng ID người dùng telegram của bạn trong cấu hình của bạn.\r\n\r\nID người dùng của bạn: <code>{{ .TgUserID }}</code>",
"chooseClient": "Chọn một Khách hàng cho Inbound {{ .Inbound }}",
"chooseInbound": "Chọn một Inbound"
}
},
"email": {}
}