Pull to refresh

Телеграм бот прогноза погоды на Java Spring

JavaMongoDB
Awaiting invitation

Здравствуйте, сегодня мы создадим простого бота для Телеграм, который демонстрирует базовые возможности работы с Telegram API. Работать он будет следующим образом:

Демонстрация

Регистрация бота в Telegram и получение токена

Тут всё довольно просто, необходимо написать @BotFather и следовать его инструкциям, если вы всё сделаете правильно, то получите сообщение такого вида:

Это и есть необходимый токен для бота.

Регистрация в openweather и получение ключа доступа

Заходим на сайт https://openweathermap.org/ и проходим регистрацию, ключ находится в разделе MyAPI keys. По бесплатному тарифу вам доступно до 60 звонков в минуту и до 1 000 000 в месяц.

Наш бот будет получать данные по текущей погоде, поэтому шаблон API ссылки будет такой - http://api.openweathermap.org/data/2.5/weather?q={city}&appid={key}&units=metric&lang=ru , где units=metric отвечает за единицу измерения температуры в цельсиях.

О других возможностях API можно почитать в документации на сайте сервиса.

Подготовка проекта

Далее создаем пустой проект Spring Boot с помощью https://start.spring.io/, если вы используете IntelliJ IDEA, то можете использовать встроенный инициализатор Spring Boot проекта.

После создания проекта добавляем необходимые зависимости:

pom.xml
<dependencies>
  
  			<!--Драйвер для MongoDB-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
  
				<!--Аннотации для оптимизации Java кода-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
  			<!--Библиотека для удобной работы с Telegram API-->
        <dependency>
            <groupId>org.telegram</groupId>
            <artifactId>telegrambots</artifactId>
            <version>5.2.0</version>
        </dependency>
  			
  			<!--Библиотека для парсинга эмоджи-->
        <dependency>
            <groupId>com.vdurmont</groupId>
            <artifactId>emoji-java</artifactId>
            <version>5.1.1</version>
        </dependency>
  
  			<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

Наш бот будет использовать MongoDB для хранения конфигурации, а также для хранения состояния относительно чатов.

Пройдёмся по необходимым сущностям (документам):

BotConfig - конфигурация нашего бота
@Getter
@Setter
@NoArgsConstructor
@Document(collection = "bot_config")
public class BotConfig {
    @Id
    private BigInteger id;

  	//имя бота, которое вы указали при регистрации
    private String name;

  	//токен
    private String accessToken;

  	//http://api.openweathermap.org/data/2.5/weather?q={city}&appid=ВАШ_КЛЮЧ&units=metric&lang=ru
    private String nowWeatherApiTemp;

  	//подробнее о данной ссылке ниже
  	//https://api.telegram.org/bot{token}/answerCallbackQuery?callback_query_id={id}
    private String telegramCallbackAnswerTemp;

    private List<Command> commands;
}

@Getter
@Setter
@NoArgsConstructor
public class Command {
    private String name;    // /command
    private String description;  //  bla bla bla
}
ChatConfig - Информация о чатах с пользователями
@Getter
@Setter
@NoArgsConstructor
@RequiredArgsConstructor
@Document(collection = "chats_config")
public class ChatConfig {
    @Id
    private BigInteger id;

    @NonNull
    private Long chatId;

    @NonNull
    @Field(targetType = FieldType.STRING)
    private BotState botState;
    
  	//стандартный город для пользователя
    private String city;
}

Также при разработке нам понадобятся три enum:

BotState - Состояния бота
public enum BotState {
    DEFAULT,SEARCH_NOW,SEARCH_PREDICT,NOW,PREDICT,SET_CITY
}
KeyboardType - Группы кнопок в Телеграм чате, в нашем случае понадобится только одна
public enum KeyboardType {
    CITY_CHOOSE
}
MainCommand - Команды, которые бот будет воспринимать, находясь в состоянии DEFAULT
public enum MainCommand {
    START,HELP,CITY,SETCITY,NOW,CANCEL
}

Создание компонентов для работы с базой данных и API Openweather

Далее необходимо создать репозитории и сервисы для документов

BotConfigService - Сервис для работы с конфигурацией бота
@Service
public class BotConfigService {
    @Autowired 
  	//пустой интерфейс, наследуемый от MongoRepository<BotConfig, BigInteger>
    private BotConfigRepo botConfigRepo; 

    public String getTelegramCallbackAnswerTemp(){
        return this.botConfigRepo.findAll().get(0).getTelegramCallbackAnswerTemp();
    }

    public String getNowApiTemp(){
        return this.botConfigRepo.findAll().get(0).getNowWeatherApiTemp();
    }

    public List<Command> getAllCommands(){
        return botConfigRepo.findAll().get(0).getCommands();
    }

    public String getBotUsername(){
        return botConfigRepo.findAll().get(0).getName();
    }

    public String getBotAccessToken(){
        return botConfigRepo.findAll().get(0).getAccessToken();
    }
}
ChatConfigRepo и ChatConfigService
public interface ChatConfigRepo extends MongoRepository<ChatConfig, BigInteger> {
    ChatConfig findAllByChatId(Long chatId);
    void deleteByChatId(Long chatId);
}


@Service
public class ChatConfigService {
    @Autowired
    private ChatConfigRepo chatConfigRepo;

    public boolean isChatInit(Long chatId){
        return chatConfigRepo.findAllByChatId(chatId) != null;
    }

  	//создание нового чата
    public void initChat(Long chatId){
        chatConfigRepo.save(new ChatConfig(chatId, BotState.DEFAULT));
    }

    public void deleteChat(Long chatId){
        chatConfigRepo.deleteByChatId(chatId);
    }

    public void setBotState(Long chatId,BotState botState){
        ChatConfig chatConfig = chatConfigRepo.findAllByChatId(chatId);
        chatConfig.setBotState(botState);
        chatConfigRepo.save(chatConfig);
    }

    public BotState getBotState(Long chatId){
        return chatConfigRepo.findAllByChatId(chatId).getBotState();
    }

    public void setCity(Long chatId,String city){
        ChatConfig chatConfig = chatConfigRepo.findAllByChatId(chatId);
        chatConfig.setCity(city);
        chatConfigRepo.save(chatConfig);
    }

    public String getCity(Long chatId){
        return chatConfigRepo.findAllByChatId(chatId).getCity();
    }
}

Теперь давайте создадим классы для работы с API погоды. При запросе нам приходит ответ вида:

current weather json response
{
    "coord": {
        "lon": 37.6156,
        "lat": 55.7522
    },
    "weather": [
        {
            "id": 500,
            "main": "Rain",
            "description": "небольшой дождь",
            "icon": "10n"
        }
    ],
    "base": "stations",
    "main": {
        "temp": 13.78,
        "feels_like": 13.69,
        "temp_min": 12.37,
        "temp_max": 14.24,
        "pressure": 1013,
        "humidity": 95,
        "sea_level": 1013,
        "grnd_level": 995
    },
    "visibility": 10000,
    "wind": {
        "speed": 3.52,
        "deg": 43,
        "gust": 9.4
    },
    "rain": {
        "1h": 0.22
    },
    "clouds": {
        "all": 100
    },
    "dt": 1623359195,
    "sys": {
        "type": 2,
        "id": 2000314,
        "country": "RU",
        "sunrise": 1623372350,
        "sunset": 1623435164
    },
    "timezone": 10800,
    "id": 524901,
    "name": "Москва",
    "cod": 200
}

Из всего этого огромного количества полей мы будем использовать всего несколько: weather.main, weather.description, main.temp, main.feels_like.

Создадим модель для ответа:

WeatherNow
@Getter
@Setter
@NoArgsConstructor
public class WeatherNow {
    private List<Weather> weather;
    private Main main;
}

@Getter
@Setter
@NoArgsConstructor
public class Weather {
    private String main;
    private String description;
}

@Getter
@Setter
@NoArgsConstructor
public class Main {
    private Integer temp;

    @JsonProperty("feels_like")
    private Integer feelsLike;
}

Далее создадим класс, методы которого будут делать запросы на API, а также сервис для него

WeatherRestMap
@Component
public class WeatherRestMap {
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private BotConfigService botConfigService;

		//получение текущей погоды
    public WeatherNow getNowWeather(String city){
        try {
            return restTemplate.getForObject(botConfigService.getNowApiTemp()
            										.replace("{city}",city), 
            										WeatherNow.class);
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

		//проверка существования города
    public boolean isCity(String city) throws IOException {

        URL weatherApiUrl = new URL(botConfigService.getNowApiTemp().replace("{city}",city));
        
        HttpURLConnection weatherApiConnection = (HttpURLConnection)weatherApiUrl.openConnection();
        weatherApiConnection.setRequestMethod("GET");
        weatherApiConnection.connect();
        return weatherApiConnection.getResponseCode() == HttpURLConnection.HTTP_OK;
    }
}
WeatherService
@Service
public class WeatherService {
    @Autowired
    private WeatherRestMap weatherRestMap;

    public boolean isCity(String city) throws IOException {
        return weatherRestMap.isCity(city);
    }

    public WeatherNow getCurrentWeather(String city){
        return weatherRestMap.getNowWeather(city);
    }
}

Создание логики по взаимодействию с Telegram API

Для начала создадим основной класс нашего бота, наследуемый от TelegramLongPollingBot из библиотеки для работы с Telegram API

Класс WeatherBot
@Component
public class WeatherBot extends TelegramLongPollingBot {
    @Autowired
    private BotConfigService botConfigService;
    @Autowired
    private WeatherBotFacade weatherBotFacade;

    @Override
    public String getBotUsername() {
        return botConfigService.getBotUsername();
    }

    @Override
    public String getBotToken() {
        return botConfigService.getBotAccessToken();
    }

  	
    @SneakyThrows //отслеживание Exceptions
    @Override
    public void onUpdateReceived(Update update) {
        weatherBotFacade.handleUpdate(update);
    }
}

Метод onUpdateReceived получает с Telegram API так называемые апдейты, это может быть как сообщение, поступившее боту, так и какое-либо другое изменение в чате с ботом (изменение сообщения, удаление чата и.т.д.)

Также необходимо инициализировать нашего бота после запуска приложения

Класс BotInit
@Component
public class BotInit {
    @Autowired
    private WeatherBot weatherBot;
   
  
  	//после того, как приложение полностью запущено
    @EventListener({ApplicationReadyEvent.class})
    public void init() throws TelegramApiException {
        TelegramBotsApi telegramBotsApi = new TelegramBotsApi(
          																			DefaultBotSession.class);
        try {
            telegramBotsApi.registerBot(weatherBot);
        } catch (TelegramApiRequestException e) {
            e.printStackTrace();
        }
    }
}

Для создания класса WeatherBotFacade, в котором будет реализована основная логика по взаимодействию с Telegram API, необходимо создать несколько вспомогательных классов:

Первый - сервис, который будет возвращать строки с сообщениями от бота:

MessageGenerator
@Service
public class MessageGenerator {
    @Autowired
    private BotConfigService botConfigService;
    @Autowired
    private WeatherService weatherService;

    private String message;

    public String generateStartMessage(String name){
        return EmojiParser.parseToUnicode("Привет, " + name + " :wave: \nЧтобы узнать, как мной пользоваться - введите /help");
    }

    public String generateHelpMessage(){
        message = "";
        message = ":sunny: Вот мои доступные команды :sunny:\n\n";
        botConfigService.getAllCommands()
                .forEach(command -> {
                    message = message + command.getName() + " - " + command.getDescription() + "\n";
                });
        return EmojiParser.parseToUnicode(message);
    }

    public String generateSuccessCancel(){
        return EmojiParser.parseToUnicode(":white_check_mark: Активная команда успешно отклонена");
    }

    public String generateSuccessSetCity(String city){
        return EmojiParser.parseToUnicode(":white_check_mark: Новый стандартный город - " + city);
    }

    public String generateErrorCity(){
        return EmojiParser.parseToUnicode(":x: Такого города не существует");
    }

    public String generateSuccessGetCity(String city){
        return EmojiParser.parseToUnicode(":cityscape: Стандартный город - " + city);
    }

    public String generateErrorGetCity(){
        return EmojiParser.parseToUnicode(":x: Стандартный город не назначен");
    }

    public String generateCurrentWeather(String city){
        WeatherNow weatherNow = weatherService.getCurrentWeather(city);
        return EmojiParser.parseToUnicode("Текущая погода\n\n" +
                "В городе " + city + " " + weatherNow.getWeather().get(0).getDescription() + "\n" +
                ":thermometer: Температура: " + weatherNow.getMain().getTemp() + "°C, ощущается как " + weatherNow.getMain().getFeelsLike() + "°C");
    }
}

Здесь мы используем библиотеку для парсинга иконок, код вида :icon: для любого эмоджи можно найти на сайте https://emojipedia.org/

Далее создаём класс, который будет создавать кнопки в сообщениях от бота

KeyboardService
@Service
public class KeyboardService {
    @Autowired
    private ChatConfigService chatConfigService;

    private final InlineKeyboardMarkup keyboard = new InlineKeyboardMarkup();


    public InlineKeyboardMarkup setChooseCityKeyboard(Long chatId){
        List<InlineKeyboardButton> keyboardRow = new ArrayList<>();
        InlineKeyboardButton button1 = new InlineKeyboardButton();
       
      	//текст на кнопке
      	button1.setText(chatConfigService.getCity(chatId));
      
      	//сообщение, которое она возвращает
        button1.setCallbackData(getCurrentCityNowButton(chatConfigService
                                                        .getCity(chatId)));

        InlineKeyboardButton button2 = new InlineKeyboardButton();
        button2.setText("Другой");
        button2.setCallbackData(getChooseCityNowButtonData());

        keyboardRow.add(button1);
        keyboardRow.add(button2);
        keyboard.setKeyboard(Arrays.asList(keyboardRow));

        return keyboard;
    }

    public String getChooseCityNowButtonData(){
        return "Введите необходимый город";
    }

    public String getCurrentCityNowButton(String city){
        return "Сейчас " + city;
    }
}

Кнопки с CallbackData часто используются для перенаправления пользователей на сторонние ресурсы, например для совершения оплаты, в нашем случае они будут просто возвращать сообщение, однако согласно документации Telegram API, необходимо вернуть callbackAnswer, содержащий поле callback_query_id. Подробнее о методе и его полях - https://core.telegram.org/bots/api#answercallbackquery

Если не вернуть callbackAnswer, то у кнопки будет состояние загрузки до окончания таймаута (около 15 секунд), что может ввести в заблуждение пользователя

Загрузка кнопки

Для использования callbackAnswer создадим одноименный класс, в котором будем делать одиночный HTTP запрос на нужный метод - https://api.telegram.org/bot{token}/answerCallbackQuery?callback_query_id={id}

Класс CallbackAnswer
@Service
public class CallbackAnswer {
    @Autowired
    private BotConfigService botConfigService;

    public void callbackAnswer(String callbackId) throws IOException, InterruptedException {
        HttpClient telegramApiClient = HttpClient.newHttpClient();
        HttpRequest telegramCallbackAnswerReq = HttpRequest.newBuilder(URI
                            .create(botConfigService
                								.getTelegramCallbackAnswerTemp()
                								.replace("{token}",botConfigService.getBotAccessToken())
                								.replace("{id}",callbackId)))
                    				.GET().build();

        telegramApiClient.send(telegramCallbackAnswerReq, HttpResponse.BodyHandlers
                               		.ofString());
    }
}

Теперь же приступим к основному классу WeatherBotFacade

Для начала создадим метод для отправки сообщения ботом:

void sendMessage
		private Long setChatIdToMessageBuilder(Update update, SendMessage.SendMessageBuilder messageBuilder){
        Long chatId = null;
        if (update.hasMessage()) {
            chatId = update.getMessage().getChatId();
            messageBuilder.chatId(update.getMessage().getChatId().toString());
        } else if (update.hasChannelPost()) {
            chatId = update.getChannelPost().getChatId();
            messageBuilder.chatId(update.getChannelPost().getChatId().toString());
        }else if (update.hasCallbackQuery()){
            chatId = update.getCallbackQuery().getMessage().getChatId();
            messageBuilder.chatId(update.getCallbackQuery().getMessage().getChatId().toString());
        }
        return chatId;
    }

		private void sendMessage(Update update,String messageText){
        SendMessage.SendMessageBuilder messageBuilder = SendMessage.builder();

        Long chatId = setChatIdToMessageBuilder(update,messageBuilder);

        messageBuilder.text(messageText);

        try {
            weatherBot.execute(messageBuilder.build());
        }catch (TelegramApiException telegramApiException){
            telegramApiException.printStackTrace();
        }
    }

    private void sendMessage(Update update, String messageText, KeyboardType keyboardType) {
        SendMessage.SendMessageBuilder messageBuilder = SendMessage.builder();

        Long chatId = setChatIdToMessageBuilder(update, messageBuilder);

        messageBuilder.text(messageText);

        switch (keyboardType) {
            case CITY_CHOOSE: {
              	//устанавливаем кнопки, созданные выше
                messageBuilder.replyMarkup(keyboardService.setChooseCityKeyboard(chatId));
                break;
            }
        }

        try {
            weatherBot.execute(messageBuilder.build());
        }catch (TelegramApiException telegramApiException){
            telegramApiException.printStackTrace();
        }
    }

Далее нам понадобится метод для отслеживания апдейтов, который будет использован в методе onUpdateReceived в классе нашего бота, созданного выше:

void handleUpdate
public void handleUpdate(Update update) throws IOException, InterruptedException {
        String messageText;
        Long chatId;
        String userFirstName = "";
				
  			//если сообщение пришло в лс боту
        if (update.hasMessage()) {
            chatId = update.getMessage().getChatId();
            messageText = update.getMessage().getText().toUpperCase(Locale.ROOT).replace("/","");
            userFirstName = update.getMessage().getChat().getFirstName();
        }

  			//если пришло сообщение с кнопок, которые мы создавали выше
        else if (update.hasCallbackQuery()){
            callbackAnswer.callbackAnswer(update.getCallbackQuery().getId());

            chatId = update.getCallbackQuery().getMessage().getChatId();
            messageText = update.getCallbackQuery().getData().toUpperCase(Locale.ROOT);
            sendMessage(update,update.getCallbackQuery().getData());

            if (messageText.equals(keyboardService.getChooseCityNowButtonData().toUpperCase(Locale.ROOT))){
                chatConfigService.setBotState(chatId,BotState.SEARCH_NOW);
                return;
            }

            else if (messageText.equals(keyboardService.getCurrentCityNowButton(chatConfigService.getCity(chatId)).toUpperCase(Locale.ROOT))){
                chatConfigService.setBotState(chatId,BotState.NOW);
            }
        }

  			//если человек присоединился к чату или покинул его
        else if (update.hasMyChatMember()) {
          	//удаляем данные о чате из бд, если пользователь покинул чат с ботом
            if (update.getMyChatMember().getNewChatMember().getStatus().equals("kicked")){
                chatConfigService.deleteChat(update.getMyChatMember().getChat().getId());
            }

            return;
        }else {

            return;
        }

  			//создаём запись о чате в бд и возвращаем приветствие 
        if (!chatConfigService.isChatInit(chatId)){
            chatConfigService.initChat(chatId);
            sendMessage(update, messageGenerator.generateStartMessage(userFirstName));
        }else{
          	//отслеживаем состояние бота относительно текущего чата
            handleBotState(update,chatId,messageText,userFirstName);
        }
    }

Ну и последний метод, который нам понадобится будет отслеживать состояние бота относительно чата и возвращать нужные сообщения:

void handleBotState
private void handleBotState(Update update,Long chatId,String messageText,String userFirstName) throws IOException {
        BotState botState = chatConfigService.getBotState(chatId);
				
  			// /start - Приветствие
        if (messageText.equals(MainCommand.START.name())) {
            chatConfigService.setBotState(chatId,BotState.DEFAULT);
            sendMessage(update,messageGenerator.generateStartMessage(userFirstName));
            return;
        }

  			// /cancel Возвращение бота в состояние DEFAULT (отмена текущей команды)
        if (messageText.equals(MainCommand.CANCEL.name())){
            if (botState == BotState.DEFAULT){
                sendMessage(update,"Нет активной команды для отклонения");
            }else {
                chatConfigService.setBotState(chatId,BotState.DEFAULT);
                sendMessage(update,messageGenerator.generateSuccessCancel());
                return;
            }
        }

        switch (botState) {
            case DEFAULT: {

              	// /help - Список команд
                if (messageText.equals(MainCommand.HELP.name())) {
                    sendMessage(update, messageGenerator.generateHelpMessage());
                }

              	// /setcity - Установка стандартного города
                else if (messageText.equals(MainCommand.SETCITY.name())) {
                    chatConfigService.setBotState(chatId, BotState.SET_CITY);
                    sendMessage(update, "Введите новый стандартный город");
                }

              	// /city - Текущий стандартный город для чата 
                else if (messageText.equals(MainCommand.CITY.name())) {
                    if (chatConfigService.getCity(chatId) != null && !chatConfigService.getCity(chatId).equals("")) sendMessage(update, messageGenerator.generateSuccessGetCity(chatConfigService.getCity(chatId)));
                    else sendMessage(update, messageGenerator.generateErrorGetCity());
                }
								
              	// /now - Узнать текущую погоду
                else if (messageText.equals(MainCommand.NOW.name())) {
                    chatConfigService.setBotState(chatId, BotState.NOW);
                    sendMessage(update, "Выберите город", KeyboardType.CITY_CHOOSE);
                }

                break;
            }

            case SET_CITY: {
								
              	//проверка - существует ли введенный пользователем город
                if (weatherService.isCity(messageText.toLowerCase(Locale.ROOT))) {
                    chatConfigService.setCity(chatId, messageText.charAt(0)+messageText.substring(1).toLowerCase(Locale.ROOT));
                    chatConfigService.setBotState(chatId, BotState.DEFAULT);
                    sendMessage(update, messageGenerator.generateSuccessSetCity(chatConfigService.getCity(chatId)));
                }

                else sendMessage(update, messageGenerator.generateErrorCity());

                break;
            }

            case NOW: {

              	// если выбран не стандартный город
                if (messageText.equals(keyboardService.getChooseCityNowButtonData().toUpperCase(Locale.ROOT)))
                {
                    chatConfigService.setBotState(chatId,BotState.SEARCH_NOW);
                }

              	// погода для стандартного города
                else {
                    chatConfigService.setBotState(chatId,BotState.DEFAULT);
                    sendMessage(update,messageGenerator.generateCurrentWeather(chatConfigService.getCity(chatId)));
                }
                break;
            }

            case SEARCH_NOW: {
              	// проверка на существование города
                if (!weatherService.isCity(messageText)){
                    sendMessage(update,messageGenerator.generateErrorCity());
                }

              	// погода для введенного города
                else {
                    sendMessage(update,messageGenerator.generateCurrentWeather(messageText.charAt(0) + messageText.substring(1).toLowerCase(Locale.ROOT)));
                    chatConfigService.setBotState(chatId,BotState.DEFAULT);
                }

                break;
            }
        }
    }

Полный код класса WeatherBotFacade:

WeatherBotFacade
@Component
public class WeatherBotFacade {
    @Autowired
    private ChatConfigService chatConfigService;
    @Autowired
    private MessageGenerator messageGenerator;
    @Autowired
    private WeatherService weatherService;
    @Autowired
    private KeyboardService keyboardService;
    @Autowired
    private WeatherBot weatherBot;
    @Autowired
    private CallbackAnswer callbackAnswer;


    public void handleUpdate(Update update) throws IOException, InterruptedException {
        String messageText;
        Long chatId;
        String userFirstName = "";

        if (update.hasMessage()) {
            chatId = update.getMessage().getChatId();
            messageText = update.getMessage().getText().toUpperCase(Locale.ROOT).replace("/","");
            userFirstName = update.getMessage().getChat().getFirstName();
        }

        else if (update.hasChannelPost()){
            chatId = update.getChannelPost().getChatId();
            messageText = update.getChannelPost().getText().toUpperCase(Locale.ROOT).replace("/","");
            userFirstName = update.getChannelPost().getChat().getFirstName();
        }

        else if (update.hasCallbackQuery()){
            callbackAnswer.callbackAnswer(update.getCallbackQuery().getId());

            chatId = update.getCallbackQuery().getMessage().getChatId();
            messageText = update.getCallbackQuery().getData().toUpperCase(Locale.ROOT);
            sendMessage(update,update.getCallbackQuery().getData());

            if (messageText.equals(keyboardService.getChooseCityNowButtonData().toUpperCase(Locale.ROOT))){
                chatConfigService.setBotState(chatId,BotState.SEARCH_NOW);
                return;
            }

            else if (messageText.equals(keyboardService.getCurrentCityNowButton(chatConfigService.getCity(chatId)).toUpperCase(Locale.ROOT))){
                chatConfigService.setBotState(chatId,BotState.NOW);
            }
        }

        else if (update.hasMyChatMember()) {
            if (update.getMyChatMember().getNewChatMember().getStatus().equals("kicked")){
                chatConfigService.deleteChat(update.getMyChatMember().getChat().getId());
            }

            return;
        }else {

            return;
        }

        if (!chatConfigService.isChatInit(chatId)){
            chatConfigService.initChat(chatId);
            sendMessage(update, messageGenerator.generateStartMessage(userFirstName));
        }else{
            handleBotState(update,chatId,messageText,userFirstName);
        }
    }


    private Long setChatIdToMessageBuilder(Update update, SendMessage.SendMessageBuilder messageBuilder){
        Long chatId = null;
        if (update.hasMessage()) {
            chatId = update.getMessage().getChatId();
            messageBuilder.chatId(update.getMessage().getChatId().toString());
        } else if (update.hasChannelPost()) {
            chatId = update.getChannelPost().getChatId();
            messageBuilder.chatId(update.getChannelPost().getChatId().toString());
        }else if (update.hasCallbackQuery()){
            chatId = update.getCallbackQuery().getMessage().getChatId();
            messageBuilder.chatId(update.getCallbackQuery().getMessage().getChatId().toString());
        }
        return chatId;
    }

    private void sendMessage(Update update,String messageText){
        SendMessage.SendMessageBuilder messageBuilder = SendMessage.builder();

        Long chatId = setChatIdToMessageBuilder(update,messageBuilder);

        messageBuilder.text(messageText);

        try {
            weatherBot.execute(messageBuilder.build());
        }catch (TelegramApiException telegramApiException){
            telegramApiException.printStackTrace();
        }
    }

    private void sendMessage(Update update, String messageText, KeyboardType keyboardType) {
        SendMessage.SendMessageBuilder messageBuilder = SendMessage.builder();

        Long chatId = setChatIdToMessageBuilder(update, messageBuilder);

        messageBuilder.text(messageText);

        switch (keyboardType) {
            case CITY_CHOOSE: {
                messageBuilder.replyMarkup(keyboardService.setChooseCityKeyboard(chatId));
                break;
            }
        }

        try {
            weatherBot.execute(messageBuilder.build());
        }catch (TelegramApiException telegramApiException){
            telegramApiException.printStackTrace();
        }
    }

    private void handleBotState(Update update,Long chatId,String messageText,String userFirstName) throws IOException {
        BotState botState = chatConfigService.getBotState(chatId);

        if (messageText.equals(MainCommand.START.name())) {
            chatConfigService.setBotState(chatId,BotState.DEFAULT);
            sendMessage(update,messageGenerator.generateStartMessage(userFirstName));
            return;
        }

        if (messageText.equals(MainCommand.CANCEL.name())){
            if (botState == BotState.DEFAULT){
                sendMessage(update,"Нет активной команды для отклонения");
            }else {
                chatConfigService.setBotState(chatId,BotState.DEFAULT);
                sendMessage(update,messageGenerator.generateSuccessCancel());
                return;
            }
        }

        switch (botState) {
            case DEFAULT: {

                if (messageText.equals(MainCommand.HELP.name())) {
                    sendMessage(update, messageGenerator.generateHelpMessage());
                }

                else if (messageText.equals(MainCommand.SETCITY.name())) {
                    chatConfigService.setBotState(chatId, BotState.SET_CITY);
                    sendMessage(update, "Введите новый стандартный город");
                }

                else if (messageText.equals(MainCommand.CITY.name())) {
                    if (chatConfigService.getCity(chatId) != null && !chatConfigService.getCity(chatId).equals("")) sendMessage(update, messageGenerator.generateSuccessGetCity(chatConfigService.getCity(chatId)));
                    else sendMessage(update, messageGenerator.generateErrorGetCity());
                }

                else if (messageText.equals(MainCommand.NOW.name())) {
                    chatConfigService.setBotState(chatId, BotState.NOW);
                    sendMessage(update, "Выберите город", KeyboardType.CITY_CHOOSE);
                }

                break;
            }

            case SET_CITY: {

                if (weatherService.isCity(messageText.toLowerCase(Locale.ROOT))) {
                    chatConfigService.setCity(chatId, messageText.charAt(0)+messageText.substring(1).toLowerCase(Locale.ROOT));
                    chatConfigService.setBotState(chatId, BotState.DEFAULT);
                    sendMessage(update, messageGenerator.generateSuccessSetCity(chatConfigService.getCity(chatId)));
                }

                else sendMessage(update, messageGenerator.generateErrorCity());

                break;
            }

            case NOW: {

                if (messageText.equals(keyboardService.getChooseCityNowButtonData().toUpperCase(Locale.ROOT)))
                {
                    chatConfigService.setBotState(chatId,BotState.SEARCH_NOW);
                }

                else {
                    chatConfigService.setBotState(chatId,BotState.DEFAULT);
                    sendMessage(update,messageGenerator.generateCurrentWeather(chatConfigService.getCity(chatId)));
                }
                break;
            }

            case SEARCH_NOW: {
                if (!weatherService.isCity(messageText)){
                    sendMessage(update,messageGenerator.generateErrorCity());
                }

                else {
                    sendMessage(update,messageGenerator.generateCurrentWeather(messageText.charAt(0) + messageText.substring(1).toLowerCase(Locale.ROOT)));
                    chatConfigService.setBotState(chatId,BotState.DEFAULT);
                }

                break;
            }
        }
    }
}
Tags:Javaspringmongodbtelegrambotrest
Hubs:JavaMongoDB
You can’t comment this post because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.