Телеграм-бот, который считает, кто кому должен

Мой пет-проект: бот для группового чата, который ведёт учёт совместных расходов и сам сводит, кто кому сколько остался должен. Проверен в поездке к другу в Турцию.

В августе 2025-го я поехал в Турцию к своему другу Лёхе. Жил у него, так что за жильё голова не болела, а вот всё остальное мы делили: то он платит за такси, то я за ужин, то он за продукты в магазине. Через пару дней уже никто не помнит, кто за что отдавал. Обычно это заканчивается неловким вечером с калькулятором и фразой «так, погоди, а кто платил за тот завтрак». Чтобы такого вечера не было, я написал бота.

Живёт он в Telegram под именем @StasBaikalBot. Добавляешь его в групповой чат, и он молча ведёт бухгалтерию: записывает расходы, считает средний чек и держит в закреплённом сообщении актуальный баланс. В Турции мы пользовались им каждый день, и проверку реальной поездкой он прошёл.

Что он делает

Логика такая. В чате есть закреплённое сообщение со сводкой. Каждый раз, когда кто-то пишет расход, бот его записывает и переписывает эту сводку заново. В любой момент можно открыть закреп и увидеть, сколько потрачено всего, сколько в среднем на человека и кто кому в итоге должен.

Записать общий расход просто: пишешь в чат 1500 продукты. Бот понимает, что 1500 — это сумма, а всё остальное — описание, и заносит трату на тебя.

Бывают и личные долги, когда деньги не общие, а конкретно один занял у другого. Для этого есть команда /owe: отвечаешь реплаем на сообщение того, кому должен, и пишешь /owe 1000 майка. Бот фиксирует, что ты должен этому человеку тысячу.

Ошибся — отвечаешь реплаем на свою запись командой /delete, и она пропадает из подсчётов.

Стек

Бот написан на Python, и за время жизни проекта он успел сменить архитектуру. Сейчас это третья версия, и она устроена иначе, чем первые две.

  • python-telegram-bot — библиотека для работы с Telegram. Бот принимает апдейты вебхуком, а не поллингом, потому что живёт в облаке и не крутится постоянным процессом.
  • firebase-functions — обёртка для serverless. Весь бот — это одна облачная функция, которая просыпается на входящий запрос от Telegram и засыпает обратно.
  • Flask — внутри функции крутится маленькое Flask-приложение с единственным эндпоинтом /webhook, через который Telegram присылает сообщения.
  • Firestore — база данных. Туда складываются расходы, долги, имена участников и id закреплённого сообщения для каждого чата.

Первые версии бота были обычным процессом на VPS: висел демон, держал поллинг, ел память даже когда в чате тишину. Для бота, который реально работает минут двадцать в день, это расточительно. Поэтому третья версия переехала на serverless: функция запускается только когда кто-то что-то написал, а в остальное время не стоит ничего. Для пет-проекта на пару чатов это идеальный режим — платишь за факт использования, а не за то, что сервер просто включён.

Как устроен подсчёт

Самое интересное здесь не в том, как бот записывает расходы, а в том, как он сводит итог.

Когда в чате появляется новая запись, бот не дописывает строчку к старой сводке. Он берёт из базы вообще все расходы и все долги этого чата и пересобирает сводку с нуля. Сначала складывает, сколько потратил каждый, потом считает средний чек — общую сумму делит на число людей, которые вообще тратились. Дальше у каждого появляется баланс: потратил больше среднего — тебе должны, меньше — должен ты.

Поверх общих расходов накладываются личные долги из /owe: они просто двигают балансы двух конкретных людей.

А дальше начинается то, ради чего всё затевалось. Сырые балансы вида «Аня в плюсе на 800, Петя в минусе на 300, я в минусе на 500» — это ещё не ответ. Никому не хочется делать три перевода, если можно обойтись одним. Поэтому бот прогоняет балансы через жадный алгоритм взаимозачёта: берёт самого большого должника и самого большого кредитора, гасит между ними максимально возможную сумму, и так по кругу, пока все не сойдутся в ноль. На выходе получается минимальный список переводов: кто, кому и сколько. Не «все скидываются в общий котёл», а конкретные стрелочки.

Путь одного сообщения выглядит так:

"1500 продукты"  →  Telegram шлёт вебхук  →  облачная функция просыпается
                                                      │
                                                      ▼
                          Flask разбирает апдейт, отдаёт нужному хендлеру
                                                      │
                                                      ▼
                          запись расхода в Firestore (сумма, кто, описание)
                                                      │
                                                      ▼
                          читаем ВСЕ расходы и долги чата заново
                                                      │
                                                      ▼
                          средний чек → балансы → взаимозачёт долгов
                                                      │
                                                      ▼
                          переписываем закреплённое сообщение

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

Как начать

  1. Добавить @StasBaikalBot в групповой чат.
  2. Сделать его администратором с правом закреплять сообщения. Без этого права он не сможет держать сводку в закрепе.
  3. Отправить /start_tracking. Бот создаст и закрепит таблицу, которая дальше будет обновляться сама.

Дальше просто пишете расходы в формате сумма описание, а личные долги через /owe реплаем. Есть ещё /ping, чтобы проверить, что бот жив, и /start с краткой инструкцией.

Чего тут осознанно нет

Это бот для своих, а не сервис на тысячу чатов, и многое в нём сделано «достаточно», а не «правильно».

Валюта зашита турецкими лирами прямо в текст ответов, потому что писался он под конкретную поездку. Нет ни мультивалютности, ни курсов, ни выбора языка. Сводка обрезается, если перевалит за лимит Telegram в 4096 символов, — для отпуска на неделю столько расходов не набирается, так что я не стал городить разбивку на несколько сообщений. Нет экспорта в таблицу, нет графиков, нет напоминаний.

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

Код целиком тут, если захотите поковыряться или поднять свою копию:

github.com/filimonovadm/telegram-expense-bot

← Back to Blog