Животные из «Скотного двора» Оруэлла кивают, слушая заповеди, и учат их наизусть. Сами заповеди перед этим написали на стене амбара. Разумное дополнение к кивку. Telegram-бот тоже умеет ответить «Запомнил» так убедительно, что ему хочется доверить всё лето. Затем программу перезапускают — и он заново выясняет, кто вы такой и чего хотели.

Для небольшого бота с одной работающей копией программы базой данных может служить SQLite на постоянном диске. У каждого человека будет своя запись: бот сохраняет выбор, фиксирует изменение и при следующем обращении читает значение по ID собеседника. Ниже пройдём этот путь для одной настройки, проверим двух пользователей, повтор, перезапуск и резервную копию.

Допустим, бот помогает выбрать занятие на выходной. Вы любите гулять, друг — кататься на велосипеде. Пока последний выбор хранится в общей переменной, у всех один «любимый отдых». Гардероб открылся с единственным крючком на двоих: походный ботинок уже носит велосипедный шлем. Сам бот совершенно спокоен. Вещи выдали, следующий!

База данных для Telegram-бота: начнём с одной привычки

Возьмём три режима: walk — прогулка, bike — велосипед, museum — музей. Сохраняем ID человека и выбранное значение. Всю переписку, телефоны и биографии знакомых эта операция не требует.

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

Если с кодом пока не знакомы, начните с помощника. Откройте бесплатный чат DeepSeek, вставьте запрос и следом отправьте эту инструкцию. Если уже пользуетесь ChatGPT или Claude, можно продолжить там.

Хочу научить своего Telegram-бота помнить выбор walk, bike или museum.
Сначала спроси мою систему, язык бота и где он запущен. Помоги выяснить
SQLite-драйвер и постоянный путь к файлу, если я этих слов не понимаю.
Начни с отдельной пробы на вымышленных ID 101 и 202, без рабочей базы.
Давай одно действие: что открыть, куда вставить команду или SQL,
какой ответ ждать. Жди моего результата. Затем помоги связать хранение
с моим обработчиком: ключ — ID отправителя, запросы с параметрами,
подтверждение после фиксации. /start сохраняет прежний выбор.
Токены и пароли я не передаю. При ошибке попроси обезличенный текст,
объясни причину и одну проверку. Спорное сверяй с официальной
документацией SQLite и моего драйвера. Рабочую базу не перезаписывай.
Если не уверен, скажи; без поиска попроси нужный фрагмент справки.

Первый результат — увидеть walk в отдельном учебном файле. У терминала и консоли SQLite разные приглашения: sqlite3 preferences.sqlite открывает консоль, а SQL ниже вводят уже в ней. Попросите помощника разобрать, какое окно у вас сейчас открыто, прежде чем копировать следующий блок. Затем по таблице пройдите двух людей, повтор и перезапуск.

В личном сообщении ключ берём из message.from.id — ID отправителя. В Bot API Telegram предупреждает, что значение может быть больше 32 бит. Для него в коде используйте 64-битный целый тип; диапазон целых SQLite это позволяет. Имя и username ключом нашей таблицы не служат.

Перед сохранением обработчик проверяет, что сообщение относится к разрешённому личному диалогу и отправитель определён. Номер чужой записи из текста команды в качестве ключа не принимаем. Для локальной пробы используем 101 и 202; в боте на их место приходят ID из сообщений.

Памяти нужен постоянный адрес

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

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

Вторая причина — временная файловая система хостинга. Например, Render документирует, что без постоянного диска изменения локальных файлов теряются после перезапуска или развёртывания. Постоянные диски там доступны платным сервисам, и сохраняются только данные внутри точки подключения. Файл с расширением .sqlite вне этого места будет столь же временным, как его соседи.

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

Для локальной пробы откройте терминал в новой отдельной папке и запустите:

sqlite3 preferences.sqlite

Дальше команды выполняются внутри консоли SQLite. В работающем боте файл открывает его SQLite-драйвер; консоль служит способом отдельно посмотреть данные.

По одной строке на человека

Создайте таблицу:

CREATE TABLE IF NOT EXISTS user_settings (
  user_id INTEGER PRIMARY KEY,
  mode TEXT NOT NULL CHECK (mode IN ('walk', 'bike', 'museum'))
);

user_id — первичный ключ: по одному месту для каждого ID. Ограничение CHECK пропускает только три наших режима. Эти свойства описаны в документации таблиц SQLite. В обработчике тоже проверяйте режим до запроса, чтобы неизвестному выбору можно было ответить понятными словами.

Задайте параметры первой записи:

.parameter init
.parameter set @user_id 101
.parameter set @mode walk

Теперь запишите выбор и прочитайте его:

BEGIN;
INSERT INTO user_settings (user_id, mode)
VALUES (@user_id, @mode)
ON CONFLICT(user_id) DO UPDATE SET mode = excluded.mode;
COMMIT;

SELECT mode FROM user_settings WHERE user_id = @user_id;

Должно получиться walk. Если строки ещё нет, INSERT создаёт её. Если этот пользователь уже выбирал, ON CONFLICT обновляет его mode. Такой приём называется UPSERT. excluded.mode обозначает новое значение, которое мы передали для записи.

Текст запроса и данные идут отдельно. В консоли значения задаёт .parameter, в программе — привязка параметров выбранного драйвера. Команды с точкой не вставляют в обработчик как SQL. Формы параметров можно сверить в справке SQLite. Склеивать запрос с текстом сообщения для этого не требуется.

BEGIN начинает транзакцию, COMMIT фиксирует изменения. В программе проверяйте ошибки записи и фиксации. Если операция не удалась, выполните откат средствами драйвера и сообщите об отказе. Подтверждение «Сохранено» отправляйте после успешной фиксации. Гардеробщик сначала принимает вещи, затем выдаёт номерок. Правила транзакций SQLite помогают проверить этот порядок.

Два человека, два разных выходных

Не закрывая консоль, смените параметры:

.parameter set @user_id 202
.parameter set @mode bike

Повторите блок записи от BEGIN до COMMIT. Теперь для первого человека сменим прогулку на музей — допустим, испортилась погода:

.parameter set @user_id 101
.parameter set @mode museum

Выполните тот же блок записи. Затем повторите его ещё раз с теми же параметрами. Посмотрите, что осталось:

SELECT user_id, mode FROM user_settings ORDER BY user_id;
SELECT COUNT(*) FROM user_settings;

Ожидаются 101 — museum, 202 — bike, всего две строки. Первый поменял планы, второй сохранил свой велосипед. Повтор установки museum не размножил пользователя. Если после этого второй тоже отправился в музей, проверять нужно не прогноз погоды.

Случай Что должно быть в базе Что это показывает
101 выбрал walk, 202 выбрал bike Разные значения у двух ID Записи разделены
101 сменил выбор на museum 101 — museum, 202 — bike Обновилась нужная строка
101 повторил museum Те же две строки Нет дубля пользователя
303 ещё ничего не выбирал Его строки нет Нужно предложить первый выбор
Файл открыт новым процессом Те же museum и bike Значения сохранились на диске
Восстановлена резервная копия Те же две записи Из копии можно прочитать данные

В обработчике чтения этот запрос получает ID текущего собеседника:

SELECT mode FROM user_settings WHERE user_id = @user_id;

Если в таблице всё правильно, а ответы смешаны, посмотрите, где хранится результат SELECT. Одна общая current_mode способна снова собрать вещи на единственный крючок уже после правильного чтения из базы. ID и прочитанное значение должны принадлежать текущему сообщению.

Запись должна стать частью ответа

Свяжите запросы с обработчиками существующего бота. При выборе пройдите цепочку: проверили личный диалог и ID → приняли допустимый режим → передали параметры в запись → проверили фиксацию → подтвердили человеку.

При просмотре настроек различайте два результата. Строки нет — человек ещё ничего не выбирал, предложите варианты. Чтение завершилось ошибкой — сообщите о сбое. Если оба случая превращать в «вы ещё не выбирали», бот предложит заново сдать вещи, которые на самом деле висят у него за спиной.

Отдельно посмотрите на /start. Сначала команда читает прежнее значение. Безусловная запись режима по умолчанию стирает выбор при каждом приветствии. Диск всё помнит, зато утренний дежурный со шваброй уже расчистил крючки для новых посетителей.

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

Закройте программу и спросите снова

В консоли выполните .quit, затем снова откройте тот же preferences.sqlite и прочитайте обе строки. Это проверка файла новым процессом. Сам бот проверяется следующей цепочкой в двух разрешённых личных диалогах:

  1. Первый человек выбирает один режим, второй — другой.
  2. Вы останавливаете программу бота и запускаете её снова.
  3. Каждый собеседник запрашивает своё сохранённое значение.
  4. Первый меняет выбор; у второго значение остаётся прежним.

Так SQL соединяется с настоящими обработчиками. Сообщение «бот запущен» ещё не показывает, какую строку он прочитал и кому её вернул.

Если выбор исчез, сузьте поиск. Пустой другой файл — проверьте путь. Прежние строки есть, ответ пустой — ID и обработчик чтения. После развёртывания файла нет — постоянство диска. Выбор сбросился после /start — запись в приветствии. Это четыре разных причины, и заменой базы данных на более модную их сразу не устранить.

Достаньте настройки из копии

В консоли, подключённой к вашей базе, выполните:

.backup preferences-backup.sqlite

У SQLite есть механизм согласованного резервного копирования. Используйте его вместо копирования одного файла наугад во время работы программы: рядом могут находиться файлы журнала, связанные с состоянием базы.

Проверим восстановление отдельно. Выйдите через .quit, откройте новый файл:

sqlite3 restored.sqlite

В этой консоли выполните:

.restore preferences-backup.sqlite

Прочитайте обе строки через SELECT. Если копия находится в другой папке, укажите её полный путь. Команды .backup и .restore описаны в справке по консоли. Рабочую базу восстанавливают при остановленном боте; проба в отдельном файле позволяет сначала посмотреть результат. Копию храните отдельно от единственного диска с оригиналом.

Теперь проверена содержательная вещь: из копии возвращаются конкретные museum и bike. Папка с красивым названием «Резервные копии» сама по себе ничего о них не говорит.

Частые вопросы

SQLite достаточно для настоящего бота?

Смотрите на архитектуру и нагрузку. SQLite допускает одного писателя в конкретный момент. Для нескольких серверов с общей базой или высокой одновременной нагрузки на запись может быть удобнее серверная СУБД. Официальная памятка объясняет эти различия без универсального порога в числе пользователей.

UPSERT защищает от любого повтора?

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

Что делать при ошибке сохранения?

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

Эти запросы подходят боту на Python?

Их можно выполнять через SQLite-драйвер языка, на котором написана ваша программа. Размещение готового Python-бота разобрано отдельно. Сначала проверьте хранение, затем переносите его вместе с постоянным файлом.

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