JavaRush /Java Blog /Random-KO /Spring Boot Pt.2를 사용하여 텔레그램 봇 만들기: 퀴즈 봇
Whiskels
레벨 41
Москва

Spring Boot Pt.2를 사용하여 텔레그램 봇 만들기: 퀴즈 봇

Random-KO 그룹에 게시되었습니다
1부 알로하! 이전 기사에서는 모든 이벤트에 우리를 환영하는 간단한 봇을 만들었습니다. 우리는 이미 수천 줄의 코드를 작성했으며 이제 봇에 더 복잡한 기능을 추가할 때입니다. 오늘 우리는 인터뷰 전에 자유 시간에 Java Core에 대한 지식을 연마할 수 있도록 간단한 봇을 작성하려고 노력할 것입니다(놀랍게도 이런 종류의 작동하는 봇은 단 하나도 찾지 못했습니다). 이를 위해 우리는 다음을 수행할 것입니다:
  • 외부 Postgres 데이터베이스를 Heroku에 연결합니다.
  • 데이터베이스를 초기화하고 채우는 첫 번째 스크립트를 작성해 보겠습니다.
  • 데이터베이스 작업을 위해 Spring Boot Data JPA를 연결해 보겠습니다.
  • 우리는 봇 동작에 대한 다양한 시나리오를 구현합니다.
이것이 당신에게 흥미롭고 기사를 읽은 후 썩은 토마토를 던지고 싶지 않다면 내 저장소 에 별표를 추가하면 기뻐할 것입니다! 질문이 있으시면 댓글을 통해 논의해 보겠습니다. 이것이 우리가 결국 얻게 될 것입니다: Spring Boot Pt.2를 사용하여 텔레그램 봇 만들기: 퀴즈 봇 - 1Spring Boot Pt.2를 사용하여 텔레그램 봇 만들기: Quiz Bot - 2Spring Boot를 사용하여 텔레그램 봇 만들기 Pt.2: Quiz Bot - 3<h2>자, 가자!</h2><h3>Heroku에 데이터베이스 만들기</h3>첫 번째 외부 데이터베이스를 만드는 것부터 시작하겠습니다. 로컬 작업의 경우 pgAdmin을 확인하는 것이 좋습니다 . 하지만 앞으로는 봇이 로컬 컴퓨터에 의존하지 않도록 Heroku에 배포할 것이라는 사실을 명심하고 이를 위해 이 서비스에 대해 알아두시기 바랍니다. 절차:
  • Heroku 에 등록하세요 .
  • 대시보드 -> 새로 만들기 -> 새 앱 만들기 로 이동하여 새 애플리케이션을 만듭니다.
  • 새로 생성된 응용 프로그램으로 이동하면 많은 버튼이 겁이 나지만 "설치된 추가 기능" 패널에 집중합니다. 그 옆에는 추가 기능 구성 버튼이 있으며 클릭합니다.
  • 검색에 "Heroku Postgres"를 입력하고 "Hobby Dev - Free" 플랜 -> 주문 양식 제출을 선택합니다.
  • 새로 얻은 데이터베이스 -> 설정 -> 자격 증명 보기를 엽니다. 이 탭에는 데이터베이스에 액세스하기 위한 키가 포함되어 있습니다. 우리는 그들의 위치를 ​​기억합니다. IDEA의 DataSource와 같이 데이터베이스를 연결하는 데 필요합니다.
<h3>pom.xml에 종속성 추가</h3>봇 작업의 일환으로 Lombok, Spring Boot Data JPA, PostgreSQL과 같은 종속성을 pom에 추가합니다. 멈추다! 이 모든 것이 무엇이며 왜 추가합니까?
  • Lombok은 다양한 코드의 양을 크게 줄여주는 라이브러리입니다. 이를 통해 생성자, 설정자, 게터 등을 자동으로 생성할 수 있습니다.
  • Spring Data JPA 는 데이터베이스 작업을 위한 프레임워크 입니다 (너무 단순해 보이지만). Spring Data JPA의 기능에 대한 설명은 일련의 기사에 해당하며 우리가 지정한 종속성에는 Hibernate 등이 포함되므로 세부 사항은 건너뛰고 오늘은 Spring JPA를 사용하여 무언가를 작성해 보겠습니다.
  • PostgreSQL - 데이터베이스에서 작동할 드라이버를 얻기 위해 라이브러리를 가져옵니다.
pom.xml은 다음과 같이 보입니다. 속성: Spring Boot Pt.2를 사용하여 텔레그램 봇 만들기: 퀴즈 봇 - 1종속성: Spring Boot Pt.2를 사용하여 텔레그램 봇 만들기: Quiz Bot - 2이전 기사를 읽지 않았다면 여기에 새 종속성을 추가하므로 이것이 완전한 pom.xml 구조가 아니라는 점에 유의하세요. 이러한 종속성을 우리 프로젝트에 로드하는 것을 잊지 마세요(예: Maven -> 모든 Maven 프로젝트 다시 가져오기 창으로 이동).<h3>IDEA에서 데이터베이스 연결</h3>IDEA Community Edition을 사용하는 경우 다음을 수행할 수 있습니다. 다음 과 같이 DataSource 탭을 활성화합니다 . 플러그인을 추가한 후에는 DataSource를 구성해야 합니다. 이렇게 하려면 먼저 플러그인 표시를 활성화하십시오: 보기 -> 도구 창 -> DB 브라우저. 열리는 창에서 녹색 더하기(새 연결) -> PostgreSQL을 클릭합니다. 여기에는 이미 Heroku에서 본 자격 증명이 필요합니다. 창을 작성 Spring Boot를 사용하여 텔레그램 봇 만들기 Pt.2: Quiz Bot - 3하고 "연결 테스트"를 클릭하세요. 모든 작업이 올바르게 완료되면 데이터베이스 연결에 성공했음을 알리는 팝업 창이 나타납니다. 데이터 소스를 저장합니다.<h3>데이터베이스에 테이블 생성</h3>이제 작업할 테이블을 생성해 보겠습니다. 먼저 PostgreSQL을 설치해보겠습니다 . 설치 후 src/main/resources 폴더에 initDB.sql 파일을 생성합니다.
DROP TABLE IF EXISTS java_quiz;
DROP TABLE IF EXISTS users;
CREATE SEQUENCE global_seq START WITH 100000;

CREATE TABLE users
(
    id         INTEGER PRIMARY KEY DEFAULT nextval('global_seq'),
    chat_id    INTEGER UNIQUE                NOT NULL,
    name       VARCHAR                       NOT NULL,
    score      INTEGER             DEFAULT 0 NOT NULL,
    high_score INTEGER             DEFAULT 0 NOT NULL,
    bot_state  VARCHAR                       NOT NULL
);

CREATE TABLE java_quiz
(
    id             INTEGER PRIMARY KEY DEFAULT nextval('global_seq'),
    question       VARCHAR NOT NULL,
    answer_correct VARCHAR NOT NULL,
    option1        VARCHAR NOT NULL,
    option2        VARCHAR NOT NULL,
    option3        VARCHAR NOT NULL
);
우리 스크립트는 무엇을 합니까? 처음 두 줄은 테이블이 있으면 삭제하여 다시 만듭니다. 세 번째 줄은 데이터베이스에 고유 ID 항목을 생성하는 데 사용되는 시퀀스를 생성합니다. 다음으로 사용자용 테이블과 질문용 테이블을 만듭니다. 사용자는 고유 ID, 텔레그램 채팅 ID, 이름, 포인트 수(현재 및 최대), 봇의 현재 상태를 갖게 됩니다. 질문에는 고유 ID와 질문 및 답변 옵션을 담당하는 필드도 있습니다. 결과 스크립트를 마우스 오른쪽 버튼으로 클릭하고 "SQL 스크립트 실행"을 선택하여 실행할 수 있습니다. "Cmd-Line 인터페이스" 항목에 특별한 주의를 기울여야 합니다. 여기서는 새로 설치된 PostgreSQL이 필요합니다. 이 필드를 구성할 때 "새 Cmd-Line 인터페이스"를 선택하고 psql.exe에 대한 경로를 지정하십시오. 결과적으로 설정은 다음과 같아야 합니다. Spring Boot를 사용하여 텔레그램 봇 만들기 Pt.2: Quiz Bot - 4스크립트를 실행하고 아무데도 실수하지 않았다면 작업 결과는 다음과 같습니다. Spring Boot Pt.2를 사용하여 텔레그램 봇 만들기: Quiz Bot - 8<h3>모델 만들기</h3>이제 시간입니다. Java 코드 작성으로 돌아가려면 글의 내용을 간략화하기 위해 클래스 작성에 사용된 주석에 대한 설명은 여러분이 숙지하실 수 있도록 생략하겠습니다. 세 가지 클래스가 있는 모델 패키지를 만들어 보겠습니다.
  • AbstractBaseEntity 는 ID를 가질 수 있는 모든 개체를 설명하는 클래스입니다(이 클래스는 인턴십에서 볼 수 있는 내용을 강력하게 단순화한 것입니다).
    package com.whiskels.telegram.model;
    
    import lombok.Getter;
    import lombok.Setter;
    
    import javax.persistence.*;
    // Аннотация, которая говорит нам, что это суперкласс для всех Entity
    // https://vladmihalcea.com/how-to-inherit-properties-from-a-base-class-entity-using-mappedsuperclass-with-jpa-and-hibernate/
    @MappedSuperclass
    // http://stackoverflow.com/questions/594597/hibernate-annotations-which-is-better-field-or-property-access
    @Access(AccessType.FIELD)
    
    // Аннотации Lombok для автогенерации сеттеров и геттеров на все поля
    @Getter
    @Setter
    public abstract class AbstractBaseEntity {
    
    // Аннотации, описывающие механизм генерации id - разберитесь в documentации каждой!
        @Id
        @SequenceGenerator(name = "global_seq", sequenceName = "global_seq", allocationSize = 1, initialValue = START_SEQ)
        @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "global_seq")
    //  See https://hibernate.atlassian.net/browse/HHH-3718 and https://hibernate.atlassian.net/browse/HHH-12034
    //  Proxy initialization when accessing its identifier managed now by JPA_PROXY_COMPLIANCE setting
        protected Integer id;
    
        protected AbstractBaseEntity() {
        }
    }
  • 사용자 :
    package com.whiskels.telegram.model;
    
    import com.whiskels.telegram.bot.State;
    import lombok.AllArgsConstructor;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    import lombok.Setter;
    import org.hibernate.annotations.BatchSize;
    
    import javax.persistence.*;
    import javax.validation.constraints.NotBlank;
    import javax.validation.constraints.NotNull;
    import java.util.Set;
    
    import static javax.persistence.FetchType.EAGER;
    
    @Entity
    @Table(name = "users", uniqueConstraints = {@UniqueConstraint(columnNames = "chat_id", name = "users_unique_chatid_idx")})
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public class User extends AbstractBaseEntity {
        @Column(name = "chat_id", unique = true, nullable = false)
        @NotNull
        private Integer chatId;
    
        @Column(name = "name", unique = true, nullable = false)
        @NotBlank
        private String name;
    
        @Column(name = "score", nullable = false)
        @NotNull
        private Integer score;
    
        @Column(name = "high_score", nullable = false)
        @NotNull
        private Integer highScore;
    
        @Column(name = "bot_state", nullable = false)
        @NotBlank
        private State botState;
    
    // Конструктор нужен для создания нового пользователя (а может и нет? :))
        public User(int chatId) {
            this.chatId = chatId;
            this.name = String.valueOf(chatId);
            this.score = 0;
            this.highScore = 0;
            this.botState = State.START;
        }
    }
  • 질문 클래스 :
    package com.whiskels.telegram.model;
    
    import lombok.AllArgsConstructor;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    import lombok.Setter;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.Table;
    import javax.validation.constraints.NotBlank;
    
    @Entity
    @Table(name = "java_quiz")
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public class Question extends AbstractBaseEntity {
        @Column(name = "question", nullable = false)
        @NotBlank
        private String question;
    
        @Column(name = "answer_correct", nullable = false)
        @NotBlank
        private String correctAnswer;
    
        @Column(name = "option2", nullable = false)
        @NotBlank
        private String optionOne;
    
        @Column(name = "option1", nullable = false)
        @NotBlank
        private String optionTwo;
    
        @Column(name = "option3", nullable = false)
        @NotBlank
        private String optionThree;
    
        @Override
        public String toString() {
            return "Question{" +
                    "question='" + question + '\'' +
                    ", correctAnswer='" + correctAnswer + '\'' +
                    ", optionOne='" + optionOne + '\'' +
                    ", optionTwo='" + optionTwo + '\'' +
                    ", optionThree='" + optionThree + '\'' +
                    '}';
        }
    }
<h3>저장소 생성</h3>이제 Spring Data Jpa 저장소를 작성해 보겠습니다. JpaUserRepository, JpaQuestionRepository라는 두 가지 인터페이스를 갖는 저장소 패키지를 만듭니다 . 이는 실제로 마법을 생성할 수 있는 Spring Data 인터페이스인 JpaRepository에서 상속됩니다. 그들의 작업을 이해하려면 Evgeny Borisov의 비디오를 시청하는 것이 좋습니다 . 수업은 매우 작습니다.
  • JpaUserRepository:
    package com.whiskels.telegram.repository;
    
    import com.whiskels.telegram.model.User;
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.stereotype.Repository;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.util.Optional;
    
    @Repository
    @Transactional(readOnly = true)
    public interface JpaUserRepository extends JpaRepository<user, integer=""> {
    // По названию метода Spring сам поймет, что мы хотим получить пользователя по переданному chatId
        Optional<user> getByChatId(int chatId);
    }
    </user></user,>
  • JpaQuestion저장소:
    package com.whiskels.telegram.repository;
    
    import com.whiskels.telegram.model.Question;
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.data.jpa.repository.Query;
    
    @Repository
    @Transactional(readOnly = true)
    public interface JpaQuestionRepository extends JpaRepository<question, integer=""> {
    // А здесь мы написали SQL Query, которая будет выбирать 1 случайный вопрос из таблицы вопросов
        @Query(nativeQuery = true, value = "SELECT *  FROM java_quiz ORDER BY random() LIMIT 1")
        Question getRandomQuestion();
    }
    </question,>
<h3>봇에 기능 추가</h3> User 클래스에는 아직 생성되지 않은 State 클래스 필드가 있는데 , 이는 사용자가 현재 봇 작업의 어느 단계에 있는지 알려줍니다. /bot 패키지에서 생성해 보겠습니다.
package com.whiskels.telegram.bot;

public enum State {
    NONE,
    START,
    ENTER_NAME,
    PLAYING_QUIZ,
}
다음으로 핸들러 인터페이스를 선언할 봇/핸들러 패키지를 생성합니다.
package com.whiskels.telegram.bot.handler;

import com.whiskels.telegram.bot.State;
import com.whiskels.telegram.model.User;
import org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod;

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

public interface Handler {

// основной метод, который будет обрабатывать действия пользователя
    List<partialbotapimethod<? extends="" serializable="">> handle(User user, String message);
// метод, который позволяет узнать, можем ли мы обработать текущий State у пользователя
    State operatedBotState();
// метод, который позволяет узнать, Howие команды CallBackQuery мы можем обработать в этом классе
    List<string> operatedCallBackQuery();
}

</string></partialbotapimethod<?>
잠시 후에 핸들러를 생성할 예정이지만 지금은 봇 패키지의 루트에 생성할 새로운 UpdateReceiver 클래스에 이벤트 처리를 위임하겠습니다. 주의! 여기에는 List> handler(args);로 표시되는 메소드가 있습니다. 실제로는 다음과 같이 보이지만 코드 포맷터가 이를 깨뜨렸습니다.Spring Boot Pt.2를 사용하여 텔레그램 봇 만들기: 퀴즈 봇 - 6
package com.whiskels.telegram.bot;

import com.whiskels.telegram.bot.handler.Handler;
import com.whiskels.telegram.model.User;
import com.whiskels.telegram.repository.JpaUserRepository;
import org.springframework.stereotype.Component;
import org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod;
import org.telegram.telegrambots.meta.api.objects.CallbackQuery;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.api.objects.Update;

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

@Component
public class UpdateReceiver {
    // Храним доступные хендлеры в списке (подсмотрел у Miroha)
    private final List<handler> handlers;
    // Имеем доступ в базу пользователей
    private final JpaUserRepository userRepository;

    public UpdateReceiver(List<handler> handlers, JpaUserRepository userRepository) {
        this.handlers = handlers;
        this.userRepository = userRepository;
    }

    // Обрабатываем полученный Update
    public List<partialbotapimethod<? extends="" serializable="">> handle(Update update) {
        // try-catch, чтобы при несуществующей команде просто возвращать пустой список
        try {
            // Проверяем, если Update - сообщение с текстом
            if (isMessageWithText(update)) {
                // Получаем Message из Update
                final Message message = update.getMessage();
                // Получаем айди чата с пользователем
                final int chatId = message.getFrom().getId();

                // Просим у репозитория пользователя. Если такого пользователя нет - создаем нового и возвращаем его.
                // Как раз на случай нового пользователя мы и сделали конструктор с одним параметром в классе User
                final User user = userRepository.getByChatId(chatId)
                        .orElseGet(() -> userRepository.save(new User(chatId)));
                // Ищем нужный обработчик и возвращаем результат его работы
                return getHandlerByState(user.getBotState()).handle(user, message.getText());

            } else if (update.hasCallbackQuery()) {
                final CallbackQuery callbackQuery = update.getCallbackQuery();
                final int chatId = callbackQuery.getFrom().getId();
                final User user = userRepository.getByChatId(chatId)
                        .orElseGet(() -> userRepository.save(new User(chatId)));

                return getHandlerByCallBackQuery(callbackQuery.getData()).handle(user, callbackQuery.getData());
            }

            throw new UnsupportedOperationException();
        } catch (UnsupportedOperationException e) {
            return Collections.emptyList();
        }
    }

    private Handler getHandlerByState(State state) {
        return handlers.stream()
                .filter(h -> h.operatedBotState() != null)
                .filter(h -> h.operatedBotState().equals(state))
                .findAny()
                .orElseThrow(UnsupportedOperationException::new);
    }

    private Handler getHandlerByCallBackQuery(String query) {
        return handlers.stream()
                .filter(h -> h.operatedCallBackQuery().stream()
                        .anyMatch(query::startsWith))
                .findAny()
                .orElseThrow(UnsupportedOperationException::new);
    }

    private boolean isMessageWithText(Update update) {
        return !update.hasCallbackQuery() && update.hasMessage() && update.getMessage().hasText();
    }
}

</partialbotapimethod<?></handler></handler>
그리고 Bot 클래스에서 처리를 위임합니다.
package com.whiskels.telegram.bot;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.telegram.telegrambots.bots.TelegramLongPollingBot;
import org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod;
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;

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

@Slf4j
@Component
public class Bot extends TelegramLongPollingBot {
    @Value("${bot.name}")
    @Getter
    private String botUsername;

    @Value("${bot.token}")
    @Getter
    private String botToken;

    private final UpdateReceiver updateReceiver;

    public Bot(UpdateReceiver updateReceiver) {
        this.updateReceiver = updateReceiver;
    }

    @Override
    public void onUpdateReceived(Update update) {
        List<partialbotapimethod<? extends="" serializable="">> messagesToSend = updateReceiver.handle(update);

        if (messagesToSend != null && !messagesToSend.isEmpty()) {
            messagesToSend.forEach(response -> {
                if (response instanceof SendMessage) {
                    executeWithExceptionCheck((SendMessage) response);
                }
            });
        }
    }

    public void executeWithExceptionCheck(SendMessage sendMessage) {
        try {
            execute(sendMessage);
        } catch (TelegramApiException e) {
            log.error("oops");
        }
    }
}

</partialbotapimethod<?>
이제 봇이 이벤트 처리를 UpdateReceiver 클래스에 위임 하지만 아직 핸들러가 없습니다. 만들어 봅시다! 부인 성명! 저는 그러한 봇을 작성할 수 있는 가능성을 정말로 공유하고 싶었습니다. 따라서 추가 코드(원칙적으로 UpdateReceiver 코드와 같은)는 다양한 패턴을 사용하여 매우 잘 리팩토링될 수 있습니다. 하지만 우리는 학습 중이고 우리의 목표는 최소한으로 실행 가능한 봇이므로 또 다른 숙제로 여러분이 본 모든 것을 리팩토링할 수 있습니다 :) util 패키지를 만들고 그 안에 TelegramUtil 클래스를 만듭니다 .
package com.whiskels.telegram.util;

import com.whiskels.telegram.model.User;
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton;

public class TelegramUtil {
    public static SendMessage createMessageTemplate(User user) {
        return createMessageTemplate(String.valueOf(user.getChatId()));
    }

    // Создаем шаблон SendMessage с включенным Markdown
    public static SendMessage createMessageTemplate(String chatId) {
        return new SendMessage()
                .setChatId(chatId)
                .enableMarkdown(true);
    }

    // Создаем кнопку
    public static InlineKeyboardButton createInlineKeyboardButton(String text, String command) {
        return new InlineKeyboardButton()
                .setText(text)
                .setCallbackData(command);
    }
}
HelpHandler, QuizHandler, RegistrationHandler, StartHandler의 네 가지 핸들러를 작성하겠습니다. 시작 핸들러:
package com.whiskels.telegram.bot.handler;

import com.whiskels.telegram.bot.State;
import com.whiskels.telegram.model.User;
import com.whiskels.telegram.repository.JpaUserRepository;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod;
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;

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

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

@Component
public class StartHandler implements Handler {
    @Value("${bot.name}")
    private String botUsername;

    private final JpaUserRepository userRepository;

    public StartHandler(JpaUserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public List<partialbotapimethod<? extends="" serializable="">> handle(User user, String message) {
        // Приветствуем пользователя
        SendMessage welcomeMessage = createMessageTemplate(user)
                .setText(String.format(
                        "Hola! I'm *%s*%nI am here to help you learn Java", botUsername
                ));
        // Просим назваться
        SendMessage registrationMessage = createMessageTemplate(user)
                .setText("In order to start our journey tell me your name");
        // Меняем пользователю статус на - "ожидание ввода имени"
        user.setBotState(State.ENTER_NAME);
        userRepository.save(user);

        return List.of(welcomeMessage, registrationMessage);
    }

    @Override
    public State operatedBotState() {
        return State.START;
    }

    @Override
    public List<string> operatedCallBackQuery() {
        return Collections.emptyList();
    }
}

</string></partialbotapimethod<?>
등록 처리기:
package com.whiskels.telegram.bot.handler;

import com.whiskels.telegram.bot.State;
import com.whiskels.telegram.model.User;
import com.whiskels.telegram.repository.JpaUserRepository;
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.List;

import static com.whiskels.telegram.bot.handler.QuizHandler.QUIZ_START;
import static com.whiskels.telegram.util.TelegramUtil.createInlineKeyboardButton;
import static com.whiskels.telegram.util.TelegramUtil.createMessageTemplate;

@Component
public class RegistrationHandler implements Handler {
    //Храним поддерживаемые CallBackQuery в виде констант
    public static final String NAME_ACCEPT = "/enter_name_accept";
    public static final String NAME_CHANGE = "/enter_name";
    public static final String NAME_CHANGE_CANCEL = "/enter_name_cancel";

    private final JpaUserRepository userRepository;

    public RegistrationHandler(JpaUserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public List<partialbotapimethod<? extends="" serializable="">> handle(User user, String message) {
        // Проверяем тип полученного события
        if (message.equalsIgnoreCase(NAME_ACCEPT) || message.equalsIgnoreCase(NAME_CHANGE_CANCEL)) {
            return accept(user);
        } else if (message.equalsIgnoreCase(NAME_CHANGE)) {
            return changeName(user);
        }
        return checkName(user, message);

    }

    private List<partialbotapimethod<? extends="" serializable="">> accept(User user) {
        // Если пользователь принял Name - меняем статус и сохраняем
        user.setBotState(State.NONE);
        userRepository.save(user);

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

        List<inlinekeyboardbutton> inlineKeyboardButtonsRowOne = List.of(
                createInlineKeyboardButton("Start quiz", QUIZ_START));

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

        return List.of(createMessageTemplate(user).setText(String.format(
                "Your name is saved as: %s", user.getName()))
                .setReplyMarkup(inlineKeyboardMarkup));
    }

    private List<partialbotapimethod<? extends="" serializable="">> checkName(User user, String message) {
        // При проверке имени мы превентивно сохраняем пользователю новое Name в базе
        // идея для рефакторинга - добавить временное хранение имени
        user.setName(message);
        userRepository.save(user);

        // Doing кнопку для применения изменений
        InlineKeyboardMarkup inlineKeyboardMarkup = new InlineKeyboardMarkup();

        List<inlinekeyboardbutton> inlineKeyboardButtonsRowOne = List.of(
                createInlineKeyboardButton("Accept", NAME_ACCEPT));

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

        return List.of(createMessageTemplate(user)
                .setText(String.format("You have entered: %s%nIf this is correct - press the button", user.getName()))
                .setReplyMarkup(inlineKeyboardMarkup));
    }

    private List<partialbotapimethod<? extends="" serializable="">> changeName(User user) {
        // При requestе изменения имени мы меняем State
        user.setBotState(State.ENTER_NAME);
        userRepository.save(user);

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

        List<inlinekeyboardbutton> inlineKeyboardButtonsRowOne = List.of(
                createInlineKeyboardButton("Cancel", NAME_CHANGE_CANCEL));

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

        return List.of(createMessageTemplate(user).setText(String.format(
                "Your current name is: %s%nEnter new name or press the button to continue", user.getName()))
                .setReplyMarkup(inlineKeyboardMarkup));
    }

    @Override
    public State operatedBotState() {
        return State.ENTER_NAME;
    }

    @Override
    public List<string> operatedCallBackQuery() {
        return List.of(NAME_ACCEPT, NAME_CHANGE, NAME_CHANGE_CANCEL);
    }
}

</string></inlinekeyboardbutton></partialbotapimethod<?></inlinekeyboardbutton></partialbotapimethod<?></inlinekeyboardbutton></partialbotapimethod<?></partialbotapimethod<?>
도움말 핸들러:
package com.whiskels.telegram.bot.handler;

import com.whiskels.telegram.bot.State;
import com.whiskels.telegram.model.User;
import org.springframework.stereotype.Component;
import org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod;
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
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.bot.handler.RegistrationHandler.NAME_CHANGE;
import static com.whiskels.telegram.util.TelegramUtil.createInlineKeyboardButton;
import static com.whiskels.telegram.util.TelegramUtil.createMessageTemplate;

@Component
public class HelpHandler implements Handler {

    @Override
    public List<partialbotapimethod<? extends="" serializable="">> handle(User user, String message) {
        // Создаем кнопку для смены имени
        InlineKeyboardMarkup inlineKeyboardMarkup = new InlineKeyboardMarkup();

        List<inlinekeyboardbutton> inlineKeyboardButtonsRowOne = List.of(
                createInlineKeyboardButton("Change name", NAME_CHANGE));

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

        return List.of(createMessageTemplate(user).setText(String.format("" +
                "You've asked for help %s? Here it comes!", user.getName()))
        .setReplyMarkup(inlineKeyboardMarkup));

    }

    @Override
    public State operatedBotState() {
        return State.NONE;
    }

    @Override
    public List<string> operatedCallBackQuery() {
        return Collections.emptyList();
    }
}

</string></inlinekeyboardbutton></partialbotapimethod<?>
QuizHandler(최악
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION