JavaRush /Blog Java /Random-ES /Creando un bot de Telegram usando Spring Boot Pt.3: Quiz ...
Whiskels
Nivel 41
Москва

Creando un bot de Telegram usando Spring Boot Pt.3: Quiz Bot

Publicado en el grupo Random-ES
PARTE 1 PARTE 2 Desafortunadamente, la primera versión del artículo no cabía en el límite de caracteres, así que terminaré aquí: QuizHandler:
package com.whiskels.telegram.bot.handler;

import com.whiskels.telegram.bot.State;
import com.whiskels.telegram.model.Question;
import com.whiskels.telegram.model.User;
import com.whiskels.telegram.repository.JpaQuestionRepository;
import com.whiskels.telegram.repository.JpaUserRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod;
import org.telegram.telegrambots.meta.api.objects.replykeyboard.InlineKeyboardMarkup;
import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static com.whiskels.telegram.util.TelegramUtil.createInlineKeyboardButton;
import static com.whiskels.telegram.util.TelegramUtil.createMessageTemplate;

@Slf4j
@Component
public class QuizHandler implements Handler {
    //Храним поддерживаемые CallBackQuery в виде констант
    public static final String QUIZ_CORRECT = "/quiz_correct";
    public static final String QUIZ_INCORRECT = "/quiz_incorrect";
    public static final String QUIZ_START = "/quiz_start";
    //Храним варианты ответа
    private static final List<string> OPTIONS = List.of("A", "B", "C", "D");

    private final JpaUserRepository userRepository;
    private final JpaQuestionRepository questionRepository;

    public QuizHandler(JpaUserRepository userRepository, JpaQuestionRepository questionRepository) {
        this.userRepository = userRepository;
        this.questionRepository = questionRepository;
    }

    @Override
    public List<partialbotapimethod<? extends="" serializable="">> handle(User user, String message) {
        if (message.startsWith(QUIZ_CORRECT)) {
            // действие на коллбек с правильным ответом
            return correctAnswer(user, message);
        } else if (message.startsWith(QUIZ_INCORRECT)) {
            // действие на коллбек с неправильным ответом
            return incorrectAnswer(user);
        } else {
            return startNewQuiz(user);
        }
    }

    private List<partialbotapimethod<? extends="" serializable="">> correctAnswer(User user, String message) {
        log.info("correct");
        final int currentScore = user.getScore() + 1;
        user.setScore(currentScore);
        userRepository.save(user);

        return nextQuestion(user);
    }

    private List<partialbotapimethod<? extends="" serializable="">> incorrectAnswer(User user) {
        final int currentScore = user.getScore();
        // Обновляем лучший итог
        if (user.getHighScore() < currentScore) {
            user.setHighScore(currentScore);
        }
        // Меняем статус пользователя
        user.setScore(0);
        user.setBotState(State.NONE);
        userRepository.save(user);

        // Создаем кнопку для повторного начала игры
        InlineKeyboardMarkup inlineKeyboardMarkup = new InlineKeyboardMarkup();

        List<inlinekeyboardbutton> inlineKeyboardButtonsRowOne = List.of(
                createInlineKeyboardButton("Try again?", QUIZ_START));

        inlineKeyboardMarkup.setKeyboard(List.of(inlineKeyboardButtonsRowOne));

        return List.of(createMessageTemplate(user)
                .setText(String.format("Incorrect!%nYou scored *%d* points!", currentScore))
                .setReplyMarkup(inlineKeyboardMarkup));
    }

    private List<partialbotapimethod<? extends="" serializable="">> startNewQuiz(User user) {
        user.setBotState(State.PLAYING_QUIZ);
        userRepository.save(user);

        return nextQuestion(user);
    }

    private List<partialbotapimethod<? extends="" serializable="">> nextQuestion(User user) {
        Question question = questionRepository.getRandomQuestion();

        // Собираем список возможных вариантов ответа
        List<string> options = new ArrayList<>(List.of(question.getCorrectAnswer(), question.getOptionOne(), question.getOptionTwo(), question.getOptionThree()));
        // Перемешиваем
        Collections.shuffle(options);

        // Начинаем формировать сообщение с вопроса
        StringBuilder sb = new StringBuilder();
        sb.append('*')
                .append(question.getQuestion())
                .append("*\n\n");

        InlineKeyboardMarkup inlineKeyboardMarkup = new InlineKeyboardMarkup();

        // Создаем два ряда кнопок
        List<inlinekeyboardbutton> inlineKeyboardButtonsRowOne = new ArrayList<>();
        List<inlinekeyboardbutton> inlineKeyboardButtonsRowTwo = new ArrayList<>();

        // Формируем сообщение и записываем CallBackData на кнопки
        for (int i = 0; i < options.size(); i++) {
            InlineKeyboardButton button = new InlineKeyboardButton();

            final String callbackData = options.get(i).equalsIgnoreCase(question.getCorrectAnswer()) ? QUIZ_CORRECT : QUIZ_INCORRECT;

            button.setText(OPTIONS.get(i))
                    .setCallbackData(String.format("%s %d", callbackData, question.getId()));

            if (i < 2) {
                inlineKeyboardButtonsRowOne.add(button);
            } else {
                inlineKeyboardButtonsRowTwo.add(button);
            }
            sb.append(OPTIONS.get(i) + ". " + options.get(i));
            sb.append("\n");
        }

        inlineKeyboardMarkup.setKeyboard(List.of(inlineKeyboardButtonsRowOne, inlineKeyboardButtonsRowTwo));
        return List.of(createMessageTemplate(user)
                .setText(sb.toString())
                .setReplyMarkup(inlineKeyboardMarkup));
    }

    @Override
    public State operatedBotState() {
        return null;
    }

    @Override
    public List<string> operatedCallBackQuery() {
        return List.of(QUIZ_START, QUIZ_CORRECT, QUIZ_INCORRECT);
    }
}
<h3>Llenar la base de datos</h3>Aquí escribiremos nuestras preguntas por analogía con el script de inicialización de la base de datos.
DELETE
FROM java_quiz;

INSERT INTO java_quiz (question, answer_correct, option1, option2, option3)
VALUES ('What is a correct syntax to output "Hello World" in Java?', 'System.out.println("Hello World!");',
        'print("Hello World!");', 'sout("Hello World!");', 'Systemout.print("Hello world!");'),
       ('What is the correct way to create an object called foo of Bar class?', 'Bar foo = new Bar();',
        'Foo bar = new Foo();', 'Bar foo() = new Foo();', 'Foo bar() = new Bar();'),
       ('Which operator can be used to compare two values?', '==', '=', '&', '==='),
       ('Which method can be used to return a string in upper case letters?', 'toUpperCase()', 'camelCase()',
        'upperCase()', 'formatUpper()'),
       ('Which method can be used to find the length of a string?', 'length()', 'getSize()', 'len()', 'getLength()'),
       ('Which data type is used to create a variable that should store text?', 'String', 'Text', 'Varchar', 'const'),
       ('How to insert a comment?', '// like this', '# like this', '<-- like this -->', '/ like this');
Todo lo que queda es corregir application.yaml:
bot:
  name: JavaQuiz
  token: 1234567:AAF0Wru1Z60p8vPtKihx3odbwSv9O0y_-MM
spring:
  datasource:
    url: jdbc:postgresql://ec2-54-75-199-252.eu-west-1.compute.amazonaws.com:5432/d5p9skg6nin3mh?user=bozuqwnhjhoubl&password=8a40050de8a0014c14df49aeaac5880f1ac633cc20c78a4cc3b32323231
    driver-class-name: org.postgresql.Driver
    initialization-mode: never
  jpa:
    database-platform: org.hibernate.dialect.PostgreSQLDialect
    show-sql: true
    hibernate:
      ddl-auto: update
La URL de la base de datos es diferente de lo que aparece en el campo URI en Heroku, por lo que aquí hay una hoja de trucos rápida sobre cómo construirla:
jdbc:postgresql://{Host}:{Port}/{Database}?user={User}&password={Password}
Si hicimos todo correctamente, cuando ejecutemos main no veremos el stacktrace, ni siquiera al revés:
.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.4.RELEASE)

2020-10-02 19:27:43.081  INFO 14536 --- [           main] com.whiskels.telegram.App                : Starting App on Kuzmin with PID 14536 (D:\utilities\forJavaRush\target\classes started by whiskels in D:\utilities\forJavaRush)
2020-10-02 19:27:43.087  INFO 14536 --- [           main] com.whiskels.telegram.App                : No active profile set, falling back to default profiles: default
2020-10-02 19:27:44.014  INFO 14536 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2020-10-02 19:27:44.131  INFO 14536 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 102ms. Found 2 JPA repository interfaces.
2020-10-02 19:27:44.774  INFO 14536 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2020-10-02 19:27:44.885  INFO 14536 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate Core {5.4.10.Final}
2020-10-02 19:27:45.053  INFO 14536 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.0.Final}
2020-10-02 19:27:45.278  INFO 14536 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2020-10-02 19:27:46.770  INFO 14536 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2020-10-02 19:27:46.790  INFO 14536 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.PostgreSQLDialect
2020-10-02 19:27:49.731  INFO 14536 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2020-10-02 19:27:49.741  INFO 14536 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2020-10-02 19:27:50.817  INFO 14536 --- [           main] c.g.x.bots.TelegramBotAutoConfiguration  : Starting auto config for telegram bots
2020-10-02 19:27:50.830  INFO 14536 --- [           main] c.g.x.bots.TelegramBotAutoConfiguration  : Initializing API without webhook support
2020-10-02 19:27:50.831  INFO 14536 --- [           main] c.g.x.bots.TelegramBotAutoConfiguration  : Registering polling bot: JavaQuiz
2020-10-02 19:27:51.556  INFO 14536 --- [           main] com.whiskels.telegram.App                : Started App in 9.196 seconds (JVM running for 10.023)
Intentemos comunicarnos con nuestro bot: Conozcamos Vamos a Creando un bot de Telegram usando Spring Boot Pt.3: Quiz Bot - 1jugar Creando un bot de Telegram usando Spring Boot Pt.3: Quiz Bot - 2Pide ayuda Creando un bot de Telegram usando Spring Boot Pt.3: Quiz Bot - 3¡Listo! Escribimos un robot de prueba. Ahora tenemos un trabajo enorme por delante: necesitamos:
  • entender el código escrito hoy;
  • lea la documentación de Spring Data;
  • agregar registrador;
  • hacer refactorización;
  • aplicar patrones de diseño;
  • permitir pedir ayuda no sólo al Estado.NONE;
  • agregar nueva funcionalidad;
  • tablas de clasificación;
  • emitir sólo preguntas únicas;
  • categorías de preguntas.
Espero que este doble artículo te haya resultado útil. Si es así, pon una estrella en mi repositorio , ¡estaré encantado! Si tiene alguna pregunta, la discutiremos en los comentarios. Y finalmente, el diagrama del proyecto: Creando un bot de Telegram usando Spring Boot Pt.3: Quiz Bot - 4 ACTUALIZACIÓN 10.12: El código del proyecto se publicó en git tal como está, sin cambios.
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION