JavaRush /בלוג Java /Random-HE /יצירת בוט טלגרם באמצעות Spring Boot Pt.2: Quiz Bot
Whiskels
רָמָה
Москва

יצירת בוט טלגרם באמצעות Spring Boot Pt.2: Quiz Bot

פורסם בקבוצה
חלק 1 אלוהה! במאמר הקודם יצרנו בוט פשוט שקיבל את פנינו לכל אירוע. כבר כתבנו אלפי שורות קוד, והגיע הזמן להוסיף פונקציונליות מורכבת יותר לבוט שלנו. היום ננסה לכתוב בוט פשוט כדי שבזמננו הפנוי נוכל לחדד את הידע שלנו ב-Java Core לפני ראיונות (באופן מפתיע, לא מצאתי בוט עובד אחד מהסוג הזה). לשם כך נעשה את הפעולות הבאות:
  • חבר מסד נתונים חיצוני Postgres להרוקו;
  • בואו נכתוב את הסקריפטים הראשונים שלנו כדי לאתחל ולאכלס את מסד הנתונים;
  • בואו נחבר את Spring Boot Data JPA לעבודה עם מסד הנתונים;
  • אנו מיישמים תרחישים שונים של התנהגות בוטים.
אם זה מעניין אותך, ואחרי שקראת את המאמר אתה לא רוצה לזרוק עגבניות רקובות, אז שים כוכב במאגר שלי , אני אהיה מרוצה! אם יש לך שאלות, נדון בהן בהערות. זה מה שנקבל בסופו של דבר: יצירת בוט טלגרם באמצעות Spring Boot Pt.2: Quiz Bot - 1יצירת בוט טלגרם באמצעות Spring Boot Pt.2: Quiz Bot - 2יצירת בוט טלגרם באמצעות Spring Boot Pt.2: Quiz Bot - 3<h2>אז, בוא נלך!</h2><h3>יצירת מסד נתונים ב-Heroku</h3>בוא נתחיל ביצירת מסד הנתונים החיצוני הראשון שלנו. עבור עבודה מקומית, אני ממליץ לבדוק את pgAdmin . אבל אני רוצה שתהיה מונחה על ידי העובדה שבעתיד תפרוס את הבוט ל-Heroku כך שהוא לא יהיה תלוי במחשב המקומי שלך, ולשם כך בואו להכיר את השירות הזה. תהליך:
  • הירשם ב- Heroku ;
  • עבור ללוח המחוונים שלנו -> חדש -> צור אפליקציה חדשה וצור אפליקציה חדשה;
  • אנחנו נכנסים לאפליקציה החדשה שנוצרה, אנחנו מפחדים מהלחצנים הרבים, אבל אנחנו מתרכזים בחלונית "תוספות מותקנות" - לידו יש כפתור Configure Add-ons, אנחנו לוחצים עליו;
  • הזן "Heroku Postgres" לחיפוש, בחר בתוכנית "הובי Dev - חינם" -> שלח טופס הזמנה;
  • פתח את מסד הנתונים החדש שהושג -> הגדרות -> הצג אישורים. כרטיסייה זו תכיל את המפתחות שלנו לגישה למסד הנתונים. אנחנו זוכרים את מיקומם - נצטרך אותם כדי לחבר את מסד הנתונים, כמו DataSource ב-IDEA.
<h3>הוסף תלות ל-pom.xml</h3>כחלק מהעבודה על הבוט שלנו, נוסיף את התלות הבאות ל-pom שלנו: Lombok, Spring Boot Data JPA, PostgreSQL. תפסיק! מה זה כל זה ולמה אנחנו מוסיפים את זה?
  • Lombok היא ספרייה שבזכותה נצמצם משמעותית את כמות הקוד השונה. בעזרתו נוכל ליצור באופן אוטומטי בנאים, מגדירים, מגברים ועוד הרבה יותר.
  • Spring Data JPA היא מסגרת לעבודה עם מסדי נתונים (למרות שזה נשמע פשוט מדי). תיאור היכולות של Spring Data JPA יסתכם בסדרה של מאמרים, והתלות שציינו טומנת בחובה גם Hibernate ועוד הרבה יותר, אז בואו נדלג על הפרטים ופשוט ננסה לכתוב משהו באמצעות Spring JPA היום.
  • PostgreSQL - אנו מושכים את הספרייה כדי לקבל דרייבר שיעבוד עם מסד הנתונים שלנו.
ה-pom.xml שלנו מתחיל להיראות כך: מאפיינים: יצירת בוט טלגרם באמצעות Spring Boot Pt.2: Quiz Bot - 1תלות: יצירת בוט טלגרם באמצעות Spring Boot Pt.2: Quiz Bot - 2אם לא קראת את המאמר הקודם, שים לב שאנו מוסיפים כאן תלות חדשה, כך שזה לא המבנה המלא של pom.xml. אל תשכח לטעון את התלות הללו לתוך הפרויקט שלנו (לדוגמה, על ידי מעבר לחלון Maven -> ייבא מחדש את כל הפרויקטים של Maven).<h3>חיבור מסד הנתונים ב-IDEA</h3>אם אתה משתמש ב-IDEA Community Edition, אתה יכול הפעל את הכרטיסייה DataSource באופן הבא . לאחר הוספת התוסף, עלינו להגדיר את DataSource. לשם כך, הפעל תחילה את תצוגת הפלאגין: View -> Tool Windows -> DB Browser. בחלון שנפתח לחצו על הפלוס הירוק (חיבור חדש) -> PostgreSQL. כאן נצטרך אישורים, שכבר ראינו ב-Heroku. מלא את החלון: יצירת בוט טלגרם באמצעות Spring Boot Pt.2: Quiz Bot - 3ולחץ על "בדוק חיבור". אם הכל נעשה כהלכה, יופיע חלון מוקפץ המציין שהחיבור למסד הנתונים הצליח. שמור את מקור הנתונים שלנו.<h3>צור טבלאות במסד הנתונים</h3>עכשיו בואו ניצור את הטבלאות איתן נעבוד. ראשית, בואו נתקין את PostgreSQL . לאחר ההתקנה, צור את הקובץ initDB.sql בתיקיית src/main/resources:
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
);
מה עושה התסריט שלנו? שתי השורות הראשונות מוחקות טבלאות, אם קיימות, כדי ליצור אותן מחדש. השורה השלישית יוצרת רצף שישמש ליצירת ערכי זיהוי ייחודיים במסד הנתונים שלנו. לאחר מכן, אנו יוצרים שתי טבלאות: למשתמשים ולשאלות. למשתמש יהיה מזהה ייחודי, מזהה צ'אט בטלגרם, שם, מספר נקודות (נוכחי ומקסימום), כמו גם המצב הנוכחי של הבוט. לשאלות יהיה גם מזהה ייחודי, כמו גם שדות שאחראים לאפשרויות השאלה והתשובה עבורו. נוכל להפעיל את הסקריפט שנוצר על ידי לחיצה ימנית עליו ובחירה ב"הפעל סקריפט 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 היא מחלקה שמתארת ​​כל אובייקט שיכול להיות בעל מזהה (מחלקה זו היא פשטות חזקה של מה שאתה עשוי לראות בהתמחות):
    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. הם יירשו מ-JpaRepository, ממשק Spring Data המאפשר לנו ליצור קסם באופן מעשי. כדי להבין את עבודתם, אני ממליץ לצפות בסרטון של יבגני בוריסוב . הכיתות יהיו קטנות מאוד:
  • 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,>
  • JpaQuestionRepository:
    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 יש לנו שדה של מחלקת המדינה שטרם נוצרה , שיגיד לנו באיזה שלב של העבודה עם הבוט נמצא המשתמש כרגע. בואו ניצור אותו בחבילת /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 , אותה ניצור בשורש חבילת הבוט: ATTENTION! כאן ועוד יהיו שיטות שיוצגו בתור List> handle(args); במציאות הם נראים כך, אבל פורמט הקוד שבר אותם:יצירת בוט טלגרם באמצעות Spring Boot Pt.2: Quiz Bot - 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. 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