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

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Здравствуйте, сегодня мы создадим простого бота для Телеграм, который демонстрирует базовые возможности работы с 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;
            }
        }
    }
}
Источник: https://habr.com/ru/post/571762/


Интересные статьи

Интересные статьи

Что делает программа: Рисуешь смайлик радостный или грустный, нейронной сети нужно распознать эмоцию по картинке. При этом каждый раз можно рисовать смайлики ± по-разному...
Привет, Хаброжители! Программирование и тестирование обычно принято относить к разным профессиональным сферам. Скотт Оукс — признанный эксперт по языку Java — уверен, что если вы хотите ...
Привет. Меня зовут Ваня, и я Java-разработчик. Так получилось, что я много работаю с PostgreSQL – занимаюсь настройкой БД, оптимизацией структуры, производительностью и немного играю в DBA по вы...
Приветствую, Дорогие Друзья. Продолжаем цикл статей, освещающий деятельность (бурную) нашей некоммерческой организации. Как и обещал — переходим от простого (логирование) к более сложному: мета...
Бизнес-смыслы появились в Битриксе в начале 2016 года, но мало кто понимает, как их правильно использовать для удобной настройки интернет-магазинов.