package tgbot import ( "context" "strings" "time" "github.com/mhsanaei/3x-ui/v3/internal/logger" "github.com/mymmrac/telego" tu "github.com/mymmrac/telego/telegoutil" ) // sendResponse sends the response message based on the onlyMessage flag. func (t *Tgbot) sendResponse(chatId int64, msg string, onlyMessage, isAdmin bool) { if onlyMessage { t.SendMsgToTgbot(chatId, msg) } else { t.SendAnswer(chatId, msg, isAdmin) } } // SendAnswer sends a response message with an inline keyboard to the specified chat. func (t *Tgbot) SendAnswer(chatId int64, msg string, isAdmin bool) { numericKeyboard := tu.InlineKeyboard( tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.SortedTrafficUsageReport")).WithCallbackData(t.encodeQuery("get_sorted_traffic_usage_report")), ), tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.serverUsage")).WithCallbackData(t.encodeQuery("get_usage")), tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.ResetAllTraffics")).WithCallbackData(t.encodeQuery("reset_all_traffics")), ), tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.dbBackup")).WithCallbackData(t.encodeQuery("get_backup")), 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")), 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")), tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.onlines")).WithCallbackData(t.encodeQuery("onlines")), ), tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.allClients")).WithCallbackData(t.encodeQuery("get_inbounds")), tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.addClient")).WithCallbackData(t.encodeQuery("add_client")), ), tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("pages.settings.subSettings")).WithCallbackData(t.encodeQuery("admin_client_sub_links")), tu.InlineKeyboardButton(t.I18nBot("subscription.individualLinks")).WithCallbackData(t.encodeQuery("admin_client_individual_links")), tu.InlineKeyboardButton(t.I18nBot("qrCode")).WithCallbackData(t.encodeQuery("admin_client_qr_links")), ), // TODOOOOOOOOOOOOOO: Add restart button here. ) 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")), ), tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("pages.settings.subSettings")).WithCallbackData(t.encodeQuery("client_sub_links")), tu.InlineKeyboardButton(t.I18nBot("subscription.individualLinks")).WithCallbackData(t.encodeQuery("client_individual_links")), ), tu.InlineKeyboardRow( tu.InlineKeyboardButton(t.I18nBot("qrCode")).WithCallbackData(t.encodeQuery("client_qr_links")), ), ) var ReplyMarkup telego.ReplyMarkup if isAdmin { ReplyMarkup = numericKeyboard } else { ReplyMarkup = numericKeyboardClient } t.SendMsgToTgbot(chatId, msg, ReplyMarkup) } // SendMsgToTgbot sends a message to the Telegram bot with optional reply markup. 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 } } if strings.TrimSpace(allMessages[len(allMessages)-1]) == "" { allMessages = allMessages[:len(allMessages)-1] } } else { allMessages = append(allMessages, msg) } for n, message := range allMessages { params := telego.SendMessageParams{ ChatID: tu.ID(chatId), Text: message, ParseMode: "HTML", } // only add replyMarkup to last message if len(replyMarkup) > 0 && n == (len(allMessages)-1) { params.ReplyMarkup = replyMarkup[0] } // Retry logic with exponential backoff for connection errors maxRetries := 3 for attempt := range maxRetries { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) _, err := bot.SendMessage(ctx, ¶ms) cancel() if err == nil { break // Success } // Check if error is a connection error errStr := err.Error() isConnectionError := strings.Contains(errStr, "connection") || strings.Contains(errStr, "timeout") || strings.Contains(errStr, "closed") if isConnectionError && attempt < maxRetries-1 { // Exponential backoff: 1s, 2s, 4s backoff := time.Duration(1< 0 { for _, adminId := range adminIds { t.SendMsgToTgbot(adminId, msg, replyMarkup[0]) } } else { for _, adminId := range adminIds { t.SendMsgToTgbot(adminId, msg) } } } // sendCallbackAnswerTgBot answers a callback query with a message. func (t *Tgbot) sendCallbackAnswerTgBot(id string, message string) { params := telego.AnswerCallbackQueryParams{ CallbackQueryID: id, Text: message, } if err := bot.AnswerCallbackQuery(context.Background(), ¶ms); err != nil { logger.Warning(err) } } // editMessageCallbackTgBot edits the reply markup of a message. 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(context.Background(), ¶ms); err != nil { logger.Warning(err) } } // editMessageTgBot edits the text and reply markup of a message. 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(context.Background(), ¶ms); err != nil { logger.Warning(err) } } // SendMsgToTgbotDeleteAfter sends a message and deletes it after a specified delay. func (t *Tgbot) SendMsgToTgbotDeleteAfter(chatId int64, msg string, delayInSeconds int, replyMarkup ...telego.ReplyMarkup) { // Determine if replyMarkup was passed; otherwise, set it to nil var replyMarkupParam telego.ReplyMarkup if len(replyMarkup) > 0 { replyMarkupParam = replyMarkup[0] // Use the first element } // Send the message sentMsg, err := bot.SendMessage(context.Background(), &telego.SendMessageParams{ ChatID: tu.ID(chatId), Text: msg, ReplyMarkup: replyMarkupParam, // Use the correct replyMarkup value }) if err != nil { logger.Warning("Failed to send message:", err) return } // Delete the sent message after the specified number of seconds go func() { time.Sleep(time.Duration(delayInSeconds) * time.Second) // Wait for the specified delay t.deleteMessageTgBot(chatId, sentMsg.MessageID) // Delete the message delete(userStates, chatId) }() } // deleteMessageTgBot deletes a message from the chat. func (t *Tgbot) deleteMessageTgBot(chatId int64, messageID int) { params := telego.DeleteMessageParams{ ChatID: tu.ID(chatId), MessageID: messageID, } if err := bot.DeleteMessage(context.Background(), ¶ms); err != nil { logger.Warning("Failed to delete message:", err) } else { logger.Info("Message deleted successfully") } }