Advertisement
Guest User

func ListenBot телеграмм

a guest
Dec 20th, 2023
67
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Go 18.34 KB | Source Code | 0 0
  1. func ListenBot(bot *tgbotapi.BotAPI, syncRequestChan chan tgRequest, answersChan chan tgRequest, waitQueueList chan tgRequest) error {
  2.     var err error
  3.     var noActivityTG bool  // Нет активности в телеграмме
  4.     var exitThisFunc bool  // Завершение функции
  5.     var errorsNetFlag bool // В текущей итерации были ошибки сети
  6.     //Сообщение
  7.     var Request tgRequest               // Переменная для хранения текущего запроса
  8.     var Answer tgRequest                // Переменная в которую помещается запрос с ответами. Нужна что бы не дублировать код синхронной и асинхронной обработки
  9.     var msg tgbotapi.MessageConfig      // Формирование ответного сообщения
  10.     var updates tgbotapi.UpdatesChannel // Канал для приёма данных от telegram
  11.     var commandRequest string           // команда в нижнем регистре
  12.     var StopProgram bool                // Означает что программа должна остановится
  13.     var StopMSG tgbotapi.MessageConfig  // Сообщение в котором была подана команда остановки
  14.  
  15.     var AnswerS []tgRequest
  16.     var RequestS []tgRequest // Для того что бы сообщения не терялись, решил поместить их в срезы
  17.  
  18.     err = nil
  19.     u := tgbotapi.NewUpdate(0)
  20.     u.Timeout = 60
  21.     updates = bot.GetUpdatesChan(u)
  22.  
  23.     StartOfflineNotifications(updates, bot) // Сбрасываем сообщения пришедшие до старта бота
  24.  
  25.     log.Printf("Бот слушает.")
  26.     for !exitThisFunc { // Основной цикл
  27.         log.Printf("Итерация основного цикла")
  28.         Answer = tgRequest{}  // Очистка переменной для ответа
  29.         Request = tgRequest{} // Очистка переменной для запроса
  30.         AnswerS = []tgRequest{}
  31.         RequestS = []tgRequest{} // Срезы для запросов и ответов
  32.  
  33.         select {
  34.         case update := <-updates: // Обработка сообщений от телеграм-бота
  35.             if update.CallbackQuery != nil { // Нажата кнопка
  36.                 lastActivityTelegramUpdate() // Обновление времени последней активности telegram
  37.                 log.Printf("Нажата кнопка")
  38.                 Request = tgRequest{} // Очистка переменной для запроса
  39.                 Request.KeyData = update.CallbackQuery.Data
  40.                 Request.TextRequest = update.CallbackQuery.Data
  41.                 Request.ChatID = update.CallbackQuery.Message.Chat.ID
  42.                 Request.ClientUserName = update.CallbackQuery.From.UserName
  43.                 Request.MessageReplyID = update.CallbackQuery.Message.MessageID
  44.                 tb := time.Now()
  45.                 Request.TimeUNIXtoWaitNotification = tb.Unix() + WaitingTimeMatter
  46.                 RequestS = append(RequestS, copyRequestClear(Request)) // Добавление в срез запросов копии текущего запроса
  47.             }
  48.  
  49.             if update.Message != nil { // Получено сообщение
  50.                 lastActivityTelegramUpdate() // Обновление времени последней активности telegram
  51.                 Request = tgRequest{}        // Очистка переменной для запроса
  52.                 Request.ChatID = update.Message.Chat.ID
  53.                 Request.MessageText = update.Message.Text
  54.                 Request.TextRequest = update.Message.Text
  55.                 Request.ClientUserName = update.Message.From.UserName
  56.                 Request.MessageReplyID = update.Message.MessageID
  57.                 tm := time.Now()
  58.                 Request.TimeUNIXtoWaitNotification = tm.Unix() + WaitingTimeMatter
  59.                 //log.Printf("Запрос загружен")
  60.                 RequestS = append(RequestS, copyRequestClear(Request)) // Добавление в срез запросов копии текущего запроса
  61.             }
  62.  
  63.             // Распределение запросов
  64.             for _, Request := range RequestS { // распределение запросов сделано в цикле что бы предовратить их потерю
  65.                 if len(Request.ClientUserName) != 0 { // Имя пользователя не пустое
  66.                     if cacheUserChatID[Request.ClientUserName] == 0 { // ChatID пользователя не кэширован
  67.                         cacheUserChatID[Request.ClientUserName] = Request.ChatID // Кэшируем
  68.                         cacheUpdateFileChatID = true                             // Надо будет обновить файл в котором кэшированы ChatID
  69.                     } else { // Уже кэшировано
  70.                         if cacheUserChatID[Request.ClientUserName] != Request.ChatID { // Кэшированное знание не актуально
  71.                             cacheUserChatID[Request.ClientUserName] = Request.ChatID // Кэшируем
  72.                             cacheUpdateFileChatID = true                             // Надо будет обновить файл в котором кэшированы ChatID
  73.                         }
  74.                     }
  75.                 }
  76.  
  77.                 // log.Printf("Распределение запросов")
  78.                 partsTextRequest := strings.Fields(Request.TextRequest)
  79.                 if len(partsTextRequest) > 0 { //запрос не пустой
  80.                     commandRequest = strings.ToLower(partsTextRequest[0]) // Преобразование команды в нижний регистр
  81.                 }
  82.                 DialogsMu.Lock() // Безопасное обращение со списком
  83.                 dChan, dChanExists := DialogsProcessesChannels[Request.ChatID]
  84.                 DialogsMu.Unlock() // Разблокировка
  85.                 if dChanExists {   // Пользователь в состоянии диалога
  86.                     log.Printf("Обработка диалога пользователя: %s", Request.ClientUserName)
  87.                     dChan <- Request // Отправка сообщения в диалог
  88.                 } else { // Обычные запросы
  89.                     if commandRequest == "/stop" && tableAdminUsers[Request.ClientUserName] { // Команда завершения работы бота от администратора
  90.                         msg = tgbotapi.NewMessage(Request.ChatID, "Завершение работы программы.")
  91.                         msg.ReplyToMessageID = Request.MessageReplyID
  92.                         StopMSG = msg
  93.                         StopProgram = true // Выход из программы, но только после отправки ответов
  94.                     }
  95.                     if commandRequest == "/testignore" { // Пришла тестовая команда которую надо игнорировать
  96.                         errorGoogleTableOnline = false // Соединение функционирует
  97.                         log.Printf("Пришёл тестовый запрос /testignore")
  98.                     } else { // Обычная обработка запросов
  99.                         if SyncСommandPrefixes[commandRequest] { // Команда входит в список синхроннных запросов
  100.                             tsma := "Запрос получен."
  101.                             log.Printf("ChatID %v Отправка запроса № %v в очередь. Запрос %s", Request.ChatID, Request.MessageReplyID, Request.TextRequest)
  102.                             if FlagSyncProcess {
  103.                                 tsma = tsma + " Он отправлен в очередь. Подождите."
  104.                             }
  105.                             sma := tgbotapi.NewMessage(Request.ChatID, tsma)
  106.                             sma.ReplyToMessageID = Request.MessageReplyID
  107.                             sma.DisableNotification = true               // Тихое сообщение
  108.                             syncRequestChan <- copyRequestClear(Request) // Запрос отправлен в очередь синхронных.
  109.                             _, _ = bot.Send(sma)                         // Отправка уведомления о том что запрос принят.
  110.  
  111.                             log.Printf("Регистрация запроса № %v в листе ожидания. Запрос %s", Request.MessageReplyID, Request.TextRequest)
  112.                             waitQueueList <- Request // Запрос зарегистрирован.
  113.                             _ = writeLogFile(InfoLog, "Синхронный запрос: "+Request.TextRequest+" от пользователя "+Request.ClientUserName)
  114.                         } else { // Команда не входит в список синхронных запросов
  115.                             // log.Printf("Запрос %v  распознан как асинхронный", Request.MessageReplyID)
  116.                             log.Printf("ChatID %v Асинхронный запрос № %v. Запрос %s", Request.ChatID, Request.MessageReplyID, Request.TextRequest)
  117.                             log.Printf("Регистрация запроса %v в листе ожидания", Request.MessageReplyID)
  118.                             waitQueueList <- copyRequestClear(Request) // Запрос зарегистрирован.
  119.                             _ = writeLogFile(InfoLog, "Асинхронный запрос: "+Request.TextRequest+" от пользователя "+Request.ClientUserName)
  120.                             log.Printf("Создание потока для выполнения асинхронного запроса %v ", Request.MessageReplyID)
  121.                             go processResponseAsync(answersChan, copyRequestClear(Request)) // Создан процесс для выполнения конкретного асинхронного запроса, который вернёт результат в канал answersChan
  122.                         }
  123.                     }
  124.                 }
  125.             }
  126.             // Конец распределения запросов
  127.  
  128.         case Answer = <-answersChan: // Получение ответов
  129.             if Answer.CloseRequest { // Закрытие запроса
  130.                 waitQueueList <- Answer // Запрос отправляется в процесс отслеживания задержек, но уже с установленным флагом CloseRequest, и она находя этот запрос в листе ожидания, удаляет его.
  131.  
  132.             }
  133.             AnswerS = append(AnswerS, Answer) // Добавление копии ответа в срез с ответами ответов
  134.  
  135.         case <-time.After(time.Second * time.Duration(errorTimeNoActivityTG)): // Сработает, если прошло более errorTimeNoActivityTG секунд без активности
  136.             if reconnectTelegramTimeoutForce { // Если отсутствие активности в tg отслеживается для переподключений
  137.                 noActivityTG = true
  138.                 fmt.Printf("Долгое отсутствие активности в telegram, более %v секунд.\n", errorTimeNoActivityTG)
  139.                 _ = writeLogFile(InfoLog, "Долгое отсутствие активности в telegram.")
  140.             } else {
  141.                 fmt.Printf("Основная функция не отслеживает время без активности в telegram.\n")
  142.             }
  143.             if cacheUpdateFileChatID { // Надо обновить кэш ChatID пользователей
  144.                 errUpdateCacheChatID := saveMapStringInt64ToFile(fileNameCacheChatID, cacheUserChatID) // Сохранение кэша в специальный файл в каталоге журнала
  145.                 if errUpdateCacheChatID == nil {
  146.                     cacheUpdateFileChatID = false
  147.                     _ = writeLogFile(InfoLog, "Успешное обновление сохраняемого кэша ChatID.")
  148.                 } else {
  149.                     _ = writeLogFile(ErrorLog, "Не удалось обновление сохраняемого кэша ChatID. "+errUpdateCacheChatID.Error())
  150.                 }
  151.             }
  152.  
  153.         }
  154.  
  155.         // Обработка ответов
  156.         for _, Answer := range AnswerS {
  157.             if len(Answer.Responses) > 0 || Answer.tgKeyboard.InlineKeyboard != nil { //Есть ответы или меню
  158.  
  159.                 if len(Answer.Responses) == 0 {
  160.                     Answer.Responses = append(Answer.Responses, "\u00AD") // Невидимый символ "Soft Hyphen" в качестве текста сообщения. Позвляет обойти ошибку которая появляется при пустом сообщении.
  161.                 }
  162.                 // fmt.Printf("Есть ответы.\n")
  163.                 tmpTextAnsver := ""
  164.                 for _, StringResponceTMP := range Answer.Responses { //Текстовые строки ответа
  165.                     tmpTextAnsver = tmpTextAnsver + "\n" + StringResponceTMP
  166.                 }
  167.                 msg = tgbotapi.NewMessage(Answer.ChatID, tmpTextAnsver) // Очистка. Новый ответ.
  168.                 msg.ReplyToMessageID = Answer.MessageReplyID            // Сообщение на которое отвечаем.
  169.  
  170.                 if Answer.tgKeyboard.InlineKeyboard != nil { // Скорее всего причина ошибки в формировании кнопок
  171.                     msg.ReplyMarkup = Answer.tgKeyboard // Кнопки
  172.                 }
  173.                 msg.DisableNotification = Answer.DisableNotification // Отключить уведовление
  174.                 _, err = bot.Send(msg)                               //Отправка ответа
  175.                 lastActivityTelegramUpdate()                         // Обновление времени последней активности telegram
  176.                 if err != nil {
  177.                     errorTelegram = true        // Устанавливаем флаг ошибки
  178.                     _ = saveLostMessage(Answer) // Что бы ответ не потерялся совсем, сохраним его в каталог журналов
  179.                 }
  180.             }
  181.         }
  182.         if StopProgram { // Выход из программы
  183.             _, _ = bot.Send(StopMSG) // Отправка оповещения о принятии команды остановки
  184.             _ = writeLogFile(InfoLog, "Завершение работы программы.")
  185.             err = fmt.Errorf("%s", "stop")
  186.             exitThisFunc = true // Выход из цикла
  187.         }
  188.  
  189.         tn0 = time.Now()
  190.         tmplogtimev := tn0.Unix() - lastActivityTG
  191.         if reconnectTelegramTimeoutForce { // Если отсутствие активности в tg отслеживается для переподключений
  192.             fmt.Printf("Проверка (вне блока select) активности в telegram. Прошло %v секунд с последней активности.  \n", tmplogtimev)
  193.             if (lastActivityTG + errorTimeNoActivityTG) < tn0.Unix() { // Слишком долго не было активности в телеграме
  194.                 noActivityTG = true
  195.                 fmt.Printf("(вне блока select) Долгое отсутствие активности в telegram.\n")
  196.                 // log.Printf("Долгое отсутствие активности в telegram.")
  197.                 _ = writeLogFile(InfoLog, "Долгое отсутствие активности в telegram.")
  198.             }
  199.         }
  200.  
  201.         // Обработка ошибок потери соединения:
  202.         errorsNetFlag = false                                        // Очистка флага ошибок
  203.         if errorGoogleTableOnline || errorTelegram || noActivityTG { // Фиксация ошибок один раз что бы не писать в журнал много записей
  204.             if errorGoogleTableOnline {
  205.                 _ = writeLogFile(ErrorLog, "Ошибка google-таблиц.")
  206.                 errorsNetFlag = true // Случились ошибки
  207.             }
  208.             if noActivityTG { // Обработка ситуации, когда нет активности в Telegram
  209.                 log.Printf("Отсутствие активности в Telegram")
  210.                 _ = writeLogFile(InfoLog, "Отсутствие активности в Telegram")
  211.             }
  212.             if errorTelegram { // Обработка ошибки при отправке сообщения в Telegram
  213.                 log.Printf("Ошибка при отправе сообщения Telegram")
  214.                 _ = writeLogFile(WarningLog, "Ошибка при отправе сообщения Telegram")
  215.                 errorsNetFlag = true // Случились ошибки
  216.             }
  217.  
  218.         }
  219.  
  220.         for errorGoogleTableOnline || errorTelegram || noActivityTG { // Логика изменена на выжидание при разрыве соединения.
  221.             // Они сами восстанавливаются видимо потому что библиотеки используют web-API
  222.             if errorGoogleTableOnline { // Обработка ошибки при обращении к таблице Google
  223.                 log.Printf("Ошибка при обращении к таблице Google")
  224.                 _, err = getSpreadsheetData(CacheReservation.GoogleService, CacheReservation.TableID, "common-f", "B3") // Тест чтения для теста соединения
  225.                 if err != nil {
  226.                     errorGoogleTableOnline = true
  227.                     _ = writeLogFile(WarningLog, "Ошибка при проверке доступа к google-таблицам.")
  228.                 } else { // Соединение с гугл-таблицами не потеряно
  229.                     errorGoogleTableOnline = false
  230.                     _ = writeLogFile(InfoLog, "Подключение к google-таблицам восстановилось.")
  231.                     log.Printf("Подключение к google-таблицам восстановилось.")
  232.                 }
  233.             }
  234.             if errorTelegram || noActivityTG {
  235.                 _, err = bot.GetMe() // Проверочная операция
  236.                 if err != nil {
  237.                     log.Printf("Ошибка проверки соединения с telegram функцией GetMe.")
  238.                     errorTelegram = true
  239.                 } else {
  240.                     errorTelegram = false
  241.                     log.Printf("Сетевое соединение c telegram активно. По данным функции GetMe.")
  242.                     noActivityTG = false
  243.                     lastActivityTelegramUpdate() // Не уверен что нужно, но позволит избегать лишних итераций.
  244.                 }
  245.             }
  246.  
  247.             if errorGoogleTableOnline || errorTelegram { // Нужна пауза
  248.                 log.Printf("Пауза перед попыткой восстановить сетевое соединение %v секунд.", timeWaitReconnectSeconds)
  249.                 _ = writeLogFile(InfoLog, fmt.Sprintf("Пауза перед попыткой восстановить сетевое соединение %v секунд.", timeWaitReconnectSeconds))
  250.                 time.Sleep(time.Duration(timeWaitReconnectSeconds) * time.Second) // Пауза
  251.             } else { // Соединения успешно восстановились
  252.                 log.Printf("Соединения восстановлены.")
  253.                 time.Sleep(time.Duration(timeWaitReconnectSeconds) * time.Second)
  254.             }
  255.         }
  256.  
  257.         if errorsNetFlag { // Ошибки были, но из цикла их коррекции таки произошёл выход. То есть соединения были восстановлены
  258.             errorsNetFlag = false
  259.             _ = writeLogFile(InfoLog, "Соединения восстановлены.")
  260.         }
  261.  
  262.     }
  263.     if StopProgram {
  264.         err = fmt.Errorf("%s", "stop")
  265.     }
  266.     log.Printf("Бот не слушает.")
  267.     return err
  268. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement