Сообщить об ошибке.

Работа с опросами в модуле python-telegram-bot

Пример бота, работающего с опросами/викторинами

Внимание! Пакеты python-telegram-bot версии 13.x будут придерживаться многопоточной парадигмы программирования (*на данный момент актуальна версия 13.15). Пакеты версий 20.x и новее предоставляют чистый асинхронный Python интерфейс для Telegram Bot API. Дополнительно смотрите основные изменения в пакете python-telegram-bot версии 20.x.

Базовый пример бота, который работает с опросами и построен на библиотеке python-telegram-bot. В примере задается лимит на количество участников/ответов (принимается только 3 ответа от 3-х пользователей), которые могут взаимодействовать с каждым опросом/викториной.

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

Хранение промежуточных данных опроса происходит в bot_data для последующего использования в функции ответа на опрос/викторину.

Пример бота, работающего с опросами, снабжен подробными комментариями.

import logging

# импорт API Telegramm
from telegram import (
    Poll,
    ParseMode,
    KeyboardButton,
    KeyboardButtonPollType,
    ReplyKeyboardMarkup,
    ReplyKeyboardRemove,
    Update,
)

# импорт расширений библиотеки
from telegram.ext import (
    Updater,
    CommandHandler,
    PollAnswerHandler,
    PollHandler,
    MessageHandler,
    Filters,
)

# подключение логирования
logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
)
logger = logging.getLogger(__name__)

### ОПРЕДЕЛЕНИЕ ФУНКЦИЙ ОБРАТНОГО ВЫЗОВА ###

def start(update, _):
    """Информация о том, что может сделать этот бот"""
    update.message.reply_text(
        'Введите `/poll` для участия в опросе, `/quiz` для участия в викторине или `/preview`'
        ' чтобы создать собственный опрос/викторину'
    )

def poll(update, context):
    """Отправка заранее подготовленного опроса"""
    # Вопрос опроса и его ответы.
    questions = "Как дела?"
    answer = ["Нормально", "Хорошо", "Отлично", "Супер!"]
    # Отправляем опрос в чат
    message = context.bot.send_poll(
        update.effective_chat.id, questions, answer, 
        is_anonymous=False, allows_multiple_answers=True,
    )
    # Сохраним информацию опроса в `bot_data` для последующего 
    # использования в функции `receive_poll_answer`
    payload = { # ключом словаря с данными будет `id` опроса
        message.poll.id: {
            "questions": questions,
            "message_id": message.message_id,
            "chat_id": update.effective_chat.id,
            "answers": 0,
        }
    }
    # сохранение промежуточных результатов в `bot_data`
    context.bot_data.update(payload)

def receive_poll_answer(update, context):
    """Итоги опроса пользователей"""
    answer = update.poll_answer
    poll_id = answer.poll_id
    try:
        questions = context.bot_data[poll_id]["questions"]
    except KeyError:  # Это ответ на старый опрос
        return
    selected_options = answer.option_ids
    answer_string = ""
    # подсчет и оформление результатов
    for question_id in selected_options:
        if question_id != selected_options[-1]:
            answer_string += questions[question_id] + " и "
        else:
            answer_string += questions[question_id]
    context.bot.send_message(
        context.bot_data[poll_id]["chat_id"],
        f"{update.effective_user.mention_html()} => {answer_string}!",
        parse_mode=ParseMode.HTML,
    )
    # изменение промежуточных результатов в `bot_data`
    context.bot_data[poll_id]["answers"] += 1
    # Закрываем опрос после того, как проголосовали три участника
    if context.bot_data[poll_id]["answers"] == 3:
        context.bot.stop_poll(
            context.bot_data[poll_id]["chat_id"], context.bot_data[poll_id]["message_id"]
        )

def quiz(update, context):
    """Отправка заранее определенную викторину"""
    # Вопрос викторины и ответы
    questions = 'Сколько яиц нужно для торта?'
    answer = ['1', '2', '4', '20']
    # посылаем сообщение с викториной, правильный ответ указывается
    # в `correct_option_id`, представляет собой индекс `answer`
    message = update.effective_message.reply_poll(
        questions, answer, type=Poll.QUIZ, correct_option_id=2
    )
    # Сохраним промежуточные данные викторины в `bot_data` для использования в `receive_quiz_answer`
    payload = { # ключом словаря с данными будет `id` викторины
        message.poll.id: {"chat_id": update.effective_chat.id, "message_id": message.message_id}
    }
    context.bot_data.update(payload)

def receive_quiz_answer(update, context):
    """Закрываем викторину после того, как ее прошли три участника"""
    # бот может получать обновления уже закрытого опроса, которые уже не волнуют
    if update.poll.is_closed:
        return
    if update.poll.total_voter_count == 3:
        try:
            quiz_data = context.bot_data[update.poll.id]
        except KeyError: # Это означает, что это ответ из старой викторины
            return
        context.bot.stop_poll(quiz_data["chat_id"], quiz_data["message_id"])

def preview(update, _):
    """Позволяет создать викторину или опрос пользователям чата"""
    # При использовании, без указания типа, позволяет пользователю 
    # выбрать то, что он хочет создать - викторину или опрос
    button = [[KeyboardButton("Нажми меня!", request_poll=KeyboardButtonPollType())]]
    message = "Нажмите кнопку, для предварительного просмотра вашего опроса"
    # использование `one_time_keyboard=True` скрывает клавиатуру
    update.effective_message.reply_text(
        message, reply_markup=ReplyKeyboardMarkup(button, one_time_keyboard=True)
    )

def receive_poll(update, _):
    """
        При получении ответа на пользовательский опрос/викторину, 
        отвечаем на него закрытым опросом, копируя полученный опрос
    """
    actual_poll = update.effective_message.poll
    # Нужно только `question` и `options`, все остальные 
    # параметры не имеют значения для закрытого опроса
    update.effective_message.reply_poll(
        question=actual_poll.question,
        options=[o.text for o in actual_poll.options],
        # с `is_closed=True` опрос/викторина немедленно закрывается
        is_closed=True,
        reply_markup=ReplyKeyboardRemove(),
    )

def help_handler(update, _):
    """Отображение справочного сообщения"""
    update.message.reply_text("Используйте /quiz, /poll или /preview для тестирования этого бота.")


if __name__ == '__main__':
    updater = Updater("TOKEN")
    dispatcher = updater.dispatcher

    # определяем соответствующие обработчики
    dispatcher.add_handler(CommandHandler('start', start))
    # команда `/pool`
    dispatcher.add_handler(CommandHandler('poll', poll))
    # обработчик ответа на опрос
    dispatcher.add_handler(PollAnswerHandler(receive_poll_answer))
    # команда `/quiz`
    dispatcher.add_handler(CommandHandler('quiz', quiz))
    # обработчик ответа на викторину
    dispatcher.add_handler(PollHandler(receive_quiz_answer))
    # команда `/preview`
    dispatcher.add_handler(CommandHandler('preview', preview))
    # обработчик создания пользовательского опроса/викторины
    dispatcher.add_handler(MessageHandler(Filters.poll, receive_poll))
    dispatcher.add_handler(CommandHandler('help', help_handler))

    # Запуск бота
    updater.start_polling()
    updater.idle()