Урок 3

«Угадай мелодию». Завершаем бота

Завершаем бота, начатого в прошлом уроке.

Добавляем клавиатуру

Базу данных и хранилище подготовлено, осталось написать саму логику. Начнем с того, что добавим в файл utils.py функцию, создающую кастомную клавиатуру с вариантами ответа.

def generate_markup(right_answer, wrong_answers):
    """
    Создаем кастомную клавиатуру для выбора ответа
    :param right_answer: Правильный ответ
    :param wrong_answers: Набор неправильных ответов
    :return: Объект кастомной клавиатуры
    """
    markup = types.ReplyKeyboardMarkup(one_time_keyboard=True, resize_keyboard=True)
    # Склеиваем правильный ответ с неправильными
    all_answers = '{},{}'.format(right_answer, wrong_answers)
    # Создаем лист (массив) и записываем в него все элементы
    list_items = []
    for item in all_answers.split(','):
        list_items.append(item)
    # Хорошенько перемешаем все элементы
    shuffle(list_items)
    # Заполняем разметку перемешанными элементами
    for item in list_items:
        markup.add(item)
    return markup

Возможно, код немного перегружен комментариями, так он совсем небольшой. Сперва создаем объект клавиатуры ReplyKeyboardMarkup, затем объединяем в единую строку правильный и неправильный варианты ответа, создаем объект типа list (по сути, обычный массив), куда вносим по очереди все варианты ответов, дробя их по запятым.

Наконец, при помощи команды shuffle (не забудьте написать сверху from random import shuffle) перемешиваем все варианты ответов и заносим в клавиатуру, возвращая её. Это делается для того, чтобы правильный ответ не был всегда первым.

Добавляем логику боту

Переходим к основной части. Создаем, если не сделали этого раньше, файл bot.py. Объявим нашего бота: bot = telebot.TeleBot(config.token).

Команда будет всего одна: /game. По этой команде мы должны обратиться к БД, выдернуть оттуда рандомную аудиозапись с вариантами ответа, загнать варианты ответа в клавиатуру, отправить аудиофайл с вариантами ответа и включить “игровой режим”.

@bot.message_handler(commands=['game'])
def game(message):
    # Подключаемся к БД
    db_worker = SQLighter(config.database_name)
    # Получаем случайную строку из БД
    row = db_worker.select_single(random.randint(1, utils.get_rows_count()))
    # Формируем разметку
    markup = utils.generate_markup(row[2], row[3])
    # Отправляем аудиофайл с вариантами ответа
    bot.send_voice(message.chat.id, row[1], reply_markup=markup)
    # Включаем "игровой режим"
    utils.set_user_game(message.chat.id, row[2])
    # Отсоединяемся от БД
    db_worker.close()

Что значит “игровой режим”? Как только юзер нажимает на /game, мы заносим его ID в хранилище (именно поэтому его и используем, каждый раз дергать БД некошерно) и предполагаем, что следующее сообщение - ответ на вопрос. Всё, что юзер введёт с клавиатуры или любая кнопка в разметке (суть отправка сообщения) считается ответом. Значит, напишем хэндлер, который реагирует на все текстовые сообщения, кроме /game (т.е. следующий код надо расположить в файле bot.py ниже предыдущего)

@bot.message_handler(func=lambda message: True, content_types=['text'])
def check_answer(message):
    # Если функция возвращает None -> Человек не в игре
    answer = utils.get_answer_for_user(message.chat.id)
    # Как Вы помните, answer может быть либо текст, либо None
    # Если None:
    if not answer:
        bot.send_message(message.chat.id, 'Чтобы начать игру, выберите команду /game')
    else:
        # Уберем клавиатуру с вариантами ответа.
        keyboard_hider = types.ReplyKeyboardRemove()
        # Если ответ правильный/неправильный
        if message.text == answer:
            bot.send_message(message.chat.id, 'Верно!', reply_markup=keyboard_hider)
        else:
            bot.send_message(message.chat.id, 'Увы, Вы не угадали. Попробуйте ещё раз!', reply_markup=keyboard_hider)
        # Удаляем юзера из хранилища (игра закончена)
        utils.finish_user_game(message.chat.id)

Наконец, включаем бота на постоянную проверку входящих сообщений (дописываем следующий код в конец файла):

if __name__ == '__main__':
    utils.count_rows()
    random.seed()
    bot.infinity_polling()

Собственно, всё! Исходные коды можно посмотреть вот тут. Если будете брать исходники, не забудьте проверить импорты (убрать в некоторых местах lesson_01 или lesson_02, например)

Запустим бота из нужного каталога командой python3 bot.py, выставив этому файлу права на исполнение владельцем (например, chmod u+x bot.py):

Бот работает

Поздравляю, теперь вы умеете писать ботов для Telegram как минимум лёгкой и средней сложности. Наверное, будет ещё пара уроков, посвященная различным плюшкам, типа вебхуков (и трюкам при их использовании), работе с Unix из-под ботов (может быть, напишем свой файловый менеджер), ну и по мелочам.

P.S. Домашнее задание:

  1. Наш бот сейчас может некорректно работать в групповых чатах. Что нужно добавить, чтобы он правильно определял игрока и предлагал клавиатуру / проверял ответы только от одного человека?
  2. Что нужно сделать, чтобы велась статистика по конкретному человеку?

← Урок №2 Урок №4 →