JavaRush /Java блог /Random UA /Telegram bot - нагадування через webHook на Java або скаж...
Vladimir Popov
41 рівень

Telegram bot - нагадування через webHook на Java або скажи ні Google-календарю! Частина 1

Стаття з групи Random UA
Мене звати Владимир. Мені 43 роки. І якщо тобі, читачу за 40, то так, після 40 можна стати програмістом, якщо тобі це подобається. Моя робота взагалі ніяк не пов'язана з програмуванням, я керівник проекту в галузі автоматизації та всього такого. Але планую змінити рід діяльності. Ох вже ці нові віяння... змінювати сферу діяльності раз на 5-7 років. Так ось : Проект вийшов не маленький так що деякі моменти доведеться опускати або говорити про них коротко, сподіваючись, що читач вміє гуглити. На просторах інтернетів повно публікацій telegram-ботів, що працюють за принципом Long polling. І дуже мало, що працюють за принципом Webhook. Що це таке? Long polling- означає ваш додаток сам опитуватиме сервер telegram на предмет наявності повідомлень з певною періодичністю, повільно. Webhook - означає сервер telegram миттєво перенаправлятиме повідомлення на вказаний вами сервер. У нашому випадку, люб'язно наданий сервіс Heroku. Докладніше про все це і взагалі про бот можна звичайно ж почитати на сайті Telegram - https://tlgrm.ru/docs/bots/api Інтерфейс бот виглядає ось так: Telegram bot - нагадування через webHook на Java або скажи ні Google-календарю!  - 1 Цей додаток я розглядаю саме як навчальний проект з тієї причини, що при написанні цього бота я засвоїв більше інформації, ніж при навчанні. Бажаєте навчитися програмувати? Почніть писати код! Але! Тут не буде докладних інструкцій, як залити додаток на github, як створити базу даних. Цього всього повно в інтернетах і розписано докладним чином, крім того, це і так вийде дуже довге читання. Додаток буде працювати наступним чином: Вводьте опис події, вводите дату та час події, вибираєте періодичність (можна на один раз, можна нагадування щодня у певний час, можна раз на місяць у певний час, або раз на рік). Варіації сповіщень можна допилювати нескінченно, у самих ідей багато. Далі введені дані зберігаються до бази даних (теж розгорнуто безкоштовно на heroku, безкоштовними є 10 000 рядків) Далі один раз на початку дня о 0:00 за серверним часом Spring витягує з бази всі події за критеріями, які повинні спрацювати в цей день і направляє їх на виконання у встановлений час. УВАГА!!! ЦЯ ЧАСТИНА ПРОГРАМИ ЕКСПЕРЕМЕНТАЛЬНА! ІСНУЄ РЕАЛІЗАЦІЯ БІЛЬШ ПРОСТІЙ І ВІРНА! ТАК ЗРОБЛЕНО СПЕЦІАЛЬНО, ЩОБ ПОДИВИТИСЯ ЯК ПРАЦЮЄ КЛАС Time! Доторкнутися своїми руками працюючого бота можна набравши в возі @calendar_event_bot але не розраховуйте на нього, бо я з нього все ще знущаюся. код - ТАК ЗРОБЛЕНО СПЕЦІАЛЬНО, ЩОБ ПОДИВИТИСЯ ЯК ПРАЦЮЄ КЛАС Time! Доторкнутися своїми руками працюючого бота можна набравши в возі @calendar_event_bot але не розраховуйте на нього, бо я з нього все ще знущаюся. код - ТАК ЗРОБЛЕНО СПЕЦІАЛЬНО, ЩОБ ПОДИВИТИСЯ ЯК ПРАЦЮЄ КЛАС Time! Доторкнутися своїми руками працюючого бота можна набравши в возі @calendar_event_bot але не розраховуйте на нього, бо я з нього все ще знущаюся. код -https://github.com/papoff8295/webHookBotForHabr В принципі, щоб запустити власного вам необхідно зробити наступні кроки: • Пройти реєстрацію в @BotFather , це не складно, отримати токен та ім'я • Зробити fork проекту на github • Зареєструватися на Heroku , створити додаток(покроково розбиратимемо), зробити deploy з вашого репозиторію. • Створити на Heroku базу даних • Замінити в репозиторії відповідні поля на свої (токен, назва таблиць по суті, webHookPath, user name, пароль та шлях до бази, це все буде розбирати) • Змусити heroku працювати 24/7 за допомогою https: / /uptimerobot.com/ Підсумкова структура проекту у мене вийшла така: Telegram bot - нагадування через webHook на Java або скажи ні Google-календарю!  - 2 Почнемо зі створення проекту https://start.spring.ioВибираємо потрібні нам залежності як показано на малюнку: Telegram bot - нагадування через webHook на Java або скажи ні Google-календарю!  - 3Вибираємо власну назву для проекту та натискаємо Generate . Далі вам запропонують зберегти проект на диск. Залишається відкрити файл pom.xm l зі свого середовища розробки. Перед Вами готовий проект. Тепер нам лишилося додати нашу основну бібліотеку. Я використовував бібліотеку з https://github.com/rubenlagus/TelegramBots Загалом можна заморочитися і обійтися без неї. Адже весь зміст роботи полягає в конкатенації URL на кшталт такого: https://api.telegram.org/bot1866835969:AAE6gJG6ptUyqhV2XX0MxyUak4QbAGGnz10/setWebhook?url=https://e9c658b548aa.ngro - Сервер telegram. bot1866835969:AAE6gJG6ptUyqhV2XX0MxyUak4QbAGGnz10/ - після слова bot- секретний токен, який отримуєте при реєстрації бота. setWebhook?url=https://e9c658b548aa.ngrok.io – назва методу та його параметри. В даному випадку ми встановлюємо Ваш webhook сервер, на нього будуть надходити всі повідомлення. Загалом, я вирішив, що проект і так не маленький для публікації, а з ручною реалізацією взагалі буде нечитабельним. Отже, кінцевий вид файлу pom :
<!--?xml version="1.0" encoding="UTF-8"?-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelversion>4.0.0</modelversion>
   <parent>
      <groupid>org.springframework.boot</groupid>
      <artifactid>spring-boot-starter-parent</artifactid>
      <version>2.5.0</version>
      <relativepath> <!-- lookup parent from repository -->
   </relativepath></parent>
   <groupid>ru.popov</groupid>
   <artifactid>telegrambot</artifactid>
   <version>0.0.1-SNAPSHOT</version>
   <name>telegrambot</name>
   <description>Demo project for Spring Boot</description>
   <properties>
      <java.version>1.8</java.version>
   </properties>
   <dependencies>
      <dependency>
         <groupid>org.springframework.boot</groupid>
         <artifactid>spring-boot-starter-web</artifactid>
      </dependency>

      <dependency>
         <groupid>org.springframework.boot</groupid>
         <artifactid>spring-boot-starter-data-jpa</artifactid>
      </dependency>

      <dependency>
         <groupid>org.springframework.boot</groupid>
         <artifactid>spring-boot-starter-test</artifactid>
         <scope>test</scope>
      </dependency>

      <!-- https://mvnrepository.com/artifact/org.telegram/telegrambots-spring-boot-starter -->
      <dependency>
         <groupid>org.telegram</groupid>
         <artifactid>telegrambots-spring-boot-starter</artifactid>
         <version>5.2.0</version>
      </dependency>

      <dependency>
         <groupid>org.projectlombok</groupid>
         <artifactid>lombok</artifactid>
         <version>1.18.16</version>
      </dependency>

      <dependency>
         <groupid>org.postgresql</groupid>
         <artifactid>postgresql</artifactid>
         <scope>runtime</scope>
      </dependency>

   </dependencies>

   <build>
      <plugins>
         <plugin>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-maven-plugin</artifactid>
         </plugin>
      </plugins>
   </build>

</project>
Все готове до написання нашого бота. Створимо клас TelegramBot . Назва папок я писати не буду, можете подивитися у структурі проекту вище.
@Getter
@Setter
@FieldDefaults(level = AccessLevel.PRIVATE)
public class TelegramBot extends SpringWebhookBot {
    String botPath;
    String botUsername;
    String botToken;

    private TelegramFacade telegramFacade;

    public TelegramBot(TelegramFacade telegramFacade, DefaultBotOptions options, SetWebhook setWebhook) {
        super(options, setWebhook);
        this.telegramFacade = telegramFacade;
    }
    public TelegramBot(TelegramFacade telegramFacade, SetWebhook setWebhook) {
        super(setWebhook);
        this.telegramFacade = telegramFacade;
    }

    @Override
    public BotApiMethod<!--?--> onWebhookUpdateReceived(Update update) {
        return telegramFacade.handleUpdate(update);
    }
}
Клас розширюється від SpringWebhookBot з нашої біліотеки telegram, і нам необхідно реалізувати всього один метод onWebhookUpdateReceived . Він приймає рапарсенний JSON як об'єкт Update і повертає те, що хоче від нас «почути» сервер telegram. Тут у нас зустрічаються інструкції з бібліотеки Lombok . Lombok – робимо життя програміста легшим!! Ну, тобто. нам не треба перевизначати гетери та сеттери, це робить за нас Lombok, а також не треба писати ідентифікатор рівня доступу. Вже й не варто писати, що цим займаються анотації @ Getter, @ Setter, @ FieldDefaults Поле botPath – означає нашу адресау webhook, яку ми отримаємо на Heroku пізніше. ПолеbotUsername – це назва нашого бота, яку ми отримаємо при реєстрації нашого бота в Telegram. Поле botToken це наш токен, який ми отримаємо при реєстрації нашого бота в Telegram. Поле telegramFacade - це наш клас, де відбуватиметься обробка повідомлень, до нього ми повернемося трохи пізніше, нехай він поки буде червоним. Тепер нам час звернутися до @BotFather і отримати заповітний botToken і botUsername. Telegram bot - нагадування через webHook на Java або скажи ні Google-календарю!  - 4Просто напишіть йому в telegram і він вам все підкаже. Записуємо дані в наш application.properties, повністю зрештою він виглядатиме ось так:
server#server.port=5000

telegrambot.userName=@calendar_event_bot
telegrambot.botToken=1731265488:AAFDjUSk3vu5SFfgdfh556gOOFmuml7SqEjwrmnEF5Ak
#telegrambot.webHookPath=https://telegrambotsimpl.herokuapp.com/
telegrambot.webHookPath=https://f5d6beeb7b93.ngrok.io


telegrambot.adminId=39376213

eventservice.period =600000

spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/telegramUsers
spring.datasource.username=postgres
spring.datasource.password=password

#spring.datasource.url=jdbc:postgresql:ec2-54-247-158-179.eu-west-1.compute.amazonaws.com:5432/d2um126le5notq?ssl=true&sslmode=require&sslfactory=org.postgresql.ssl.NonValidatingFactory
#spring.datasource.username=ulmbeywyhvsxa
#spring.datasource.password=4c7646c69dbbgeacb98fa96e8daa6d9b1bl4894e67f3f3ddd6a27fe7b0537fd
Ця конфігурація налаштована для роботи з локальною базою даних, згодом ми зробимо необхідні зміни. Замініть botToken та username на свої. Не можна використовувати дані з application.properties безпосередньо в програмі. Створимо з цих даних bean або клас обгортку.
@Component
@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)

public class TelegramBotConfig {
    @Value("${telegrambot.webHookPath}")
    String webHookPath;
    @Value("${telegrambot.userName}")
    String userName;
    @Value("${telegrambot.botToken}")
    String botToken;
Тут інструкція @Value ініціалізує відповідні рядки з файлу application.properties, про цю папку Spring знає за замовчуванням. А анотація @Component створює нам Bean під час запуску програми. Займемося тепер файлом конфігурації Spring:
@Configuration
public class AppConfig {
    private final TelegramBotConfig botConfig;

    public AppConfig(TelegramBotConfig botConfig) {
        this.botConfig = botConfig;
    }

    @Bean
    public SetWebhook setWebhookInstance() {
        return SetWebhook.builder().url(botConfig.getWebHookPath()).build();
    }

    @Bean
    public TelegramBot springWebhookBot(SetWebhook setWebhook, TelegramFacade telegramFacade) {
        TelegramBot bot = new TelegramBot(telegramFacade, setWebhook);
        bot.setBotToken(botConfig.getBotToken());
        bot.setBotUsername(botConfig.getUserName());
        bot.setBotPath(botConfig.getWebHookPath());

        return bot;
    }
}
Жодної магії тут немає, при старті Spring створює нам об'єкти SetWebhook і TelegramBot. Створимо тепер точки входу наших повідомлень:
@RestController
public class WebhookController {

    private final TelegramBot telegramBot;

    public WebhookController(TelegramBot telegramBot) {
        this.telegramBot = telegramBot;
    }

// point for message
    @PostMapping("/")
    public BotApiMethod<!--?--> onUpdateReceived(@RequestBody Update update) {
        return telegramBot.onWebhookUpdateReceived(update);
    }

    @GetMapping
    public ResponseEntity get() {
        return ResponseEntity.ok().build();
    }
}
Telegram сервер відправляє на зареєстровану адресау webhook повідомлення у форматі JSON методом POST, наш контролер їх приймає та передає бібліотеці telegram у вигляді об'єкта Update. Метод get я зробив просто так ) Тепер нам залишилося реалізувати якусь логіку обробки повідомлень та відповіді у класі TelegramFacade , я наведу його короткий код, щоб уже можна було запускати програму і далі йти своїм шляхом або перейти вже у deploy на Heroku, потім буде повна версія:
@Component
@FieldDefaults(level = AccessLevel.PRIVATE)
public class TelegramFacade {

    public BotApiMethod<!--?--> handleUpdate(Update update) {

        if (update.hasCallbackQuery()) {
            CallbackQuery callbackQuery = update.getCallbackQuery();
            return null;
        } else {

            Message message = update.getMessage();
            SendMessage sendMessage = new SendMessage();
            sendMessage.setChatId(String.valueOf(message.getChatId()));
            if (message.hasText()) {
                sendMessage.setText("Hello world");
                return sendMessage;
            }
        }
        return null;
    }

}
Цей метод відповідатиме на будь-яке повідомлення Hello world! Для того, щоб запустити наш додаток нам залишилося зробити так, щоб ми могли тестувати нашу програму прямо з IDEA. Для цього нам необхідно завантажити утиліту ngrok. https://ngrok.com/download Ця утиліта є командним рядком, який дає нам тимчасову адресау на 2 години і перенаправляє всі повідомлення на вказаний порт локального сервера. Запускаємо та пишемо у рядку ngrok http 5000 (або можете вказати свій порт): Telegram bot - нагадування через webHook на Java або скажи ні Google-календарю!  - 5Отримуємо результат: Telegram bot - нагадування через webHook на Java або скажи ні Google-календарю!  - 6https://23b1a54ccbbd.ngrok.io – це і є наша webhook адресаа. Як ви могли помітити у файлі properties ми написали server.port=5000 при старті сервера tomcat, він буде займати порт 5000, стежте, щоб ці поля були однакові. Також не забуваємо, що адресаа дається на дві години. У командному рядку за цим слідкує поле Session Expires. Коли час скінчиться потрібно буде знову отримати адресау і пройти процедуру його реєстрації на telegram. Тепер беремо рядок . отриманий рядок та натискаємо enter. Повинен вийде такий результат: Telegram bot - нагадування через webHook на Java або скажи ні Google-календарю!  - 7Все, тепер можна запускати додаток: Telegram bot - нагадування через webHook на Java або скажи ні Google-календарю!  - 8Перевірте, що Ваш клас з методомmain , був такий:
@SpringBootApplication
public class TelegramBotApplication {

   public static void main(String[] args) {
      SpringApplication.run(TelegramBotApplication.class, args);
   }
}
Якщо Ви все зробабо правильно, то тепер Ваш бот буде відгукуватися на будь-яке повідомлення фразою - Hello world . Далі можете йти своїм шляхом. Якщо ж Ви зі мною і Вам цікаво піти всі кроки, то приступимо до написання сутностей бази даних і створимо саму базу даних. Почнемо з бази: Як я вже казав, вважаю, що Ви вже маєте мінімальні навички роботи з базою, і у вас встановлена ​​локальна база postgreSQL , якщо ні пройдіть цей шлях. https://www.postgresql.org/download/ У файлі application.properties замініть логін та пароль до бази даних на свої. У IDEA справа є вкладка database, у ній потрібно натиснути на +/Data source/PostgreSQL . В результаті при натисканні на Test Connectionповинні отримати задовільний результат: Telegram bot - нагадування через webHook на Java або скажи ні Google-календарю!  - 9Тепер ви можете створити базу з таблицями прямо із середовища розробки, а можете скористатися web-інтерфейсом pgadmin , який знаходиться в меню пуск. Нам знадобляться 3 таблиці:
CREATE TABLE users
(
    id               INTEGER PRIMARY KEY UNIQUE NOT NULL,
    name             VARCHAR,
    time_zone        INTEGER DEFAULT 0,
    on_off           BOOLEAN DEFAULT true
);

CREATE TABLE user_events
(
    user_id INTEGER ,
    time timestamp ,
    description varchar ,
    event_id serial,
    event_freq varchar default 'TIME',
    FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
);

CREATE TABLE event_cash
(
    time timestamp ,
    description varchar ,
    user_id INTEGER ,
    id serial
);
Рекомендую створити окремо цей скрипт, він нам знадобиться для створення бази на Heroku, щоб не писати двічі. Пройдемося трохи. Відразу скажу таблиця event_cash нам знадобиться тільки для роботи з Heroku у зв'язку з його специфікою та моїм невгамовним бажанням попрацювати з класом Time , в локальній версії він не знадобиться. У таблицю users ми будемо записувати id облікового запису користувача telegram, його ім'я, якого може і не бути, буде розрахований часовий пояс користувача, для коректної відправки повідомлень, а також стан on/off розсилки повідомлень. У таблицю user_events у нас записуватиметься idкористувача, час повідомлення, опис, буде автоматично генеруватися id для події, та встановлюватиметься частота повідомлень. У таблиці event_cash буде записуватися повідомлення перед відправкою, і, якщо воно надіслано, видалятися з таблиці. Таблиці готові, давайте тепер додамо сутності.
@Entity
@Table(name = "user_events")
@Getter
@Setter
public class Event {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column( name = "event_id", columnDefinition = "serial")
    private int eventId;

    @Column(name = "time")
    @NotNull(message = "Need date!")
    private Date date;

    @Column(name = "description")
    @Size(min = 4, max = 200, message = "Description must be between 0 and 200 chars!")
    private String description;

    @Column(name = "event_freq", columnDefinition = "TIME")
    @Enumerated(EnumType.STRING)
    private EventFreq freq;

    @ManyToOne(fetch=FetchType.EAGER)
    @JoinColumn(name="user_id")
    @OnDelete(action = OnDeleteAction.CASCADE)
    private User user;

    public Event() {
    }

    public Event(int eventId,
                 @NotNull(message = "Need date!") Date date,
                 @Size(min = 4, max = 200, message = "Description must be between 0 and 200 chars!")
                         String description,
                 EventFreq freq, User user) {
        this.eventId = eventId;
        this.date = date;
        this.description = description;
        this.freq = freq;
        this.user = user;
    }
}
@Entity
@Table(name = "users")
@Getter
@Setter
public class User {

    @Id
    @Column(name = "id")
    private long id;

    @Column(name = "name")
    private String name;

    @Column(name = "time_zone", columnDefinition = "default 0")
    //sets the broadcast time of events for your time zone
    private int timeZone;

    @OneToMany(mappedBy="user")
    private List<event> events;

    @Column(name = "on_off")
    // on/off send event
    private boolean on;

    public User() {
    }
}

</event>
@Entity
@Table(name = "event_cash")
@Getter
@Setter
//serves to save unhandled events after rebooting heroku
public class EventCashEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column( name = "id", columnDefinition = "serial")
    private long id;

    @Column(name = "time")
    private Date date;

    @Column(name = "description")
    private String description;

    @Column(name = "user_id")
    private long userId;

    public EventCashEntity() {
    }

    public static EventCashEntity eventTo(Date date, String description, long userId) {
        EventCashEntity eventCashEntity = new EventCashEntity();
        eventCashEntity.setDate(date);
        eventCashEntity.setDescription(description);
        eventCashEntity.setUserId(userId);
        return eventCashEntity;
    }
}
Трохи пробіжимося основними моментами. @Entity – позначає клас нашого dada jpa, що це клас є сутністю для бази даних, тобто. при наданні даних з бази вони будуть представлені нам у вигляді об'єкта Event, User і EventCashEntity. @Table - кажемо як називається наша таблиця в базі даних. Для того, щоб назва таблиці не підкреслювалася червоною, нам потрібно в IDEA погодитися із запропонованим варіантом виправлення помилки і натиснути Assign data sources. І вибрати там нашу основу. @id і @GeneratedValue – використовується Spring для створення бази даних, якщо її ще немає. @Column – використовується для позначення назви полів у таблиці, якщо вони збігаються, але правила хорошого коду рекомендують це писати. СтавленняOneToMany - рекомендую витратити час і розібратися що це таке тут https://en.wikibooks.org/wiki/Java_Persistence Я не зможу викласти більш зрозуміло, просто повірте. Скажу лише, що в даному випадку анотація @OneToMany каже, що в одного користувача може бути багато подій, і вони будуть надані нам у вигляді списку. Тепер нам треба діставати дані із таблиць. У бібліотеці SRING DATA JPA вже все за нас написано, нам треба просто створити інтерфейс для кожної таблиці та розширити його від JpaRepository.
public interface EventRepository extends JpaRepository<event, long=""> {
    Event findByEventId(long id);
}
public interface UserRepository extends JpaRepository<user, long=""> {

    User findById(long id);
}
public interface EventCashRepository extends JpaRepository<eventcashentity, long=""> {
    EventCashEntity findById(long id);
}

</eventcashentity,></user,></event,>
Основну логіку роботи з базою даних винесено на сервіс, так званий Data Access Object(DAO):
@Service
public class UserDAO {

    private final UserRepository userRepository;

    @Autowired
    public UserDAO(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User findByUserId(long id) {
        return userRepository.findById(id);
    }

    public List<user> findAllUsers() {
        return userRepository.findAll();
    }

    public void removeUser(User user) {
        userRepository.delete(user);
    }


    public void save(User user) {
        userRepository.save(user);
    }

    public boolean isExist(long id) {
        User user = findByUserId(id);
        return user != null;
    }
}
@Service
public class EventDAO {

    private final UserRepository userRepository;
    private final EventRepository eventRepository;

    @Autowired
    public EventDAO(UserRepository userRepository, EventRepository eventRepository) {
        this.userRepository = userRepository;
        this.eventRepository = eventRepository;
    }

    public List<event> findByUserId(long userId) {
        User user = userRepository.findById(userId);
        return user.getEvents();
    }
    public List<event> findAllEvent() {
       return eventRepository.findAll();
    }

    public Event findByEventId(long eventId) {
        return eventRepository.findByEventId(eventId);
    }

    public void remove(Event event) {
        eventRepository.delete(event);
    }

    public void save(Event event) {
        eventRepository.save(event);
    }
}

</event></event></user>
@Service
//handles events not dispatched after reboot heroku
public class EventCashDAO {

    private EventCashRepository eventCashRepository;

    @Autowired
    public void setEventCashRepository(EventCashRepository eventCashRepository) {
        this.eventCashRepository = eventCashRepository;
    }

    public List<eventcashentity> findAllEventCash() {
        return eventCashRepository.findAll();
    }

    public void save(EventCashEntity eventCashEntity) {
        eventCashRepository.save(eventCashEntity);
    }

    public void delete(long id) {
        eventCashRepository.deleteById(id);
    }
}

</eventcashentity>
В даному випадку ми не маємо ніякої обробки даних, ми просто дістаємо дані з таблиць. У нас все готово, щоб привести повний код класу T elegramFacade і почати розбирати логіку.
@Component
@FieldDefaults(level = AccessLevel.PRIVATE)
public class TelegramFacade {

    final MessageHandler messageHandler;
    final CallbackQueryHandler callbackQueryHandler;
    final BotStateCash botStateCash;

    @Value("${telegrambot.adminId}")
    int adminId;


    public TelegramFacade(MessageHandler messageHandler, CallbackQueryHandler callbackQueryHandler, BotStateCash botStateCash) {
        this.messageHandler = messageHandler;
        this.callbackQueryHandler = callbackQueryHandler;
        this.botStateCash = botStateCash;
    }

    public BotApiMethod<!--?--> handleUpdate(Update update) {

        if (update.hasCallbackQuery()) {
            CallbackQuery callbackQuery = update.getCallbackQuery();
            return callbackQueryHandler.processCallbackQuery(callbackQuery);
        } else {

            Message message = update.getMessage();
            if (message != null && message.hasText()) {
                return handleInputMessage(message);
            }
        }
        return null;
    }

    private BotApiMethod<!--?--> handleInputMessage(Message message) {
        BotState botState;
        String inputMsg = message.getText();
        //we process messages of the main menu and any other messages
        //set state
        switch (inputMsg) {
            case "/start":
                botState = BotState.START;
                break;
            case "Мои напоминания":
                botState = BotState.MYEVENTS;
                break;
            case "Создать напоминание":
                botState = BotState.CREATE;
                break;
            case "Отключить напоминания":
            case "Включить напоминания":
                botState = BotState.ONEVENT;
                break;
            case "All users":
                if (message.getFrom().getId() == adminId)
                botState = BotState.ALLUSERS;
                else botState = BotState.START;
                break;
            case "All events":
                if (message.getFrom().getId() == adminId)
                botState = BotState.ALLEVENTS;
                else botState = BotState.START;
                break;
            default:
                botState = botStateCash.getBotStateMap().get(message.getFrom().getId()) == null?
                        BotState.START: botStateCash.getBotStateMap().get(message.getFrom().getId());
        }
        //we pass the corresponding state to the handler
        //the corresponding method will be called
        return messageHandler.handle(message, botState);

    }
}
Розберемо для чого потрібні поля
final MessageHandler messageHandler;
    final CallbackQueryHandler callbackQueryHandler;
    final BotStateCash botStateCash;
Якщо ми всі кодуватимемо в одному класі, то в нас вийде онуча до місяця, у зв'язку з цим ми відносимо логіку роботи з текстовими повідомленнями в клас MessageHandler, логіку роботи з повідомленнями callbackquery в клас СallbackQueryHandler . Настав час розібрати, що таке c allbackquery і які види меню бувають. Для цього наведу ще раз картинку з інтерфейсом робота: Telegram bot - нагадування через webHook на Java або скажи ні Google-календарю!  - 10Бувають два типи меню. Ті, що закріплені знизу вікна - основне меню, і ті, що закріплені за повідомленням, наприклад, кнопки видалити, редагувати, змінити пояс. При натисканні на кнопку основного меню надсилається однойменне повідомлення, наприклад, при натисканні «Мої нагадування» , буде просто надіслано текст «Мої нагадування». А при встановленні клавіатури callbackquery, для кожної кнопки встановлюється певне значення і буде спрямоване значення без виведення на екран. Далі у нас є поле BotStateCash . Це спеціально створений клас, який стежитиме за станом бота, і увага, це складний елемент, треба напружитися. Було перевищено кількість символів, про що, до речі ніде не нписано )). Так що ось посилання на другу частину
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ