X-Notifier. Пишем оповещалку для трекера и диалогов на Хабарахабр

в 8:47, , рубрики: api, Firefox, Google Chrome, javascript, mail, notifications, браузеры, метки: , , , ,

X Notifier. Пишем оповещалку для трекера и диалогов на Хабарахабр
Есть хороший плагин для всех популярных браузеров, X-Notifier. Он позволяет получать уведомления о новых письмах, сообщениях, да о чем угодно с различных сервисов в одном месте. Для него написано множество скриптов (Gmail, Яндекс.Почта, Google+, Facebook, Twitter и прочих). Но скрипта для Хабра, до сих пор никто не написал, пора исправить это недоразумение!

Вступление

Скрипт созданный по методам используемыми в это, с большой вероятностью будет работать в любом браузере, для которого существует это дополнение. Скрипт написанный для Хабра, тестировался только в Firefox и Google Chrome, в последнем он работает с ограничениями. Так же на данном этапе поддерживается одновременная работа только с одним аккаунтом. Для тех кто не хочет читать статью, а просто хочет оповещалку, можете перейти к заключению.

Изучение цели

Если описать процесс поверхностно, то нам нужно сделать следующее. Отправить запрос с данными на авторизацию, получить куки и время от времени получать страницу авторизованного пользователя и парсить счетчики трекера и диалогов. Все предельно просто!
Рассмотрим форму авторизации на странице https://id.tmtm.ru/login/ (лишние детали убраны):

Форма

X Notifier. Пишем оповещалку для трекера и диалогов на Хабарахабр

<form novalidate="" data-remote="true" method="post" id="login_form" class="s-form login_form validateble"
      action="/ajax/login/">

    <input type="hidden" value="180351c318af67fa0ec59ecad9ebae72" name="state">
    <input type="hidden" value="habrahabr" name="consumer">

    <div class="s-field s-with-error">
<input type="email" data-validate_url="/ajax/validate/email/" id="email_field" tabindex="1" autofocus="" data-required="true" name="email" placeholder="E-mail" value="">
    </div>

    <div class="s-field s-with-error">
        <input type="password" tabindex="2" name="password" data-required="true" placeholder="Пароль" value="">
    </div>

    <div class="s-field">
        <input type="hidden" name="captcha">
        <input type="hidden" id="recaptcha_challenge_field" name="recaptcha_challenge_field">
        <input type="text" name="recaptcha_response_field" id="recaptcha_response_field" data-required="true"
               placeholder="Символы с картинки" value="" autocomplete="off" tabindex="3">
        <div class="icon_captcha"></div>

        <script src="//www.google.com/recaptcha/api/challenge?k=6LftHuoSAAAAAORONRXn_6xb2f_QCtXqfbRPfY2e"
                type="text/javascript">
        </script>
        
        <input type="hidden" value="recaptcha" name="captcha_type">
    </div>
</form>

Здесь мы видим 3 видимых поля для отправки (email, password, recaptcha_response_field), с ними все понятно. А так же 5 скрытых (state, consumer, captcha, recaptcha_challenge_field, captcha_type). Поле state представляет собой некий уникальный идентификатор который генерируется для каждого логина, consumer для нас это статичное значение и всегда равно habrahabr, значение captcha отправляется всегда пустое, recaptcha_challenge_field уникальный идентификатор капчи, captcha_type всегда равно recaptcha.
С формой все понятно, теперь перейдем к данным для счетчика. Это панель пользователя.

Панель

X Notifier. Пишем оповещалку для трекера и диалогов на Хабарахабр

<div class="userpanel silver">
    <div class="bottom">
        <a href="http://habrahabr.ru/tracker/">трекер</a>
        <a class="count" href="http://habrahabr.ru/tracker/">+2</a>
         <a href="http://habrahabr.ru/conversations/">диалоги</a>            
        <a href="http://habrahabr.ru/users/BloodUnit/favorites/">избранное</a>
    </div>
  </div>

Тут все просто, нам необходимо просто пройтись по строке регуляркой, и захватить счетчик трекера и диалогов если они есть.
Приступим к реализации.

API

У плагина есть что-то вроде API, но документации к нему мне найти не удалось, и скорее всего, ее нет. Единственное что нашлось, это заметка как влючить логи и небольшой пост от разработчика аддона, но он подходит только для сайтов с простой авторизацией, хабр не из таких. Поэтому пришлось читать исходники и пробовать, и пробовать…

Рассмотрим методы которые предстоит использовать.
Всего их нам понадобится 5:

  • init() — метод инициализации скрипта, запускается один раз при старте
  • getCount(aData) — метод принимающий строку(обычно это HTML), и возвращающий счетчик. Если счетчик >= 0, проверка проведена успешно, иначе проверка завершилась ошибкой
  • checkLogin(aData, aHttp) — проверяет состояние логина, aData строка(как правило HTML), aHttp объект XMLHttpRequest
  • process(aData, aHttp) — метод запускается на каждом этапе(ниже приведен список) работы плагина, aData строка данных(как правило HTML), aHttp объект XMLHttpRequest
  • dlog(name, data) — метод для записи в лог, принимает два строковых параметра

Список этапов, число это последовательность выполнения:

  • ST_CHECK = 0 — проверка авторизации
  • ST_PRE = 100 — подготовка к логину
  • ST_PRE_RES = 101 — прием ответа от запроса запущенного на предыдущем этапе
  • ST_LOGIN = 200 — логин
  • ST_LOGIN_RES = 201 — прием ответа от запроса запущенного на предыдущем этапе
  • ST_DATA = 300 — запрос данных для обработки, например подсчета непрочитанных сообщений
  • ST_DATA_RES = 301 — прием ответа от запроса запущенного на предыдущем этапе

Некоторые этапы, например такие как ST_LOGIN реализованы плагином и подходят для большинства сайтов с простой авторизацией, но вы можете их переопределить, что мы и сделаем.

Пишем

Для начала нам надо инициализировать скрипт. Здесь мы зададим некоторые статичные параметры и этап с которого предстоит выполнение скрипта.

Скрытый текст
function init() {
    this.initStage = ST_PRE; // По умолчанию, первым этап это ST_LOGIN, но так как нам предстоит разгадывать капчу, то мы ставим подготовку
    this.loginData = ["https://id.tmtm.ru/ajax/login/", "email", "password", "consumer=habrahabr&captcha_type=recaptcha&captcha="]; // Первый элемент массива URL для постинга формы, второй и трейтий значения атрибутов name для e-mail и пароля, четвертый дополнительные параметры

    this.dataURL = "http://habrahabr.ru/"; // URL для парсинга
    this.viewURL = "http://habrahabr.ru/tracker/"; // URL который будет открываться при клике
    this.cookieDomain = "habrahabr.ru"; // Домен для которого будут ставиться куки
}

Следующий шаг — это авторизация, все этапы расположены в хронологическом порядке:

Скрытый текст

function process(aData, aHttp) {
    switch (this.stage) {
        /* Переходим по ссылке логина, чтобы хабр сгнерировал нам state */
        case ST_PRE:
            this.getHtml("https://auth.habrahabr.ru/login/");
            return false;
        /* Получаем страницу с формой, парсим state и ссылку на скрипт recaptcha,
            скачиваем скрипт и переходим к следующему этапу */
        case ST_PRE_RES:
            var recaptchaScriptLink = aData.match(/(//www.google.com/recaptcha/api/challengeS+?)"/);
            var state = aData.match(/state=([wn]+)/);
            if (recaptchaScriptLink && state) {
                this.originPostData = this.loginData[LOGIN_POST];
                this.loginData[LOGIN_POST] += "&state=" + encodeURIComponent(state[1]);
                this.referer = this.loginData[LOGIN_URL] + "?" + "&state=" + encodeURIComponent(state[1]) + "&consumer=habrahabr";
                this.getHtml("https:" + recaptchaScriptLink[1]);
                return false;
            }
            this.onError();
            break;
        /* Получаем ссылку на капчу и выводим окно ввода пользователю */
        case ST_PRE_RES + 1:
            var recaptchaUid = aData.match(/challenges*:s*'(S+?)'/);
            if (recaptchaUid) {
                this.loginData[LOGIN_POST] += "&recaptcha_challenge_field=" + encodeURIComponent(recaptchaUid[1]);
                this.openCaptchaDialog(this.id, this.user, "https://www.google.com/recaptcha/api/image?c=" + recaptchaUid[1]);
                return false;
            }
            this.onError();
            break;
        /* Добавляем введенные пользователем данные в запрос */
        case ST_PRE_RES + 2:
            this.loginData[LOGIN_POST] += "&recaptcha_response_field=" + encodeURIComponent(aData);
            this.stage = ST_LOGIN;
            return this.process(aData, aHttp);
            break;
        /* Посылаем запрос авторизации */
        case ST_LOGIN:
            this.getHtml(this.loginData[LOGIN_URL], this.loginData[LOGIN_POST], {
                Referer: this.referer
            });
            return false;
        /* Обрабатываем запрос авторизации 
            и устанавлиаем следующим шагом получение данных для обработки */
        case ST_LOGIN_RES:
            this.loginData[LOGIN_POST] = this.originPostData;
            var habrRedirectLink = aData.match(/'(.*?)'/);
            if (habrRedirectLink) {
                this.getHtml(habrRedirectLink[1]);
            }
            this.stage = ST_DATA;
            return true;
    }
    return this.baseProcess(aData, aHttp);
}

Тут есть несколько моментов.

  • Javascript на странице авторизации не выполняется в контексте плагина, поэтому нам необходимо делать лишние телодвижения для получения ссылки на капчу
  • Если этап возвращает false, будет инкрементировано значение последовательности.
  • Данные которые возвращают методы this.getHtml и this.openCaptchaDialog, будут переданы следующему этапу
  • На этапе ST_LOGIN, нам необходимо устанавливать значение Referer, иначе авторизация не пройдет. Google Chrome не позволяет устанавливать этот параметр (и это стандарт, хотя и в Working Draft), соответственно авторизация через плагин в нем работать не будет, но если вы залогинены на сайте, то все будет работать нормально!
  • На этапе ST_LOGIN, Хабр возвращает скрипт с редиректом на главную который выглядит примерно так:
    Скрытый текст

    window.location.href = 'https://habrahabr.ru/ac/entrance/?token=5a15a5d48c7fdeaed5ab20e852107dc6&state=26593fdea0963d8241aab3f20a6893b4&time=1390388377&sign=bb8f45d63c768ed6aebc5ae2bb22de3b';
    

Реализация метода проверки логина очень проста:

Скрытый текст

function checkLogin(aData, aHttp) {
    switch (this.stage) {
        /* Получаем HTML главной страницы */
        case ST_CHECK:
            this.getHtml(this.dataURL);
            return false;
        /* Ищем ссылку логина на ней, если не находим, залогинены */
        case ST_CHECK + 1:
            var loginLink = aData.match(/<a.+?class="login"/);
            if (!loginLink) {
                this.stage = ST_DATA;
                this.getHtml(this.dataURL);
                return true;
            } else {
                this.stage = this.initStage;
                return this.process("");
            }
    }
    this.onError();
    return true;
}

И наконец последний метод, парсинг счетчиков:

Скрытый текст

function getCount(aData) {
    var userMenu = aData.match(/userpanel[sS]*?charge_string/);
    if (!userMenu) {
        return -1;
    } else {
        var counter = 0;
        var counterRegex = /class="count"[^>]*>+?(d*)/g
        var counterResult;
        while ((counterResult = counterRegex.exec(userMenu[0])) !== null) {
            counter += +counterResult[1] || 0;
        }
        return counter;
    }
}

Тут пояснять даже особо нечего, просто выбираем значения регулярными выражениями, и возвращаем полученный результат.

Заключение

В данном посте был показан пример, как написать простой скрипт для проверки сайта на предмет новых сообщений. Следуя этим правилам, вы можете написать скрипт практически для любого сайта. Некоторые скрипты будут гораздо проще в написании, особенно если у сайта простая схема авторизации (например скрипт для Яндекс.Почты умещается в 30 строк).

Готовый скрипт для Хабра можно скачать на GitHub. Форки и пулл реквесты приветствуются.
Страница дополнения.
Скрипты для других сервисов.

Автор: BloodUnit

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js