Внимание! Пакеты
python-telegram-bot
версии 13.x будут придерживаться многопоточной парадигмы программирования (на данный момент актуальна версия 13.14). Пакеты версий 20.x и новее будут полностью асинхронными и на октябрь 2022 года, первый из них находится в предрелизе. Дополнительно смотрите основные изменения в пакетеpython-telegram-bot
версии 20.x.
Иногда необходимо временно сохранить некоторую информацию о текущем пользователе и/или чате для дальнейшего использования. Примером этого может быть бот для опроса, который задает пользователю ряд вопросов один за другим и сохраняет их в базе данных.
bot_data
, user_data
и chat_data
.Расширение telegram.ext
предоставляет встроенное решение для этой общей задачи.
Пример для python-telegram-bot
версии 13.x:
from uuid import uuid4 from telegram.ext import Updater, CommandHandler def put(update, context): """Usage: /put value""" # генерируем идентификатор key = str(uuid4()) # Здесь не используется context.args, # т.к. значение может содержать пробелы. value = update.message.text.partition(' ')[2] # сохраняем значение в контекст context.user_data[key] = value # отправляем ключ пользователю update.message.reply_text(key) def get(update, context): """Usage: /get uuid""" # отделяем идентификатор от команды key = context.args[0] # загружаем значение и отправляем пользователю value = context.user_data.get(key, 'Not found') update.message.reply_text(value) if __name__ == '__main__': updater = Updater('TOKEN', use_context=True) dp = updater.dispatcher dp.add_handler(CommandHandler('put', put)) dp.add_handler(CommandHandler('get', get)) updater.start_polling() updater.idle()
Тоже самый пример для асинхронного python-telegram-bot
версии 20.x:
Изменения кода для асинхронного python-telegram-bot
версии 20.x смотрите в обзорном материале в подразделе "Асинхронный модуль расширения telegram.ext (версия 20.x)".
from uuid import uuid4 from telegram.ext import Application, CommandHandler async def put(update, context): # асинхронная функция key = str(uuid4()) value = update.message.text.partition(' ')[2] # операция не связана с сетевым подключением context.user_data[key] = value # операция связана с сетевым подключением # добавляем оператор `await` await update.message.reply_text(key) async def get(update, context): # асинхронная функция key = context.args[0] value = context.user_data.get(key, 'Not found') # операция связана с сетевым подключением # добавляем оператор `await` await update.message.reply_text(value) if __name__ == '__main__': # экземпляр приложения создается по новому application = Application.builder().token('TOKEN').build() application.add_handler(CommandHandler('put', put)) application.add_handler(CommandHandler('get', get)) application.run_polling()
Используя контекст context.user_data
в любом обратном вызове обработчика, можно получить доступ к пользовательскому словарю dict
.
Каждый раз, когда бот получает сообщение, обработчик этого сообщения находит или создает context.user_data
пользователя, отправившего сообщение. Этот словарь используется всеми обработчиками бота.
Словарь context.chat_data
работает точно так же, как context.user_data
, за исключением того, что он создается для каждого чата, а не для каждого пользователя.
Начиная с версии библиотеки 12.4, также предоставляется context.bot_data
и работает точно так же, как user_data
, за исключением того, что это единственный словарь для вашего бота.
bot_data
, user_data
и chat_data
удаляются после завершения процесса бота. Если это нежелательно, то загляните на страницу сохранения.bot_data
, user_data
и chat_data
не пустые, то они будут храниться до завершения процесса.user_data
и chat_data
- это разные словари даже для приватных чатов.bot_data
, user_data
или chat_data
присвоить нельзя!!! Вместо вызовов context.user_data = {}
и context.user_data = other_dict
используйте context.user_data.clear()
и/или context.user_data.update(other_dict)
соответственно.Если групповой чат переходит в супер-группу, то идентификатор чата изменится. Поскольку словари context.chat_data
хранятся для каждого идентификатора чата, следовательно необходимо перенести данные на новый идентификатор. Вот две ситуации, с которыми можно столкнуться:
Когда группа мигрирует, то Telegram отправит обновление, в котором просто указана новая информация. Чтобы их отловить, просто определите соответствующий обработчик:
def chat_migration(update, context): m = update.message dp = context.dispatcher # Получим старые и новые идентификаторы чата old_id = m.migrate_from_chat_id or m.chat_id new_id = m.migrate_to_chat_id or m.chat_id # передача данных, если старые данные все еще присутствуют if old_id in dp.chat_data: dp.chat_data[new_id].update(dp.chat_data.get(old_id)) del dp.chat_data[old_id] ... def main(): updater = Updater("TOKEN", use_context=True) dp = updater.dispatcher # available since version 12.4 dp.add_handler(MessageHandler(Filters.status_update.migrate, chat_migration)) ...
Пример для асинхронного python-telegram-bot
версии 20.x:
async def chat_migration(update, context): message = update.message application = context.application application.migrate_chat_data(message=message) ... def main(): application = Application.builder().token('TOKEN').build() application.add_handler( MessageHandler(filters.StatusUpdate.MIGRATE, chat_migration) ) ...
Для полной уверенности, что обновление будет обработано этим обработчиком, либо сначала добавьте его, либо поместите в отдельную группу.
ChatMigrated
.Если попробовать, например, отправить сообщение на старый идентификатор чата, то Telegram ответит неверным запросом, включая новый идентификатор чата. Можно получить к нему доступ с помощью обработчика ошибок:
def error(update, context): """Ошибки журнала, вызванные обновлениями.""" logger.warning('Update "%s" caused error "%s"', update, context.error) if isinstance(context.error, ChatMigrated): new_chat_id = context.error.new_chat_id
Тоже самое для асинхронного python-telegram-bot
версии 20.x:
async def error(update, context): # асинхронная функция """Ошибки журнала, вызванные обновлениями.""" logger.warning('Update "%s" caused error "%s"', update, context.error) if isinstance(context.error, ChatMigrated): new_chat_id = context.error.new_chat_id
К сожалению, Telegram не передает старый идентификатор чата, поэтому в настоящее время нет простого способа выполнить передачу данных, как указано выше, в обработчике ошибок. Так что убедитесь, что ловите обновления статуса! Тем не менее, можно заключить свои запросы в предложение try/except
:
def my_callback(update, context): # доступно с версии 12.4 dp = context.dispatcher ... try: context.bot.send_message(chat_id, text) except ChatMigrated as e: new_id = e.new_chat_id # Отправить повторно на новый идентификатор чата context.bot.send_message(new_id, text) # Передача данных if chat_id in dp.chat_data: dp.chat_data[new_id].update(dp.chat_data.get(chat_id)) del dp.chat_data[chat_id] ...
Пример для асинхронного python-telegram-bot
версии 20.x:
async def my_callback(update, context): application = context.application ... try: # сетевое соединение - добавляем `await` await context.bot.send_message(chat_id, text) except ChatMigrated as exc: new_id = exc.new_chat_id # сетевое соединение - добавляем `await` await context.bot.send_message(new_id, text) # Получение старых и новых идентификаторов чата old_id = message.migrate_from_chat_id or message.chat_id new_id = message.migrate_to_chat_id or message.chat_id # Передача данных, только если старые данные все еще присутствуют, # этот шаг важен, так как Telegram отправляет два обновления о переносе if old_id in application.chat_data: application.migrate_chat_data( old_chat_id=old_id, new_chat_id=new_id ) ...