Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- func ListenBot(bot *tgbotapi.BotAPI, syncRequestChan chan tgRequest, answersChan chan tgRequest, waitQueueList chan tgRequest) error {
- var err error
- var noActivityTG bool // Нет активности в телеграмме
- var exitThisFunc bool // Завершение функции
- var errorsNetFlag bool // В текущей итерации были ошибки сети
- //Сообщение
- var Request tgRequest // Переменная для хранения текущего запроса
- var Answer tgRequest // Переменная в которую помещается запрос с ответами. Нужна что бы не дублировать код синхронной и асинхронной обработки
- var msg tgbotapi.MessageConfig // Формирование ответного сообщения
- var updates tgbotapi.UpdatesChannel // Канал для приёма данных от telegram
- var commandRequest string // команда в нижнем регистре
- var StopProgram bool // Означает что программа должна остановится
- var StopMSG tgbotapi.MessageConfig // Сообщение в котором была подана команда остановки
- var AnswerS []tgRequest
- var RequestS []tgRequest // Для того что бы сообщения не терялись, решил поместить их в срезы
- err = nil
- u := tgbotapi.NewUpdate(0)
- u.Timeout = 60
- updates = bot.GetUpdatesChan(u)
- StartOfflineNotifications(updates, bot) // Сбрасываем сообщения пришедшие до старта бота
- log.Printf("Бот слушает.")
- for !exitThisFunc { // Основной цикл
- log.Printf("Итерация основного цикла")
- Answer = tgRequest{} // Очистка переменной для ответа
- Request = tgRequest{} // Очистка переменной для запроса
- AnswerS = []tgRequest{}
- RequestS = []tgRequest{} // Срезы для запросов и ответов
- select {
- case update := <-updates: // Обработка сообщений от телеграм-бота
- if update.CallbackQuery != nil { // Нажата кнопка
- lastActivityTelegramUpdate() // Обновление времени последней активности telegram
- log.Printf("Нажата кнопка")
- Request = tgRequest{} // Очистка переменной для запроса
- Request.KeyData = update.CallbackQuery.Data
- Request.TextRequest = update.CallbackQuery.Data
- Request.ChatID = update.CallbackQuery.Message.Chat.ID
- Request.ClientUserName = update.CallbackQuery.From.UserName
- Request.MessageReplyID = update.CallbackQuery.Message.MessageID
- tb := time.Now()
- Request.TimeUNIXtoWaitNotification = tb.Unix() + WaitingTimeMatter
- RequestS = append(RequestS, copyRequestClear(Request)) // Добавление в срез запросов копии текущего запроса
- }
- if update.Message != nil { // Получено сообщение
- lastActivityTelegramUpdate() // Обновление времени последней активности telegram
- Request = tgRequest{} // Очистка переменной для запроса
- Request.ChatID = update.Message.Chat.ID
- Request.MessageText = update.Message.Text
- Request.TextRequest = update.Message.Text
- Request.ClientUserName = update.Message.From.UserName
- Request.MessageReplyID = update.Message.MessageID
- tm := time.Now()
- Request.TimeUNIXtoWaitNotification = tm.Unix() + WaitingTimeMatter
- //log.Printf("Запрос загружен")
- RequestS = append(RequestS, copyRequestClear(Request)) // Добавление в срез запросов копии текущего запроса
- }
- // Распределение запросов
- for _, Request := range RequestS { // распределение запросов сделано в цикле что бы предовратить их потерю
- if len(Request.ClientUserName) != 0 { // Имя пользователя не пустое
- if cacheUserChatID[Request.ClientUserName] == 0 { // ChatID пользователя не кэширован
- cacheUserChatID[Request.ClientUserName] = Request.ChatID // Кэшируем
- cacheUpdateFileChatID = true // Надо будет обновить файл в котором кэшированы ChatID
- } else { // Уже кэшировано
- if cacheUserChatID[Request.ClientUserName] != Request.ChatID { // Кэшированное знание не актуально
- cacheUserChatID[Request.ClientUserName] = Request.ChatID // Кэшируем
- cacheUpdateFileChatID = true // Надо будет обновить файл в котором кэшированы ChatID
- }
- }
- }
- // log.Printf("Распределение запросов")
- partsTextRequest := strings.Fields(Request.TextRequest)
- if len(partsTextRequest) > 0 { //запрос не пустой
- commandRequest = strings.ToLower(partsTextRequest[0]) // Преобразование команды в нижний регистр
- }
- DialogsMu.Lock() // Безопасное обращение со списком
- dChan, dChanExists := DialogsProcessesChannels[Request.ChatID]
- DialogsMu.Unlock() // Разблокировка
- if dChanExists { // Пользователь в состоянии диалога
- log.Printf("Обработка диалога пользователя: %s", Request.ClientUserName)
- dChan <- Request // Отправка сообщения в диалог
- } else { // Обычные запросы
- if commandRequest == "/stop" && tableAdminUsers[Request.ClientUserName] { // Команда завершения работы бота от администратора
- msg = tgbotapi.NewMessage(Request.ChatID, "Завершение работы программы.")
- msg.ReplyToMessageID = Request.MessageReplyID
- StopMSG = msg
- StopProgram = true // Выход из программы, но только после отправки ответов
- }
- if commandRequest == "/testignore" { // Пришла тестовая команда которую надо игнорировать
- errorGoogleTableOnline = false // Соединение функционирует
- log.Printf("Пришёл тестовый запрос /testignore")
- } else { // Обычная обработка запросов
- if SyncСommandPrefixes[commandRequest] { // Команда входит в список синхроннных запросов
- tsma := "Запрос получен."
- log.Printf("ChatID %v Отправка запроса № %v в очередь. Запрос %s", Request.ChatID, Request.MessageReplyID, Request.TextRequest)
- if FlagSyncProcess {
- tsma = tsma + " Он отправлен в очередь. Подождите."
- }
- sma := tgbotapi.NewMessage(Request.ChatID, tsma)
- sma.ReplyToMessageID = Request.MessageReplyID
- sma.DisableNotification = true // Тихое сообщение
- syncRequestChan <- copyRequestClear(Request) // Запрос отправлен в очередь синхронных.
- _, _ = bot.Send(sma) // Отправка уведомления о том что запрос принят.
- log.Printf("Регистрация запроса № %v в листе ожидания. Запрос %s", Request.MessageReplyID, Request.TextRequest)
- waitQueueList <- Request // Запрос зарегистрирован.
- _ = writeLogFile(InfoLog, "Синхронный запрос: "+Request.TextRequest+" от пользователя "+Request.ClientUserName)
- } else { // Команда не входит в список синхронных запросов
- // log.Printf("Запрос %v распознан как асинхронный", Request.MessageReplyID)
- log.Printf("ChatID %v Асинхронный запрос № %v. Запрос %s", Request.ChatID, Request.MessageReplyID, Request.TextRequest)
- log.Printf("Регистрация запроса %v в листе ожидания", Request.MessageReplyID)
- waitQueueList <- copyRequestClear(Request) // Запрос зарегистрирован.
- _ = writeLogFile(InfoLog, "Асинхронный запрос: "+Request.TextRequest+" от пользователя "+Request.ClientUserName)
- log.Printf("Создание потока для выполнения асинхронного запроса %v ", Request.MessageReplyID)
- go processResponseAsync(answersChan, copyRequestClear(Request)) // Создан процесс для выполнения конкретного асинхронного запроса, который вернёт результат в канал answersChan
- }
- }
- }
- }
- // Конец распределения запросов
- case Answer = <-answersChan: // Получение ответов
- if Answer.CloseRequest { // Закрытие запроса
- waitQueueList <- Answer // Запрос отправляется в процесс отслеживания задержек, но уже с установленным флагом CloseRequest, и она находя этот запрос в листе ожидания, удаляет его.
- }
- AnswerS = append(AnswerS, Answer) // Добавление копии ответа в срез с ответами ответов
- case <-time.After(time.Second * time.Duration(errorTimeNoActivityTG)): // Сработает, если прошло более errorTimeNoActivityTG секунд без активности
- if reconnectTelegramTimeoutForce { // Если отсутствие активности в tg отслеживается для переподключений
- noActivityTG = true
- fmt.Printf("Долгое отсутствие активности в telegram, более %v секунд.\n", errorTimeNoActivityTG)
- _ = writeLogFile(InfoLog, "Долгое отсутствие активности в telegram.")
- } else {
- fmt.Printf("Основная функция не отслеживает время без активности в telegram.\n")
- }
- if cacheUpdateFileChatID { // Надо обновить кэш ChatID пользователей
- errUpdateCacheChatID := saveMapStringInt64ToFile(fileNameCacheChatID, cacheUserChatID) // Сохранение кэша в специальный файл в каталоге журнала
- if errUpdateCacheChatID == nil {
- cacheUpdateFileChatID = false
- _ = writeLogFile(InfoLog, "Успешное обновление сохраняемого кэша ChatID.")
- } else {
- _ = writeLogFile(ErrorLog, "Не удалось обновление сохраняемого кэша ChatID. "+errUpdateCacheChatID.Error())
- }
- }
- }
- // Обработка ответов
- for _, Answer := range AnswerS {
- if len(Answer.Responses) > 0 || Answer.tgKeyboard.InlineKeyboard != nil { //Есть ответы или меню
- if len(Answer.Responses) == 0 {
- Answer.Responses = append(Answer.Responses, "\u00AD") // Невидимый символ "Soft Hyphen" в качестве текста сообщения. Позвляет обойти ошибку которая появляется при пустом сообщении.
- }
- // fmt.Printf("Есть ответы.\n")
- tmpTextAnsver := ""
- for _, StringResponceTMP := range Answer.Responses { //Текстовые строки ответа
- tmpTextAnsver = tmpTextAnsver + "\n" + StringResponceTMP
- }
- msg = tgbotapi.NewMessage(Answer.ChatID, tmpTextAnsver) // Очистка. Новый ответ.
- msg.ReplyToMessageID = Answer.MessageReplyID // Сообщение на которое отвечаем.
- if Answer.tgKeyboard.InlineKeyboard != nil { // Скорее всего причина ошибки в формировании кнопок
- msg.ReplyMarkup = Answer.tgKeyboard // Кнопки
- }
- msg.DisableNotification = Answer.DisableNotification // Отключить уведовление
- _, err = bot.Send(msg) //Отправка ответа
- lastActivityTelegramUpdate() // Обновление времени последней активности telegram
- if err != nil {
- errorTelegram = true // Устанавливаем флаг ошибки
- _ = saveLostMessage(Answer) // Что бы ответ не потерялся совсем, сохраним его в каталог журналов
- }
- }
- }
- if StopProgram { // Выход из программы
- _, _ = bot.Send(StopMSG) // Отправка оповещения о принятии команды остановки
- _ = writeLogFile(InfoLog, "Завершение работы программы.")
- err = fmt.Errorf("%s", "stop")
- exitThisFunc = true // Выход из цикла
- }
- tn0 = time.Now()
- tmplogtimev := tn0.Unix() - lastActivityTG
- if reconnectTelegramTimeoutForce { // Если отсутствие активности в tg отслеживается для переподключений
- fmt.Printf("Проверка (вне блока select) активности в telegram. Прошло %v секунд с последней активности. \n", tmplogtimev)
- if (lastActivityTG + errorTimeNoActivityTG) < tn0.Unix() { // Слишком долго не было активности в телеграме
- noActivityTG = true
- fmt.Printf("(вне блока select) Долгое отсутствие активности в telegram.\n")
- // log.Printf("Долгое отсутствие активности в telegram.")
- _ = writeLogFile(InfoLog, "Долгое отсутствие активности в telegram.")
- }
- }
- // Обработка ошибок потери соединения:
- errorsNetFlag = false // Очистка флага ошибок
- if errorGoogleTableOnline || errorTelegram || noActivityTG { // Фиксация ошибок один раз что бы не писать в журнал много записей
- if errorGoogleTableOnline {
- _ = writeLogFile(ErrorLog, "Ошибка google-таблиц.")
- errorsNetFlag = true // Случились ошибки
- }
- if noActivityTG { // Обработка ситуации, когда нет активности в Telegram
- log.Printf("Отсутствие активности в Telegram")
- _ = writeLogFile(InfoLog, "Отсутствие активности в Telegram")
- }
- if errorTelegram { // Обработка ошибки при отправке сообщения в Telegram
- log.Printf("Ошибка при отправе сообщения Telegram")
- _ = writeLogFile(WarningLog, "Ошибка при отправе сообщения Telegram")
- errorsNetFlag = true // Случились ошибки
- }
- }
- for errorGoogleTableOnline || errorTelegram || noActivityTG { // Логика изменена на выжидание при разрыве соединения.
- // Они сами восстанавливаются видимо потому что библиотеки используют web-API
- if errorGoogleTableOnline { // Обработка ошибки при обращении к таблице Google
- log.Printf("Ошибка при обращении к таблице Google")
- _, err = getSpreadsheetData(CacheReservation.GoogleService, CacheReservation.TableID, "common-f", "B3") // Тест чтения для теста соединения
- if err != nil {
- errorGoogleTableOnline = true
- _ = writeLogFile(WarningLog, "Ошибка при проверке доступа к google-таблицам.")
- } else { // Соединение с гугл-таблицами не потеряно
- errorGoogleTableOnline = false
- _ = writeLogFile(InfoLog, "Подключение к google-таблицам восстановилось.")
- log.Printf("Подключение к google-таблицам восстановилось.")
- }
- }
- if errorTelegram || noActivityTG {
- _, err = bot.GetMe() // Проверочная операция
- if err != nil {
- log.Printf("Ошибка проверки соединения с telegram функцией GetMe.")
- errorTelegram = true
- } else {
- errorTelegram = false
- log.Printf("Сетевое соединение c telegram активно. По данным функции GetMe.")
- noActivityTG = false
- lastActivityTelegramUpdate() // Не уверен что нужно, но позволит избегать лишних итераций.
- }
- }
- if errorGoogleTableOnline || errorTelegram { // Нужна пауза
- log.Printf("Пауза перед попыткой восстановить сетевое соединение %v секунд.", timeWaitReconnectSeconds)
- _ = writeLogFile(InfoLog, fmt.Sprintf("Пауза перед попыткой восстановить сетевое соединение %v секунд.", timeWaitReconnectSeconds))
- time.Sleep(time.Duration(timeWaitReconnectSeconds) * time.Second) // Пауза
- } else { // Соединения успешно восстановились
- log.Printf("Соединения восстановлены.")
- time.Sleep(time.Duration(timeWaitReconnectSeconds) * time.Second)
- }
- }
- if errorsNetFlag { // Ошибки были, но из цикла их коррекции таки произошёл выход. То есть соединения были восстановлены
- errorsNetFlag = false
- _ = writeLogFile(InfoLog, "Соединения восстановлены.")
- }
- }
- if StopProgram {
- err = fmt.Errorf("%s", "stop")
- }
- log.Printf("Бот не слушает.")
- return err
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement