как сделать викторину на aiogram?
форумчане.Уже вторую неделю не пойму как сделать викторину на Aiogram, python.Получилось такое решение:
МЫ создаём кнопки, но под каждый вопрос отдельно, ибо если ввести переменную с номером вопроса, то ответить сможет только один юзер; а здесь я обрабатываю ответы:
Прошу вас помочь мне грамотно и главное кратко реализовать такую викторину, ведь вопросов планируется 43, а делать кнопки под каждый вопрос не очень компитентно. Вот код:
exit_key = InlineKeyboardButton('Выход>', callback_data='exit') replay_test_key = InlineKeyboardButton('Заного', callback_data='replaytest') christmas_yes_key1 = InlineKeyboardButton('', callback_data='да1') christmas_no_key1 = InlineKeyboardButton('', callback_data='нет1') christmas_keyboard1 = InlineKeyboardMarkup().add(christmas_yes_key1, christmas_no_key1).add(exit_key) christmas_yes_key2 = InlineKeyboardButton('', callback_data='да2') christmas_no_key2 = InlineKeyboardButton('', callback_data='нет2') christmas_keyboard2 = InlineKeyboardMarkup().add(christmas_yes_key2, christmas_no_key2).add(exit_key,) christmas_yes_key3 = InlineKeyboardButton('', callback_data='да3') christmas_no_key3 = InlineKeyboardButton('', callback_data='нет3') christmas_keyboard3 = InlineKeyboardMarkup().add(christmas_yes_key3, christmas_no_key3).add(exit_key) christmas_yes_key4 = InlineKeyboardButton('', callback_data='да4') christmas_no_key4 = InlineKeyboardButton('', callback_data='нет4') christmas_keyboard4 = InlineKeyboardMarkup().add(christmas_yes_key4, christmas_no_key4).add(exit_key, replay_test_key) end_vic_keyboard = InlineKeyboardMarkup().add(replay_test_key) @dp.callback_query_handler(lambda c: c.data) async def inlines(c): if c.data == 'нет1': await bot.answer_callback_query(c.id, text='') await bot.edit_message_text(chat_id=c.message.chat.id, message_id=c.message.message_id, text='Вы дарите подарки?', reply_markup=christmas_keyboard2) if c.data == 'да1': await bot.answer_callback_query(c.id, text='') await bot.edit_message_text(chat_id=c.message.chat.id, message_id=c.message.message_id, text='Вы дарите подарки?', reply_markup=christmas_keyboard2) if c.data == 'нет2': await bot.answer_callback_query(c.id, text='') await bot.edit_message_text(chat_id=c.message.chat.id, message_id=c.message.message_id, text='Вы любите новый год?', reply_markup=christmas_keyboard3) if c.data == 'да2': await bot.answer_callback_query(c.id, text='') await bot.edit_message_text(chat_id=c.message.chat.id, message_id=c.message.message_id, text='Вы любите новый год?', reply_markup=christmas_keyboard3) if c.data == 'нет3': await bot.answer_callback_query(c.id, text='') await bot.edit_message_text(chat_id=c.message.chat.id, message_id=c.message.message_id, text='Викторина закончена, спасибо за участие!', reply_markup=end_vic_keyboard) if c.data == 'да3': await bot.answer_callback_query(c.id, text='') await bot.edit_message_text(chat_id=c.message.chat.id, message_id=c.message.message_id, text='Викторина закончена, спасибо за участие!', reply_markup=end_vic_keyboard) if c.data == 'replaytest': await bot.answer_callback_query(c.id, text='') await bot.edit_message_text(chat_id=c.message.chat.id, message_id=c.message.message_id, text='У вас появилось новогоднее настроение?', reply_markup=christmas_keyboard1) if c.data == 'exit': await bot.answer_callback_query(c.id, text='') await bot.edit_message_text(chat_id=c.message.chat.id, message_id=c.message.message_id, text='Спасибо за участие, викторина закончена!', reply_markup=end_vic_keyboard)
Опросы v2.0
Пятиминутка ненависти к telebot или Привет, aiogram!
Как вы знаете, во всех предыдущих уроках использовалась библиотека pyTelegramBotAPI, именуемая в коде telebot. В 2015-2017 годах, возможно, она ещё была актуальна, но прогресс не стоит на месте. А telebot, увы, стоит. Кривая реализация поллинга, проблемный next_step_handler, медленная поддержка новых версий Bot API и т.д.
В течение 2019 года я постепенно переносил своих ботов на другой фреймворк, который по многим пунктам превосходит pyTelegramBotAPI, и имя ему – aiogram. «Почему?», спросит меня уважаемый читатель. Что ж, приведу следующие аргументы:
- это полноценный фреймворк, т.е. позволяет сделать больше полезных вещей;
- асинхронный, что делает его быстрее в некоторых задачах;
- поддерживается Python 3.7+ и выше, что сподвигнет обновить свой старенький интерпретатор и использовать новые возможности языка;
- множество встроенных «помощников» (синтаксический «сахар»), улучшающих читабельность кода;
- оперативные обновления (поддержка новых опросов появилась в тот же день, что и в самом Bot API);
- русскоязычный чат поддержки и обсуждений, где сидит, в том числе, и сам разработчик фреймворка;
- мой любимый пункт: нормально работающий поллинг.
Прокомментирую последний пункт: в настоящий момент почти все мои боты работают на aiogram-ном поллинге и не падают ежедневно, как в случае с pyTelegramBotAPI.
Введение получилось очень большим, поэтому давайте уже перейдём к делу.
Плацдарм для бота
Напишем элементарного эхо-бота на aiogram с поллингом, чтобы бегло ознакомиться с фреймворком. Прежде всего, добавим нужные импорты (предполагается, что мы используем Virtual Environment, подробнее о нём – в уроке №0):
#!venv/bin/python import logging from aiogram import Bot, Dispatcher, executor, types logging.basicConfig(level=logging.INFO)
Теперь создадим объект бота. А за хэндлеры здесь отвечает специальный Диспетчер:
bot = Bot(token="12345678:AABcdeFGhIJkXyZ") dp = Dispatcher(bot)
Далее напишем простейший хэндлер, повторяющий текстовые сообщения:
@dp.message_handler() async def echo(message: types.Message): await message.reply(message.text)
Началась магия.
Во-первых, как я написал чуть выше, за хэндлеры отвечает диспетчер (dp).
Во-вторых, подхэндлерные функции в aiogram асинхронные (async def), вызовы Bot API тоже асинхронные, поэтому необходимо использовать ключевое слово await .
В-третьих, вместо bot.send_message можно для удобства использовать message.reply( ) без указания chat_id и message.id , чтобы бот сделал «ответ» (reply), либо аналог message.answer( ) , чтобы просто отправить в тот же чат, не создавая «ответ». Само выражение в хэндлере пустое, т.к. нас устроят любые текстовые сообщения.
if __name__ == "__main__": executor.start_polling(dp, skip_updates=True)
Параметр skip_updates=True позволяет пропустить накопившиеся входящие сообщения, если они нам не важны.
Запускаем код, убеждаемся в его работоспособности, после чего удаляем хэндлер вместе с функцией echo, нам они больше не понадобятся, в отличие от остального кода.
Запрашиваем викторину у пользователя
В BotAPI 4.6 появилась новая кнопка для обычной (не инлайн) клавиатуры с типом KeyboardButtonPollType. При нажатии на неё в приложении Telegram появляется окно для создания опроса. В самой кнопке можно выставить ограничение по типу создаваемого объекта: опрос, викторина или что угодно. Опросы нас пока не интересуют, поэтому напишем обработчик команды /start , выводящий приветственное сообщение и обычную клавиатуру с двумя кнопками: “Создать викторину” и “Отмена”, причём вторая отправляет ReplyKeyboardRemove, удаляя первую клавиатуру.
# Хэндлер на команду /start @dp.message_handler(commands=["start"]) async def cmd_start(message: types.Message): poll_keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True) poll_keyboard.add(types.KeyboardButton(text="Создать викторину", request_poll=types.KeyboardButtonPollType(type=types.PollType.QUIZ))) poll_keyboard.add(types.KeyboardButton(text="Отмена")) await message.answer("Нажмите на кнопку ниже и создайте викторину!", reply_markup=poll_keyboard) # Хэндлер на текстовое сообщение с текстом “Отмена” @dp.message_handler(lambda message: message.text == "Отмена") async def action_cancel(message: types.Message): remove_keyboard = types.ReplyKeyboardRemove() await message.answer("Действие отменено. Введите /start, чтобы начать заново.", reply_markup=remove_keyboard)
Клавиатура с кнопками
Сохраняем и предлагаем
В 11-м уроке я использовал библиотеку Vedis для сохранения состояний в файле, чтобы те не сбрасывались после перезагрузки бота. В этот раз мы будем сохранять всё в памяти, а выбор постоянного хранилища останется за читателем, чтобы не навязывать то или иное решение. Разумеется, данные в памяти сотрутся при остановке бота, но для примера так даже лучше.
Наше хранилище будет основано на стандартных питоновских словарях (dict), причём их будет два: первый словарь содержит пары (“id пользователя”, “массив сохранённых викторин”), а второй — пары (“id викторины”, “id автора викторины”). Зачем два словаря? В дальнейшем нам нужно будет по идентификатору викторины получать некоторую информацию о ней. Необходимые нам сведения лежат в первом словаре, но в виде значений, а не ключей. Поэтому нам пришлось бы проходиться по всем возможным парам ключ-значение, чтобы найти нужную викторину.
Для ускорения поиска мы заведём второй словарь, чтобы по идентификатору викторины сразу же найти идентификатор её автора, который, в свою очередь, является ключом в первом словаре. А дальше проход по небольшому массиву и вуаля! Наши данные получены. На словах звучит сложно, но на практике реализуется довольно быстро и с минимальной избыточностью. Если придумаете решение лучше — пишите, буду рад исправить текст.
Помимо определения викторины, нам нужно хранить некоторую дополнительную информацию. Поэтому давайте создадим файл quizzer.py , опишем наш класс Quiz со всеми нужными полями в конструкторе класса (обратите внимание, в конструктор передаются не все поля, т.к. часть из них будет заполнена позднее):
from typing import List class Quiz: type: str = "quiz" def __init__(self, quiz_id, question, options, correct_option_id, owner_id): # Используем подсказки типов, чтобы было проще ориентироваться. self.quiz_id: str = quiz_id # ID викторины. Изменится после отправки от имени бота self.question: str = question # Текст вопроса self.options: List[str] = [*options] # "Распакованное" содержимое массива m_options в массив options self.correct_option_id: int = correct_option_id # ID правильного ответа self.owner: int = owner_id # Владелец опроса self.winners: List[int] = [] # Список победителей self.chat_id: int = 0 # Чат, в котором опубликована викторина self.message_id: int = 0 # Сообщение с викториной (для закрытия)
Если вы раньше не сталкивались с подсказками типов (type hints), код вида “chat_id: int = 0” может ввести в замешательство. Здесь chat_id — это имя переменной, далее через двоеточие int — её тип (число), а дальше инициализация числом 0. Python по-прежнему является языком с динамической типизацией, отсюда и название “подсказка типа”. В реальности это влияет только на восприятие кода и предупреждения в полноценных IDE типа PyCharm. Никто не мешает вам написать quiz_id: int = «чемодан» , но зачем так делать? Вернёмся в наш основной файл (я его далее буду называть bot.py ) и импортируем наш класс: from quizzer import Quiz . Также добавим в начале файла под определением бота два пустых словаря:
quizzes_database = <> # здесь хранится информация о викторинах quizzes_owners = <> # здесь хранятся пары "id викторины id её создателя"
Теперь будем отлавливать викторины, приходящие в бота. Как только прилетает что-то, похожее на неё, извлекаем информацию и создаём две записи. В первом словаре храним параметры викторины, чтобы потом её воспроизвести, а во втором просто создаём пару викторина-создатель. Идентификаторы, составляющие ключ словаря, конвертируем в строки методом str() :
Раз уж мы сохраняем викторины, давайте теперь позволим пользователям их отправлять, причём через инлайн-режим. Есть одна загвоздка: в BotAPI через инлайн-режим нельзя напрямую отправлять опросы (нет объекта InlineQueryResultPoll), поэтому придётся доставать костыли. Будем возвращать обычное сообщение с URL-кнопкой вида https://t.me/нашбот?startgroup=id_викторины. Параметры startgroup и start — это т.н. “глубокие ссылки” (Deep Linking). Когда пользователь нажмёт на кнопку, он перейдёт по указанной выше ссылке, что, в свою очередь, благодаря параметру startgroup перекинет его к выбору группы, а затем, уже после подтверждения выбора, бот будет добавлен в группу с вызовом команды /start id_викторины .
Начнём разбираться с инлайн-режимом (не забудьте включить его у @BotFather). Когда пользователь вызывает нашего бота через инлайн, показываем все созданные им викторины, плюс кнопку “Создать новую”. Если ничего нет, то только кнопку.
Очень важно выставить флаг is_personal равным True (ответ на запрос будет уникален для каждого Telegram ID) и указать небольшое значение параметра cache_time , чтобы кэш инлайн-ответов оперативно обновлялся по мере появления новых викторин.
Теперь при вызове бота через инлайн мы увидим наши сохранённые викторины, а при выборе одной из них — сообщение с кнопкой, по нажатию на которую нам предложат выбрать группу для отправки сообщения. Как только группа будет выбрана, в неё будет автоматически добавлен бот с сообщением вида /start@имя_бота . Но ничего не происходит! Сейчас разберёмся.
Отправляем викторину и получаем ответы
Помните наш простой обработчик команды /start , возвращающий сообщение с кнопкой? Настало время переписать этот хэндлер. Первым делом, будем проверять, куда отправлено сообщение – в диалог с ботом или нет. Если в диалог, то всё остаётся по-прежнему: приветствие (на этот раз укажем, что викторина принудительно будет сделана неанонимной) и кнопка для создания викторины.
А вот если сообщение отправлено в группу, то применяем следующую логику: проверяем количество “слов” в сообщении. Одно всегда есть (команда /start ), но может быть и второе, невидимое в интерфейсе приложения Telegram – параметр, переданный в качестве параметра startgroup , в нашем случае это ID викторины. Если второго слова нет (количество слов = 1), то показываем сообщение с предложением перейти в личку к боту с принудительным показом кнопки /start .
В случае, если второе слово есть, то считаем его идентификатором и пробуем отправить викторину в ту же группу. При этом мы, по сути, воспроизводим её [викторину] заново, просто от своего имени: повторяем вопрос, варианты ответов и отключаем анонимный режим, т.к. нам нужно знать, кто победитель.
Очень важный момент: при отправке викторины, в объекте Message будет записан уже новый её идентификатор, который нужно подставить в наши словари. Далее по этому новому ID мы будем смотреть и считать ответы. Побочным эффектом такого подхода будет возможность использования конкретной викторины лишь однажды и в одном чате, если отправить сообщение из инлайна в другой чат, то зашитый в ссылке инлайн-кнопки ID будет недействительным.
Далее необходимо научиться как-то обрабатывать новые ответы. В свежем обновлении API добавилось два новых типа обновлений (updates, т.е. входящие события): PollAnswer и просто Poll . Первый срабатывает при получении новых ответов в викторинах и опросах, в последнем случае ещё и при отзыве голоса (массив голосов от пользователя будет пустой). Второй срабатывает при изменении состояния опроса в целом, т.е. не только при получении новых ответов/голосов, но и при смене состояния “открыт/закрыт” и др. Опять-таки, в обучающих целях мы задействуем хэндлеры на оба типа событий.
Начнём с PollAnswer . Когда прилетает событие с новым ответом на викторину, прежде всего достаём её ID, по ней ищем автора во втором словаре. Если находим, то гуляем по всем викторинам этого пользователя и ищем совпадение по ID самой викторины, т.е. в точности обратное действие, только уже в первом словаре. Когда обнаружится нужная викторина, то проверяем, верный ответ или нет (сравниваем с correct_option_id ), и если да, то записываем ID пользователя в список победителей. Если количество победителей при этом достигает двух, то останавливаем викторину.
Остановка викторины (метод stop_poll( )) вызовет срабатывание хэндлера на тип обновлений Poll с условием is_closed is True . Снова извлекаем нужный нам экземпляр класса Quiz, вытаскиваем ID победителей и вызываем метод get_chat_member, после чего, используя aiogram-ный вспомогательный метод get_mention , формируем ссылку на каждого из победителей в HTML-разметке и создаём поздравительное сообщение. Викторины у нас одноразовые, поэтому подчищаем за собой словари, дабы не раздувать объекты в памяти.
Код готов. Закинем викторину в группу и попросим друзей правильно ответить, а сами ответим неправильно. После первого правильного ответа:
2 ответа, только один правильный
После второго правильного ответа:
3 ответа, 2 правильных, опрос закрыт
На этом всё! Если у вас возникли вопросы, не стесняйтесь задавать их в нашем чатике, а если вы нашли ошибку/опечатку, либо есть чем дополнить материал, то добро пожаловать на GitHub (ну, или всё так же в чате). Полный код урока можно найти здесь.
Как сделать викторину в питоне
викторина на Python
x='eto viktorina' print(x) y='Avtor Voini I Mira' z='pravilno' begin = input("hochesh nachat?") if begin =="yes" : print(y) begin = input('podumai') if begin =="Tolstoi" : print (z)
мой блог :patreon.com/hopkins1988 . facebook группы:»программирование на Андроид»,»
«Программирование чистый код»
| Lektorfuja |
| Посмотреть профиль |
| Найти ещё сообщения от Lektorfuja |
| Похожие темы | ||||
| Тема | Автор | Раздел | Ответов | Последнее сообщение |
| викторина | мацак | Общие вопросы по Java, Java SE, Kotlin | 7 | 27.02.2013 19:33 |
| нужна программка викторина | мацак | Софт | 3 | 09.02.2013 16:37 |
| Викторина на C# как сделать | Sylar9 | C# (си шарп) | 2 | 22.10.2012 13:03 |
| Викторина | sofolka | Microsoft Office Excel | 2 | 25.10.2009 13:09 |
| Исправление ошибок. игра Викторина | Vladya | Помощь студентам | 3 | 23.11.2008 21:38 |
20 игр на Python своими руками с полным исходным кодом
20 проектов по созданию игр на Python с.полным кодом.
Делаем Марио. Создаем днопользовательскую игру, где игрок (Марио) должен уворачиваться от огненных шаров, вылетающих из дракона.
Это клон адаптация игры с динозавриком на google chrome под названием «T-Rex Dino Run».
Игра ведется в простом интерфейсе, в котором используются только кнопки и текст.
Человек может начать викторину, нажав на кнопку «Старт». Также можно выбрать тип слов, которые необходимо исправить в викторине.
Игра «Камень, бумага, ножницы» на Python разработана с использованием Tkinter и графического интерфейса пользователя (GUI).
Эта игра «Прыгающий мяч» использует Canvas для прорисовки объектов.
Игра «Виселица» не требует никаких специальных модулей, кроме random и time.
Игра «Змейка» — это классическая аркадная игра.
Военная игра «Самолеты» на Python на pygame
Это игра между компьютером и пользователем. Простая танковая игра Python.
Игра с хорощей графикой и удобным управлением.
Создаем свой тетрис на питоне.