Здравствуйте, сегодня мы создадим простого бота для Телеграм, который демонстрирует базовые возможности работы с Telegram API. Работать он будет следующим образом:
Демонстрация
Это и есть необходимый токен для бота.
Наш бот будет получать данные по текущей погоде, поэтому шаблон API ссылки будет такой - http://api.openweathermap.org/data/2.5/weather?q={city}&appid={key}&units=metric&lang=ru , где units=metric отвечает за единицу измерения температуры в цельсиях.
О других возможностях API можно почитать в документации на сайте сервиса.
После создания проекта добавляем необходимые зависимости:
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
}
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);
}
}
Класс 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 + " \nЧтобы узнать, как мной пользоваться - введите /help");
}
public String generateHelpMessage(){
message = "";
message = " Вот мои доступные команды \n\n";
botConfigService.getAllCommands()
.forEach(command -> {
message = message + command.getName() + " - " + command.getDescription() + "\n";
});
return EmojiParser.parseToUnicode(message);
}
public String generateSuccessCancel(){
return EmojiParser.parseToUnicode(" Активная команда успешно отклонена");
}
public String generateSuccessSetCity(String city){
return EmojiParser.parseToUnicode(" Новый стандартный город - " + city);
}
public String generateErrorCity(){
return EmojiParser.parseToUnicode(" Такого города не существует");
}
public String generateSuccessGetCity(String city){
return EmojiParser.parseToUnicode(" Стандартный город - " + city);
}
public String generateErrorGetCity(){
return EmojiParser.parseToUnicode(" Стандартный город не назначен");
}
public String generateCurrentWeather(String city){
WeatherNow weatherNow = weatherService.getCurrentWeather(city);
return EmojiParser.parseToUnicode("Текущая погода\n\n" +
"В городе " + city + " " + weatherNow.getWeather().get(0).getDescription() + "\n" +
" Температура: " + 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;
}
}
}
}
Демонстрация
Регистрация бота в 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 + " \nЧтобы узнать, как мной пользоваться - введите /help");
}
public String generateHelpMessage(){
message = "";
message = " Вот мои доступные команды \n\n";
botConfigService.getAllCommands()
.forEach(command -> {
message = message + command.getName() + " - " + command.getDescription() + "\n";
});
return EmojiParser.parseToUnicode(message);
}
public String generateSuccessCancel(){
return EmojiParser.parseToUnicode(" Активная команда успешно отклонена");
}
public String generateSuccessSetCity(String city){
return EmojiParser.parseToUnicode(" Новый стандартный город - " + city);
}
public String generateErrorCity(){
return EmojiParser.parseToUnicode(" Такого города не существует");
}
public String generateSuccessGetCity(String city){
return EmojiParser.parseToUnicode(" Стандартный город - " + city);
}
public String generateErrorGetCity(){
return EmojiParser.parseToUnicode(" Стандартный город не назначен");
}
public String generateCurrentWeather(String city){
WeatherNow weatherNow = weatherService.getCurrentWeather(city);
return EmojiParser.parseToUnicode("Текущая погода\n\n" +
"В городе " + city + " " + weatherNow.getWeather().get(0).getDescription() + "\n" +
" Температура: " + 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;
}
}
}
}
Телеграм бот прогноза погоды на Java Spring
Здравствуйте, сегодня мы создадим простого бота для Телеграм, который демонстрирует базовые возможности работы с Telegram API. Работать он будет следующим образом: Демонстрация Регистрация бота в...
habr.com