tgbot_send.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. package tgbot
  2. import (
  3. "context"
  4. "strings"
  5. "time"
  6. "github.com/mhsanaei/3x-ui/v3/internal/logger"
  7. "github.com/mymmrac/telego"
  8. tu "github.com/mymmrac/telego/telegoutil"
  9. )
  10. // sendResponse sends the response message based on the onlyMessage flag.
  11. func (t *Tgbot) sendResponse(chatId int64, msg string, onlyMessage, isAdmin bool) {
  12. if onlyMessage {
  13. t.SendMsgToTgbot(chatId, msg)
  14. } else {
  15. t.SendAnswer(chatId, msg, isAdmin)
  16. }
  17. }
  18. // SendAnswer sends a response message with an inline keyboard to the specified chat.
  19. func (t *Tgbot) SendAnswer(chatId int64, msg string, isAdmin bool) {
  20. numericKeyboard := tu.InlineKeyboard(
  21. tu.InlineKeyboardRow(
  22. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.SortedTrafficUsageReport")).WithCallbackData(t.encodeQuery("get_sorted_traffic_usage_report")),
  23. ),
  24. tu.InlineKeyboardRow(
  25. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.serverUsage")).WithCallbackData(t.encodeQuery("get_usage")),
  26. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.ResetAllTraffics")).WithCallbackData(t.encodeQuery("reset_all_traffics")),
  27. ),
  28. tu.InlineKeyboardRow(
  29. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.dbBackup")).WithCallbackData(t.encodeQuery("get_backup")),
  30. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.getBanLogs")).WithCallbackData(t.encodeQuery("get_banlogs")),
  31. ),
  32. tu.InlineKeyboardRow(
  33. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.getInbounds")).WithCallbackData(t.encodeQuery("inbounds")),
  34. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.depleteSoon")).WithCallbackData(t.encodeQuery("deplete_soon")),
  35. ),
  36. tu.InlineKeyboardRow(
  37. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.commands")).WithCallbackData(t.encodeQuery("commands")),
  38. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.onlines")).WithCallbackData(t.encodeQuery("onlines")),
  39. ),
  40. tu.InlineKeyboardRow(
  41. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.allClients")).WithCallbackData(t.encodeQuery("get_inbounds")),
  42. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.addClient")).WithCallbackData(t.encodeQuery("add_client")),
  43. ),
  44. tu.InlineKeyboardRow(
  45. tu.InlineKeyboardButton(t.I18nBot("pages.settings.subSettings")).WithCallbackData(t.encodeQuery("admin_client_sub_links")),
  46. tu.InlineKeyboardButton(t.I18nBot("subscription.individualLinks")).WithCallbackData(t.encodeQuery("admin_client_individual_links")),
  47. tu.InlineKeyboardButton(t.I18nBot("qrCode")).WithCallbackData(t.encodeQuery("admin_client_qr_links")),
  48. ),
  49. // TODOOOOOOOOOOOOOO: Add restart button here.
  50. )
  51. numericKeyboardClient := tu.InlineKeyboard(
  52. tu.InlineKeyboardRow(
  53. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.clientUsage")).WithCallbackData(t.encodeQuery("client_traffic")),
  54. tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.commands")).WithCallbackData(t.encodeQuery("client_commands")),
  55. ),
  56. tu.InlineKeyboardRow(
  57. tu.InlineKeyboardButton(t.I18nBot("pages.settings.subSettings")).WithCallbackData(t.encodeQuery("client_sub_links")),
  58. tu.InlineKeyboardButton(t.I18nBot("subscription.individualLinks")).WithCallbackData(t.encodeQuery("client_individual_links")),
  59. ),
  60. tu.InlineKeyboardRow(
  61. tu.InlineKeyboardButton(t.I18nBot("qrCode")).WithCallbackData(t.encodeQuery("client_qr_links")),
  62. ),
  63. )
  64. var ReplyMarkup telego.ReplyMarkup
  65. if isAdmin {
  66. ReplyMarkup = numericKeyboard
  67. } else {
  68. ReplyMarkup = numericKeyboardClient
  69. }
  70. t.SendMsgToTgbot(chatId, msg, ReplyMarkup)
  71. }
  72. // SendMsgToTgbot sends a message to the Telegram bot with optional reply markup.
  73. func (t *Tgbot) SendMsgToTgbot(chatId int64, msg string, replyMarkup ...telego.ReplyMarkup) {
  74. if !isRunning {
  75. return
  76. }
  77. if msg == "" {
  78. logger.Info("[tgbot] message is empty!")
  79. return
  80. }
  81. var allMessages []string
  82. limit := 2000
  83. // paging message if it is big
  84. if len(msg) > limit {
  85. messages := strings.Split(msg, "\r\n\r\n")
  86. lastIndex := -1
  87. for _, message := range messages {
  88. if (len(allMessages) == 0) || (len(allMessages[lastIndex])+len(message) > limit) {
  89. allMessages = append(allMessages, message)
  90. lastIndex++
  91. } else {
  92. allMessages[lastIndex] += "\r\n\r\n" + message
  93. }
  94. }
  95. if strings.TrimSpace(allMessages[len(allMessages)-1]) == "" {
  96. allMessages = allMessages[:len(allMessages)-1]
  97. }
  98. } else {
  99. allMessages = append(allMessages, msg)
  100. }
  101. for n, message := range allMessages {
  102. params := telego.SendMessageParams{
  103. ChatID: tu.ID(chatId),
  104. Text: message,
  105. ParseMode: "HTML",
  106. }
  107. // only add replyMarkup to last message
  108. if len(replyMarkup) > 0 && n == (len(allMessages)-1) {
  109. params.ReplyMarkup = replyMarkup[0]
  110. }
  111. // Retry logic with exponential backoff for connection errors
  112. maxRetries := 3
  113. for attempt := range maxRetries {
  114. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
  115. _, err := bot.SendMessage(ctx, &params)
  116. cancel()
  117. if err == nil {
  118. break // Success
  119. }
  120. // Check if error is a connection error
  121. errStr := err.Error()
  122. isConnectionError := strings.Contains(errStr, "connection") ||
  123. strings.Contains(errStr, "timeout") ||
  124. strings.Contains(errStr, "closed")
  125. if isConnectionError && attempt < maxRetries-1 {
  126. // Exponential backoff: 1s, 2s, 4s
  127. backoff := time.Duration(1<<uint(attempt)) * time.Second
  128. logger.Warningf("Connection error sending telegram message (attempt %d/%d), retrying in %v: %v",
  129. attempt+1, maxRetries, backoff, err)
  130. time.Sleep(backoff)
  131. } else {
  132. logger.Warning("Error sending telegram message:", err)
  133. break
  134. }
  135. }
  136. // Reduced delay to improve performance (only needed for rate limiting)
  137. if n < len(allMessages)-1 { // Only delay between messages, not after the last one
  138. time.Sleep(100 * time.Millisecond)
  139. }
  140. }
  141. }
  142. // SendMsgToTgbotAdmins sends a message to all admin Telegram chats.
  143. func (t *Tgbot) SendMsgToTgbotAdmins(msg string, replyMarkup ...telego.ReplyMarkup) {
  144. if len(replyMarkup) > 0 {
  145. for _, adminId := range adminIds {
  146. t.SendMsgToTgbot(adminId, msg, replyMarkup[0])
  147. }
  148. } else {
  149. for _, adminId := range adminIds {
  150. t.SendMsgToTgbot(adminId, msg)
  151. }
  152. }
  153. }
  154. // sendCallbackAnswerTgBot answers a callback query with a message.
  155. func (t *Tgbot) sendCallbackAnswerTgBot(id string, message string) {
  156. params := telego.AnswerCallbackQueryParams{
  157. CallbackQueryID: id,
  158. Text: message,
  159. }
  160. if err := bot.AnswerCallbackQuery(context.Background(), &params); err != nil {
  161. logger.Warning(err)
  162. }
  163. }
  164. // editMessageCallbackTgBot edits the reply markup of a message.
  165. func (t *Tgbot) editMessageCallbackTgBot(chatId int64, messageID int, inlineKeyboard *telego.InlineKeyboardMarkup) {
  166. params := telego.EditMessageReplyMarkupParams{
  167. ChatID: tu.ID(chatId),
  168. MessageID: messageID,
  169. ReplyMarkup: inlineKeyboard,
  170. }
  171. if _, err := bot.EditMessageReplyMarkup(context.Background(), &params); err != nil {
  172. logger.Warning(err)
  173. }
  174. }
  175. // editMessageTgBot edits the text and reply markup of a message.
  176. func (t *Tgbot) editMessageTgBot(chatId int64, messageID int, text string, inlineKeyboard ...*telego.InlineKeyboardMarkup) {
  177. params := telego.EditMessageTextParams{
  178. ChatID: tu.ID(chatId),
  179. MessageID: messageID,
  180. Text: text,
  181. ParseMode: "HTML",
  182. }
  183. if len(inlineKeyboard) > 0 {
  184. params.ReplyMarkup = inlineKeyboard[0]
  185. }
  186. if _, err := bot.EditMessageText(context.Background(), &params); err != nil {
  187. logger.Warning(err)
  188. }
  189. }
  190. // SendMsgToTgbotDeleteAfter sends a message and deletes it after a specified delay.
  191. func (t *Tgbot) SendMsgToTgbotDeleteAfter(chatId int64, msg string, delayInSeconds int, replyMarkup ...telego.ReplyMarkup) {
  192. // Determine if replyMarkup was passed; otherwise, set it to nil
  193. var replyMarkupParam telego.ReplyMarkup
  194. if len(replyMarkup) > 0 {
  195. replyMarkupParam = replyMarkup[0] // Use the first element
  196. }
  197. // Send the message
  198. sentMsg, err := bot.SendMessage(context.Background(), &telego.SendMessageParams{
  199. ChatID: tu.ID(chatId),
  200. Text: msg,
  201. ReplyMarkup: replyMarkupParam, // Use the correct replyMarkup value
  202. })
  203. if err != nil {
  204. logger.Warning("Failed to send message:", err)
  205. return
  206. }
  207. // Delete the sent message after the specified number of seconds
  208. go func() {
  209. time.Sleep(time.Duration(delayInSeconds) * time.Second) // Wait for the specified delay
  210. t.deleteMessageTgBot(chatId, sentMsg.MessageID) // Delete the message
  211. delete(userStates, chatId)
  212. }()
  213. }
  214. // deleteMessageTgBot deletes a message from the chat.
  215. func (t *Tgbot) deleteMessageTgBot(chatId int64, messageID int) {
  216. params := telego.DeleteMessageParams{
  217. ChatID: tu.ID(chatId),
  218. MessageID: messageID,
  219. }
  220. if err := bot.DeleteMessage(context.Background(), &params); err != nil {
  221. logger.Warning("Failed to delete message:", err)
  222. } else {
  223. logger.Info("Message deleted successfully")
  224. }
  225. }