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

В статье рассматривалось, чем полезны карты Strava Heatmap для туристов и велосипедистов и о том, как подключить к ним навигационное приложение на смартфоне на примере Osmand. Дело в том, что для загрузки каждого кусочка карты нужно указывать параметры с авторизационными данными. Примерно так:

GET https://heatmap-external-{abc}.strava.com/tiles-auth/all/hot/{z}/{x}/{y}.png?px=256&Signature={CloudFront-Signature}&Key-Pair-Id={CloudFront-Key-Pair-Id}&Policy={CloudFront-Policy}

Чтобы получить эти данные, предлагалось авторизироваться на сайте Strava с десктопного браузера. Как результат, в браузере должны появиться cookie файлы. Нужно найти в них требуемые строки и скопировать их в адрес запроса. А затем, вручную ввести запрос в Osmand, чтобы он смог скачивать с его помощью карту.

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

Добавляем промежуточное звено


Чтобы пользователю не приходилось заменять в смартфоне URL запроса каждый раз, когда cookie-данные устаревают, нужно указать какой-нибудь неизменный URL. Я указал ссылку на свое серверное приложение. Это приложение будет редиректить пользователя на различные адреса в зависимости от указанных параметров.

https://anygis.herokuapp.com/Tracks_Strava_All/{x}/{y}/{z}

Чтобы быстрее перейти к сути, я не буду подробно останавливаться на описании этого серверного приложения. Расскажу лишь об основных его действиях.

Если запрашивается тайл с зумом до 12 (такие Stava отдает без авторизации), то пользователь сразу перенаправляется на публичный URL.

https://heatmap-external-a.strava.com/tiles/all/hot/10/619/318.png

Если же нет, то выполняется проверка. Для быстрого доступа приложение хранит в своей базе данных последнюю рабочую версию файла cookie. Когда оно получает запрос, оно парсит этот файл и создает URL со всеми подставленными параметрами.

Получается что-то вроде этого
https://heatmap-external-a.strava.com/tiles-auth/all/hot/10/619/318.png?px=256<b>&Signature</b>=Q47FWl1RX-5tLNK9fGfa7hdoxqwwjCLfrxwb~L3eke8h~glz5IBHmkLmu8ofh6eNWUM3usHTz4Z3rypbQGByC2jRhdzL2iJndIOu2TY9ZU34YUJV9QgMEf0L5cDHGjEsksYSVdkCqNRvOMnnzUc96wR9sktK2a0pcsI~E5eNvqjfsGbSWi6KCdfc1-~2D8t9YjbKftokhvMY20pM~PD6Y-fGYmpoTO5LOyMfIYboXnKGm29VnA9kA8LIxD-LzpADWO81i4pOMBvkVkJuLBGtO96a79P5D4tRP05DpI7y457LuKcuqRZaVQRB1L2AXgKvQgnx6nqr9T2jRAZNoy06ng__
<b>&Key-Pair-Id</b>=APKAIDPUN4QMG7VUQPSA
<b>&Policy</b>=eyJTdGF0ZW1lbnQiOiBbeyJSZXNvdXJjZSI6Imh0dHBzOi8vaGVhdG1hcC1leHRlcm5hbC0qLnN0cmF2YS5jb20vKiIsIkNvbmRpdGlvbiI6eyJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTU1ODUwODc2Mn0sIkRhdGVHcmVhdGVyVGhhbiI6eyJBV1M6RXBvY2hUaW1lIjoxNTU3Mjg0NzYyfX19XX0_


После этого оно отправляет HEAD запрос на этот адрес, чтобы проверить, доступен он или нет. Если в ответ приходит статус код “200 Success”, значит cookie все еще рабочие. Приложение просто редиректит пользователя на этот адрес и у него прекрасно загружается карта.

А вот если приходит код “401 Unauthorized”, то значит cookie устарели и нужно заново их получить. В этом случае приложение запускает скрипт для получение авторизационных данных.

Автоматическая авторизация


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

Для этого я воспользовался браузером с дистанционным управлением Headless Chrome и написал для него скрипт, чтобы он “физически” загрузил странницу авторизации, ввел туда логин и пароль, а затем кликнул на кнопку.

Для этого существует библиотека Puppeteer, которая умеет управлять браузером с помощью Node.js скриптов. Синтаксис отлично описан в этой статье. Предлагаю ознакомиться с ней самостоятельно.

После ее прочтения остается лишь вопрос, на чем запускать наши скрипты. Если вы уже опытный Node.js разработчик, то можете пропустить этот раздел. Для остальных могу предложить воспользоваться готовым сервисом от Apify.com. Это избавит нас от необходимости создавать и настраивать свой сервер. Для наших задач вполне хватит бесплатного аккаунта.

Настройки аккаунта Apify для запуска скрипта
Для начала вам потребуется зарегистрироваться на этом сервисе. После этого откройте радел со своим аккаунтом, перейдите в раздел Actors и создайте новый скрипт.



В поле Name укажите название, которое будет использоваться при запуске скрипта через Api. Нажмите Save и перейдите на страницу Source.



Для запуска Headless Chrome выберите образ сервера “Node.js 10 + Chrome on Debian” и нажмите Save.



Теперь перейдем в раздел Api и скопируем URL POST-запроса, с помощью которого мы будем запускать наш скрипт.



В тело этого запроса можно прикреплять JSON с данными для нашего скрипта. Я буду отправлять туда свой логин и пароль для авторизации на Strava.

{ "email": "your_nick@gmail.com" , "password": "Your_Password" }


Скрипт для автоматического получения cookie данных со Strava
Теперь вернемся в раздел Source и перейдем в окошко с редактором кода. Наш скрипт будет выглядеть так:

const Apify = require('apify');

Apify.main(async () => {

  // ПОДГОТОВКА

    // Извлечь параметры из принятого JSON
    const input = await Apify.getInput();
    if (!input || !input.email || !input.password) throw new Error('Invalid input, must be a JSON object with the "email" and "password" field!');

    // Запустить среду управления браузером
    const browser = await Apify.launchPuppeteer();



  // СТРАНИЦА 1 - Получаем базовую часть cookie

    // Открыть новую вкладку
    const page1 = await browser.newPage();
    await page1.setViewport({width: 1280, height: 1024});

    // Перейти на страницу авторизации  
    await page1.goto('https://www.strava.com/login', {waitUntil: 'networkidle2'});

    // Найти на html-странице поля логин/пароль и заполнить их  
    await page1.waitForSelector('form');
    await page1.type('input#email', input.email);
    await page1.type('input#password', input.password);

    // Немного подождать, чтобы выглядеть, 
    // как настоящий человек, которому требуется время на ввод данных
    await page1.waitFor(200);

    // Найти кнопку Войти и кликнуть на нее
    await page1.evaluate(()=>document
      .querySelector('button#login-button')
      .click()
    );

    // Дождаться результатов и загрузить базовый cookie файл
    await page1.waitForNavigation();
    const sessionFourCookie = await page1.cookies();



  // СТРАНИЦА 2 - получаем дополненный cookie с ключами для просмотра карты

    // Открыть новую вкладку 
    const page2 = await browser.newPage();

    // Перейти на страницу с просомотром карты, как залогиненный пользователь.
    // То есть, указав при этом полученные в предыдущем шаге cookie
    await page2.setCookie(...sessionFourCookie);
    await page2.goto('https://heatmap-external-a.strava.com/auth');

    // Получить новый дополненный файл cookie
    const cloudfontCookie = await page2.cookies();



  // ЗАВЕРШЕНИЕ 
    
    // Закрыть среду исполнения
    await browser.close();

    // Вернуть дополненный файл cookie в теле ответа
    return cloudfontCookie;
});


Финальный этап


Когда скрипт отработает, он вернет файл cookie с авторизационными данными. Серверное приложение сохранит его в свою базу данных и будет использовать для всех последующих запросов карты Strava. До тех пор, пока cookie не устареют и не потребуется еще раз повторить эту процедуру. Благо, теперь все происходит автоматически. Без лишних действий со стороны пользователя.

Увы, но не все так гладко. У этого метода есть слабое место – это скорость работы. Дело в том, что на запуск сервера, загрузку удаленного браузера, загрузку двух веб страниц и на авторизацию на каждой из них требуется некоторое время. По моим прикидкам, выходит более минуты.

За это время навигационное приложение попросту оборвет загрузку из-за превышения тайм-аута. Поэтому лучше сразу вернуть пользователь код ошибки 401, чтобы лишний раз не высаживать ему батарею.

Для этого серверное приложение будет устанавливать флаг, когда поймет, что cookie данные устарели. После этого этого оно отправит запрос на запуск скрипта, а пользователям будет сразу возвращать код ошибки. Иными словами, на протяжении минуты сервер будет недоступен.

И вот когда от нашего скрипта придет ответ с новыми cookie, приложение сохранит их в свою базу данных и снимет флаг. После этого сервер снова начнет работать в штатном режиме и моментально перенаправлять пользователя на страницы для скачивания карты.

Результат


Результатом наших трудов стали неизменяющиеся URL-адреса, с помощью которых пользователи могут подключать свой навигатор к картам Strava.

https://anygis.herokuapp.com/Tracks_Strava_All_HD/{x}/{y}/{z}
https://anygis.herokuapp.com/Tracks_Strava_Run_HD/{x}/{y}/{z}
https://anygis.herokuapp.com/Tracks_Strava_Ride_HD/{x}/{y}/{z}
https://anygis.herokuapp.com/Tracks_Strava_Water_HD/{x}/{y}/{z}
https://anygis.herokuapp.com/Tracks_Strava_Winter_HD/{x}/{y}/{z}

Как вариант, можно сделать готовые пресеты с этими адресами для своего навигатора. Например, на этой станице я выложил такие пресеты в форматах для Osmand, Locus, GuruMaps и Orux. Ссылки на скачивание карты Strava находятся в разделе “Полный набор” в подразделах “Overlay” или “Глобальные — OSM – Ways”.

Комментарии (3)


  1. GritsanY
    14.05.2019 09:38

    Не совсем понял, как это (https://anygis.herokuapp.com/Tracks_Strava_Ride_HD/{x}/{y}/{z}) засунуть в OsmAnd. Аутентификацией занимается ваше приложение? Оно публичное?
    Я добавил источник в OsmAnd с URL вида anygis.herokuapp.com/Tracks_Strava_Ride_HD{0}/{1}/{2} но ничего не отображается.


    1. nnngrach Автор
      15.05.2019 00:34

      Конкретно для Османда надо использовать вот такой вариант ссылок:
      https://anygis.herokuapp.com/Tracks_Strava_All/{1}/{2}/{0}
      https://anygis.herokuapp.com/Tracks_Strava_Run/{1}/{2}/{0}
      https://anygis.herokuapp.com/Tracks_Strava_Ride/{1}/{2}/{0}
      https://anygis.herokuapp.com/Tracks_Strava_Water/{1}/{2}/{0}
      https://anygis.herokuapp.com/Tracks_Strava_Winter/{1}/{2}/{0}


      Минимальное увеличение 0
      Минимальное увеличение 15

      Или же можно скачать уже настроенный пресет и положить его в папку Android\data\net.osmand\files\tiles
      Strava Heatmap — All
      Strava Heatmap — Ride
      Strava Heatmap — Run
      Strava Heatmap — Water
      Strava Heatmap — Winter


      1. GritsanY
        15.05.2019 07:20

        Спасибо, ссылки в приведённом вами виде заработали. Теперь Strava Heatmap будет использоваться не только при планировании катки, но и в процессе =)