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": "无到期",
"copyAllConfigs": "复制全部配置",
"copyAllConfigsCopied": "已复制全部配置",
"email": "邮箱"
"email": "邮箱",
"individualLinks": "单独链接"
},
"menu": {
"theme": "主题",
@@ -1168,7 +1169,44 @@
"httpMethodDesc": "探活请求使用的 HTTP 方法。",
"note": "leastPing/leastLoad 均衡器始终带有 burstObservatory。此开关自定义其探活参数 — 关闭以使用内置默认值。更改在面板重启后生效。"
}
}
},
"TGBotSettings": "Telegram 机器人",
"notifications": "通知",
"notifyTime": {
"custom": "自定义 (crontab)",
"daily": "@daily — 每天 00:00",
"every": "@every — 按间隔重复",
"hourly": "@hourly — 每小时",
"hours": "小时",
"interval": "间隔",
"minutes": "分钟",
"monthly": "@monthly — 每月",
"seconds": "秒",
"unit": "单位",
"weekly": "@weekly — 每周"
},
"telegramAPIServer": "Telegram API 服务器",
"telegramAPIServerDesc": "要使用的 Telegram API 服务器。留空以使用默认服务器。",
"telegramBotEnable": "启用 Telegram 机器人",
"telegramBotEnableDesc": "启用 Telegram 机器人功能",
"telegramBotLanguage": "Telegram 机器人语言",
"telegramChatId": "管理员聊天 ID",
"telegramChatIdDesc": "Telegram 管理员聊天 ID (多个以逗号分隔)(可通过 {'@'}userinfobot 获取,或在机器人中使用 '/id' 命令获取)",
"telegramNotifyTime": "通知时间",
"telegramNotifyTimeDesc": "Telegram 机器人发送周期性报告的频率。选择预设间隔,或选择“自定义”以输入 crontab 表达式。",
"telegramToken": "Telegram Token",
"telegramTokenConfigured": "已配置;留空则保留当前令牌。",
"telegramTokenDesc": "从 '{'@'}BotFather' 获取的 Telegram 机器人令牌",
"telegramTokenPlaceholder": "已配置——输入新令牌以替换",
"testTgBot": "发送测试消息",
"tgBotNotEnabled": "Telegram 机器人未启用",
"tgBotNotRunning": "Telegram 机器人未运行",
"tgEventBusNotify": "Telegram 事件通知",
"tgEventBusNotifyDesc": "选择触发 Telegram 通知的事件",
"tgNotifyBackup": "数据库备份",
"tgNotifyBackupDesc": "发送带有报告的数据库备份文件",
"tgTestFailed": "Telegram 测试失败",
"tgTestSuccess": "测试消息已发送至 Telegram"
},
"xray": {
"importRules": "导入规则",
@@ -1657,7 +1695,182 @@
}
},
"tgbot": {
"noIpRecord": "❗ 没有 IP 记录!"
"noIpRecord": "❗ 没有 IP 记录!",
"keyboardClosed": "❌ 自定义键盘已关闭!",
"noResult": "❗ 没有结果!",
"noQuery": "❌ 未找到查询!请再次使用该命令!",
"wentWrong": "❌ 出了点问题!",
"noInbounds": "❗ 未找到入站!",
"unlimited": "♾ 无限(重置)",
"add": "添加",
"month": "月",
"months": "月",
"days": "天",
"hours": "小时",
"minutes": "分钟",
"unknown": "未知",
"inbounds": "入站",
"clients": "客户端",
"offline": "🔴 离线",
"online": "🟢 在线",
"commands": {
"unknown": "❗ 未知命令",
"pleaseChoose": "👇 请选择:\r\n",
"help": "🤖 欢迎使用本机器人!它旨在为您提供来自服务器的特定数据,并允许您进行必要的修改。\r\n\r\n",
"start": "👋 你好,<i>{{ .Firstname }}</i>。\r\n",
"welcome": "🤖 欢迎来到 <b>{{ .Hostname }}</b> 管理机器人。\r\n",
"status": "✅ 机器人正常运行!",
"usage": "❗ 请输入要搜索的文本!",
"getID": "🆔 您的 ID 为:<code>{{ .ID }}</code>",
"helpAdminCommands": "要重新启动 Xray Core:\r\n<code>/restart</code>\r\n\r\n要搜索客户电子邮件:\r\n<code>/usage [电子邮件]</code>\r\n\r\n要搜索入站(带有客户统计数据):\r\n<code>/inbound [备注]</code>\r\n\r\nTelegram 聊天 ID:\r\n<code>/id</code>",
"helpClientCommands": "要搜索统计数据,请使用以下命令:\r\n<code>/usage [电子邮件]</code>\r\n\r\nTelegram 聊天 ID:\r\n<code>/id</code>",
"restartUsage": "\r\n\r\n<code>/restart</code>",
"restartSuccess": "✅ 操作成功!",
"restartFailed": "❗ 操作错误。\r\n\r\n<code>错误: {{ .Error }}</code>.",
"xrayNotRunning": "❗ Xray Core 未运行。",
"startDesc": "显示主菜单",
"helpDesc": "机器人帮助",
"statusDesc": "检查机器人状态",
"idDesc": "显示您的 Telegram ID",
"usageDesc": "查看客户端用量:/usage 邮箱",
"inboundDesc": "搜索入站:/inbound 备注(管理员)",
"restartDesc": "重启 Xray 内核(管理员)",
"clearallDesc": "重置所有客户端流量(管理员)"
},
"messages": {
"cpuThreshold": "CPU 使用率为 {{ .Percent }}%,超过阈值 {{ .Threshold }}%",
"selectUserFailed": "❌ 用户选择错误!",
"userSaved": "✅ 电报用户已保存。",
"loginSuccess": "✅ 成功登录到面板。\r\n",
"loginFailed": "❗️ 面板登录失败。\r\n",
"report": "🕰 定时报告:{{ .RunTime }}\r\n",
"datetime": "⏰ 日期时间:{{ .DateTime }}\r\n",
"hostname": "💻 主机: {{ .Hostname }}\r\n",
"version": "🚀 X-UI 版本:{{ .Version }}\r\n",
"xrayVersion": "📡 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": "⏳ 服务器运行时间:{{ .UpTime }} {{ .Unit }}\r\n",
"serverLoad": "📈 服务器负载:{{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n",
"serverMemory": "📋 RAM: {{ .Current }}/{{ .Total }}\r\n",
"tcpCount": "🔹 TCP: {{ .Count }}\r\n",
"udpCount": "🔸 UDP: {{ .Count }}\r\n",
"traffic": "🚦 流量:{{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n",
"xrayStatus": "ℹ️ 状态: {{ .State }}\r\n",
"username": "👤 用户名:{{ .Username }}\r\n",
"reason": "❗️ 原因:{{ .Reason }}\r\n",
"time": "⏰ 时间:{{ .Time }}\r\n",
"inbound": "📍 入站: {{ .Remark }}\r\n",
"port": "🔌 端口: {{ .Port }}\r\n",
"expire": "📅 过期日期:{{ .Time }}\r\n",
"expireIn": "📅 剩余时间:{{ .Time }}\r\n",
"active": "💡 激活:{{ .Enable }}\r\n",
"enabled": "🚨 已启用:{{ .Enable }}\r\n",
"online": "🌐 连接状态:{{ .Status }}\r\n",
"lastOnline": "🔙 上次在线: {{ .Time }}\r\n",
"email": "📧 邮箱: {{ .Email }}\r\n",
"upload": "🔼 上传: ↑{{ .Upload }}\r\n",
"download": "🔽 下载: ↓{{ .Download }}\r\n",
"total": "📊 总计: ↑↓{{ .UpDown }} / {{ .Total }}\r\n",
"TGUser": "👤 电报用户:{{ .TelegramID }}\r\n",
"exhaustedCount": "🚨 耗尽的 {{ .Type }} 数量:\r\n",
"onlinesCount": "🌐 在线客户:{{ .Count }}\r\n",
"disabled": "🛑 禁用:{{ .Disabled }}\r\n",
"depleteSoon": "🔜 即将耗尽:{{ .Deplete }}\r\n\r\n",
"backupTime": "🗄 备份时间:{{ .Time }}\r\n",
"refreshedOn": "\r\n📋🔄 刷新时间:{{ .Time }}\r\n\r\n",
"yes": "✅ 是的",
"no": "❌ 否",
"received_email": "📧📥 邮箱已更新。",
"received_comment": "💬📥 评论已更新。",
"email_prompt": "📧 默认邮箱: {{ .ClientEmail }}\n\n请输入您的邮箱。",
"comment_prompt": "💬 默认评论: {{ .ClientComment }}\n\n请输入您的评论。",
"cancel": "❌ 进程已取消!\n\n您可以随时使用 /start 重新开始。 🔄",
"error_add_client": "⚠️ 错误:\n\n {{ .error }}",
"using_default_value": "好的,我会使用默认值。 😊",
"incorrect_input": "您的输入无效。\n短语应连续输入,不能有空格。\n正确示例: aaaaaa\n错误示例: aaa aaa 🚫",
"AreYouSure": "你确定吗?🤔",
"SuccessResetTraffic": "📧 邮箱: {{ .ClientEmail }}\n🏁 结果: ✅ 成功",
"FailedResetTraffic": "📧 邮箱: {{ .ClientEmail }}\n🏁 结果: ❌ 失败 \n\n🛠️ 错误: [ {{ .ErrorMessage }} ]",
"FinishProcess": "🔚 所有客户的流量重置已完成。",
"eventOutboundDown": "出站 {{ .Tag }} 已断开",
"eventOutboundUp": "出站 {{ .Tag }} 已恢复",
"eventErrorDetail": "错误:{{ .Error }}",
"eventDelayDetail": "延迟:{{ .Delay }} 毫秒",
"eventXrayCrash": "Xray 已崩溃",
"eventXrayCrashError": "错误:{{ .Error }}",
"eventNodeDown": "节点 {{ .Name }} 已离线",
"eventNodeUp": "节点 {{ .Name }} 已上线",
"eventLoginFallback": "来自 {{ .Source }} 的登录失败",
"memoryThreshold": "内存使用率 {{ .Percent }}% 超过阈值 {{ .Threshold }}%"
},
"buttons": {
"closeKeyboard": "❌ 关闭键盘",
"cancel": "❌ 取消",
"cancelReset": "❌ 取消重置",
"cancelIpLimit": "❌ 取消 IP 限制",
"confirmResetTraffic": "✅ 确认重置流量?",
"confirmClearIps": "✅ 确认清除 IP?",
"confirmRemoveTGUser": "✅ 确认移除 Telegram 用户?",
"confirmToggle": "✅ 确认启用/禁用用户?",
"dbBackup": "获取数据库备份",
"serverUsage": "服务器使用情况",
"getInbounds": "获取入站信息",
"depleteSoon": "即将耗尽",
"clientUsage": "获取使用情况",
"onlines": "在线客户端",
"commands": "命令",
"refresh": "🔄 刷新",
"clearIPs": "❌ 清除 IP",
"removeTGUser": "❌ 移除 Telegram 用户",
"selectTGUser": "👤 选择 Telegram 用户",
"selectOneTGUser": "👤 选择一个 Telegram 用户:",
"resetTraffic": "📈 重置流量",
"resetExpire": "📅 更改到期日期",
"ipLog": "🔢 IP 日志",
"ipLimit": "🔢 IP 限制",
"setTGUser": "👤 设置 Telegram 用户",
"toggle": "🔘 启用/禁用",
"custom": "🔢 自定义",
"confirmNumber": "✅ 确认: {{ .Num }}",
"confirmNumberAdd": "✅ 确认添加:{{ .Num }}",
"limitTraffic": "🚧 流量限制",
"getBanLogs": "禁止日志",
"allClients": "所有客户",
"addClient": "添加客户",
"submitDisable": "提交为禁用 ☑️",
"submitEnable": "提交为启用 ✅",
"use_default": "🏷️ 使用默认",
"change_email": "⚙️📧 邮箱",
"change_comment": "⚙️💬 评论",
"ResetAllTraffics": "重置所有流量",
"SortedTrafficUsageReport": "排序的流量使用报告"
},
"answers": {
"successfulOperation": "✅ 成功!",
"errorOperation": "❗ 操作错误。",
"getInboundsFailed": "❌ 获取入站信息失败。",
"getClientsFailed": "❌ 获取客户失败。",
"canceled": "❌ {{ .Email }}:操作已取消。",
"clientRefreshSuccess": "✅ {{ .Email }}:客户端刷新成功。",
"IpRefreshSuccess": "✅ {{ .Email }}:IP 刷新成功。",
"TGIdRefreshSuccess": "✅ {{ .Email }}:客户端的 Telegram 用户刷新成功。",
"resetTrafficSuccess": "✅ {{ .Email }}:流量已重置成功。",
"setTrafficLimitSuccess": "✅ {{ .Email }}: 流量限制保存成功。",
"expireResetSuccess": "✅ {{ .Email }}:过期天数已重置成功。",
"resetIpSuccess": "✅ {{ .Email }}:成功保存 IP 限制数量为 {{ .Count }}。",
"clearIpSuccess": "✅ {{ .Email }}:IP 已成功清除。",
"getIpLog": "✅ {{ .Email }}:获取 IP 日志。",
"getUserInfo": "✅ {{ .Email }}:获取 Telegram 用户信息。",
"removedTGUserSuccess": "✅ {{ .Email }}:Telegram 用户已成功移除。",
"enableSuccess": "✅ {{ .Email }}:已成功启用。",
"disableSuccess": "✅ {{ .Email }}:已成功禁用。",
"askToAddUserId": "未找到您的配置!\r\n请向管理员询问,在您的配置中使用您的 Telegram 用户 ChatID。\r\n\r\n您的用户 ChatID:<code>{{ .TgUserID }}</code>",
"chooseClient": "为入站 {{ .Inbound }} 选择一个客户",
"chooseInbound": "选择一个入站"
}
},
"email": {}
}