package service import ( "embed" "fmt" "net" "os" "strconv" "strings" "time" "x-ui/config" "x-ui/database/model" "x-ui/logger" "x-ui/util/common" "x-ui/web/global" "x-ui/web/locale" "x-ui/xray" "github.com/mymmrac/telego" th "github.com/mymmrac/telego/telegohandler" tu "github.com/mymmrac/telego/telegoutil" ) var bot *telego.Bot var botHandler *th.BotHandler var adminIds []int64 var isRunning bool var hostname string var hashStorage *global.HashStorage type LoginStatus byte const ( LoginSuccess LoginStatus = 1 LoginFail LoginStatus = 0 ) type Tgbot struct { inboundService InboundService settingService SettingService serverService ServerService xrayService XrayService lastStatus *Status } func (t *Tgbot) NewTgbot() *Tgbot { return new(Tgbot) } func (t *Tgbot) I18nBot(name string, params ...string) string { return locale.I18n(locale.Bot, name, params...) } func (t *Tgbot) GetHashStorage() *global.HashStorage { return hashStorage } func (t *Tgbot) Start(i18nFS embed.FS) error { err := locale.InitLocalizer(i18nFS, &t.settingService) if err != nil { return err } // init hash storage => store callback queries hashStorage = global.NewHashStorage(20 * time.Minute) t.SetHostname() tgBottoken, err := t.settingService.GetTgBotToken() if err != nil || tgBottoken == "" { logger.Warning("Get TgBotToken failed:", err) return err } tgBotid, err := t.settingService.GetTgBotChatId() if err != nil { logger.Warning("Get GetTgBotChatId failed:", err) return err } if tgBotid != "" { for _, adminId := range strings.Split(tgBotid, ",") { id, err := strconv.Atoi(adminId) if err != nil { logger.Warning("Failed to get IDs from GetTgBotChatId:", err) return err } adminIds = append(adminIds, int64(id)) } } bot, err = telego.NewBot(tgBottoken) if err != nil { fmt.Println("Get tgbot's api error:", err) return err } // listen for TG bot income messages if !isRunning { logger.Info("Starting Telegram receiver ...") go t.OnReceive() isRunning = true } return nil } func (t *Tgbot) IsRunning() bool { return isRunning } func (t *Tgbot) SetHostname() { host, err := os.Hostname() if err != nil { logger.Error("get hostname error:", err) hostname = "" return } hostname = host } func (t *Tgbot) Stop() { botHandler.Stop() bot.StopLongPolling() logger.Info("Stop Telegram receiver ...") isRunning = false adminIds = nil } func (t *Tgbot) encodeQuery(query string) string { // NOTE: we only need to hash for more than 64 chars if len(query) <= 64 { return query } return hashStorage.SaveHash(query) } func (t *Tgbot) decodeQuery(query string) (string, error) { if !hashStorage.IsMD5(query) { return query, nil } decoded, exists := hashStorage.GetValue(query) if !exists { return "", common.NewError("hash not found in storage!") } return decoded, nil } func (t *Tgbot) OnReceive() { params := telego.GetUpdatesParams{ Timeout: 10, } updates, _ := bot.UpdatesViaLongPolling(¶ms) botHandler, _ = th.NewBotHandler(bot, updates) botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) { t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.keyboardClosed"), tu.ReplyKeyboardRemove()) }, th.TextEqual(t.I18nBot("tgbot.buttons.closeKeyboard"))) botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) { t.answerCommand(&message, message.Chat.ID, checkAdmin(message.From.ID)) }, th.AnyCommand()) botHandler.HandleCallbackQuery(func(_ *telego.Bot, query telego.CallbackQuery) { t.asnwerCallback(&query, checkAdmin(query.From.ID)) }, th.AnyCallbackQueryWithMessage()) botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) { if message.UserShared != nil { if checkAdmin(message.From.ID) { err := t.inboundService.SetClientTelegramUserID(message.UserShared.RequestID, strconv.FormatInt(message.UserShared.UserID, 10)) output := "" if err != nil { output += t.I18nBot("tgbot.messages.selectUserFailed") } else { output += t.I18nBot("tgbot.messages.userSaved") } t.SendMsgToTgbot(message.Chat.ID, output, tu.ReplyKeyboardRemove()) } else { t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.noResult"), tu.ReplyKeyboardRemove()) } } }, th.AnyMessage()) botHandler.Start() } func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin bool) { msg, onlyMessage := "", false command, commandArgs := tu.ParseCommand(message.Text) // Extract the command from the Message. switch command { case "help": msg += t.I18nBot("tgbot.commands.help") msg += t.I18nBot("tgbot.commands.pleaseChoose") case "start": msg += t.I18nBot("tgbot.commands.start", "Firstname=="+message.From.FirstName) if isAdmin { msg += t.I18nBot("tgbot.commands.welcome", "Hostname=="+hostname) } msg += "\n\n" + t.I18nBot("tgbot.commands.pleaseChoose") case "status": onlyMessage = true msg += t.I18nBot("tgbot.commands.status") case "id": onlyMessage = true msg += t.I18nBot("tgbot.commands.getID", "ID=="+strconv.FormatInt(message.From.ID, 10)) case "usage": onlyMessage = true if len(commandArgs) > 0 { if isAdmin { t.searchClient(chatId, commandArgs[0]) } else { t.searchForClient(chatId, commandArgs[0]) } } else { msg += t.I18nBot("tgbot.commands.usage") } case "inbound": onlyMessage = true if isAdmin && len(commandArgs) > 0 { t.searchInbound(chatId, commandArgs[0]) } else { msg += t.I18nBot("tgbot.commands.unknown") } default: msg += t.I18nBot("tgbot.commands.unknown") } if onlyMessage { t.SendMsgToTgbot(chatId, msg) return } t.SendAnswer(chatId, msg, isAdmin) } func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool) { chatId := callbackQuery.Message.Chat.ID if isAdmin { // get query from hash storage decodedQuery, err := t.decodeQuery(callbackQuery.Data) if err != nil { t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.noQuery")) return } dataArray := strings.Split(decodedQuery, " ") if len(dataArray) >= 2 && len(dataArray[1]) > 0 { email := dataArray[1] switch dataArray[0] { case "client_refresh": t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.clientRefreshSuccess", "Email=="+email)) t.searchClient(chatId, email, callbackQuery.Message.MessageID) case "client_cancel": t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email)) t.searchClient(chatId, email, callbackQuery.Message.MessageID) case "ips_refresh": t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.IpRefreshSuccess", "Email=="+email)) t.searchClientIps(chatId, email, callbackQuery.Message.MessageID) case "ips_cancel": t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email)) t.searchClientIps(chatId, email, callbackQuery.Message.MessageID) case "tgid_refresh": t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.TGIdRefreshSuccess", "Email=="+email)) t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.MessageID) case "tgid_cancel": t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email)) t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.MessageID) case "reset_traffic": inlineKeyboard := tu.InlineKeyboard( tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancelReset")).WithCallbackData(t.encodeQuery("client_cancel "+email)), ), tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmResetTraffic")).WithCallbackData(t.encodeQuery("reset_traffic_c "+email)), ), ) t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard) case "reset_traffic_c": err := t.inboundService.ResetClientTrafficByEmail(email) if err == nil { t.xrayService.SetToNeedRestart() t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.resetTrafficSuccess", "Email=="+email)) t.searchClient(chatId, email, callbackQuery.Message.MessageID) } else { t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation")) } case "reset_exp": inlineKeyboard := tu.InlineKeyboard( tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancelReset")).WithCallbackData(t.encodeQuery("client_cancel "+email)), ), tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.unlimited")).WithCallbackData(t.encodeQuery("reset_exp_c "+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.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.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.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")), ), ) t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard) case "reset_exp_c": if len(dataArray) == 3 { days, err := strconv.Atoi(dataArray[2]) if err == nil { var date int64 = 0 if days > 0 { date = int64(-(days * 24 * 60 * 60000)) } err := t.inboundService.ResetClientExpiryTimeByEmail(email, date) if err == nil { t.xrayService.SetToNeedRestart() t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.expireResetSuccess", "Email=="+email)) t.searchClient(chatId, email, callbackQuery.Message.MessageID) return } } } t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation")) t.searchClient(chatId, email, callbackQuery.Message.MessageID) case "ip_limit": inlineKeyboard := tu.InlineKeyboard( tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancelIpLimit")).WithCallbackData(t.encodeQuery("client_cancel "+email)), ), tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.unlimited")).WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 0")), ), tu.InlineKeyboardRow( tu.InlineKeyboardButton("1").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 1")), tu.InlineKeyboardButton("2").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 2")), ), tu.InlineKeyboardRow( tu.InlineKeyboardButton("3").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 3")), tu.InlineKeyboardButton("4").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 4")), ), tu.InlineKeyboardRow( tu.InlineKeyboardButton("5").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 5")), tu.InlineKeyboardButton("6").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 6")), tu.InlineKeyboardButton("7").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 7")), ), tu.InlineKeyboardRow( tu.InlineKeyboardButton("8").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 8")), tu.InlineKeyboardButton("9").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 9")), tu.InlineKeyboardButton("10").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 10")), ), ) t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard) case "ip_limit_c": if len(dataArray) == 3 { count, err := strconv.Atoi(dataArray[2]) if err == nil { err := t.inboundService.ResetClientIpLimitByEmail(email, count) if err == nil { t.xrayService.SetToNeedRestart() t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.resetIpSuccess", "Email=="+email, "Count=="+strconv.Itoa(count))) t.searchClient(chatId, email, callbackQuery.Message.MessageID) return } } } t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation")) t.searchClient(chatId, email, callbackQuery.Message.MessageID) case "clear_ips": inlineKeyboard := tu.InlineKeyboard( tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("ips_cancel "+email)), ), tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmClearIps")).WithCallbackData(t.encodeQuery("clear_ips_c "+email)), ), ) t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard) case "clear_ips_c": err := t.inboundService.ClearClientIps(email) if err == nil { t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.clearIpSuccess", "Email=="+email)) t.searchClientIps(chatId, email, callbackQuery.Message.MessageID) } else { t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation")) } case "ip_log": t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.getIpLog", "Email=="+email)) t.searchClientIps(chatId, email) case "tg_user": t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.getUserInfo", "Email=="+email)) t.clientTelegramUserInfo(chatId, email) case "tgid_remove": inlineKeyboard := tu.InlineKeyboard( tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.cancel")).WithCallbackData(t.encodeQuery("tgid_cancel "+email)), ), tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmRemoveTGUser")).WithCallbackData(t.encodeQuery("tgid_remove_c "+email)), ), ) t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard) case "tgid_remove_c": traffic, err := t.inboundService.GetClientTrafficByEmail(email) if err != nil || traffic == nil { t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation")) return } err = t.inboundService.SetClientTelegramUserID(traffic.Id, "") if err == nil { t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.removedTGUserSuccess", "Email=="+email)) t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.MessageID) } else { t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation")) } case "toggle_enable": enabled, err := t.inboundService.ToggleClientEnableByEmail(email) if err == nil { t.xrayService.SetToNeedRestart() if enabled { t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.enableSuccess", "Email=="+email)) } else { t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.disableSuccess", "Email=="+email)) } t.searchClient(chatId, email, callbackQuery.Message.MessageID) } else { t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation")) } } return } } // 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.SendMsgToTgbot(chatId, t.getServerUsage()) case "inbounds": t.SendMsgToTgbot(chatId, t.getInboundUsages()) case "deplete_soon": t.SendMsgToTgbot(chatId, t.getExhausted()) case "get_backup": t.sendBackup(chatId) case "client_traffic": t.getClientUsage(chatId, callbackQuery.From.Username, strconv.FormatInt(callbackQuery.From.ID, 10)) case "client_commands": t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.helpClientCommands")) case "commands": t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.helpAdminCommands")) } } func checkAdmin(tgId int64) bool { for _, adminId := range adminIds { if adminId == tgId { return true } } return false } 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.InlineKeyboardButton(t.I18nBot("tgbot.buttons.dbBackup")).WithCallbackData(t.encodeQuery("get_backup")), ), tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.getInbounds")).WithCallbackData(t.encodeQuery("inbounds")), tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.depleteSoon")).WithCallbackData(t.encodeQuery("deplete_soon")), ), tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.commands")).WithCallbackData(t.encodeQuery("commands")), ), ) numericKeyboardClient := tu.InlineKeyboard( tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.clientUsage")).WithCallbackData(t.encodeQuery("client_traffic")), tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.commands")).WithCallbackData(t.encodeQuery("client_commands")), ), ) var ReplyMarkup telego.ReplyMarkup if isAdmin { ReplyMarkup = numericKeyboard } else { ReplyMarkup = numericKeyboardClient } t.SendMsgToTgbot(chatId, msg, ReplyMarkup) } func (t *Tgbot) SendMsgToTgbot(chatId int64, msg string, replyMarkup ...telego.ReplyMarkup) { if !isRunning { return } if msg == "" { logger.Info("[tgbot] message is empty!") return } var allMessages []string limit := 2000 // paging message if it is big if len(msg) > limit { messages := strings.Split(msg, "\r\n \r\n") lastIndex := -1 for _, message := range messages { if (len(allMessages) == 0) || (len(allMessages[lastIndex])+len(message) > limit) { allMessages = append(allMessages, message) lastIndex++ } else { allMessages[lastIndex] += "\r\n \r\n" + message } } } else { allMessages = append(allMessages, msg) } for _, message := range allMessages { params := telego.SendMessageParams{ ChatID: tu.ID(chatId), Text: message, ParseMode: "HTML", } if len(replyMarkup) > 0 { params.ReplyMarkup = replyMarkup[0] } _, err := bot.SendMessage(¶ms) if err != nil { logger.Warning("Error sending telegram message :", err) } time.Sleep(500 * time.Millisecond) } } func (t *Tgbot) SendMsgToTgbotAdmins(msg string) { for _, adminId := range adminIds { t.SendMsgToTgbot(adminId, msg) } } func (t *Tgbot) SendReport() { runTime, err := t.settingService.GetTgbotRuntime() if err == nil && len(runTime) > 0 { msg := "" msg += t.I18nBot("tgbot.messages.report", "RunTime=="+runTime) msg += t.I18nBot("tgbot.messages.datetime", "DateTime=="+time.Now().Format("2006-01-02 15:04:05")) t.SendMsgToTgbotAdmins(msg) } info := t.getServerUsage() t.SendMsgToTgbotAdmins(info) exhausted := t.getExhausted() t.SendMsgToTgbotAdmins(exhausted) backupEnable, err := t.settingService.GetTgBotBackup() if err == nil && backupEnable { t.SendBackupToAdmins() } } func (t *Tgbot) SendBackupToAdmins() { if !t.IsRunning() { return } for _, adminId := range adminIds { t.sendBackup(int64(adminId)) } } func (t *Tgbot) getServerUsage() string { info, ipv4, ipv6 := "", "", "" info += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname) info += t.I18nBot("tgbot.messages.version", "Version=="+config.GetVersion()) // get ip address netInterfaces, err := net.Interfaces() 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" } else { for i := 0; i < len(netInterfaces); i++ { if (netInterfaces[i].Flags & net.FlagUp) != 0 { addrs, _ := netInterfaces[i].Addrs() for _, address := range addrs { if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { if ipnet.IP.To4() != nil { ipv4 += ipnet.IP.String() + " " } else if ipnet.IP.To16() != nil && !ipnet.IP.IsLinkLocalUnicast() { ipv6 += ipnet.IP.String() + " " } } } } } info += t.I18nBot("tgbot.messages.ipv4", "IPv4=="+ipv4) info += t.I18nBot("tgbot.messages.ipv6", "IPv6=="+ipv6) } // get latest status of server t.lastStatus = t.serverService.GetStatus(t.lastStatus) 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.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))) info += t.I18nBot("tgbot.messages.xrayStatus", "State=="+fmt.Sprint(t.lastStatus.Xray.State)) return info } func (t *Tgbot) UserLoginNotify(username string, ip string, time string, status LoginStatus) { if !t.IsRunning() { return } if username == "" || ip == "" || time == "" { logger.Warning("UserLoginNotify failed, invalid info!") return } msg := "" if status == LoginSuccess { msg += t.I18nBot("tgbot.messages.loginSuccess") } else if status == LoginFail { msg += t.I18nBot("tgbot.messages.loginFailed") } msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname) msg += t.I18nBot("tgbot.messages.username", "Username=="+username) msg += t.I18nBot("tgbot.messages.ip", "IP=="+ip) msg += t.I18nBot("tgbot.messages.time", "Time=="+time) t.SendMsgToTgbotAdmins(msg) } func (t *Tgbot) getInboundUsages() string { info := "" // get traffic inbouds, err := t.inboundService.GetAllInbounds() if err != nil { logger.Warning("GetAllInbounds run failed:", err) info += t.I18nBot("tgbot.answers.getInboundsFailed") } else { // NOTE:If there no any sessions here,need to notify here // TODO:Sub-node push, automatic conversion format for _, inbound := range inbouds { info += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark) info += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port)) info += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic((inbound.Up+inbound.Down)), "Upload=="+common.FormatTraffic(inbound.Up), "Download=="+common.FormatTraffic(inbound.Down)) if inbound.ExpiryTime == 0 { info += t.I18nBot("tgbot.messages.expire", "Time=="+t.I18nBot("tgbot.unlimited")) } else { info += t.I18nBot("tgbot.messages.expire", "Time=="+time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05")) } } } return info } func (t *Tgbot) getClientUsage(chatId int64, tgUserName string, tgUserID 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 { if len(tgUserName) == 0 { msg := t.I18nBot("tgbot.answers.askToAddUserId", "TgUserID=="+tgUserID) t.SendMsgToTgbot(chatId, msg) return } traffics, err = t.inboundService.GetClientTrafficTgBot(tgUserName) } 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) 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 += 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) } t.SendAnswer(chatId, t.I18nBot("tgbot.commands.pleaseChoose"), false) } func (t *Tgbot) searchClientIps(chatId int64, email string, messageID ...int) { ips, err := t.inboundService.GetInboundClientIps(email) if err != nil || len(ips) == 0 { ips = t.I18nBot("tgbot.noIpRecord") } output := "" output += t.I18nBot("tgbot.messages.email", "Email=="+email) output += t.I18nBot("tgbot.messages.ips", "IPs=="+ips) inlineKeyboard := tu.InlineKeyboard( tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("ips_refresh "+email)), ), tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.clearIPs")).WithCallbackData(t.encodeQuery("clear_ips "+email)), ), ) if len(messageID) > 0 { t.editMessageTgBot(chatId, messageID[0], output, inlineKeyboard) } else { t.SendMsgToTgbot(chatId, output, inlineKeyboard) } } func (t *Tgbot) clientTelegramUserInfo(chatId int64, email string, messageID ...int) { traffic, client, err := t.inboundService.GetClientByEmail(email) if err != nil { logger.Warning(err) msg := t.I18nBot("tgbot.wentWrong") t.SendMsgToTgbot(chatId, msg) return } if client == nil { msg := t.I18nBot("tgbot.noResult") t.SendMsgToTgbot(chatId, msg) return } tgId := "None" if len(client.TgID) > 0 { tgId = client.TgID } output := "" output += t.I18nBot("tgbot.messages.email", "Email=="+email) output += t.I18nBot("tgbot.messages.TGUser", "TelegramID=="+tgId) output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05")) inlineKeyboard := tu.InlineKeyboard( tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("tgid_refresh "+email)), ), tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.removeTGUser")).WithCallbackData(t.encodeQuery("tgid_remove "+email)), ), ) if len(messageID) > 0 { t.editMessageTgBot(chatId, messageID[0], output, inlineKeyboard) } else { t.SendMsgToTgbot(chatId, output, inlineKeyboard) requestUser := telego.KeyboardButtonRequestUser{ RequestID: int32(traffic.Id), UserIsBot: false, } keyboard := tu.Keyboard( tu.KeyboardRow( tu.KeyboardButton(t.I18nBot("tgbot.buttons.selectTGUser")).WithRequestUser(&requestUser), ), tu.KeyboardRow( tu.KeyboardButton(t.I18nBot("tgbot.buttons.closeKeyboard")), ), ).WithIsPersistent().WithResizeKeyboard() t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.buttons.selectOneTGUser"), keyboard) } } func (t *Tgbot) searchClient(chatId int64, email string, messageID ...int) { 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 } 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")) inlineKeyboard := tu.InlineKeyboard( tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("client_refresh "+email)), ), tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.resetTraffic")).WithCallbackData(t.encodeQuery("reset_traffic "+email)), ), tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.resetExpire")).WithCallbackData(t.encodeQuery("reset_exp "+email)), ), tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.ipLog")).WithCallbackData(t.encodeQuery("ip_log "+email)), tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.ipLimit")).WithCallbackData(t.encodeQuery("ip_limit "+email)), ), tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.setTGUser")).WithCallbackData(t.encodeQuery("tg_user "+email)), ), tu.InlineKeyboardRow( 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 { t.SendMsgToTgbot(chatId, output, inlineKeyboard) } } func (t *Tgbot) searchInbound(chatId int64, remark string) { inbouds, err := t.inboundService.SearchInbounds(remark) if err != nil { logger.Warning(err) msg := t.I18nBot("tgbot.wentWrong") t.SendMsgToTgbot(chatId, msg) return } if len(inbouds) == 0 { msg := t.I18nBot("tgbot.noInbounds") t.SendMsgToTgbot(chatId, msg) return } now := time.Now().Unix() for _, inbound := range inbouds { info := "" info += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark) info += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port)) info += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic((inbound.Up+inbound.Down)), "Upload=="+common.FormatTraffic(inbound.Up), "Download=="+common.FormatTraffic(inbound.Down)) if inbound.ExpiryTime == 0 { info += t.I18nBot("tgbot.messages.expire", "Time=="+t.I18nBot("tgbot.unlimited")) } else { info += t.I18nBot("tgbot.messages.expire", "Time=="+time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05")) } 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)) } 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) 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 { trDiff := int64(0) exDiff := int64(0) now := time.Now().Unix() * 1000 var exhaustedInbounds []model.Inbound var exhaustedClients []xray.ClientTraffic var disabledInbounds []model.Inbound var disabledClients []xray.ClientTraffic 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) } for _, inbound := range inbounds { if inbound.Enable { if (inbound.ExpiryTime > 0 && (inbound.ExpiryTime-now < exDiff)) || (inbound.Total > 0 && (inbound.Total-(inbound.Up+inbound.Down) < trDiff)) { exhaustedInbounds = append(exhaustedInbounds, *inbound) } if len(inbound.ClientStats) > 0 { for _, client := range inbound.ClientStats { if client.Enable { if (client.ExpiryTime > 0 && (client.ExpiryTime-now < exDiff)) || (client.Total > 0 && (client.Total-(client.Up+client.Down) < trDiff)) { exhaustedClients = append(exhaustedClients, client) } } else { disabledClients = append(disabledClients, client) } } } } else { disabledInbounds = append(disabledInbounds, *inbound) } } // Inbounds output := "" 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")) for _, inbound := range exhaustedInbounds { output += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark) output += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port)) output += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic((inbound.Up+inbound.Down)), "Upload=="+common.FormatTraffic(inbound.Up), "Download=="+common.FormatTraffic(inbound.Down)) if inbound.ExpiryTime == 0 { output += t.I18nBot("tgbot.messages.expire", "Time=="+t.I18nBot("tgbot.unlimited")) } 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" } } // Clients 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")) 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 } total := "" if traffic.Total == 0 { total = t.I18nBot("tgbot.unlimited") } else { total = common.FormatTraffic((traffic.Total)) } 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 += "\r\n \r\n" } } return output } func (t *Tgbot) sendBackup(chatId int64) { output := t.I18nBot("tgbot.messages.backupTime", "Time=="+time.Now().Format("2006-01-02 15:04:05")) t.SendMsgToTgbot(chatId, output) 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) } file, err = os.Open(xray.GetConfigPath()) if err != nil { logger.Warning("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) } } func (t *Tgbot) sendCallbackAnswerTgBot(id string, message string) { params := telego.AnswerCallbackQueryParams{ CallbackQueryID: id, Text: message, } if err := bot.AnswerCallbackQuery(¶ms); err != nil { logger.Warning(err) } } func (t *Tgbot) editMessageCallbackTgBot(chatId int64, messageID int, inlineKeyboard *telego.InlineKeyboardMarkup) { params := telego.EditMessageReplyMarkupParams{ ChatID: tu.ID(chatId), MessageID: messageID, ReplyMarkup: inlineKeyboard, } if _, err := bot.EditMessageReplyMarkup(¶ms); err != nil { logger.Warning(err) } } func (t *Tgbot) editMessageTgBot(chatId int64, messageID int, text string, inlineKeyboard ...*telego.InlineKeyboardMarkup) { params := telego.EditMessageTextParams{ ChatID: tu.ID(chatId), MessageID: messageID, Text: text, ParseMode: "HTML", } if len(inlineKeyboard) > 0 { params.ReplyMarkup = inlineKeyboard[0] } if _, err := bot.EditMessageText(¶ms); err != nil { logger.Warning(err) } }