JavaRush /Java блог /Random /Создаём простой погодный бот для Telegram за несколько ве...
Philip J.
40 уровень
Днепр

Создаём простой погодный бот для Telegram за несколько вечеров

Статья из группы Random
Всем привет! Тема создания ботов для Телеграмм несколько избита, и гайдов написано очень много (например, вот этот). Поэтому, мы лучше рассмотрим подробнее работу со каким-нибудь сторонним API, так как это критически важный навык для любого web-разработчика. Сразу скажу, что приложение не ставило целью предоставить максимально функциональный и полезный прогноз, конкурировать с погодными сайтами нет смысла, важно было научиться работать с удалёнными соединениями и парсингом данных средствами Java. Итак, выясним для начала, что нам нужно. Наше приложение, по сути, состоит из трёх логических частей:
  • принять сообщение от пользователя
  • обработать сообщение, и, если это валидная команда, подготовить данные для ответа. В нашем случае, подготовить прогноз погоды, если пользователь ввёл корректный город
  • отправить готовую информацию пользователю в чат
Первый и третий пункт довольно прост, и касается только работы с Telegram API, желающие могут изучить ссылку, оставленную выше. Мы же заострим внимание на втором пункте. API используют тогда, когда одни разработчики хотят предоставить доступ к своим данным другим разработчика. Возьмём, к примеру, Вконтакте. У каждого есть список друзей, он хранится где-то в базе данных на серверах ВК. Предположим, что какой-то программист решил создать игру в шашки с друзьями. Чтобы его приложение работало правильно, программа должна уметь получать список друзей любого игрока. Для этого программист находит документацию к ВК API, и смотрит, какой запрос нужно сделать, чтобы это список получить. Этот запрос называется HTTP-запросом. А два самых распространённых HTTP-запроса — это GET и POST. Про них в сети тоже достаточно, останавливать не буду. Для наших целей, а именно получения данных прогноза погоды будет достаточно простого GET-запроса. Если обратиться с GET запросом к обычному веб-серверу, он, зачастую, вернёт html-код, который браузер преобразует в удобную для пользователя страницу, применив стили, скрипты и пр. Если же мы обращаемся с таким запросом к API серверу, в ответ обычно возвращаются только сырые данные без стилей и скриптов. В окне браузера это выглядит примерно так: Создаём простой погодный бот для Telegram за несколько вечеров - 1Эти данные предназначены не для людей, а для других программ, поэтому ничего лишнего, кроме самой информации в таких страницах нет. Сырые данные чаще всего пересылают по одному из двух стандартов: JSON или XML. У каждого свои плюсы и минусы, однако, важно разбираться в обоих. JSON вы уже видели на скрине выше, а ХМL выглядит вот так: Создаём простой погодный бот для Telegram за несколько вечеров - 2После недолгого поиска, был найден англоязычный проект Open Weather Map, который отдаёт данные бесплатно, если не делать больше 50 запросов в минуту. Для нас этого вполне достаточно, регистрируемся, получаем уникальный токен(код) по которому сервер будет знать, что мы не самозванцы, а приличные будущие разработчики. Заходим на страницу с API-документацией (тыц), и выясняем, что прогноз на 5 дней по любому городу можно получить, если отправить запрос вида.

https://api.openweathermap.org/data/2.5/forecast?q=(город)&APPID=(уникальный токен, полученный при регистрации)
Можно посмотреть как ответ на такой запрос выглядит в браузере тыц Мы уже выяснили, что, по сути, нужно просто перейти по правильной ссылке и сервер выдаст нужные данные. Осталось научиться это делать средствами Java. Простой GET запрос на Java выглядит так:

//создаём строку со ссылкой на нужную страницу, 
//я тут её склеиваю из заранее определённых констант, меняя только сам город
String urlString = API_CALL_TEMPLATE + city + API_KEY_TEMPLATE;
//создаём объект который будет содержать ссылку
URL urlObject = new URL(urlString);
//создаём соединение, используя объект
HttpURLConnection connection = (HttpURLConnection) urlObject.openConnection();
//выбираем тип запроса (GET)
connection.setRequestMethod("GET");
//тут мы указываем, данные о себе, что мы можем принять всё то, 
//что примет и любой современный браузер
connection.setRequestProperty("User-Agent", "Mozilla/5.0");
//В начало ответа сервер всегда вставляет число, по которому можно судить, прошло ли всё хорошо. 
//200 - значит OK
int responseCode = connection.getResponseCode();
//на несуществующий город или город с опечаткой, сервер выдаст код ответа 404, 
//бросаем на него исключение, чтобы обработать на уровне повыше и предложить
//пользователю ввести город заново
if (responseCode == 404) {
     throw new IllegalArgumentException();
}
// создаём поток, вычитываем все строки, и склеиваем в одну большую строку, 
//которую будем потом обрабатывать в других методах
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
     response.append(inputLine);
}
in.close();
return response.toString();
Если запрос был корректным, а сервер - доступным, мы получим простыню данных, в которой полезная информация перемешана с той, которая сейчас не нужна. Чтобы удобно извлекать из JSON и XML нужные данные, под Java написано тонны библиотек на любой вкус. Так как я предпочёл JSON, для его обработки выбрал весьма популярную библиотеку под названием Jackson. Она, кстати немного изучается на JavaRush на каких-то высоких уровнях. Для обработки больших объёмов JSON-данных важно понимать структуру документа. На помощь приходят полезные сайты вроде этого . Слева у нас оригинальный JSON, справа - структурированный: Создаём простой погодный бот для Telegram за несколько вечеров - 3 Видно что ответ состоит из 5 JSON-объектов высшего уровня, 2 из которых сложные и являются узлами для следующих ветвей. Интересующие нас данные хранятся в узле list. Внутри list - массив из 38 JSON-строк, в каждой описана погода в определённое время. То есть это такая себе древовидная структура, где есть корень, ветви, веточки и даже листья :) А на узлах как раз и проходит разветвление. К счастью, Jackson умеет представлять любой валидный JSON в виде дерева. Таким образом, зная как называется нужный нам атрибут(например, температура воздуха), и на каком уровне дерева он находится, достать его не составит особых проблем. Для начала я извлёк все строчки из массива "list" и добавил их в отдельный список. Я, грубо говоря, порезал простыню с данными на куски, каждый из которых - это отдельный прогноз. Маленькие части проще держать в голове и оперировать ими.

//JsonNode - это один из узлов в древовидной иерархии, от которого идут ветви
//получаем узел, который называется "list"
JsonNode arrNode = new ObjectMapper().readTree(data).get("list");
//если это действительно массив узлов
if (arrNode.isArray()) {
//выполняем для каждого узла, который содержится в массиве
      for (final JsonNode objNode : arrNode) {
//в атрибуте "dt_txt" каждого маленького узла хранилось время прогноза, я отобрал данные за 9 утра и 6 вечера
                String forecastTime = objNode.get("dt_txt").toString();
                if (forecastTime.contains("09:00") || forecastTime.contains("18:00")) {
                weatherList.add(objNode.toString());
            }
      }
}
Так мы получили список строк, в котором каждая строка представляет собой JSON-сводку погоды в определённое время. Осталось извлечь желаемое и отформатировать. Если у нас есть такая строка:

"main":{"temp":261.45,"temp_min":259.086,"temp_max":261.45,"pressure":1023.48,"sea_level":1045.39,"grnd_level":1023.48,"humidity":79,"temp_kf":2.37}
то, это узел под названием "main". Чтобы получить из него любые данные, к примеру уровень моря, достаточно такого кода:

ObjectMapper objectMapper = new ObjectMapper();
//line - это наша JSON-строка
mainNode = objectMapper.readTree(line).get("main");
String seaLevel = mainNode.get("sea_level");
Jackson позволяет сразу представлять данные в числовом формате:

double seaLevel = mainNode.get("sea_level").asDouble();
Теперь мы можем извлекать любые данные из прогноза, и осталось только их склеить, как пожелается, и отправить пользователю в Telegram. Полный исходный код у меня на гитхабе, а вот попробовать бота в действии можно по ссылке, либо найдя @denifoBot в поиске Телеграмм. Название города нужно писать в латинской транслитерации, например "Kyiv" или "Moscow". Спасибо, если осилили до конца, принимаю разумную критику, так как только учусь и нарабатываю простенькие проекты на гитхаб, чтобы конкурировать с голодными талантами из своего города :) Всем пока! P.S Полагаю, что тут могут быть опечатки, поэтому можете всё присылать в личку, ну, или в комментарии, если вы прям очень злые :)
Комментарии (7)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Dima Makarov Уровень 42
15 сентября 2023
Автору спасибо, статья полезная. Я для себя парсер сделал по проще, с меньшим обходом этих массивов, обхожу только ветку list и оттуда сразу вытягиваю значения. Но тут уж каждый сам себе хозяин. Вот если бы кто запилил статью как задеплоить приложение на какой бесплатный сервер, было бы супер.
Antony.Glim Уровень 36 Expert
17 июля 2019
Полезная статья
Alex Уровень 38
16 июля 2019
Для более простых случаев я использовал метод ObjectMapper#readValue(URL src, Class<T> valueType), который сам читает JSON по URL без необходимости открывать вручную коннекшен, считывать инпутстрим, проч. и десериализует его в объект соответствующего класса, который уже можно вертеть как хочешь. Например:

Clazz clazz= mapper.readValue(new URL("http://www.бла-бла"), Clazz.class);
Можно десериализовать просто в мапку типа

Map<String,Object> map = mapper.readValue(url, Map.class);
(для этого случая может не оч,т.к. мапа будет содержать в value листы листов, но для линейного JSON такой подход будет очень простым: вбить url, десериализовать в мапу и развернуть мапу в строку). Наверняка есть элегантные решения и для таких ветвистых JSON. UPD: ObjectMapper#readTree(URL source)
Artem Murk Уровень 35
29 ноября 2018
Спасибо! На данный момент тоже занимаюсь разработкой ботов на телеге. В копилке кстати есть погодный бот, использовал ту же апишку) советую использовать её тем кто решился написать что то своё. Сохранил ссылку на json viewer, полезная штука) мне пришлось ручками разбирать.