JavaRush/Java блог/Java-проекты/Telegram bot — напоминалка через webHook на Java или скаж...
Vladimir Popov
41 уровень

Telegram bot — напоминалка через webHook на Java или скажи нет Google-календарю! Часть 1

Статья из группы Java-проекты
участников
Меня зовут Владимир. Мне 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 но не рассчитывайте на него, ибо я над ним все еще издеваюсь. код - 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.xml из своей среды разработки. Перед Вами готовый проект. Теперь нам осталось добавить нашу основную библиотеку. Я использовал библиотеку с https://github.com/rubenlagus/TelegramBots В общем-то можно заморочиться и обойтись без нее. Ведь весь смысл работы заключается в конкатенации URL вроде такого: https://api.telegram.org/bot1866835969:AAE6gJG6ptUyqhV2XX0MxyUak4QbAGGnz10/setWebhook?url=https://e9c658b548aa.ngrok.io Немного разберем: https://api.telegram.org – сервер 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. Теперь берем строку https://api.telegram.org/bot1866835969:AAE6gJG6ptUyqhV2XX0MxyUak4QbAGGnz10/setWebhook?url=https://e9c658b548aa.ngrok.io И ловкими движениями рук заменяем токен на свой, url на свой, вставляем в браузер полученную строку и нажимаем 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>
В данном случае у нас нет никакой обработки данных, мы просто достаем данные из таблиц. У нас все готово, для того, чтоб привести полный код класса TelegramFacade и начать разбирать логику.
@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. Настало время разобрать что такое callbackquery и какие виды меню бывают. Для этого приведу еще раз картинку с интерфейсом бота: Telegram bot — напоминалка через webHook на Java или скажи нет Google-календарю! - 10Бывают два типа меню. Те, которые закреплены снизу окна - основное меню, и те, которые закреплены за сообщением, например, кнопки удалить, редактировать, изменить пояс. При нажатии на кнопку основного меню отправляется одноименное сообщение, например при нажатии «Мои напоминания», будет просто отправлен текст «Мои напоминания». А при установке клавиатуры callbackquery, для каждой кнопки устанавливается определенное значение и будет направлено его значение без вывода на экран. Дальше у нас есть поле BotStateCash. Это специально созданный класс, который будет следить за состоянием бота, и внимание, это сложный элемент, надо напрячься. Было превышено кол-во символов, о чем, кстати нигде не нписано )). Так что вот ссылка на вторую часть
Комментарии (6)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Anonymous #1384518
Уровень 35
Expert
23 января, 00:16
Что за wildcard такой в дженериках стоит:
BotApiMethod<!--?-->
Нигде такое не видел. Что это означает?
Павел Перминов
Уровень 51
8 августа 2023, 12:24
Спасибище, классная статья
Максим
Уровень 19
12 ноября 2022, 14:05
Всё в перемешку, части кода не хватает. Ужасная статья
Денис
Уровень 1
24 октября 2022, 06:04
Спасибо. Как реализовали @Value токен не понятно?
Alexey Finik
Уровень 14
29 мая 2022, 13:07
Столько старались и никто не написал:(. Спасибо за такую работу, правда пугает Наличие Spring. Неужели нет проще методом запуска по расписанию. Даже удивительно.
1 января, 23:58
В принципе есть, можно использовать решения из коробки java, но поверь мне, ты не захочешь этого делать)))