Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/MHSanaei/3x-ui.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authornistootsin <104831639+nistootsin@users.noreply.github.com>2025-05-06 19:27:17 +0300
committerGitHub <noreply@github.com>2025-05-06 19:27:17 +0300
commitd39ccf4b8f77f99d4468580085e9d89e8b5f0b1c (patch)
tree90ca065b6aed3171771f9c87ab3b6371eb91f1ea /web/service
parent1aed2d8cdcd7b971d3bc055d428a7958a71c9226 (diff)
Added 3 new buttons to telegram bot (#2965)
* Add a new button to but : Reset All Clients * handel translation for `Reset All Clients` button * refactoring * add a new button to telegram bot >> `Sorted Traffic Usage Report` * - refactoring * add ip limit conifg on new client adding time
Diffstat (limited to 'web/service')
-rw-r--r--web/service/inbound.go35
-rw-r--r--web/service/tgbot.go201
2 files changed, 233 insertions, 3 deletions
diff --git a/web/service/inbound.go b/web/service/inbound.go
index fce01634..f2646dbb 100644
--- a/web/service/inbound.go
+++ b/web/service/inbound.go
@@ -3,6 +3,7 @@ package service
import (
"encoding/json"
"fmt"
+ "sort"
"strconv"
"strings"
"time"
@@ -2025,3 +2026,37 @@ func (s *InboundService) MigrateDB() {
func (s *InboundService) GetOnlineClients() []string {
return p.GetOnlineClients()
}
+
+func (s *InboundService) FilterAndSortClientEmails(emails []string) ([]string, []string, error) {
+ db := database.GetDB()
+
+ // Step 1: Get ClientTraffic records for emails in the input list
+ var clients []xray.ClientTraffic
+ err := db.Where("email IN ?", emails).Find(&clients).Error
+ if err != nil && err != gorm.ErrRecordNotFound {
+ return nil, nil, err
+ }
+
+ // Step 2: Sort clients by (Up + Down) descending
+ sort.Slice(clients, func(i, j int) bool {
+ return (clients[i].Up + clients[i].Down) > (clients[j].Up + clients[j].Down)
+ })
+
+ // Step 3: Extract sorted valid emails and track found ones
+ validEmails := make([]string, 0, len(clients))
+ found := make(map[string]bool)
+ for _, client := range clients {
+ validEmails = append(validEmails, client.Email)
+ found[client.Email] = true
+ }
+
+ // Step 4: Identify emails that were not found in the database
+ extraEmails := make([]string, 0)
+ for _, email := range emails {
+ if !found[email] {
+ extraEmails = append(extraEmails, email)
+ }
+ }
+
+ return validEmails, extraEmails, nil
+}
diff --git a/web/service/tgbot.go b/web/service/tgbot.go
index 2c572192..2f36697e 100644
--- a/web/service/tgbot.go
+++ b/web/service/tgbot.go
@@ -1069,6 +1069,83 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
}
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
+ case "add_client_ip_limit_c":
+ if len(dataArray) == 2 {
+ count, _ := strconv.Atoi(dataArray[1])
+ client_LimitIP = count
+ }
+
+ 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)
+
+ t.addClient(chatId, message_text, messageId)
+ t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
+ case "add_client_ip_limit_in":
+ if len(dataArray) >= 2 {
+ oldInputNumber, err := strconv.Atoi(dataArray[1])
+ inputNumber := oldInputNumber
+ if err == nil {
+ if len(dataArray) == 3 {
+ num, err := strconv.Atoi(dataArray[2])
+ if err == nil {
+ if num == -2 {
+ inputNumber = 0
+ } else if num == -1 {
+ if inputNumber > 0 {
+ inputNumber = (inputNumber / 10)
+ }
+ } else {
+ inputNumber = (inputNumber * 10) + num
+ }
+ }
+ if inputNumber == oldInputNumber {
+ t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
+ return
+ }
+ if inputNumber >= 999999 {
+ t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
+ return
+ }
+ }
+ inlineKeyboard := tu.InlineKeyboard(
+ tu.InlineKeyboardRow(
+ tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("add_client_default_ip_limit")),
+ ),
+ tu.InlineKeyboardRow(
+ tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmNumber", "Num=="+strconv.Itoa(inputNumber))).WithCallbackData(t.encodeQuery("add_client_ip_limit_c "+strconv.Itoa(inputNumber))),
+ ),
+ tu.InlineKeyboardRow(
+ tu.InlineKeyboardButton("1").WithCallbackData(t.encodeQuery("add_client_ip_limit_in "+strconv.Itoa(inputNumber)+" 1")),
+ tu.InlineKeyboardButton("2").WithCallbackData(t.encodeQuery("add_client_ip_limit_in "+strconv.Itoa(inputNumber)+" 2")),
+ tu.InlineKeyboardButton("3").WithCallbackData(t.encodeQuery("add_client_ip_limit_in "+strconv.Itoa(inputNumber)+" 3")),
+ ),
+ tu.InlineKeyboardRow(
+ tu.InlineKeyboardButton("4").WithCallbackData(t.encodeQuery("add_client_ip_limit_in "+strconv.Itoa(inputNumber)+" 4")),
+ tu.InlineKeyboardButton("5").WithCallbackData(t.encodeQuery("add_client_ip_limit_in "+strconv.Itoa(inputNumber)+" 5")),
+ tu.InlineKeyboardButton("6").WithCallbackData(t.encodeQuery("add_client_ip_limit_in "+strconv.Itoa(inputNumber)+" 6")),
+ ),
+ tu.InlineKeyboardRow(
+ tu.InlineKeyboardButton("7").WithCallbackData(t.encodeQuery("add_client_ip_limit_in "+strconv.Itoa(inputNumber)+" 7")),
+ tu.InlineKeyboardButton("8").WithCallbackData(t.encodeQuery("add_client_ip_limit_in "+strconv.Itoa(inputNumber)+" 8")),
+ tu.InlineKeyboardButton("9").WithCallbackData(t.encodeQuery("add_client_ip_limit_in "+strconv.Itoa(inputNumber)+" 9")),
+ ),
+ tu.InlineKeyboardRow(
+ tu.InlineKeyboardButton("🔄").WithCallbackData(t.encodeQuery("add_client_ip_limit_in "+strconv.Itoa(inputNumber)+" -2")),
+ tu.InlineKeyboardButton("0").WithCallbackData(t.encodeQuery("add_client_ip_limit_in "+strconv.Itoa(inputNumber)+" 0")),
+ tu.InlineKeyboardButton("⬅️").WithCallbackData(t.encodeQuery("add_client_ip_limit_in "+strconv.Itoa(inputNumber)+" -1")),
+ ),
+ )
+ t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
+ return
+ }
+ }
+ t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
+ t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
case "clear_ips":
inlineKeyboard := tu.InlineKeyboard(
tu.InlineKeyboardRow(
@@ -1382,6 +1459,35 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
),
)
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
+ case "add_client_ch_default_ip_limit":
+ inlineKeyboard := tu.InlineKeyboard(
+ tu.InlineKeyboardRow(
+ tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("add_client_default_ip_limit")),
+ ),
+ tu.InlineKeyboardRow(
+ tu.InlineKeyboardButton(t.I18nBot("tgbot.unlimited")).WithCallbackData(t.encodeQuery("add_client_ip_limit_c 0")),
+ tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.custom")).WithCallbackData(t.encodeQuery("add_client_ip_limit_in 0")),
+ ),
+ tu.InlineKeyboardRow(
+ tu.InlineKeyboardButton("1").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 1")),
+ tu.InlineKeyboardButton("2").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 2")),
+ ),
+ tu.InlineKeyboardRow(
+ tu.InlineKeyboardButton("3").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 3")),
+ tu.InlineKeyboardButton("4").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 4")),
+ ),
+ tu.InlineKeyboardRow(
+ tu.InlineKeyboardButton("5").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 5")),
+ tu.InlineKeyboardButton("6").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 6")),
+ tu.InlineKeyboardButton("7").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 7")),
+ ),
+ tu.InlineKeyboardRow(
+ tu.InlineKeyboardButton("8").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 8")),
+ tu.InlineKeyboardButton("9").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 9")),
+ tu.InlineKeyboardButton("10").WithCallbackData(t.encodeQuery("add_client_ip_limit_c 10")),
+ ),
+ )
+ t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
case "add_client_default_info":
t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
t.SendMsgToTgbotDeleteAfter(chatId, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
@@ -1403,6 +1509,16 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
t.addClient(chatId, message_text, messageId)
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+client_Email))
+ case "add_client_default_ip_limit":
+ 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)
+ t.addClient(chatId, message_text, messageId)
+ t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+client_Email))
case "add_client_submit_disable":
client_Enable = false
_, err := t.SubmitAddClient()
@@ -1423,6 +1539,71 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.successfulOperation"), tu.ReplyKeyboardRemove())
}
+ case "reset_all_traffics_cancel":
+ t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
+ t.SendMsgToTgbotDeleteAfter(chatId, t.I18nBot("tgbot.messages.cancel"), 1, tu.ReplyKeyboardRemove())
+ case "reset_all_traffics":
+ inlineKeyboard := tu.InlineKeyboard(
+ tu.InlineKeyboardRow(
+ tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancelReset")).WithCallbackData(t.encodeQuery("reset_all_traffics_cancel")),
+ ),
+ tu.InlineKeyboardRow(
+ tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmResetTraffic")).WithCallbackData(t.encodeQuery("reset_all_traffics_c")),
+ ),
+ )
+ t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.messages.AreYouSure"), inlineKeyboard)
+ case "reset_all_traffics_c":
+ t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
+ emails, err := t.inboundService.getAllEmails()
+ if err != nil {
+ t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation"), tu.ReplyKeyboardRemove())
+ return
+ }
+
+ for _, email := range emails {
+ err := t.inboundService.ResetClientTrafficByEmail(email)
+ if err == nil {
+ msg := t.I18nBot("tgbot.messages.SuccessResetTraffic", "ClientEmail=="+email)
+ t.SendMsgToTgbot(chatId, msg, tu.ReplyKeyboardRemove())
+ } else {
+ msg := t.I18nBot("tgbot.messages.FailedResetTraffic", "ClientEmail=="+email, "ErrorMessage=="+err.Error())
+ t.SendMsgToTgbot(chatId, msg, tu.ReplyKeyboardRemove())
+ }
+ }
+
+ t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.messages.FinishProcess"), tu.ReplyKeyboardRemove())
+ case "get_sorted_traffic_usage_report":
+ t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
+ emails, err := t.inboundService.getAllEmails()
+
+ if err != nil {
+ t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation"), tu.ReplyKeyboardRemove())
+ return
+ }
+ valid_emails, extra_emails, err := t.inboundService.FilterAndSortClientEmails(emails)
+
+ for _, valid_emails := range valid_emails {
+ traffic, err := t.inboundService.GetClientTrafficByEmail(valid_emails)
+ if err != nil {
+ logger.Warning(err)
+ msg := t.I18nBot("tgbot.wentWrong")
+ t.SendMsgToTgbot(chatId, msg)
+ continue
+ }
+ if traffic == nil {
+ msg := t.I18nBot("tgbot.noResult")
+ t.SendMsgToTgbot(chatId, msg)
+ continue
+ }
+
+ output := t.clientInfoMsg(traffic, false, false, false, false, true, false)
+ t.SendMsgToTgbot(chatId, output, tu.ReplyKeyboardRemove())
+ }
+ for _, extra_emails := range extra_emails {
+ msg := fmt.Sprintf("📧 %s\n%s", extra_emails, t.I18nBot("tgbot.noResult"))
+ t.SendMsgToTgbot(chatId, msg, tu.ReplyKeyboardRemove())
+
+ }
}
}
@@ -1451,15 +1632,22 @@ func (t *Tgbot) BuildInboundClientDataMessage(inbound_remark string, protocol mo
traffic_value = common.FormatTraffic(client_TotalGB)
}
+ ip_limit := ""
+ if client_LimitIP == 0 {
+ ip_limit = "♾️ Unlimited(Reset)"
+ } else {
+ ip_limit = fmt.Sprint(client_LimitIP)
+ }
+
switch protocol {
case model.VMESS, model.VLESS:
- message = t.I18nBot("tgbot.messages.inbound_client_data_id", "InboundRemark=="+inbound_remark, "ClientId=="+client_Id, "ClientEmail=="+client_Email, "ClientTraffic=="+traffic_value, "ClientExp=="+expiryTime, "ClientComment=="+client_Comment)
+ message = t.I18nBot("tgbot.messages.inbound_client_data_id", "InboundRemark=="+inbound_remark, "ClientId=="+client_Id, "ClientEmail=="+client_Email, "ClientTraffic=="+traffic_value, "ClientExp=="+expiryTime, "IpLimit=="+ip_limit, "ClientComment=="+client_Comment)
case model.Trojan:
- message = t.I18nBot("tgbot.messages.inbound_client_data_pass", "InboundRemark=="+inbound_remark, "ClientPass=="+client_TrPassword, "ClientEmail=="+client_Email, "ClientTraffic=="+traffic_value, "ClientExp=="+expiryTime, "ClientComment=="+client_Comment)
+ message = t.I18nBot("tgbot.messages.inbound_client_data_pass", "InboundRemark=="+inbound_remark, "ClientPass=="+client_TrPassword, "ClientEmail=="+client_Email, "ClientTraffic=="+traffic_value, "ClientExp=="+expiryTime, "IpLimit=="+ip_limit, "ClientComment=="+client_Comment)
case model.Shadowsocks:
- message = t.I18nBot("tgbot.messages.inbound_client_data_pass", "InboundRemark=="+inbound_remark, "ClientPass=="+client_ShPassword, "ClientEmail=="+client_Email, "ClientTraffic=="+traffic_value, "ClientExp=="+expiryTime, "ClientComment=="+client_Comment)
+ message = t.I18nBot("tgbot.messages.inbound_client_data_pass", "InboundRemark=="+inbound_remark, "ClientPass=="+client_ShPassword, "ClientEmail=="+client_Email, "ClientTraffic=="+traffic_value, "ClientExp=="+expiryTime, "IpLimit=="+ip_limit, "ClientComment=="+client_Comment)
default:
return "", errors.New("unknown protocol")
@@ -1576,7 +1764,11 @@ func checkAdmin(tgId int64) bool {
func (t *Tgbot) SendAnswer(chatId int64, msg string, isAdmin bool) {
numericKeyboard := tu.InlineKeyboard(
tu.InlineKeyboardRow(
+ tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.SortedTrafficUsageReport")).WithCallbackData(t.encodeQuery("get_sorted_traffic_usage_report")),
+ ),
+ tu.InlineKeyboardRow(
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.serverUsage")).WithCallbackData(t.encodeQuery("get_usage")),
+ tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.ResetAllTraffics")).WithCallbackData(t.encodeQuery("reset_all_traffics")),
),
tu.InlineKeyboardRow(
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.dbBackup")).WithCallbackData(t.encodeQuery("get_backup")),
@@ -2223,6 +2415,7 @@ func (t *Tgbot) addClient(chatId int64, msg string, messageID ...int) {
),
tu.InlineKeyboardRow(
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_comment")).WithCallbackData("add_client_ch_default_comment"),
+ tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.ipLimit")).WithCallbackData("add_client_ch_default_ip_limit"),
),
tu.InlineKeyboardRow(
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.submitDisable")).WithCallbackData("add_client_submit_disable"),
@@ -2249,6 +2442,7 @@ func (t *Tgbot) addClient(chatId int64, msg string, messageID ...int) {
),
tu.InlineKeyboardRow(
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_comment")).WithCallbackData("add_client_ch_default_comment"),
+ tu.InlineKeyboardButton("ip limit").WithCallbackData("add_client_ch_default_ip_limit"),
),
tu.InlineKeyboardRow(
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.submitDisable")).WithCallbackData("add_client_submit_disable"),
@@ -2275,6 +2469,7 @@ func (t *Tgbot) addClient(chatId int64, msg string, messageID ...int) {
),
tu.InlineKeyboardRow(
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.change_comment")).WithCallbackData("add_client_ch_default_comment"),
+ tu.InlineKeyboardButton("ip limit").WithCallbackData("add_client_ch_default_ip_limit"),
),
tu.InlineKeyboardRow(
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.submitDisable")).WithCallbackData("add_client_submit_disable"),