From ceee1e4277953b68aeded8af01cf2eddfee2fbd5 Mon Sep 17 00:00:00 2001 From: somebodywashere <68244480+somebodywashere@users.noreply.github.com> Date: Mon, 1 Jan 2024 18:07:56 +0300 Subject: Major changes to tgbot, also small changes for panel (#1463) * Reduce outage time on Xray errors * Improved logs clearing, added previous logs File name change: 3xipl-access-persistent.log -> 3xipl-ap.log All previous logs have .prev suffix * Preparations for tgbot additions * [tgbot] Improvements, Additions and Fixes * Changed interaction with Expire Date for Clients * Added more info and interactions with Online Clients * Added a way to get Ban Logs (also added them to backup) * Few fixes and optimizations in code * Fixed RU translation * [tgbot] More updates and fixes * [tgbot] Quick Fix * [tgbot] Quick Fix 2 * [tgbot] Big Updates Added Notifications for Clients throught Tgbot (when Expire) Added compability for Usernames both w/wo @ Added more buttons overall for admins * [tgbot] Fixes * [tbot] Fixes 2 * [tgbot] Removed usernames support for Notifications to work * [tgbot] Fix * [tgbot] Fix Notify * [tgbot] small fixes * [tgbot] replyMarkup only for last message on big messages * [tgbot] Fixed last message is empty * [tgbot] Fix messages split --- web/job/check_client_ip_job.go | 3 + web/job/check_xray_running_job.go | 21 +- web/job/clear_logs_job.go | 31 +- web/service/inbound.go | 42 +- web/service/tgbot.go | 698 +++++++++++++++++++-------------- web/service/xray.go | 2 +- web/translation/translate.en_US.toml | 34 +- web/translation/translate.es_ES.toml | 38 +- web/translation/translate.fa_IR.toml | 32 +- web/translation/translate.ru_RU.toml | 34 +- web/translation/translate.vi_VN.toml | 34 +- web/translation/translate.zh_Hans.toml | 30 +- web/web.go | 8 +- 13 files changed, 628 insertions(+), 379 deletions(-) (limited to 'web') diff --git a/web/job/check_client_ip_job.go b/web/job/check_client_ip_job.go index b15473c5..65257170 100644 --- a/web/job/check_client_ip_job.go +++ b/web/job/check_client_ip_job.go @@ -22,8 +22,11 @@ var job *CheckClientIpJob var disAllowedIps []string var ipFiles = []string{ xray.GetIPLimitLogPath(), +xray.GetIPLimitPrevLogPath(), xray.GetIPLimitBannedLogPath(), +xray.GetIPLimitBannedPrevLogPath(), xray.GetAccessPersistentLogPath(), +xray.GetAccessPersistentPrevLogPath(), } func NewCheckClientIpJob() *CheckClientIpJob { diff --git a/web/job/check_xray_running_job.go b/web/job/check_xray_running_job.go index f1a848bf..d9ffeb7a 100644 --- a/web/job/check_xray_running_job.go +++ b/web/job/check_xray_running_job.go @@ -1,6 +1,9 @@ package job -import "x-ui/web/service" +import ( + "x-ui/logger" + "x-ui/web/service" +) type CheckXrayRunningJob struct { xrayService service.XrayService @@ -15,11 +18,15 @@ func NewCheckXrayRunningJob() *CheckXrayRunningJob { func (j *CheckXrayRunningJob) Run() { if j.xrayService.IsXrayRunning() { j.checkTime = 0 - return + } else { + j.checkTime++ + //only restart if it's down 2 times in a row + if j.checkTime > 1 { + err := j.xrayService.RestartXray(false) + j.checkTime = 0 + if err != nil { + logger.Error("Restart xray failed:", err) + } + } } - j.checkTime++ - if j.checkTime < 2 { - return - } - j.xrayService.SetToNeedRestart() } diff --git a/web/job/clear_logs_job.go b/web/job/clear_logs_job.go index 34f13eaa..5ceb5a75 100644 --- a/web/job/clear_logs_job.go +++ b/web/job/clear_logs_job.go @@ -15,10 +15,37 @@ func NewClearLogsJob() *ClearLogsJob { // Here Run is an interface method of the Job interface func (j *ClearLogsJob) Run() { logFiles := []string{xray.GetIPLimitLogPath(), xray.GetIPLimitBannedLogPath(), xray.GetAccessPersistentLogPath()} + logFilesPrev := []string{xray.GetIPLimitPrevLogPath(), xray.GetIPLimitBannedPrevLogPath(), xray.GetAccessPersistentPrevLogPath()} - // clear log files + // clear old previous logs + for i := 0; i < len(logFilesPrev); i++ { + if err := os.Truncate(logFilesPrev[i], 0); err != nil { + logger.Warning("clear logs job err:", err) + } + } + + // clear log files and copy to previous logs for i := 0; i < len(logFiles); i++ { - if err := os.Truncate(logFiles[i], 0); err != nil { + + // copy to previous logs + logFilePrev, err := os.OpenFile(logFilesPrev[i], os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644) + if err != nil { + logger.Warning("clear logs job err:", err) + } + + logFile, err := os.ReadFile(logFiles[i]) + if err != nil { + logger.Warning("clear logs job err:", err) + } + + _, err = logFilePrev.Write(logFile) + if err != nil { + logger.Warning("clear logs job err:", err) + } + defer logFilePrev.Close() + + err = os.Truncate(logFiles[i], 0) + if err != nil { logger.Warning("clear logs job err:", err) } } diff --git a/web/service/inbound.go b/web/service/inbound.go index 01d4eb57..30619791 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -1146,6 +1146,8 @@ func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId string) err if oldClient.Email == clientEmail { if inbound.Protocol == "trojan" { clientId = oldClient.Password + } else if inbound.Protocol == "shadowsocks" { + clientId = oldClient.Email } else { clientId = oldClient.ID } @@ -1184,6 +1186,32 @@ func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId string) err return nil } +func (s *InboundService) checkIsEnabledByEmail(clientEmail string) (bool, error) { + _, inbound, err := s.GetClientInboundByEmail(clientEmail) + if err != nil { + return false, err + } + if inbound == nil { + return false, common.NewError("Inbound Not Found For Email:", clientEmail) + } + + clients, err := s.GetClients(inbound) + if err != nil { + return false, err + } + + isEnable := false + + for _, client := range clients { + if client.Email == clientEmail { + isEnable = client.Enable + break + } + } + + return isEnable, err +} + func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, error) { _, inbound, err := s.GetClientInboundByEmail(clientEmail) if err != nil { @@ -1205,6 +1233,8 @@ func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, er if oldClient.Email == clientEmail { if inbound.Protocol == "trojan" { clientId = oldClient.Password + } else if inbound.Protocol == "shadowsocks" { + clientId = oldClient.Email } else { clientId = oldClient.ID } @@ -1266,6 +1296,8 @@ func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int if oldClient.Email == clientEmail { if inbound.Protocol == "trojan" { clientId = oldClient.Password + } else if inbound.Protocol == "shadowsocks" { + clientId = oldClient.Email } else { clientId = oldClient.ID } @@ -1324,6 +1356,8 @@ func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry if oldClient.Email == clientEmail { if inbound.Protocol == "trojan" { clientId = oldClient.Password + } else if inbound.Protocol == "shadowsocks" { + clientId = oldClient.Email } else { clientId = oldClient.ID } @@ -1385,6 +1419,8 @@ func (s *InboundService) ResetClientTrafficLimitByEmail(clientEmail string, tota if oldClient.Email == clientEmail { if inbound.Protocol == "trojan" { clientId = oldClient.Password + } else if inbound.Protocol == "shadowsocks" { + clientId = oldClient.Email } else { clientId = oldClient.ID } @@ -1613,10 +1649,10 @@ func (s *InboundService) DelDepletedClients(id int) (err error) { return nil } -func (s *InboundService) GetClientTrafficTgBot(tguname string) ([]*xray.ClientTraffic, error) { +func (s *InboundService) GetClientTrafficTgBot(tgId string) ([]*xray.ClientTraffic, error) { db := database.GetDB() var inbounds []*model.Inbound - err := db.Model(model.Inbound{}).Where("settings like ?", fmt.Sprintf(`%%"tgId": "%s"%%`, tguname)).Find(&inbounds).Error + err := db.Model(model.Inbound{}).Where("settings like ?", fmt.Sprintf(`%%"tgId": "%s"%%`, tgId)).Find(&inbounds).Error if err != nil && err != gorm.ErrRecordNotFound { return nil, err } @@ -1627,7 +1663,7 @@ func (s *InboundService) GetClientTrafficTgBot(tguname string) ([]*xray.ClientTr logger.Error("Unable to get clients from inbound") } for _, client := range clients { - if client.TgID == tguname { + if client.TgID == tgId { emails = append(emails, client.Email) } } diff --git a/web/service/tgbot.go b/web/service/tgbot.go index 0847e418..9f11c19d 100644 --- a/web/service/tgbot.go +++ b/web/service/tgbot.go @@ -7,6 +7,7 @@ import ( "os" "strconv" "strings" + "slices" "time" "x-ui/config" "x-ui/database" @@ -218,7 +219,7 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo if isAdmin { t.searchClient(chatId, commandArgs[0]) } else { - t.searchForClient(chatId, commandArgs[0]) + t.getClientUsage(chatId, strconv.FormatInt(message.From.ID, 10), commandArgs[0]) } } else { msg += t.I18nBot("tgbot.commands.usage") @@ -234,11 +235,14 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo msg += t.I18nBot("tgbot.commands.unknown") } - if onlyMessage { - t.SendMsgToTgbot(chatId, msg) - return + if msg != ""{ + if onlyMessage { + t.SendMsgToTgbot(chatId, msg) + return + } else { + t.SendAnswer(chatId, msg, isAdmin) + } } - t.SendAnswer(chatId, msg, isAdmin) } func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool) { @@ -257,6 +261,9 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool if len(dataArray) >= 2 && len(dataArray[1]) > 0 { email := dataArray[1] switch dataArray[0] { + case "client_get_usage": + t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.messages.email", "Email=="+email)) + t.searchClient(chatId, email) case "client_refresh": t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.clientRefreshSuccess", "Email=="+email)) t.searchClient(chatId, email, callbackQuery.Message.MessageID) @@ -352,7 +359,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool inputNumber = 0 } else if num == -1 { if inputNumber > 0 { - inputNumber = (inputNumber / 10) ^ 0 + inputNumber = (inputNumber / 10) } } else { inputNumber = (inputNumber * 10) + num @@ -372,7 +379,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("client_cancel "+email)), ), tu.InlineKeyboardRow( - tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmNumber", "Num=="+strconv.Itoa(inputNumber))).WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" "+strconv.Itoa(inputNumber))), + tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmNumberAdd", "Num=="+strconv.Itoa(inputNumber))).WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" "+strconv.Itoa(inputNumber))), ), tu.InlineKeyboardRow( tu.InlineKeyboardButton("1").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" 1")), @@ -411,20 +418,20 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.custom")).WithCallbackData(t.encodeQuery("reset_exp_in "+email+" 0")), ), tu.InlineKeyboardRow( - tu.InlineKeyboardButton("1 "+t.I18nBot("tgbot.month")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 30")), - tu.InlineKeyboardButton("2 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 60")), + tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 7 "+t.I18nBot("tgbot.days")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 7")), + tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 10 "+t.I18nBot("tgbot.days")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 10")), ), tu.InlineKeyboardRow( - tu.InlineKeyboardButton("3 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 90")), - tu.InlineKeyboardButton("6 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 180")), + tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 14 "+t.I18nBot("tgbot.days")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 14")), + tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 20 "+t.I18nBot("tgbot.days")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 20")), ), tu.InlineKeyboardRow( - tu.InlineKeyboardButton("9 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 270")), - tu.InlineKeyboardButton("12 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 360")), + tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 1 "+t.I18nBot("tgbot.month")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 30")), + tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 3 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 90")), ), tu.InlineKeyboardRow( - tu.InlineKeyboardButton("10 "+t.I18nBot("tgbot.days")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 10")), - tu.InlineKeyboardButton("20 "+t.I18nBot("tgbot.days")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 20")), + tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 6 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 180")), + tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 12 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 365")), ), ) t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard) @@ -434,7 +441,29 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool if err == nil { var date int64 = 0 if days > 0 { - date = int64(-(days * 24 * 60 * 60000)) + traffic, err := t.inboundService.GetClientTrafficByEmail(email) + if err != nil { + logger.Warning(err) + msg := t.I18nBot("tgbot.wentWrong") + t.SendMsgToTgbot(chatId, msg) + return + } + if traffic == nil { + msg := t.I18nBot("tgbot.noResult") + t.SendMsgToTgbot(chatId, msg) + return + } + + if traffic.ExpiryTime > 0 { + if traffic.ExpiryTime-time.Now().Unix()*1000 < 0 { + date = -int64(days * 24 * 60 * 60000) + } else { + date = traffic.ExpiryTime + int64(days*24*60*60000) + } + } else { + date = traffic.ExpiryTime - int64(days*24*60*60000) + } + } err := t.inboundService.ResetClientExpiryTimeByEmail(email, date) if err == nil { @@ -459,7 +488,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool inputNumber = 0 } else if num == -1 { if inputNumber > 0 { - inputNumber = (inputNumber / 10) ^ 0 + inputNumber = (inputNumber / 10) } } else { inputNumber = (inputNumber * 10) + num @@ -564,7 +593,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool inputNumber = 0 } else if num == -1 { if inputNumber > 0 { - inputNumber = (inputNumber / 10) ^ 0 + inputNumber = (inputNumber / 10) } } else { inputNumber = (inputNumber * 10) + num @@ -661,6 +690,16 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation")) } case "toggle_enable": + inlineKeyboard := tu.InlineKeyboard( + tu.InlineKeyboardRow( + tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("client_cancel "+email)), + ), + tu.InlineKeyboardRow( + tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmToggle")).WithCallbackData(t.encodeQuery("toggle_enable_c "+email)), + ), + ) + t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard) + case "toggle_enable_c": enabled, err := t.inboundService.ToggleClientEnableByEmail(email) if err == nil { t.xrayService.SetToNeedRestart() @@ -678,24 +717,36 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool } } - // Respond to the callback query, telling Telegram to show the user - // a message with the data received. - t.sendCallbackAnswerTgBot(callbackQuery.ID, callbackQuery.Data) - switch callbackQuery.Data { case "get_usage": + t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.serverUsage")) t.SendMsgToTgbot(chatId, t.getServerUsage()) case "inbounds": + t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.getInbounds")) t.SendMsgToTgbot(chatId, t.getInboundUsages()) case "deplete_soon": - t.SendMsgToTgbot(chatId, t.getExhausted()) + t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.depleteSoon")) + t.getExhausted(chatId) case "get_backup": + t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.dbBackup")) t.sendBackup(chatId) + case "get_banlogs": + t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.getBanLogs")) + t.sendBanLogs(chatId, true) case "client_traffic": - t.getClientUsage(chatId, callbackQuery.From.Username, strconv.FormatInt(callbackQuery.From.ID, 10)) + t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.clientUsage")) + t.getClientUsage(chatId, strconv.FormatInt(callbackQuery.From.ID, 10)) case "client_commands": + t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.commands")) t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.helpClientCommands")) + case "onlines": + t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.onlines")) + t.onlineClients(chatId) + case "onlines_refresh": + t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation")) + t.onlineClients(chatId, callbackQuery.Message.MessageID) case "commands": + t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.commands")) t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.helpAdminCommands")) } } @@ -713,7 +764,10 @@ func (t *Tgbot) SendAnswer(chatId int64, msg string, isAdmin bool) { numericKeyboard := tu.InlineKeyboard( tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.serverUsage")).WithCallbackData(t.encodeQuery("get_usage")), + ), + tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.dbBackup")).WithCallbackData(t.encodeQuery("get_backup")), + tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.getBanLogs")).WithCallbackData(t.encodeQuery("get_banlogs")), ), tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.getInbounds")).WithCallbackData(t.encodeQuery("inbounds")), @@ -721,6 +775,7 @@ func (t *Tgbot) SendAnswer(chatId int64, msg string, isAdmin bool) { ), tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.commands")).WithCallbackData(t.encodeQuery("commands")), + tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.onlines")).WithCallbackData(t.encodeQuery("onlines")), ), ) numericKeyboardClient := tu.InlineKeyboard( @@ -754,7 +809,7 @@ func (t *Tgbot) SendMsgToTgbot(chatId int64, msg string, replyMarkup ...telego.R // paging message if it is big if len(msg) > limit { - messages := strings.Split(msg, "\r\n \r\n") + messages := strings.Split(msg, "\r\n\r\n") lastIndex := -1 for _, message := range messages { @@ -762,19 +817,23 @@ func (t *Tgbot) SendMsgToTgbot(chatId int64, msg string, replyMarkup ...telego.R allMessages = append(allMessages, message) lastIndex++ } else { - allMessages[lastIndex] += "\r\n \r\n" + message + allMessages[lastIndex] += "\r\n\r\n" + message } } + if strings.TrimSpace(allMessages[len(allMessages)-1]) == "" { + allMessages = allMessages[:len(allMessages)-1] + } } else { allMessages = append(allMessages, msg) } - for _, message := range allMessages { + for n, message := range allMessages { params := telego.SendMessageParams{ ChatID: tu.ID(chatId), Text: message, ParseMode: "HTML", } - if len(replyMarkup) > 0 { + //only add replyMarkup to last message + if len(replyMarkup) > 0 && n == (len(allMessages)-1) { params.ReplyMarkup = replyMarkup[0] } _, err := bot.SendMessage(¶ms) @@ -785,9 +844,15 @@ func (t *Tgbot) SendMsgToTgbot(chatId int64, msg string, replyMarkup ...telego.R } } -func (t *Tgbot) SendMsgToTgbotAdmins(msg string) { - for _, adminId := range adminIds { - t.SendMsgToTgbot(adminId, msg) +func (t *Tgbot) SendMsgToTgbotAdmins(msg string, replyMarkup ...telego.ReplyMarkup) { + if len(replyMarkup) > 0 { + for _, adminId := range adminIds { + t.SendMsgToTgbot(adminId, msg, replyMarkup[0]) + } + } else { + for _, adminId := range adminIds { + t.SendMsgToTgbot(adminId, msg) + } } } @@ -803,8 +868,8 @@ func (t *Tgbot) SendReport() { info := t.getServerUsage() t.SendMsgToTgbotAdmins(info) - exhausted := t.getExhausted() - t.SendMsgToTgbotAdmins(exhausted) + t.sendExhaustedToAdmins() + t.notifyExhausted() backupEnable, err := t.settingService.GetTgBotBackup() if err == nil && backupEnable { @@ -821,6 +886,15 @@ func (t *Tgbot) SendBackupToAdmins() { } } +func (t *Tgbot) sendExhaustedToAdmins() { + if !t.IsRunning() { + return + } + for _, adminId := range adminIds { + t.getExhausted(int64(adminId)) + } +} + func (t *Tgbot) getServerUsage() string { info, ipv4, ipv6 := "", "", "" info += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname) @@ -831,7 +905,7 @@ func (t *Tgbot) getServerUsage() string { if err != nil { logger.Error("net.Interfaces failed, err: ", err.Error()) info += t.I18nBot("tgbot.messages.ip", "IP=="+t.I18nBot("tgbot.unknown")) - info += " \r\n" + info += "\r\n" } else { for i := 0; i < len(netInterfaces); i++ { if (netInterfaces[i].Flags & net.FlagUp) != 0 { @@ -855,9 +929,11 @@ func (t *Tgbot) getServerUsage() string { // get latest status of server t.lastStatus = t.serverService.GetStatus(t.lastStatus) + onlines := p.GetOnlineClients() info += t.I18nBot("tgbot.messages.serverUpTime", "UpTime=="+strconv.FormatUint(t.lastStatus.Uptime/86400, 10), "Unit=="+t.I18nBot("tgbot.days")) info += t.I18nBot("tgbot.messages.serverLoad", "Load1=="+strconv.FormatFloat(t.lastStatus.Loads[0], 'f', 2, 64), "Load2=="+strconv.FormatFloat(t.lastStatus.Loads[1], 'f', 2, 64), "Load3=="+strconv.FormatFloat(t.lastStatus.Loads[2], 'f', 2, 64)) info += t.I18nBot("tgbot.messages.serverMemory", "Current=="+common.FormatTraffic(int64(t.lastStatus.Mem.Current)), "Total=="+common.FormatTraffic(int64(t.lastStatus.Mem.Total))) + info += t.I18nBot("tgbot.messages.onlinesCount", "Count=="+fmt.Sprint(len(onlines))) info += t.I18nBot("tgbot.messages.tcpCount", "Count=="+strconv.Itoa(t.lastStatus.TcpCount)) info += t.I18nBot("tgbot.messages.udpCount", "Count=="+strconv.Itoa(t.lastStatus.UdpCount)) info += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic(int64(t.lastStatus.NetTraffic.Sent+t.lastStatus.NetTraffic.Recv)), "Upload=="+common.FormatTraffic(int64(t.lastStatus.NetTraffic.Sent)), "Download=="+common.FormatTraffic(int64(t.lastStatus.NetTraffic.Recv))) @@ -914,85 +990,136 @@ func (t *Tgbot) getInboundUsages() string { } else { info += t.I18nBot("tgbot.messages.expire", "Time=="+time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05")) } + info += "\r\n" } } return info } -func (t *Tgbot) getClientUsage(chatId int64, tgUserName string, tgUserID string) { - traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserID) +func (t *Tgbot) clientInfoMsg(traffic *xray.ClientTraffic, printEnabled bool, printOnline bool, printActive bool, + printDate bool, printTraffic bool, printRefreshed bool) string { + + now := time.Now().Unix() + expiryTime := "" + flag := false + diff := traffic.ExpiryTime/1000 - now + if traffic.ExpiryTime == 0 { + expiryTime = t.I18nBot("tgbot.unlimited") + } else if diff > 172800 || !traffic.Enable { + expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05") + } else if traffic.ExpiryTime < 0 { + expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days")) + flag = true + } else { + expiryTime = fmt.Sprintf("%d %s", diff/3600, t.I18nBot("tgbot.hours")) + flag = true + } + + total := "" + if traffic.Total == 0 { + total = t.I18nBot("tgbot.unlimited") + } else { + total = common.FormatTraffic((traffic.Total)) + } + + enabled := "" + isEnabled, err := t.inboundService.checkIsEnabledByEmail(traffic.Email) if err != nil { logger.Warning(err) - msg := t.I18nBot("tgbot.wentWrong") - t.SendMsgToTgbot(chatId, msg) - return + enabled = t.I18nBot("tgbot.wentWrong") + } else if isEnabled { + enabled = t.I18nBot("tgbot.messages.yes") + } else { + enabled = t.I18nBot("tgbot.messages.no") } - if len(traffics) == 0 { - if len(tgUserName) == 0 { - msg := t.I18nBot("tgbot.answers.askToAddUserId", "TgUserID=="+tgUserID) - t.SendMsgToTgbot(chatId, msg) - return + active := "" + if traffic.Enable { + active = t.I18nBot("tgbot.messages.yes") + } else { + active = t.I18nBot("tgbot.messages.no") + } + + status := t.I18nBot("tgbot.offline") + if p.IsRunning() { + for _, online := range p.GetOnlineClients() { + if online == traffic.Email { + status = t.I18nBot("tgbot.online") + break + } + } + } + + output := "" + output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email) + if printEnabled { + output += t.I18nBot("tgbot.messages.enabled", "Enable=="+enabled) + } + if printOnline { + output += t.I18nBot("tgbot.messages.online", "Status=="+status) + } + if printActive { + output += t.I18nBot("tgbot.messages.active", "Enable=="+active) + } + if printDate { + if flag { + output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime) + } else { + output += t.I18nBot("tgbot.messages.expire", "Time=="+expiryTime) } - traffics, err = t.inboundService.GetClientTrafficTgBot(tgUserName) } + if printTraffic { + output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up)) + output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down)) + output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total) + } + if printRefreshed { + output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05")) + } + + return output +} + +func (t *Tgbot) getClientUsage(chatId int64, tgUserID string, email ...string) { + traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserID) if err != nil { logger.Warning(err) msg := t.I18nBot("tgbot.wentWrong") t.SendMsgToTgbot(chatId, msg) return } + if len(traffics) == 0 { - msg := t.I18nBot("tgbot.answers.askToAddUserName", "TgUserName=="+tgUserName, "TgUserID=="+tgUserID) - t.SendMsgToTgbot(chatId, msg) + t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.askToAddUserId", "TgUserID=="+tgUserID)) return } - now := time.Now().Unix() - for _, traffic := range traffics { - expiryTime := "" - flag := false - diff := traffic.ExpiryTime/1000 - now - if traffic.ExpiryTime == 0 { - expiryTime = t.I18nBot("tgbot.unlimited") - } else if diff > 172800 || !traffic.Enable { - expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05") - } else if traffic.ExpiryTime < 0 { - expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days")) - flag = true - } else { - expiryTime = fmt.Sprintf("%d %s", diff/3600, t.I18nBot("tgbot.hours")) - flag = true - } - - total := "" - if traffic.Total == 0 { - total = t.I18nBot("tgbot.unlimited") - } else { - total = common.FormatTraffic((traffic.Total)) - } + output := "" - output := "" - output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email) - if traffic.Enable { - output += t.I18nBot("tgbot.messages.active") - if flag { - output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime) - } else { - output += t.I18nBot("tgbot.messages.expire", "Time=="+expiryTime) + if len(traffics) > 0 { + if len(email) > 0 { + for _, traffic := range traffics { + if traffic.Email == email[0] { + output := t.clientInfoMsg(traffic, true, true, true, true, true, true) + t.SendMsgToTgbot(chatId, output) + return + } } + msg := t.I18nBot("tgbot.noResult") + t.SendMsgToTgbot(chatId, msg) + return } else { - output += t.I18nBot("tgbot.messages.inactive") - output += t.I18nBot("tgbot.messages.expire", "Time=="+expiryTime) + for _, traffic := range traffics { + output += t.clientInfoMsg(traffic, true, true, true, true, true, false) + output += "\r\n" + } } - output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up)) - output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down)) - output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total) - output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05")) - - t.SendMsgToTgbot(chatId, output) } - t.SendAnswer(chatId, t.I18nBot("tgbot.commands.pleaseChoose"), false) + + output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05")) + t.SendMsgToTgbot(chatId, output) + output = t.I18nBot("tgbot.commands.pleaseChoose") + t.SendAnswer(chatId, output, false) } func (t *Tgbot) searchClientIps(chatId int64, email string, messageID ...int) { @@ -1088,46 +1215,7 @@ func (t *Tgbot) searchClient(chatId int64, email string, messageID ...int) { return } - now := time.Now().Unix() - expiryTime := "" - flag := false - diff := traffic.ExpiryTime/1000 - now - if traffic.ExpiryTime == 0 { - expiryTime = t.I18nBot("tgbot.unlimited") - } else if diff > 172800 || !traffic.Enable { - expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05") - } else if traffic.ExpiryTime < 0 { - expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days")) - flag = true - } else { - expiryTime = fmt.Sprintf("%d %s", diff/3600, t.I18nBot("tgbot.hours")) - flag = true - } - - total := "" - if traffic.Total == 0 { - total = t.I18nBot("tgbot.unlimited") - } else { - total = common.FormatTraffic((traffic.Total)) - } - - output := "" - output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email) - if traffic.Enable { - output += t.I18nBot("tgbot.messages.active") - if flag { - output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime) - } else { - output += t.I18nBot("tgbot.messages.expire", "Time=="+expiryTime) - } - } else { - output += t.I18nBot("tgbot.messages.inactive") - output += t.I18nBot("tgbot.messages.expire", "Time=="+expiryTime) - } - output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up)) - output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down)) - output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total) - output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05")) + output := t.clientInfoMsg(traffic, true, true, true, true, true, true) inlineKeyboard := tu.InlineKeyboard( tu.InlineKeyboardRow( @@ -1151,7 +1239,6 @@ func (t *Tgbot) searchClient(chatId int64, email string, messageID ...int) { tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.toggle")).WithCallbackData(t.encodeQuery("toggle_enable "+email)), ), ) - if len(messageID) > 0 { t.editMessageTgBot(chatId, messageID[0], output, inlineKeyboard) } else { @@ -1173,7 +1260,6 @@ func (t *Tgbot) searchInbound(chatId int64, remark string) { return } - now := time.Now().Unix() for _, inbound := range inbouds { info := "" info += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark) @@ -1187,111 +1273,17 @@ func (t *Tgbot) searchInbound(chatId int64, remark string) { } t.SendMsgToTgbot(chatId, info) - for _, traffic := range inbound.ClientStats { - expiryTime := "" - flag := false - diff := traffic.ExpiryTime/1000 - now - if traffic.ExpiryTime == 0 { - expiryTime = t.I18nBot("tgbot.unlimited") - } else if diff > 172800 || !traffic.Enable { - expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05") - } else if traffic.ExpiryTime < 0 { - expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days")) - flag = true - } else { - expiryTime = fmt.Sprintf("%d %s", diff/3600, t.I18nBot("tgbot.hours")) - flag = true - } - - total := "" - if traffic.Total == 0 { - total = t.I18nBot("tgbot.unlimited") - } else { - total = common.FormatTraffic((traffic.Total)) - } - + if len(inbound.ClientStats) > 0 { output := "" - output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email) - if traffic.Enable { - output += t.I18nBot("tgbot.messages.active") - if flag { - output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime) - } else { - output += t.I18nBot("tgbot.messages.expire", "Time=="+expiryTime) - } - } else { - output += t.I18nBot("tgbot.messages.inactive") - output += t.I18nBot("tgbot.messages.expire", "Time=="+expiryTime) + for _, traffic := range inbound.ClientStats { + output += t.clientInfoMsg(&traffic, true, true, true, true, true, true) } - output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up)) - output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down)) - output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total) - output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05")) - t.SendMsgToTgbot(chatId, output) } } } -func (t *Tgbot) searchForClient(chatId int64, query string) { - traffic, err := t.inboundService.SearchClientTraffic(query) - if err != nil { - logger.Warning(err) - msg := t.I18nBot("tgbot.wentWrong") - t.SendMsgToTgbot(chatId, msg) - return - } - if traffic == nil { - msg := t.I18nBot("tgbot.noResult") - t.SendMsgToTgbot(chatId, msg) - return - } - - now := time.Now().Unix() - expiryTime := "" - flag := false - diff := traffic.ExpiryTime/1000 - now - if traffic.ExpiryTime == 0 { - expiryTime = t.I18nBot("tgbot.unlimited") - } else if diff > 172800 || !traffic.Enable { - expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05") - } else if traffic.ExpiryTime < 0 { - expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days")) - flag = true - } else { - expiryTime = fmt.Sprintf("%d %s", diff/3600, t.I18nBot("tgbot.hours")) - flag = true - } - - total := "" - if traffic.Total == 0 { - total = t.I18nBot("tgbot.unlimited") - } else { - total = common.FormatTraffic((traffic.Total)) - } - - output := "" - output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email) - if traffic.Enable { - output += t.I18nBot("tgbot.messages.active") - if flag { - output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime) - } else { - output += t.I18nBot("tgbot.messages.expire", "Time=="+expiryTime) - } - } else { - output += t.I18nBot("tgbot.messages.inactive") - output += t.I18nBot("tgbot.messages.expire", "Time=="+expiryTime) - } - output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up)) - output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down)) - output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total) - output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05")) - - t.SendMsgToTgbot(chatId, output) -} - -func (t *Tgbot) getExhausted() string { +func (t *Tgbot) getExhausted(chatId int64) { trDiff := int64(0) exDiff := int64(0) now := time.Now().Unix() * 1000 @@ -1341,10 +1333,9 @@ func (t *Tgbot) getExhausted() string { output += t.I18nBot("tgbot.messages.exhaustedCount", "Type=="+t.I18nBot("tgbot.inbounds")) output += t.I18nBot("tgbot.messages.disabled", "Disabled=="+strconv.Itoa(len(disabledInbounds))) output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+strconv.Itoa(len(exhaustedInbounds))) - output += "\r\n \r\n" if len(exhaustedInbounds) > 0 { - output += t.I18nBot("tgbot.messages.exhaustedMsg", "Type=="+t.I18nBot("tgbot.inbounds")) + output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+t.I18nBot("tgbot.inbounds")) for _, inbound := range exhaustedInbounds { output += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark) @@ -1355,63 +1346,148 @@ func (t *Tgbot) getExhausted() string { } else { output += t.I18nBot("tgbot.messages.expire", "Time=="+time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05")) } - output += "\r\n \r\n" + output += "\r\n" } } // Clients + exhaustedCC := len(exhaustedClients) output += t.I18nBot("tgbot.messages.exhaustedCount", "Type=="+t.I18nBot("tgbot.clients")) output += t.I18nBot("tgbot.messages.disabled", "Disabled=="+strconv.Itoa(len(disabledClients))) - output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+strconv.Itoa(len(exhaustedClients))) - output += "\r\n \r\n" - - if len(exhaustedClients) > 0 { - output += t.I18nBot("tgbot.messages.exhaustedMsg", "Type=="+t.I18nBot("tgbot.clients")) + output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+strconv.Itoa(exhaustedCC)) + + if exhaustedCC > 0 { + output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+t.I18nBot("tgbot.clients")) + var buttons []telego.InlineKeyboardButton for _, traffic := range exhaustedClients { - expiryTime := "" - flag := false - diff := (traffic.ExpiryTime - now) / 1000 - if traffic.ExpiryTime == 0 { - expiryTime = t.I18nBot("tgbot.unlimited") - } else if diff > 172800 || !traffic.Enable { - expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05") - } else if traffic.ExpiryTime < 0 { - expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days")) - flag = true - } else { - expiryTime = fmt.Sprintf("%d %s", diff/3600, t.I18nBot("tgbot.hours")) - flag = true - } + output += t.clientInfoMsg(&traffic, true, false, false, true, true, false) + output += "\r\n" + buttons = append(buttons, tu.InlineKeyboardButton(traffic.Email).WithCallbackData(t.encodeQuery("client_get_usage "+traffic.Email))) + } + cols := 0 + if exhaustedCC < 11 { + cols = 1 + } else { + cols = 2 + } + output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05")) + keyboard := tu.InlineKeyboardGrid(tu.InlineKeyboardCols(cols, buttons...)) + t.SendMsgToTgbot(chatId, output, keyboard) + } else { + output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05")) + t.SendMsgToTgbot(chatId, output) + } +} - total := "" - if traffic.Total == 0 { - total = t.I18nBot("tgbot.unlimited") - } else { - total = common.FormatTraffic((traffic.Total)) - } +func (t *Tgbot) notifyExhausted() { + trDiff := int64(0) + exDiff := int64(0) + now := time.Now().Unix() * 1000 - output += t.I18nBot("tgbot.messages.email", "Email=="+traffic.Email) - if traffic.Enable { - output += t.I18nBot("tgbot.messages.active") - if flag { - output += t.I18nBot("tgbot.messages.expireIn", "Time=="+expiryTime) - } else { - output += t.I18nBot("tgbot.messages.expire", "Time=="+expiryTime) + TrafficThreshold, err := t.settingService.GetTrafficDiff() + if err == nil && TrafficThreshold > 0 { + trDiff = int64(TrafficThreshold) * 1073741824 + } + ExpireThreshold, err := t.settingService.GetExpireDiff() + if err == nil && ExpireThreshold > 0 { + exDiff = int64(ExpireThreshold) * 86400000 + } + inbounds, err := t.inboundService.GetAllInbounds() + if err != nil { + logger.Warning("Unable to load Inbounds", err) + } + + var chatIDsDone []string + for _, inbound := range inbounds { + if inbound.Enable { + if len(inbound.ClientStats) > 0 { + clients, err := t.inboundService.GetClients(inbound) + if err == nil { + for _, client := range clients { + if client.TgID != "" { + chatID, err := strconv.ParseInt(client.TgID, 10, 64) + if err != nil { + logger.Warning("TgID is not a number: ", client.TgID) + continue + } + if !slices.Contains(chatIDsDone, client.TgID) && !checkAdmin(chatID) { + var disabledClients []xray.ClientTraffic + var exhaustedClients []xray.ClientTraffic + traffics, err := t.inboundService.GetClientTrafficTgBot(client.TgID) + if err == nil { + output := t.I18nBot("tgbot.messages.exhaustedCount", "Type=="+t.I18nBot("tgbot.clients")) + for _, traffic := range traffics { + if traffic.Enable { + if (traffic.ExpiryTime > 0 && (traffic.ExpiryTime-now < exDiff)) || + (traffic.Total > 0 && (traffic.Total-(traffic.Up+traffic.Down) < trDiff)) { + exhaustedClients = append(exhaustedClients, *traffic) + } + } else { + disabledClients = append(disabledClients, *traffic) + } + } + if len(exhaustedClients) > 0 { + output += t.I18nBot("tgbot.messages.disabled", "Disabled=="+strconv.Itoa(len(disabledClients))) + if len(disabledClients) > 0 { + output += t.I18nBot("tgbot.clients") + ":\r\n" + for _, traffic := range disabledClients { + output += " " + traffic.Email + } + output += "\r\n" + } + output += "\r\n" + output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+strconv.Itoa(len(exhaustedClients))) + for _, traffic := range exhaustedClients { + output += t.clientInfoMsg(&traffic, true, false, false, true, true, false) + output += "\r\n" + } + t.SendMsgToTgbot(chatID, output) + } + chatIDsDone = append(chatIDsDone, client.TgID) + } + } + } + } } - } else { - output += t.I18nBot("tgbot.messages.inactive") - output += t.I18nBot("tgbot.messages.expire", "Time=="+expiryTime) } - output += t.I18nBot("tgbot.messages.upload", "Upload=="+common.FormatTraffic(traffic.Up)) - output += t.I18nBot("tgbot.messages.download", "Download=="+common.FormatTraffic(traffic.Down)) - output += t.I18nBot("tgbot.messages.total", "UpDown=="+common.FormatTraffic((traffic.Up+traffic.Down)), "Total=="+total) - output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05")) - output += "\r\n \r\n" } } +} - return output +func (t *Tgbot) onlineClients(chatId int64, messageID ...int) { + if !p.IsRunning() { + return + } + + onlines := p.GetOnlineClients() + onlinesCount := len(onlines) + output := t.I18nBot("tgbot.messages.onlinesCount", "Count=="+fmt.Sprint(onlinesCount)) + keyboard := tu.InlineKeyboard(tu.InlineKeyboardRow( + tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("onlines_refresh")))) + + + if onlinesCount > 0 { + var buttons []telego.InlineKeyboardButton + for _, online := range onlines { + buttons = append(buttons, tu.InlineKeyboardButton(online).WithCallbackData(t.encodeQuery("client_get_usage "+online))) + } + cols := 0 + if onlinesCount < 21 { + cols = 2 + } else if onlinesCount < 61 { + cols = 3 + } else { + cols = 4 + } + keyboard.InlineKeyboard = append(keyboard.InlineKeyboard, tu.InlineKeyboardCols(cols, buttons...)...) + } + + if len(messageID) > 0 { + t.editMessageTgBot(chatId, messageID[0], output, keyboard) + } else { + t.SendMsgToTgbot(chatId, output, keyboard) + } } func (t *Tgbot) sendBackup(chatId int64) { @@ -1421,33 +1497,73 @@ func (t *Tgbot) sendBackup(chatId int64) { // Update by manually trigger a checkpoint operation err := database.Checkpoint() if err != nil { - logger.Warning("Error in trigger a checkpoint operation: ", err) + logger.Error("Error in trigger a checkpoint operation: ", err) } file, err := os.Open(config.GetDBPath()) - if err != nil { - logger.Warning("Error in opening db file for backup: ", err) - } - document := tu.Document( - tu.ID(chatId), - tu.File(file), - ) - _, err = bot.SendDocument(document) - if err != nil { - logger.Warning("Error in uploading backup: ", err) + if err == nil { + document := tu.Document( + tu.ID(chatId), + tu.File(file), + ) + _, err = bot.SendDocument(document) + if err != nil { + logger.Error("Error in uploading backup: ", err) + } + } else { + logger.Error("Error in opening db file for backup: ", err) + } file, err = os.Open(xray.GetConfigPath()) - if err != nil { - logger.Warning("Error in opening config.json file for backup: ", err) + if err == nil { + document := tu.Document( + tu.ID(chatId), + tu.File(file), + ) + _, err = bot.SendDocument(document) + if err != nil { + logger.Error("Error in uploading config.json: ", err) + } + } else { + logger.Error("Error in opening config.json file for backup: ", err) } - document = tu.Document( - tu.ID(chatId), - tu.File(file), - ) - _, err = bot.SendDocument(document) - if err != nil { - logger.Warning("Error in uploading config.json: ", err) + + t.sendBanLogs(chatId, false) +} + +func (t *Tgbot) sendBanLogs(chatId int64, dt bool) { + if dt { + output := t.I18nBot("tgbot.messages.datetime", "DateTime=="+time.Now().Format("2006-01-02 15:04:05")) + t.SendMsgToTgbot(chatId, output) + } + + file, err := os.Open(xray.GetIPLimitBannedPrevLogPath()) + if err == nil { + document := tu.Document( + tu.ID(chatId), + tu.File(file), + ) + _, err = bot.SendDocument(document) + if err != nil { + logger.Error("Error in uploading backup: ", err) + } + } else { + logger.Error("Error in opening db file for backup: ", err) + } + + file, err = os.Open(xray.GetIPLimitBannedLogPath()) + if err == nil { + document := tu.Document( + tu.ID(chatId), + tu.File(file), + ) + _, err = bot.SendDocument(document) + if err != nil { + logger.Error("Error in uploading config.json: ", err) + } + } else { + logger.Error("Error in opening config.json file for backup: ", err) } } diff --git a/web/service/xray.go b/web/service/xray.go index 7233cec5..82d1cc3f 100644 --- a/web/service/xray.go +++ b/web/service/xray.go @@ -185,7 +185,7 @@ func (s *XrayService) RestartXray(isForce bool) error { return err } - if p != nil && p.IsRunning() { + if s.IsXrayRunning() { if !isForce && p.GetConfig().Equals(xrayConfig) { logger.Debug("It does not need to restart xray") return nil diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index 88ef99e8..501671d0 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -173,7 +173,7 @@ "setDefaultCert" = "Set Cert from Panel" "xtlsDesc" = "Xray core needs to be 1.7.5" "realityDesc" = "Xray core needs to be 1.8.0 or higher." -"telegramDesc" = "use Telegram ID without @ or chat IDs ( you can get it here @userinfobot or use '/id' command in bot )" +"telegramDesc" = "Only use Chat ID (you can get it here @userinfobot or use '/id' command in bot)" "subscriptionDesc" = "you can find your sub link on Details, also you can use the same name for several configurations" "info" = "Info" "same" = "Same" @@ -439,6 +439,7 @@ "noIpRecord" = "❗ No IP Record!" "noInbounds" = "❗ No inbound found!" "unlimited" = "♾ Unlimited" +"add" = "Add" "month" = "Month" "months" = "Months" "day" = "Day" @@ -447,6 +448,8 @@ "unknown" = "Unknown" "inbounds" = "Inbounds" "clients" = "Clients" +"offline" = "🔴 Offline" +"online" = "🟢 Online" [tgbot.commands] "unknown" = "❗ Unknown command" @@ -457,8 +460,8 @@ "status" = "✅ Bot is OK!" "usage" = "❗ Please provide a text to search!" "getID" = "🆔 Your ID: {{ .ID }}" -"helpAdminCommands" = "Search for a client email:\r\n/usage [Email]\r\n \r\nSearch for inbounds (with client stats):\r\n/inbound [Remark]" -"helpClientCommands" = "To search for statistics, just use the following command:\r\n \r\n/usage [UUID|Password]\r\n \r\nUse UUID for vmess/vless and Password for Trojan." +"helpAdminCommands" = "Search for a client email:\r\n/usage [Email]\r\n\r\nSearch for inbounds (with client stats):\r\n/inbound [Remark]" +"helpClientCommands" = "To search for statistics, just use the following command:\r\n\r\n/usage [Email]" [tgbot.messages] "cpuThreshold" = "🔴 CPU Load {{ .Percent }}% is more than threshold {{ .Threshold }}%" @@ -473,7 +476,7 @@ "ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" "ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" "ip" = "🌐 IP: {{ .IP }}\r\n" -"ips" = "🔢 IPs: \r\n{{ .IPs }}\r\n" +"ips" = "🔢 IPs:\r\n{{ .IPs }}\r\n" "serverUpTime" = "⏳ Server Uptime: {{ .UpTime }} {{ .Unit }}\r\n" "serverLoad" = "📈 Server Load: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" "serverMemory" = "📋 Server RAM: {{ .Current }}/{{ .Total }}\r\n" @@ -487,8 +490,9 @@ "port" = "🔌 Port: {{ .Port }}\r\n" "expire" = "📅 Expire Date: {{ .Time }}\r\n" "expireIn" = "📅 Expire In: {{ .Time }}\r\n" -"active" = "💡 Active: ✅ Yes\r\n" -"inactive" = "💡 Active: ❌ No\r\n" +"active" = "💡 Active: {{ .Enable }}\r\n" +"enabled" = "🚨 Enabled: {{ .Enable }}\r\n" +"online" = "🌐 Connection status: {{ .Status }}\r\n" "email" = "📧 Email: {{ .Email }}\r\n" "upload" = "🔼 Upload: ↑{{ .Upload }}\r\n" "download" = "🔽 Download: ↓{{ .Download }}\r\n" @@ -496,10 +500,13 @@ "TGUser" = "👤 Telegram User: {{ .TelegramID }}\r\n" "exhaustedMsg" = "🚨 Exhausted {{ .Type }}:\r\n" "exhaustedCount" = "🚨 Exhausted {{ .Type }} count:\r\n" +"onlinesCount" = "🌐 Online clients: {{ .Count }}\r\n" "disabled" = "🛑 Disabled: {{ .Disabled }}\r\n" -"depleteSoon" = "🔜 Deplete Soon: {{ .Deplete }}\r\n \r\n" +"depleteSoon" = "🔜 Deplete Soon: {{ .Deplete }}\r\n\r\n" "backupTime" = "🗄 Backup Time: {{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 Refreshed On: {{ .Time }}\r\n \r\n" +"refreshedOn" = "\r\n📋🔄 Refreshed On: {{ .Time }}\r\n\r\n" +"yes" = "✅ Yes" +"no" = "❌ No" [tgbot.buttons] "closeKeyboard" = "❌ Close Keyboard" @@ -509,11 +516,13 @@ "confirmResetTraffic" = "✅ Confirm Reset Traffic?" "confirmClearIps" = "✅ Confirm Clear IPs?" "confirmRemoveTGUser" = "✅ Confirm Remove Telegram User?" +"confirmToggle" = "✅ Confirm Enable/Disable User?" "dbBackup" = "Get DB Backup" "serverUsage" = "Server Usage" "getInbounds" = "Get Inbounds" "depleteSoon" = "Deplete soon" "clientUsage" = "Get Usage" +"onlines" = "Online Clients" "commands" = "Commands" "refresh" = "🔄 Refresh" "clearIPs" = "❌ Clear IPs" @@ -521,14 +530,16 @@ "selectTGUser" = "👤 Select Telegram User" "selectOneTGUser" = "👤 Select a telegram user:" "resetTraffic" = "📈 Reset Traffic" -"resetExpire" = "📅 Reset Expire Days" +"resetExpire" = "📅 Change Expiration Date" "ipLog" = "🔢 IP Log" "ipLimit" = "🔢 IP Limit" "setTGUser" = "👤 Set Telegram User" "toggle" = "🔘 Enable / Disable" "custom" = "🔢 Custom" -"confirmNumber" = "✅ Confirm : {{ .Num }}" +"confirmNumber" = "✅ Confirm: {{ .Num }}" +"confirmNumberAdd" = "✅ Confirm adding: {{ .Num }}" "limitTraffic" = "🚧 Traffic Limit" +"getBanLogs" = "Get Ban Logs" [tgbot.answers] "successfulOperation" = "✅ Successful!" @@ -548,5 +559,4 @@ "removedTGUserSuccess" = "✅ {{ .Email }} : Telegram User removed successfully." "enableSuccess" = "✅ {{ .Email }} : Enabled successfully." "disableSuccess" = "✅ {{ .Email }} : Disabled successfully." -"askToAddUserId" = "Your configuration is not found!\r\nPlease ask your Admin to use your telegram user id in your configuration(s).\r\n\r\nYour user id: {{ .TgUserID }}" -"askToAddUserName" = "Your configuration is not found!\r\nPlease ask your Admin to use your telegram username or user id in your configuration(s).\r\n\r\nYour username: @{{ .TgUserName }}\r\n\r\nYour user id: {{ .TgUserID }}" +"askToAddUserId" = "Your configuration is not found!\r\nPlease ask your Admin to use your telegram user id in your configuration(s).\r\n\r\nYour user id: {{ .TgUserID }}" diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml index ed3f7b79..8645fbef 100644 --- a/web/translation/translate.es_ES.toml +++ b/web/translation/translate.es_ES.toml @@ -173,7 +173,7 @@ "setDefaultCert" = "Establecer certificado desde el panel" "xtlsDesc" = "La versión del núcleo de Xray debe ser 1.7.5" "realityDesc" = "La versión del núcleo de Xray debe ser 1.8.0 o superior." -"telegramDesc" = "Utiliza el ID de Telegram sin @ o los IDs de chat (puedes obtenerlo aquí @userinfobot o usando el comando '/id' en el bot)." +"telegramDesc" = "Utiliza únicamente IDs de chat (puedes obtenerlo aquí @userinfobot o usando el comando '/id' en el bot)." "subscriptionDesc" = "Puedes encontrar tu enlace de suscripción en Detalles, también puedes usar el mismo nombre para varias configuraciones." "info" = "Info" "same" = "misma" @@ -439,6 +439,7 @@ "noIpRecord" = "❗ ¡Sin Registro de IP!" "noInbounds" = "❗ ¡No se encontraron entradas!" "unlimited" = "♾ Ilimitado" +"add" = "Agregar" "month" = "Mes" "months" = "Meses" "day" = "Día" @@ -447,6 +448,8 @@ "unknown" = "Desconocido" "inbounds" = "Entradas" "clients" = "Clientes" +"offline" = "🔴 Sin conexión" +"online" = "🟢 En línea" [tgbot.commands] "unknown" = "❗ Comando desconocido" @@ -457,8 +460,8 @@ "status" = "✅ ¡El bot está bien!" "usage" = "❗ ¡Por favor proporciona un texto para buscar!" "getID" = "🆔 Tu ID: {{ .ID }}" -"helpAdminCommands" = "Buscar un correo electrónico de cliente:\r\n/usage [Email]\r\n \r\nBuscar entradas (con estadísticas de cliente):\r\n/inbound [Nota]" -"helpClientCommands" = "Para buscar estadísticas, simplemente usa el siguiente comando:\r\n \r\n/usage [UUID|Contraseña]\r\n \r\nUsa UUID para vmess/vless y Contraseña para Trojan." +"helpAdminCommands" = "Buscar un correo electrónico de cliente:\r\n/usage [Email]\r\n\r\nBuscar entradas (con estadísticas de cliente):\r\n/inbound [Nota]" +"helpClientCommands" = "Para buscar estadísticas, simplemente usa el siguiente comando:\r\n\r\n/usage [UUID|Contraseña]" [tgbot.messages] "cpuThreshold" = "🔴 El uso de CPU {{ .Percent }}% es mayor que el umbral {{ .Threshold }}%" @@ -473,7 +476,7 @@ "ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" "ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" "ip" = "🌐 IP: {{ .IP }}\r\n" -"ips" = "🔢 IPs: \r\n{{ .IPs }}\r\n" +"ips" = "🔢 IPs:\r\n{{ .IPs }}\r\n" "serverUpTime" = "⏳ Tiempo de actividad del servidor: {{ .UpTime }} {{ .Unit }}\r\n" "serverLoad" = "📈 Carga del servidor: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" "serverMemory" = "📋 Memoria del servidor: {{ .Current }}/{{ .Total }}\r\n" @@ -487,19 +490,23 @@ "port" = "🔌 Puerto: {{ .Port }}\r\n" "expire" = "📅 Fecha de Vencimiento: {{ .Time }}\r\n" "expireIn" = "📅 Vence en: {{ .Time }}\r\n" -"active" = "💡 Activo: ✅ Sí\r\n" -"inactive" = "💡 Activo: ❌ No\r\n" +"active" = "💡 Activo: {{ .Enable }}\r\n" +"enabled" = "🚨 Habilitado: {{ .Enable }}\r\n" +"online" = "🌐 Estado de conexión: {{ .Status }}\r\n" "email" = "📧 Email: {{ .Email }}\r\n" "upload" = "🔼 Subida: ↑{{ .Upload }}\r\n" "download" = "🔽 Bajada: ↓{{ .Download }}\r\n" "total" = "📊 Total: ↑↓{{ .UpDown }} / {{ .Total }}\r\n" "TGUser" = "👤 Usuario de Telegram: {{ .TelegramID }}\r\n" -"exhaustedMsg" = "🚨 Agotado {{ .Type }}: \r\n" -"exhaustedCount" = "🚨 Cantidad de Agotados {{ .Type }}: \r\n" +"exhaustedMsg" = "🚨 Agotado {{ .Type }}:\r\n" +"exhaustedCount" = "🚨 Cantidad de Agotados {{ .Type }}:\r\n" +"onlinesCount" = "🌐 Clientes en línea: {{ .Count }}\r\n" "disabled" = "🛑 Desactivado: {{ .Disabled }}\r\n" -"depleteSoon" = "🔜 Se agotará pronto: {{ .Deplete }}\r\n \r\n" +"depleteSoon" = "🔜 Se agotará pronto: {{ .Deplete }}\r\n\r\n" "backupTime" = "🗄 Hora de la Copia de Seguridad: {{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 Actualizado en: {{ .Time }}\r\n \r\n" +"refreshedOn" = "\r\n📋🔄 Actualizado en: {{ .Time }}\r\n\r\n" +"yes" = "✅ Sí" +"no" = "❌ No" [tgbot.buttons] "closeKeyboard" = "❌ Cerrar Teclado" @@ -509,11 +516,13 @@ "confirmResetTraffic" = "✅ ¿Confirmar Reinicio de Tráfico?" "confirmClearIps" = "✅ ¿Confirmar Limpiar IPs?" "confirmRemoveTGUser" = "✅ ¿Confirmar Eliminar Usuario de Telegram?" +"confirmToggle" = " ✅ ¿Confirmar habilitar/deshabilitar usuario?" "dbBackup" = "Obtener Copia de Seguridad de BD" "serverUsage" = "Uso del Servidor" "getInbounds" = "Obtener Entradas" "depleteSoon" = "Pronto se Agotará" "clientUsage" = "Obtener Uso" +"onlines" = "Clientes en línea" "commands" = "Comandos" "refresh" = "🔄 Actualizar" "clearIPs" = "❌ Limpiar IPs" @@ -521,14 +530,16 @@ "selectTGUser" = "👤 Seleccionar Usuario de Telegram" "selectOneTGUser" = "👤 Selecciona un usuario de telegram:" "resetTraffic" = "📈 Reiniciar Tráfico" -"resetExpire" = "📅 Reiniciar Días de Vencimiento" +"resetExpire" = "📅 Cambiar fecha de Vencimiento" "ipLog" = "🔢 Registro de IP" "ipLimit" = "🔢 Límite de IP" "setTGUser" = "👤 Establecer Usuario de Telegram" "toggle" = "🔘 Habilitar / Deshabilitar" "custom" = "🔢 Costumbre" -"confirmNumber" = "✅ Confirmar : {{ .Num }}" +"confirmNumber" = "✅ Confirmar: {{ .Num }}" +"confirmNumberAdd" = "✅ Confirmar agregando: {{ .Num }}" "limitTraffic" = "🚧 Límite de tráfico" +"getBanLogs" = "Registros de prohibición" [tgbot.answers] "successfulOperation" = "✅ ¡Exitosa!" @@ -548,5 +559,4 @@ "removedTGUserSuccess" = "✅ {{ .Email }} : Usuario de Telegram eliminado exitosamente." "enableSuccess" = "✅ {{ .Email }} : Habilitado exitosamente." "disableSuccess" = "✅ {{ .Email }} : Deshabilitado exitosamente." -"askToAddUserId" = "¡No se encuentra su configuración!\r\nPor favor, pídale a su administrador que use su ID de usuario de Telegram en su(s) configuración(es).\r\n\r\nSu ID de usuario: {{ .TgUserID }}" -"askToAddUserName" = "¡No se encuentra su configuración!\r\nPor favor, pídale a su administrador que use su nombre de usuario o ID de usuario de Telegram en su(s) configuración(es).\r\n\r\nSu nombre de usuario: @{{ .TgUserName }}\r\n\r\nSu ID de usuario: {{ .TgUserID }}" +"askToAddUserId" = "¡No se encuentra su configuración!\r\nPor favor, pídale a su administrador que use su ID de usuario de Telegram en su(s) configuración(es).\r\n\r\nSu ID de usuario: {{ .TgUserID }}" diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml index f6b3c260..a4b66234 100644 --- a/web/translation/translate.fa_IR.toml +++ b/web/translation/translate.fa_IR.toml @@ -173,7 +173,7 @@ "setDefaultCert" = "استفاده از گواهی پنل" "xtlsDesc" = "هسته Xray باید 1.7.5 باشد" "realityDesc" = "هسته Xray باید 1.8.0 و بالاتر باشد" -"telegramDesc" = "از آیدی تلگرام بدون @ یا آیدی چت استفاده کنید (می توانید آن را از اینجا دریافت کنید @userinfobot یا در ربات دستور '/id' را وارد کنید)" +"telegramDesc" = "فقط از شناسه چت استفاده کنید (می توانید آن را از اینجا دریافت کنید @userinfobot یا در ربات دستور '/id' را وارد کنید)" "subscriptionDesc" = "می توانید ساب لینک خود را در جزئیات پیدا کنید، همچنین می توانید از همین نام برای چندین کانفیگ استفاده کنید" "info" = "اطلاعات" "same" = "همسان" @@ -439,6 +439,7 @@ "noIpRecord" = "❗ رکورد IP یافت نشد!" "noInbounds" = "❗ هیچ ورودی یافت نشد!" "unlimited" = "♾ نامحدود" +"add" = "اضافه کردن" "month" = "ماه" "months" = "ماه‌ها" "day" = "روز" @@ -447,6 +448,8 @@ "unknown" = "نامشخص" "inbounds" = "ورودی‌ها" "clients" = "کلاینت‌ها" +"offline" = "🔴 آفلاین" +"online" = "🟢 برخط" [tgbot.commands] "unknown" = "❗ دستور ناشناخته" @@ -457,8 +460,8 @@ "status" = "✅ ربات در حالت عادی است!" "usage" = "❗ لطفاً یک متن برای جستجو وارد کنید!" "getID" = "🆔 شناسه شما: {{ .ID }}" -"helpAdminCommands" = "برای جستجوی ایمیل مشتری:\r\n/usage [ایمیل]\r\n \r\nبرای جستجوی ورودی‌ها (با آمار مشتری):\r\n/inbound [توضیح]" -"helpClientCommands" = "برای جستجوی آمار، فقط از دستور زیر استفاده کنید:\r\n \r\n/usage [UUID|رمز عبور]\r\n \r\nاز UUID برای vmess/vless و از رمز عبور برای Trojan استفاده کنید." +"helpAdminCommands" = "برای جستجوی ایمیل مشتری:\r\n/usage [ایمیل]\r\n\r\nبرای جستجوی ورودی‌ها (با آمار مشتری):\r\n/inbound [توضیح]" +"helpClientCommands" = "برای جستجوی آمار، فقط از دستور زیر استفاده کنید:\r\n\r\n/usage [Email]" [tgbot.messages] "cpuThreshold" = "🔴 میزان استفاده از CPU {{ .Percent }}% بیشتر از آستانه {{ .Threshold }}% است." @@ -473,7 +476,7 @@ "ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" "ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" "ip" = "🌐 آدرس IP: {{ .IP }}\r\n" -"ips" = "🔢 آدرس‌های IP: \r\n{{ .IPs }}\r\n" +"ips" = "🔢 آدرس‌های IP:\r\n{{ .IPs }}\r\n" "serverUpTime" = "⏳ زمان کارکرد سرور: {{ .UpTime }} {{ .Unit }}\r\n" "serverLoad" = "📈 بار سرور: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" "serverMemory" = "📋 حافظه سرور: {{ .Current }}/{{ .Total }}\r\n" @@ -487,8 +490,9 @@ "port" = "🔌 پورت: {{ .Port }}\r\n" "expire" = "📅 تاریخ انقضا: {{ .Time }}\r\n" "expireIn" = "📅 باقیمانده از انقضا: {{ .Time }}\r\n" -"active" = "💡 فعال: ✅\r\n" -"inactive" = "💡 فعال: ❌\r\n" +"active" = "💡 فعال: {{ .Enable }}\r\n" +"enabled" = "🚨 مشمول: {{ .Enable }}\r\n" +"online" = "🌐 وضعیت اتصال: {{ .Status }}\r\n" "email" = "📧 ایمیل: {{ .Email }}\r\n" "upload" = "🔼 آپلود↑: {{ .Upload }}\r\n" "download" = "🔽 دانلود↓: {{ .Download }}\r\n" @@ -496,10 +500,13 @@ "TGUser" = "👤 کاربر تلگرام: {{ .TelegramID }}\r\n" "exhaustedMsg" = "🚨 {{ .Type }} به اتمام رسیده است:\r\n" "exhaustedCount" = "🚨 تعداد {{ .Type }} به اتمام رسیده:\r\n" +"onlinesCount" = "🌐 مشتریان آنلاین: {{ .Count }}\r\n" "disabled" = "🛑 غیرفعال: {{ .Disabled }}\r\n" -"depleteSoon" = "🔜 به زودی به پایان خواهد رسید: {{ .Deplete }}\r\n \r\n" +"depleteSoon" = "🔜 به زودی به پایان خواهد رسید: {{ .Deplete }}\r\n\r\n" "backupTime" = "🗄 زمان پشتیبان‌گیری: {{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 تازه‌سازی شده در: {{ .Time }}\r\n \r\n" +"refreshedOn" = "\r\n📋🔄 تازه‌سازی شده در: {{ .Time }}\r\n\r\n" +"yes" = "✅ بله" +"no" = "❌ نه" [tgbot.buttons] "closeKeyboard" = "❌ بستن کیبورد" @@ -509,11 +516,13 @@ "confirmResetTraffic" = "✅ تأیید تنظیم مجدد ترافیک؟" "confirmClearIps" = "✅ تأیید پاک