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:
jugar
Pide ayuda
¡Listo! Escribimos un robot de prueba. Ahora tenemos un trabajo enorme por delante: necesitamos:
ACTUALIZACIÓN 10.12: El código del proyecto se publicó en git tal como está, sin cambios.
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 


- 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.

GO TO FULL VERSION