Обновить
16K+
1
Иван Белозёров@nightweb

Пользователь

5,2
Рейтинг
Отправить сообщение

Начал писать тесты для бота. Оказалось не так страшно как думал

Долго откладывал тесты для своих ботов. Казалось что это боль: нужно мокать API, имитировать апдейты, непонятно с чего начать.

Начал с малого. Вынес всю бизнес-логику в отдельные функции которые не знают ничего про Telegram. Просто принимают данные, возвращают результат.

python

# Не так
async def handle_payment(message: types.Message):
    amount = int(message.text)
    if amount > 10000:
        await message.answer("Сумма слишком большая")

# А так
def validate_amount(amount: int) -> tuple[bool, str]:
    if amount > 10000:
        return False, "Сумма слишком большая"
    return True, ""

async def handle_payment(message: types.Message):
    is_valid, error = validate_amount(int(message.text))
    if not is_valid:
        await message.answer(error)

Теперь validate_amount тестируется обычным pytest без всяких моков. Вызываешь функцию, проверяешь результат.

Звучит очевидно. Но я долго писал всю логику прямо в хендлерах и потом удивлялся почему тестировать неудобно. Оказывается проблема была не в тестах, а в архитектуре.

Покрытие пока небольшое, но уже несколько раз тесты поймали регрессию которую я бы заметил только в продакшене.

Кто тестирует ботов, как организуете?

Теги:
+3
Комментарии1

Понял asyncio только когда бот начал зависать под нагрузкой

Писал на Python и честно говоря asyncio воспринимал как магию. Работает и ладно.

Пока однажды бот не завис.

Пользователей было немного, штук двадцать одновременно, но один запустил команду которая делала тяжёлый запрос к внешнему API. Пока запрос выполнялся, бот молчал. Остальные подвисли в ожидании.

Начал разбираться. Оказалось я вызывал синхронную функцию прямо внутри async хендлера. requests.get внутри async def. Это блокирует весь event loop. Все корутины ждут пока эта одна функция не завершится.

Решение простое: либо заменить requests на aiohttp, либо обернуть синхронный вызов через asyncio.to_thread. Второй вариант проще когда менять библиотеку лень:

python

result = await asyncio.to_thread(requests.get, url)

После замены бот перестал зависать. Все одновременные запросы к внешнему API обрабатываются параллельно без проблем, но теперь когда вижу синхронный вызов внутри async функции ощущение что что-то не так.

Кто сталкивался с похожим, как отлаживали?

Теги:
+4
Комментарии2

Поднял домашний мониторинг на Raspberry Pi. Дешевле любого облака

Надоело платить за облачный мониторинг для домашних серверов. Решил собрать своё.

Взял Raspberry Pi 4 который валялся без дела. Поставил Prometheus и Grafana. Добавил node_exporter на все машины в сети. Уведомления настроил через Telegram-бота, теперь когда что-то падает или температура процессора уходит в красную зону, приходит уведомление.

Заняло один выходной. Большую часть времени не настройка, а борьба с тем что Grafana на Pi жрёт память активнее чем ожидалось. В итоге вынес Grafana на основной комп, а Pi оставил только для сбора метрик.

Что получилось в итоге:

Полный контроль над данными. Ничего не уходит на сторонние серверы.

Уведомления в Telegram работают быстрее чем у большинства платных сервисов которые я пробовал.

Стоимость: Pi уже был, электричество копейки.

Минус один: когда Pi сам падает, мониторинг молчит. Решил просто поставить watchdog и пока хватает.

Кто делал что-то похожее, на чём остановились в итоге?

Теги:
+4
Комментарии8

Перед каждым релизом прохожу по одному и тому же списку. Не потому что умный, а потому что каждый пункт там появился после того как я облажался.

Три вещи которые горели чаще всего.

Разные версии Android. На эмуляторе всё красиво. На реальном устройстве со старой версией что-то обязательно едет. Держу под рукой старый телефон с Android 10, туда ставлю перед каждым релизом.

Разрешения. Забываешь добавить в манифест, на новых версиях система спрашивает пользователя, пользователь жмёт «запретить» и половина функций молча перестаёт работать. Без каких-либо ошибок в логах.

ProGuard и минификация. Локально всё работает. В release сборке падает что-то что ты вообще не трогал. Потому что минификатор убрал класс который использовался через рефлексию.

Список не длинный но каждый раз спасает от как минимум одного стыдного бага в продакшене.

Что у вас в чеклисте перед релизом чего нет у большинства?

Теги:
+4
Комментарии0

Дал боту имя и работать стало приятнее

Сделал очередного рабочего бота. Как обычно прописал кто он и что делает, но вот из необычного решил дать ему имя.

Назвал Степаном.

Смешно, но код стал аккуратнее.

Потом прочитал что в Петровиче примерно так же сделали целую команду ботов с именами и маскотами. И это не прикол для корпоратива, а реальная тема которая изменила качество разработки у команды.

Наверное дело в том что когда называешь что-то именем, начинаешь нести за это ответственность иначе. Не «упадёт и ладно», а «Степан не должен падать».

Кто-нибудь ещё так делает или это только у меня странности?

Теги:
+1
Комментарии1

Telegram Stars в боте: попробовал прикрутить, делюсь что удивило

Давно хотел добавить платежи в одного из своих ботов. Раньше использовал ЮKassa через нативный Telegram Payments. Но в этот раз решил попробовать Stars, всё-таки нативная валюта платформы, без внешних провайдеров.

Настройка оказалась проще чем ожидал. Никаких provider_token, никакой возни с webhook от платёжки. Просто отправляешь инвойс с указанием суммы в Stars и обрабатываешь successful_payment. Примерно так:

python

await bot.send_invoice(
    chat_id=message.chat.id,
    title="Премиум доступ",
    description="Доступ на 30 дней",
    payload="premium_30d",
    currency="XTR",
    prices=[LabeledPrice("30 дней", 100)]
)

Работает. Пользователь платит не выходя из Telegram, конверсия реально выше чем при редиректе на внешнюю страницу.

Но есть нюансы которые я не учёл сразу.

Первое: если пользователь покупал Stars через iOS или Android, Telegram отдаёт разработчику примерно 70% от суммы, остальное Apple и Google забирают себе. Если через десктоп или веб — почти всё твоё. Это принципиально меняет экономику для аудитории которая сидит на телефоне.

Второе: возвраты. Stars можно вернуть и Telegram это делает по запросу пользователя. Нужно обрабатывать refunded_payment иначе пользователь получит деньги обратно а доступ у него останется.

Третье: вывод только через Fragment в TON. Для российского юрлица это отдельная история.

В целом для цифровых товаров и небольших сумм Stars удобнее чем внешние платёжки. Но если оборот серьёзный или нужен рублёвый вывод, ЮKassa всё ещё выглядит надёжнее.

Кто уже работает со Stars в продакшене, как решаете вопрос с выводом в рубли?

Теги:
+4
Комментарии0

Хотел сделать крутую онлайн игру. Расскажу где всё пошло не так

Идея была простая до безобразия. Браузерная змейка но с живыми соперниками. Canvas, websocket, Node.js. Всё это я более-менее знал, казалось делов на пару выходных.

Первый день вообще огонь. Змейка ползает, еду ест, растёт, цвета красивые. Думаю ну всё, осталось только игроков подключить.

Ага.

Синхронизация это ад

Подключил websocket, запустил два браузера на одной машине, вроде видят друг друга. Отлично. Добавил искусственную задержку 50мс чтобы проверить как будет на реальной сети.

Всё сломалось.

У одного игрока змейка уже повернула, у второго она ещё летит прямо. Столкновения каждый клиент считает сам по своей картинке мира. Один видит что убил соперника, второй видит что убил он. Оба правы по своей логике.

Полез гуглить. Нашёл что это называется authoritative server, client-side prediction, lag compensation. Понял что я вообще не туда смотрел когда проектировал. Думал займёт вечер. Потратил месяц и всё равно сделал через одно место.

Лобби которое я не планировал

Ок, физику перенёс на сервер. Теперь надо чтобы игроки могли найти друг друга, подождать, начать игру. Звучит как мелочь.

Это не мелочь.

Ожидание, старт, игра, конец, переиграть — каждое состояние надо синхронизировать. И самое весёлое это когда один игрок просто закрыл вкладку в середине партии. Что показывать второму? Кто победил? Как засчитать? Три вечера только на обработку разрывов соединения.

Читер за пять минут

Дал поиграть другу. Через пять минут он открыл консоль и начал отправлять серверу произвольные координаты. Змейка телепортировалась куда хочет. Я вообще не думал об этом когда писал архитектуру.

Что по итогу

Игра работает. Можно найти соперника и сыграть партию. Но код это такой клубок что я боюсь его открывать.

Главное что понял: онлайн игра это не игра плюс немного сетевого кода. Это совсем другая задача где сеть и есть основная сложность. Я недооценил это раз в десять минимум.

Код выложу на GitHub как разберу этот клубок. Пока стыдно показывать.

Кто делал нормальный мультиплеер в браузере, как решали синхронизацию? Потому что мои костыли мне самому не нравятся.

Теги:
+4
Комментарии2

Решил я значит попробовать вайбкодинг всерьез. Не поиграться, а реально взять рабочую задачу и пройти от идеи до переноса, почти без ручного написания кода.

Взял небольшой pet-проект: утилита для мониторинга изменений в директории с уведомлениями в Telegram. Задача понятная, ограниченная, без хитрой бизнес-логики. Первые два дня были магией. Описываешь что хочешь, получаешь код, он работает. Скорость ощущается раза в три выше обычной. На третий день начались проблемы. Модель начала путаться в контексте проекта. Предлагала решения которые противоречили тому что уже было написано двумя часами ранее. Пришлось самому держать в голове всю архитектуру и постоянно напоминать что куда подключено. К концу недели понял главное: вайбкодинг не убирает необходимость понимать что происходит. Он убирает необходимость это печатать. Если не понимаешь архитектуру, инструмент начинает строить что-то своё, и разбираться потом дольше чем написать самому.

Утилиту довёл до конца. Работает. Но половину кода всё равно переписал руками.

Кто использует вайбкодинг в реальных проектах, как решаете проблему с контекстом на больших задачах?

Теги:
+3
Комментарии4

Два часа потерял из-за того, что не написал один хендлер

Делал платежи в Telegram-боте. Нативные, через sendInvoice и ЮKassa.

Всё настроил: токен от BotFather получил, инвойс отправляется, кнопка оплаты появляется. Пользователь нажимает - и платёж падает с ошибкой. Молча. Без подробностей.

Payment failed

И всё. Telegram не говорит что именно не так.

Полез гуглить. Первая мысль - provider_token неверный. Проверил три раза, скопировал заново. Нет, токен правильный.

Потом решил что проблема в суммах - они передаются в копейках, не в рублях. 500 рублей = 50000. Перепроверил, у меня было правильно.

Потом подумал на webhook - может HTTPS не настроен как надо. Потратил минут сорок на проверку сертификата, перенастройку ngrok. Всё работает, но платежи всё равно падают.

Уже хотел идти спать, случайно наткнулся на строчку в документации:

Your bot must reply to this query in 10 seconds

Это про pre_checkout_query. Когда пользователь нажимает «Оплатить» - Telegram сначала отправляет боту запрос на подтверждение. Бот должен ответить в течение 10 секунд. Если не ответил - платёж автоматически отклоняется.

У меня хендлера для этого не было вообще. Бот просто молчал.

Добавил три строки:

python

@dp.pre_checkout_query()
async def pre_checkout(query: types.PreCheckoutQuery):
    await query.answer(ok=True)

Платёж прошёл с первого раза.

Два часа отладки из-за трёх строк кода которые я не написал.

Если кто-то тоже делает платежи в Telegram-боте и получает молчаливый отказ - проверьте pre_checkout_query первым делом, до всего остального.

Теги:
+7
Комментарии1

Информация

В рейтинге
1 225-й
Откуда
Мариинск, Кемеровская обл., Россия
Дата рождения
Зарегистрирован
Активность