Бот MMORPG в Telegram больше двух месяцев стирал письма игроков: одна строка в обработчике и как я собрал всё в общий ящик
Я делаю EclipseFall, MMORPG внутри Telegram, которую не нужно устанавливать. В прошлом лонге я разбирал, почему бой шёл на пяти кадрах в секунду, и та история началась с отзыва игрока. Эта про то, как часть таких отзывов до меня просто не доходила, и виноват в этом был мой же код.
Одна строка в конце обработчика
У игрового бота есть обработчик: Telegram присылает ему всё, что происходит вокруг бота, от оплаты звёздами до сообщения в личку. Писал я его под оплату, потому что без неё покупка не проходит. Две ветки разбирали платёжные события, а последней строкой стояло «прочие апдейты игнорируем».
Под эту строку попадало всё остальное. Игрок пишет боту в личку «у меня не грузится зал» — обработчик молча отвечает Telegram «принято», и текст исчезает. Личку бота, в отличие от чата при канале, человек глазами не видит: она существует только для обработчика. Седьмого сентября я разбирал, откуда вообще к нам приходят отзывы, и наткнулся на эту строку. Из писем, которые я успел увидеть другими путями, к тому моменту выросли правки двух выпусков. Сколько не увидел, узнать уже нельзя.
Сколько дверей у голоса игрока
Когда я сел пересчитывать, оказалось, что отзыв может прийти семью путями: форма в игре, автоматический отчёт об ошибке, чат при канале, личка бота и комментарии на трёх площадках, DTF, Хабре и VC. Форма и ошибки писались в свои таблицы. Письма из Telegram стирались. Комментарии собирала фоновая задача, которая открывала браузер и записывала найденное в файл.
С файлом тоже было плохо. Фоновая задача работает, только пока у меня открыто приложение, её браузерные действия срывались, а сверка повторов по файлу однажды записала один и тот же комментарий с Хабра дважды под разными номерами.
Общий ящик и что в него не пускать
Решение получилось скучным: одна таблица в базе, общий ящик обращений, и все семь путей пишут в неё. Повторы отбивает ключ «откуда пришло + номер на площадке» прямо в базе, а не память процесса.
Сложнее было решить, чего в ящик не класть. Свой пост канала Telegram сам копирует в чат обсуждения, и без фильтра каждый мой анонс стал бы «обращением». Отсеиваются ещё сообщения от имени канала, от других ботов, от меня самого и сообщения без текста. С площадками вышло нагляднее: под четырьмя нашими статьями на тот момент было 65 комментариев, и 31 из них написал я. Без фильтра своих ответов половина ящика была бы разговором с собой.
Приём писем я поставил ниже обеих платёжных веток и завернул так, что его поломка не меняет ответ Telegram. Если обработчик ответит ошибкой, площадка начнёт присылать то же событие по кругу и повторять вместе с ним платёжные. Ломать оплату ради писем я не собирался.
Сбор комментариев переехал на сервер
Браузер для комментариев оказался не нужен: площадки отдают их обычными запросами. Теперь их обходит защищённый адрес на сервере. Полный обход трёх площадок занимает 2,2 секунды при лимите функции в 10, внутри стоит бюджет в 8 секунд, а недособранное берёт следующий заход.
Молчащая площадка не должна выглядеть как «обращений нет». Сервер возвращает статус каждого источника, и если какой-то ответил ошибкой, прогон падает, и я об этом узнаю.
Первым ящиком, который я разобрал после выпуска 1.65.0, стали 34 накопленных обращения: 22 по делу и 12 пустых, шутки и просьбы поставить плюс. Среди дельных был развёрнутый отзыв игрока после двадцати минут игры с тремя конкретными правками, и одна из них, про пять кадров в бою на слабом телефоне, стала темой прошлого лонга.
Расписание, которое опаздывало на восемь часов
Первую версию сбора дёргало расписание GitHub «каждые два часа». В начале октября я замерил, как оно ходит на деле: от трёх до семи запусков в сутки вместо двенадцати, с перерывом до 8,6 часа. Для отзыва игрока это много: человек пишет вечером, а ответ получает утром, когда уже ушёл из игры.
Основной таймер теперь у хостинга, раз в два часа, а задание GitHub осталось запасным и ходит дважды в сутки. У таймера хостинга свои слабости: упавший запуск он не повторяет и никому об этом не сообщает. Поэтому досылка смотрит на сутки назад, а если Telegram отказал в отправке, снимает с обращения пометку «отправлено», и его возьмёт следующий заход.
Как это держится
Каждое правило фильтра закреплено тестом: 23 проверки на приём писем и 43 на сбор с площадок. Чтобы убедиться, что тесты ловят то, что обещают, я портил код нарочно: снимал фильтр своего поста, убирал источник, менял ответ на ошибку. Пять подмен на приёме и семь на сборе, все пойманы.
Главный вывод у меня простой. Строку «прочее игнорируем» я написал сам, в конце июня, и больше двух месяцев она выглядела разумной, потому что потерю не видно: пропавшее письмо не оставляет ошибки в журнале. Теперь в начале каждой рабочей сессии мне печатается одна строка, сколько обращений ждёт разбора.
Игра живёт внутри Telegram, ставить ничего не нужно: открыть EclipseFall. Про такие разборы я пишу в девлоге: t.me/eclipsefall_game.