JavaRush /Java блог /Random UA /Створюємо простий погодний бот для Telegram за кілька веч...
Philip J.
40 рівень
Днепр

Створюємо простий погодний бот для Telegram за кілька вечорів

Стаття з групи Random UA
Всім привіт! Тема створення ботів для телеграм дещо побита, і гайдів написано дуже багато (наприклад, ось цей ). Тому ми краще розглянемо докладніше роботу з будь-яким стороннім API, оскільки це критично важлива навичка для будь-якого веб-розробника. Відразу скажу, що додаток не ставив за мету надати максимально функціональний і корисний прогноз, конкурувати з погодними сайтами немає сенсу, важливо було навчитися працювати з віддаленими з'єднаннями та парсингом даних засобами Java. Отже, з'ясуємо спершу, що нам потрібно. Наша програма, по суті, складається з трьох логічних частин:
  • прийняти повідомлення від користувача
  • обробити повідомлення, і якщо це валідна команда, підготувати дані для відповіді. У нашому випадку підготувати прогноз погоди, якщо користувач ввів коректне місто
  • надіслати готову інформацію користувачу в чат
Перший і третій пункт досить простий, і стосується лише роботи з Telegram API, охочі можуть вивчити посилання, що залишилося вище. Ми ж звернемо увагу на другому пункті. API використовують тоді, коли одні розробники хочуть надати доступ до своїх даних іншим розробникам. Візьмемо, наприклад, Вконтакте. Кожен має список друзів, він зберігається десь у базі даних на серверах ВК. Припустимо, що якийсь програміст вирішив створити гру у шашки із друзями. Щоб його застосування працювало правильно, програма повинна вміти отримувати список друзів будь-якого гравця. Для цього програміст знаходить документацію до ВК API і дивиться, який запит потрібно зробити, щоб цей список отримати. Цей запит називається HTTP-запитом. А два найпоширеніші HTTP-запити - це GET і POST. Про них у мережі теж достатньо, зупиняти не буду. Для наших цілей, а саме отримання даних прогнозу погоди буде достатньо простого GET-запиту. Якщо звернутися з GET запитом до звичайного веб-сервера, він часто поверне html-код, який браузер перетворить на зручну для користувача сторінку, застосувавши стилі, скрипти та ін. Якщо ж ми звертаємося з таким запитом до API сервера, у відповідь зазвичай повертаються лише сирі дані без стилів та скриптів. У вікні браузера це виглядає приблизно так: Створюємо простий погодний бот для Telegram за кілька вечорів.Ці дані призначені не для людей, а для інших програм, тому нічого зайвого крім самої інформації в таких сторінках немає. Сирі дані найчастіше пересилають за одним із двох стандартів: JSON або XML. У кожного свої плюси та мінуси, однак, важливо розбиратися в обох. JSON ви вже бачабо на скрині вище, а ХМL виглядає так: Створюємо простий погодний бот для Telegram за кілька вечорів.Після недовгого пошуку, був знайдений англомовний проект 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 за кілька вечорів. Видно, що відповідь складається з 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. Дякую, якщо подужали до кінця, приймаю розумну критику, бо тільки навчаюсь і напрацьовую простенькі проекти на гітхаб, щоб конкурувати з голодними талантами зі свого міста :) Всім поки що! PS Вважаю, що тут можуть бути друкарські помилки, тому можете все надсилати в особу, ну, або в коментарі, якщо ви дуже злі :)
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ