| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- 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<<uint(attempt)) * time.Second
- logger.Warningf("Connection error sending telegram message (attempt %d/%d), retrying in %v: %v",
- attempt+1, maxRetries, backoff, err)
- time.Sleep(backoff)
- } else {
- logger.Warning("Error sending telegram message:", err)
- break
- }
- }
- // Reduced delay to improve performance (only needed for rate limiting)
- if n < len(allMessages)-1 { // Only delay between messages, not after the last one
- time.Sleep(100 * time.Millisecond)
- }
- }
- }
- // SendMsgToTgbotAdmins sends a message to all admin Telegram chats.
- 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)
- }
- }
- }
- // 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")
- }
- }
|