From 2928b52b045e9327378e60aef23af4739f5f8f8c Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Fri, 15 May 2026 13:12:54 +0200 Subject: [PATCH] feat(tgbot): add Flow picker when creating a VLESS client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bot's add-client flow already serialised client_Flow into the VLESS JSON template but never exposed a way to set it from Telegram, so every client ended up with an empty flow regardless of the inbound's transport. Added an inline "Flow" row to the VLESS protocol keyboard with three choices — None, xtls-rprx-vision, and xtls-rprx-vision-udp443 — and a matching i18n key in all 13 locale files. The row is only shown when the inbound can actually use Vision flow (mirrors the frontend's canEnableTlsFlow check: VLESS over TCP with TLS or Reality); on other transports it's hidden and any stale client_Flow value is reset, so the generated JSON stays consistent with the inbound's stream settings. --- web/service/tgbot.go | 74 +++++++++++++++++++++++++++++++++++++- web/translation/ar-EG.json | 1 + web/translation/en-US.json | 1 + web/translation/es-ES.json | 1 + web/translation/fa-IR.json | 1 + web/translation/id-ID.json | 1 + web/translation/ja-JP.json | 1 + web/translation/pt-BR.json | 1 + web/translation/ru-RU.json | 1 + web/translation/tr-TR.json | 1 + web/translation/uk-UA.json | 1 + web/translation/vi-VN.json | 1 + web/translation/zh-CN.json | 1 + web/translation/zh-TW.json | 1 + 14 files changed, 86 insertions(+), 1 deletion(-) diff --git a/web/service/tgbot.go b/web/service/tgbot.go index 3d737c70..179c082f 100644 --- a/web/service/tgbot.go +++ b/web/service/tgbot.go @@ -1398,6 +1398,25 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool return } + t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId) + t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation")) + case "add_client_set_flow": + if dataArray[1] == "none" { + client_Flow = "" + } else { + client_Flow = dataArray[1] + } + messageId := callbackQuery.Message.GetMessageID() + inbound, err := t.inboundService.GetInbound(receiver_inbound_ID) + if err != nil { + t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error()) + return + } + message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol) + if err != nil { + t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error()) + return + } t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId) t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation")) case "add_client_ip_limit_in": @@ -1865,6 +1884,22 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool ), ) t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard) + case "add_client_ch_default_flow": + inlineKeyboard := tu.InlineKeyboard( + tu.InlineKeyboardRow( + tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("add_client_default_traffic_exp")), + ), + tu.InlineKeyboardRow( + tu.InlineKeyboardButton("None").WithCallbackData(t.encodeQuery("add_client_set_flow none")), + ), + tu.InlineKeyboardRow( + tu.InlineKeyboardButton("xtls-rprx-vision").WithCallbackData(t.encodeQuery("add_client_set_flow xtls-rprx-vision")), + ), + tu.InlineKeyboardRow( + tu.InlineKeyboardButton("xtls-rprx-vision-udp443").WithCallbackData(t.encodeQuery("add_client_set_flow xtls-rprx-vision-udp443")), + ), + ) + t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard) case "add_client_ch_default_ip_limit": inlineKeyboard := tu.InlineKeyboard( tu.InlineKeyboardRow( @@ -3345,6 +3380,25 @@ func (t *Tgbot) getCommonClientButtons() [][]telego.InlineKeyboardButton { } } +// inboundCanEnableTlsFlow mirrors Inbound.canEnableTlsFlow() from the frontend +// model: xtls-rprx-vision is only valid on VLESS-over-TCP with TLS or Reality. +func inboundCanEnableTlsFlow(ib *model.Inbound) bool { + if ib == nil || ib.Protocol != model.VLESS { + return false + } + var stream struct { + Network string `json:"network"` + Security string `json:"security"` + } + if err := json.Unmarshal([]byte(ib.StreamSettings), &stream); err != nil { + return false + } + if stream.Network != "tcp" { + return false + } + return stream.Security == "tls" || stream.Security == "reality" +} + // addClient handles the process of adding a new client to an inbound. func (t *Tgbot) addClient(chatId int64, msg string, messageID ...int) { inbound, err := t.inboundService.GetInbound(receiver_inbound_ID) @@ -3357,13 +3411,31 @@ func (t *Tgbot) addClient(chatId int64, msg string, messageID ...int) { var protocolRows [][]telego.InlineKeyboardButton switch protocol { - case model.VMESS, model.VLESS: + case model.VMESS: protocolRows = [][]telego.InlineKeyboardButton{ tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_email")).WithCallbackData("add_client_ch_default_email"), tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_id")).WithCallbackData("add_client_ch_default_id"), ), } + case model.VLESS: + protocolRows = [][]telego.InlineKeyboardButton{ + tu.InlineKeyboardRow( + tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_email")).WithCallbackData("add_client_ch_default_email"), + tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_id")).WithCallbackData("add_client_ch_default_id"), + ), + } + if inboundCanEnableTlsFlow(inbound) { + flowLabel := t.I18nBot("tgbot.buttons.change_flow") + if client_Flow != "" { + flowLabel = flowLabel + ": " + client_Flow + } + protocolRows = append(protocolRows, tu.InlineKeyboardRow( + tu.InlineKeyboardButton(flowLabel).WithCallbackData("add_client_ch_default_flow"), + )) + } else if client_Flow != "" { + client_Flow = "" + } case model.Trojan: protocolRows = [][]telego.InlineKeyboardButton{ tu.InlineKeyboardRow( diff --git a/web/translation/ar-EG.json b/web/translation/ar-EG.json index 663e81f3..ecf1c19d 100644 --- a/web/translation/ar-EG.json +++ b/web/translation/ar-EG.json @@ -952,6 +952,7 @@ "change_password": "⚙️🔑 كلمة السر", "change_email": "⚙️📧 البريد الإلكتروني", "change_comment": "⚙️💬 تعليق", + "change_flow": "⚙️🚦 التدفق", "ResetAllTraffics": "إعادة ضبط جميع الترافيك", "SortedTrafficUsageReport": "تقرير استخدام الترافيك المرتب" }, diff --git a/web/translation/en-US.json b/web/translation/en-US.json index 5cf1f7c0..eedd3454 100644 --- a/web/translation/en-US.json +++ b/web/translation/en-US.json @@ -952,6 +952,7 @@ "change_password": "⚙️🔑 Password", "change_email": "⚙️📧 Email", "change_comment": "⚙️💬 Comment", + "change_flow": "⚙️🚦 Flow", "ResetAllTraffics": "Reset All Traffics", "SortedTrafficUsageReport": "Sorted Traffic Usage Report" }, diff --git a/web/translation/es-ES.json b/web/translation/es-ES.json index 9b6a5e87..34af89ca 100644 --- a/web/translation/es-ES.json +++ b/web/translation/es-ES.json @@ -952,6 +952,7 @@ "change_password": "⚙️🔑 Contraseña", "change_email": "⚙️📧 Correo electrónico", "change_comment": "⚙️💬 Comentario", + "change_flow": "⚙️🚦 Flujo", "ResetAllTraffics": "Reiniciar todo el tráfico", "SortedTrafficUsageReport": "Informe de uso de tráfico ordenado" }, diff --git a/web/translation/fa-IR.json b/web/translation/fa-IR.json index 1295e400..fd948788 100644 --- a/web/translation/fa-IR.json +++ b/web/translation/fa-IR.json @@ -952,6 +952,7 @@ "change_password": "⚙️🔑 گذرواژه", "change_email": "⚙️📧 ایمیل", "change_comment": "⚙️💬 نظر", + "change_flow": "⚙️🚦 جریان", "ResetAllTraffics": "بازنشانی همه ترافیک‌ها", "SortedTrafficUsageReport": "گزارش استفاده از ترافیک مرتب‌شده" }, diff --git a/web/translation/id-ID.json b/web/translation/id-ID.json index 84769a3b..b8f44ac6 100644 --- a/web/translation/id-ID.json +++ b/web/translation/id-ID.json @@ -952,6 +952,7 @@ "change_password": "⚙️🔑 Kata Sandi", "change_email": "⚙️📧 Email", "change_comment": "⚙️💬 Komentar", + "change_flow": "⚙️🚦 Flow", "ResetAllTraffics": "Reset Semua Lalu Lintas", "SortedTrafficUsageReport": "Laporan Penggunaan Lalu Lintas yang Terurut" }, diff --git a/web/translation/ja-JP.json b/web/translation/ja-JP.json index 913ef5e5..630f5623 100644 --- a/web/translation/ja-JP.json +++ b/web/translation/ja-JP.json @@ -952,6 +952,7 @@ "change_password": "⚙️🔑 パスワード", "change_email": "⚙️📧 メールアドレス", "change_comment": "⚙️💬 コメント", + "change_flow": "⚙️🚦 フロー", "ResetAllTraffics": "すべてのトラフィックをリセット", "SortedTrafficUsageReport": "ソートされたトラフィック使用レポート" }, diff --git a/web/translation/pt-BR.json b/web/translation/pt-BR.json index 74cd6ba7..9f1b67d7 100644 --- a/web/translation/pt-BR.json +++ b/web/translation/pt-BR.json @@ -952,6 +952,7 @@ "change_password": "⚙️🔑 Senha", "change_email": "⚙️📧 E-mail", "change_comment": "⚙️💬 Comentário", + "change_flow": "⚙️🚦 Fluxo", "ResetAllTraffics": "Redefinir Todo o Tráfego", "SortedTrafficUsageReport": "Relatório de Uso de Tráfego Ordenado" }, diff --git a/web/translation/ru-RU.json b/web/translation/ru-RU.json index 3c16ede8..f7ddafa6 100644 --- a/web/translation/ru-RU.json +++ b/web/translation/ru-RU.json @@ -952,6 +952,7 @@ "change_password": "⚙️🔑 Пароль", "change_email": "⚙️📧 Email", "change_comment": "⚙️💬 Комментарий", + "change_flow": "⚙️🚦 Поток", "ResetAllTraffics": "Сбросить весь трафик", "SortedTrafficUsageReport": "Отсортированный отчет об использовании трафика" }, diff --git a/web/translation/tr-TR.json b/web/translation/tr-TR.json index 7895c1b6..a8dc2c3c 100644 --- a/web/translation/tr-TR.json +++ b/web/translation/tr-TR.json @@ -952,6 +952,7 @@ "change_password": "⚙️🔑 Şifre", "change_email": "⚙️📧 E-posta", "change_comment": "⚙️💬 Yorum", + "change_flow": "⚙️🚦 Akış", "ResetAllTraffics": "Tüm Trafikleri Sıfırla", "SortedTrafficUsageReport": "Sıralı Trafik Kullanım Raporu" }, diff --git a/web/translation/uk-UA.json b/web/translation/uk-UA.json index 6ed76eb0..417866d1 100644 --- a/web/translation/uk-UA.json +++ b/web/translation/uk-UA.json @@ -952,6 +952,7 @@ "change_password": "⚙️🔑 Пароль", "change_email": "⚙️📧 Електронна пошта", "change_comment": "⚙️💬 Коментар", + "change_flow": "⚙️🚦 Потік", "ResetAllTraffics": "Скинути весь трафік", "SortedTrafficUsageReport": "Відсортований звіт про використання трафіку" }, diff --git a/web/translation/vi-VN.json b/web/translation/vi-VN.json index 09e8d8b2..b292c058 100644 --- a/web/translation/vi-VN.json +++ b/web/translation/vi-VN.json @@ -952,6 +952,7 @@ "change_password": "⚙️🔑 Mật Khẩu", "change_email": "⚙️📧 Email", "change_comment": "⚙️💬 Bình Luận", + "change_flow": "⚙️🚦 Flow", "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" }, diff --git a/web/translation/zh-CN.json b/web/translation/zh-CN.json index 0d65cb53..3f648cb2 100644 --- a/web/translation/zh-CN.json +++ b/web/translation/zh-CN.json @@ -952,6 +952,7 @@ "change_password": "⚙️🔑 密码", "change_email": "⚙️📧 邮箱", "change_comment": "⚙️💬 评论", + "change_flow": "⚙️🚦 流控", "ResetAllTraffics": "重置所有流量", "SortedTrafficUsageReport": "排序的流量使用报告" }, diff --git a/web/translation/zh-TW.json b/web/translation/zh-TW.json index 56a0c075..a28c0e0f 100644 --- a/web/translation/zh-TW.json +++ b/web/translation/zh-TW.json @@ -952,6 +952,7 @@ "change_password": "⚙️🔑 密碼", "change_email": "⚙️📧 電子郵件", "change_comment": "⚙️💬 評論", + "change_flow": "⚙️🚦 流控", "ResetAllTraffics": "重設所有流量", "SortedTrafficUsageReport": "排序過的流量使用報告" },