Как я ЖКХ-платежи автоматизировал
Если у вас есть возможность оплачивать все коммунальные счета из одного приложения, я вам завидую. А уж если у вас стоят умные счётчики, то вы просто счастливчик.
У меня вот две обслуживающие организации, и заведены два личных кабинета на разных сайтах. В одном кабинете я должен ежемесячно передавать показания всех счётчиков и оплачивать воду, электричество и прочие коммунальные услуги. А в другом я должен продублировать показания счётчиков горячей воды и заплатить за тепло. Дни, когда можно передавать показания по воде и электричеству, разные. А передавать показания и потом платить после выставления счёта лучше вовремя, иначе возникает неразбериха. Можно, конечно, ставить напоминалки, но это не слишком-то помогает.
Решил я немного упростить жизнь и написать программку, которая будет оповещать меня о наступивших событиях, таких как необходимость заплатить или передать показания, и позволит передавать показания счётчиков сразу на оба сайта в нужное время.
Под катом рассказ о том, что у меня из этого получилось.
Кабинеты, о которых я говорил, выглядят следующим образом. Это кабинет МосОблЕИРЦ. Здесь я ввожу данные четырёх счётчиков воды (два на кухне и два в санузле), электросчётчика и оплачиваю коммунальные счета. Как видите, ребята ещё немножко и на рекламе зарабатывают.
Кабинет теплоэнергетической компании Глобус буйством красок напоминает сайты девяностых годов, тогда они почти все были такие.
Здесь я должен продублировать показания двух счётчиков горячей воды и заплатить за тепло.
Ну хорошо, так какой быть программе? Это может быть сайт. Можно задуматься о мобильном приложении. Но мне ближе всего C#. Стало быть, создаём проект Windows Forms .NET.
Идея такая. Программа взаимодействует с сайтами посредством HTTP-запросов. Если в одном из кабинетов выставляется счёт, появляется строка типа «Выставлен счёт на 1234,00 руб». Если счёта в текущем месяце ещё нет, будет написано «Оплачено». Линки на кабинеты МосОблЕирц и Глобус открывают браузер, после чего там можно будет совершить платёж. Счётчики воды можно оплачивать с 5 числа, электросчётчик — с 15. Если в текущем месяце показания ещё не были переданы, соответствующие текстбоксы включаются, и показания можно передавать. Справа от текстовых полей находятся прежние показания.
Программа запускается при запуске Windows. Если всё оплачено, и показания отправлены, программа тихо закрывает себя, иначе пользователь может выполнить необходимые действия. Поскольку я всегда выключаю настольный компьютер на ночь, а включаю почти каждый день, это идеальный вариант.
Ну что ж, приступим к исследованию сайта МосОблЕирц (на самом деле, mosenergosbyt.ru). Сначала нам нужно пройти аутентификацию. Открываем Fiddler, вводим в браузере учётные данные и жмём «Войти».
Смотрим полученные запросы в фидлере. Туннели мы игнорируем, обращения к Яндексу — тоже, это, скорее всего, сбор статистики. Будем смотреть запросы на хост my.mosenergosbyt.ru.
Итак, первый запрос, который нас интересует — это запрос на аутентификацию. Ищем. Ага, вот:
POST https://my.mosenergosbyt.ru/gate_lkcomu? action=auth&query=login HTTP/1.1
Это то, что нам надо. В Cookie видим слова Bitrix, значит это PHP. Взглянув на тело запроса, видим, что логин и пароль передаются в явном виде.
Ну что ж, попробуем для начала создать минимальный запрос с телом, содержащим логин и пароль. Смотрим в отладчике, что происходит.
Так, код ответа — 200 OK. Хорошо. Но что это? JSON ответа содержит текст
Уважаемый пользователь, с Вашего IP-адреса была зафиксирована вредоносная активность при обращении к Единому Личному кабинету АО Мосэнергосбыт и ООО МосОблЕИРЦ. В связи с этим доступ с Вашего IP-адреса был временно заблокирован. Для разблокировки доступа или если ваш адрес был заблокирован по ошибке, просим Вас обратиться в службу поддержки пользователей по телефону +7 (499) 550–9–550. Мы заботимся о безопасности Ваших данных и благодарим за понимание!
Вот те и на! Надо было создавать тестовый кабинет… Ладно, попробуем пока сделать более полный запрос, в котором пропишем значения хедеров и добавим Cookie, как в оригинальном запросе. Вот таким образом.
Посылаем… И получаем JSON с текстом «Ошибок нет»:
{
"success": true,
"total": 1,
"data": [
{
"kd_result": 0,
"nm_result": "Ошибок нет",
"id_profile": "218e52f3-4014-4e45-8ac8-56cbafb006f6",
"cnt_auth": 0,
"new_token": null,
"session": "8IM3EOS5RHETWFMUFXHFEUNECMZMGJJ8S162DD04"
}
],
"metaData": {
"responseTime": 0.031
}
}
Ура! Зря пугали, выходит. Просто этот сайт не принимает запросы без cookies. Полученный JSON содержит хеш сессии, который будет использоваться в URL последующих запросов.
Дальше всё идёт по накатанной колее. Конструируем запрос, получаем JSON, десериализуем. Надо только учесть, что некоторые запросы требуют указания идентификаторов абонента и учётной записи. Чтобы получить их, придётся добавить соответствующий метод.
Создаём запрос для получения текущего баланса, запрос для получения данных счётчиков. Никаких проблем, всё работает.
Теперь попробуем сделать обновление показаний счётчиков. В кабинете показания передаются по одному:
Значит, так и сделаем соответствующий метод.
Работает. Замечательно! Ну, и в конце надо реализовать возможность выхода из кабинета для порядка.
Отлично, с первым кабинетом разобрались. Теперь приступим к кабинету компании Глобус. Вот аутентифицирующий запрос:
POST https://lk.globusenergo.ru/ajax/auth.php HTTP/1.1
В теле запроса также передаются учётные данные в незашифрованном виде. Смотрим cookies:
Снова битрикс. Памятуя прошлый опыт, надо будет не забыть указать cookie в нашем запросе. А что это за PHPSESSID, откуда он берётся? Так, ага… Чтобы получить это значение, нужно перед аутентификацией сделать GET-запрос на lk.globusenergo.ru, и оно будет передано в хедере Set-Cookie. Ладно, так и сделаем.
Запускаем отладчик… и что там у нас в ответе? А в ответе — html личного кабинета. Значит, аутентификация прошла.
Об автоматическом перенаправлении
Интересно то, что запрос для аутентификации возвращает HTTP-код 302, и HttpHandler автоматически делает перенаправление и выполняет второй запрос GET по адресу из хедера Location, Таким образом, после вызова HttpClient.SendAsync мы видим два запроса в фидлере — POST и GET. Возвращаемое значение при этом содержит ответ второго запроса с html. Если бы мы захотели отказаться от автоматического перенаправления, нам следовало бы создавать объект HttpClient посредством конструктора, принимающего объект HttpClientHandler со свойством AllowAutoRedirect = false.
Из html мы можем сразу получить текущий баланс. Фрагмент личного кабинета в браузере
соответствует вот этому фрагменту html:
Задолженность по счету
{{arrayResult.DEBT.DEBT_END}} руб.
0.00 руб.
Ищем в тексте html слово «DEBT_END» и находим в JS-коде большую переменную arrayResultForJs, которая, помимо прочего, содержит следующее:
"DEBT":{"DEBT_END":"0.00","~DEBT_END":"0.00"}
Это нам и нужно для получения баланса. Как получить это значение из html? Можно воспользоваться библиотекой HtmlAgilityPack, как это описано на stackoverflow, но к чему такие сложности для решения простой задачи? Просто воспользуемся регулярными выражениями.
Последнее, что надо сделать — это возможность передачи новых показаний. В кабинете показания двух счётчиков передаются в одном запросе:
Пробуем написать соответствующий код, и… ничего не получается. В ответе содержится html главной страницы, а значения счётчиков не обновляются. Попытка, ещё одна… Нет, не работает. Хорошо, а если запустить запрос из закладки фидлера Composer? Вроде, проходит… Тогда сравним текст запроса из программы и реального запроса. Ага, вот: запросы отличаются значением sessid в теле запроса. А откуда взять это значение? Оказывается, первый html после авторизации содержит определение bitrix_sessid в JS-коде. Нужно получить это значение из html и прописать его в запросе. Пробуем, и… всё в порядке!
Ну вот и всё. Программа готова. Казалось бы, не бог весть какая автоматизация, но оказалось, что это довольно-таки удобно: всё необходимое сосредоточено в одном месте, не нужно дублировать показания счётчиков на разных сайтах. Кроме того, программа служит напоминалкой, и теперь я не забываю совершить необходимые действия.
Единственный недостаток — в кабинете Глобуса сумма к оплате продолжает висеть несколько дней даже после совершения платежа. Уж не знаю, то ли платёж должен поступить на счёт, то ли они вообще вручную подтверждают внесённую сумму, но приводит это к тому, что программа не закрывается при запуске и каждый день сообщает о том, что счёт выставлен. Возможно, придётся с этим что-то делать…
Конечно, очень бы хотелось иметь умные счётчики, чтобы передавать показания на серверы автоматически, а не лазить по углам с фонарикам, записывая цифры на бумажку. В следующий раз, когда настанет время менять счётчики, обязательно задумаюсь об этом.
→ Полный код проекта на Github
Спасибо за внимание!