Как известно, практически во всех вагонах Московского Метро действуют Wi-Fi точки доступа, с помощью которых пользователи могут получить доступ в интернет и приятно скоротать время поездки в метро с работы домой: почитать новости, проверить почту, посмотреть котиков на YouTube и т.д.
Каждому устройству, прежде чем ему будет предоставлен доступ к сети интернет, необходимо аутентифицироваться. В первый раз пользователю на указанный номер телефона посылается СМС с кодом, после чего система запоминает MAC-адрес устройства и в дальнейшем пользователю для аутентификации требуется только нажать на ссылку «Войти в интернет» и немного подождать.
Неудобством такой организации сис��емы является то, что даже если пользователю не нужен браузер, а он, например, хочет залезть в почту или почитать твиттер с помощью специализированного приложения, ему все равно необходимо запустить браузер, попытаться получить доступ к какой-нибудь странице, дождаться переадресации, нажать ссылку, дождаться загрузки страницы приветствия (опционально: посмотреть рекламный ролик) и только после этого он сможет воспользоваться искомым приложением.
Если в метро вы не частый гость, то подобная схема может и не вызвать у вас раздражения, однако при ежедневном использовании она все таки надоедает, поэтому, как говорил один известный и харизматичный политик: «Хватит это терпеть!», сегодня мы будем автоматизировать аутентификацию в Московском метро.
В первую очередь нам понадобится приложение Tasker. Раздобыть его можно тут (за небольшую денежку), ну или где-нибудь тут (на свой страх и риск). Лично я предпочел первый вариант и не пожалел.
Tasker — приложение, которое позволяет в зависимости от определенных условий (дата/время/местоположение/состояние устройств/показания датчиков и т.д.) производить определенные действия (отправку сообщений/вывод уведомлений/включение-отключение устройств/отрисовка простеньких интерфейсов и т.д.). Списки и условий, и действий просто огромны и зависят от версии Android и аппаратного оснащения устройства, так что приводить их полностью смысла нет.
Итак, после запуска Tasker, в первую очередь нужно перевести интерфейс на английский язык, ибо перевод тут хромает на обе ноги: Настройки->Интерфейс->Язык->English и перезапустить приложение. Теперь перед нами есть четыре вкладки:
Перейдем на закладку Tasks и создадим новую задачу, назвав ее Metro Auth:

В открывшемся окне нам необходимо для начала определить несколько переменных. Переменные определяются следующим образом:

Так вот, нам необходимо создать следующие переменные:
Помимо простых действий, Tasker пред��ставляет нам возможность писать скрипты произвольной сложности с помощью нескольких инструментов. Мы будем использовать простой JavaScript:

Здесь необходимо выставить максимальный Timeout выполнения скрипта — 50 секунд, просто на всякий случай. Галочка Auto Exit отвечает за автоматическое завершения действия после завершения основного потока скрипта. В случае если используются асинхронные запросы (наш случай) или функция setTimeout, эту галочку необходимо снимать, а завершение действия определять самостоятельно с помощью функции exit();.
Сам скрипт я представлю в двух вариантах форматирования: приличное форматирование нужно если хочется рассмотреть скрипт, не сломав глаза, и форматирование под узкий экран позволяет скрипту выглядеть более-менее прилично на узком экране телефона. Изначально скрипт набирался на телефоне в «узком» варианте, и уже потом я переформатировал его для статьи:
Набор скрипта с телефонной клавиатуры, к сожалению, не располагает к комментариям, но я вкратце опишу алгоритм:
После завершения ввода скрипта, у нас получилась вот такая задача:

С помощью первой иконки в нижнем ряду можно попробовать ее запустить. Теперь самое время спускаться в метро, чтобы настроить ее!
В метро, подключившись к точке доступа, пробуем запустить задачу. Если нет явных проблем с доступностью серверов, то мы увидим сообщение, похожее на то, что изображено на следующем рисунке слева. Внизу мы видим идентификатор формы, которая есть на последней загруженной странице — auth-form. Эта форма — явно наш клиент, вносим ее название в переменную %forms и запускаем задачу еще раз, получаем примерно то, что изображено на следующем рисунке в центре. Новый идентификатор формы — hidden_form. Добавляем ее в переменную %forms, теперь ее значение будет 'auth-form,hidden_form'. Запускаем задачу еще раз и видим примерно то, что изображено на следующей рисунке справа — либо будет форма без идентификатора, либо пометка «отсутствуют» (зависит от ветки метро). Если теперь запустить браузер, будет понятно, что аутентификацию мы прошли. Присваиваем переменной %debug значение «0», и закрываем задачу — тут мы закончили.

Теперь дело за малым — настроить автоматический запуск задачи при подключении к нужной точке доступа. Переходим на закладку Profiles и создаем новый профиль, который будет активироваться после подключения к точке доступа московского метро. После того, как закончим формировать описание точки доступа, Tasker спросит нас с какой задачей связывать это профиль, выбираем, естественно, Metro Auth.

Еще одни нюанс: хотя и редко, но аутентификация все таки слетает, хотя отключения от точки и не происходило. Если не было отключения, не было и переподключения, а значит Tasker не запустит задачу повторно, поэтому мы настроим Tasker так, чтобы аутентификация автоматически проверялась каждые 2 минуты (минимальный возможный интервал), для этого нам надо долгим нажатием на уже ск��нфигурированное условие вызвать меню, в котором добавить временное условие, в котором установить интервал.

Ну вот и все. Отныне и до тех пор, пока не придется менять идентификаторы в переменной %forms, алгоритм ваших действий при заходе в вагон следующий:
UPD: По совету Self_Perfection я экспортировал проект и выложил одним файлом. Этот файл нужно скачать и положить в папку /sdcard/Tasker/projects, потом запустить Tasker, долгим нажатием на иконку домика в нижнем левом углу вызвать меню и выбрать Import. В этой версии я вынес проверку раз в две минуты в отдельный профиль — так должно расторопнее работать.
Каждому устройству, прежде чем ему будет предоставлен доступ к сети интернет, необходимо аутентифицироваться. В первый раз пользователю на указанный номер телефона посылается СМС с кодом, после чего система запоминает MAC-адрес устройства и в дальнейшем пользователю для аутентификации требуется только нажать на ссылку «Войти в интернет» и немного подождать.
Неудобством такой организации сис��емы является то, что даже если пользователю не нужен браузер, а он, например, хочет залезть в почту или почитать твиттер с помощью специализированного приложения, ему все равно необходимо запустить браузер, попытаться получить доступ к какой-нибудь странице, дождаться переадресации, нажать ссылку, дождаться загрузки страницы приветствия (опционально: посмотреть рекламный ролик) и только после этого он сможет воспользоваться искомым приложением.
Если в метро вы не частый гость, то подобная схема может и не вызвать у вас раздражения, однако при ежедневном использовании она все таки надоедает, поэтому, как говорил один известный и харизматичный политик: «Хватит это терпеть!», сегодня мы будем автоматизировать аутентификацию в Московском метро.
В первую очередь нам понадобится приложение Tasker. Раздобыть его можно тут (за небольшую денежку), ну или где-нибудь тут (на свой страх и риск). Лично я предпочел первый вариант и не пожалел.
Tasker — приложение, которое позволяет в зависимости от определенных условий (дата/время/местоположение/состояние устройств/показания датчиков и т.д.) производить определенные действия (отправку сообщений/вывод уведомлений/включение-отключение устройств/отрисовка простеньких интерфейсов и т.д.). Списки и условий, и действий просто огромны и зависят от версии Android и аппаратного оснащения устройства, так что приводить их полностью смысла нет.
Итак, после запуска Tasker, в первую очередь нужно перевести интерфейс на английский язык, ибо перевод тут хромает на обе ноги: Настройки->Интерфейс->Язык->English и перезапустить приложение. Теперь перед нами есть четыре вкладки:
- Profiles — профили управляют связью между состоянием устройства/различными событиями и задачами;
- Tasks — задачи описывают последовательность действий, которые необходимо выполнить;
- Scenes — сцены это как бы самодельные формы, которые задачи могут создавать и настраивать, и контролы на которых могут запускать задачи;
- Vars — список глобальных переменных, которые могут быть использованы для хранения данных между запусками задач.
Перейдем на закладку Tasks и создадим новую задачу, назвав ее Metro Auth:

В открывшемся окне нам необходимо для начала определить несколько переменных. Переменные определяются следующим образом:

- Variable name — имя переменной, должно начинаться с символа % и состоять из строчных букв. Если в имени переменной будет хоть одна заглавная буква, то переменная станет глобальной, а нам это ни к чему;
- To — значение переменной.
Так вот, нам необходимо создать следующие переменные:
- %url — содержит ссылку на страницу, по которой мы будем тестировать нужна ли аутентификация или нет. Чем меньше объем передаваемых данных при этом — тем лучше (тело страницы все равно не нужно). С протоколом HTTPS возможны проблемы, так что лучше использовать HTTP;
- %forms — содержит список идентификаторов HTML-форм, которые мы будем использовать для аутентификации. На момент написания статьи корректное значение — 'auth-form,hidden_form', однако если ребята из московского метро вдруг что-то изменят, я хотел бы показать как составить такой список самостоятельно, так что пока в качестве значения поставим один пробел (пустые переменные недопустимы);
- %debug — эта переменная, при задании значения, отличного от нуля, будет вызывать показ дополнительной отладочной информации, которая поможет нам составить вышеупомянутый список форм.
Помимо простых действий, Tasker пред��ставляет нам возможность писать скрипты произвольной сложности с помощью нескольких инструментов. Мы будем использовать простой JavaScript:

Здесь необходимо выставить максимальный Timeout выполнения скрипта — 50 секунд, просто на всякий случай. Галочка Auto Exit отвечает за автоматическое завершения действия после завершения основного потока скрипта. В случае если используются асинхронные запросы (наш случай) или функция setTimeout, эту галочку необходимо снимать, а завершение действия определять самостоятельно с помощью функции exit();.
Сам скрипт я представлю в двух вариантах форматирования: приличное форматирование нужно если хочется рассмотреть скрипт, не сломав глаза, и форматирование под узкий экран позволяет скрипту выглядеть более-менее прилично на узком экране телефона. Изначально скрипт набирался на телефоне в «узком» варианте, и уже потом я переформатировал его для статьи:
Скрипт в приличном форматировании
function getUrl(url1,url2){
url1=url1.split('?')[0];
return url2.length?
(/^http(s?):\/\//i.test(url2)?url2:
(url2[0]=='/'?url1.split('/').slice(0,3).join('/')+url2:url1.split('/').slice(0,-1).join('/')+'/'+url2)
):url1;
}
function getVars(form,tag){
vars='';
fields=form.getElementsByTagName(tag);
for(i=0;i<fields.length;i++)
vars=vars+(i?'&':'')+fields[i].name+'='+fields[i].value;
return vars;
}
function submit(xhr,request,form){
request.url=getUrl(request.url,form.action);
request.method=form.method;
vars1=getVars(form,'input');
vars2=getVars(form,'textarea');
request.vars=vars1||vars2?(vars1?vars1:'')+(vars1&&vars2?'&':'')+(vars2?vars2:''):null;
getPage(request,processPage,xhr);
}
function processPage(xhr,request){
redir=xhr.getResponseHeader('Location');
if(redir){
if(redir==request.url) finalize('Ошибка: циклическое перенаправление');
else{
log('Перенаправление\n\n');
getPage({'url':redir},processPage,xhr);
}
} else {
forms=local('forms').split(',');
id=null;
for(i=0;i<forms.length;i++)
if(xhr.response.getElementById(forms[i])) id=forms[i];
if(id)submit(xhr,request,xhr.response.getElementById(id));
else if(Number(local('debug'))){
log('Формы на странице:\n');
forms=xhr.response.getElementsByTagName('form');
if(forms.length)
for(i=0;i<forms.length;i++)
log((i?', "':'"')+forms[i].id+'"');
else log('отсутствуют');
finalize();
} else finalize('Аутентификация успешна');
}
}
function checkConn(xhr,request){
redir=xhr.getResponseHeader('Location');
if(redir){
log('Перенаправление\n\n');
getPage({'url':redir},processPage,xhr);
} else {
log('Аутентификация не требуется');
finalize();
}
}
function log(txt){
logs=logs+(txt?txt:'');
}
function requestToText(request){
return 'URL: '+request.url+'\nMethod: '+request.method+', Vars: '+request.vars+'\n\n';
}
function finalize(txt){
log(txt);
if(Number(local('debug'))) alert(logs);
else if(txt) flashLong(txt);
exit();
}
function getPage(request,func,xhr){
if(!request.method) request.method='GET';
if(!request.vars) request.vars=null;
if(!xhr){
xhr=new XMLHttpRequest();
xhr.responseType="document";
xhr.timeout=20*1000;
}
xhr.open(request.method,request.url,true);
xhr.onload=function(){
if(xhr.status==200 || xhr.status==401){
log (requestToText(request)+'HTTP status: '+xhr.status+' '+xhr.statusText+'\n');
func(xhr,request);
} else {
log(requestToText(request));
finalize('Ошибка HTTP: '+xhr.status+' '+xhr.statusText);
}
}
xhr.onerror=function(){
log(requestToText(request));
finalize('Ошибка: отсутствует соединение');
}
xhr.ontimeout=function(){
log(requestToText(request));
finalize('Ошибка: таймаут соединения');
}
xhr.send(request.vars);
}
logs='';
getPage({'url':local('url')},checkConn);
Скрипт в форматировании под узкий экран
function getUrl(url1,url2){
url1=url1.split('?')[0];
return url2.length?
(/^http(s?):\/\//i.test(url2)?
url2:
(url2[0]=='/'?
url1.split('/').slice(0,3).join('/')+url2:
url1.split('/').slice(0,-1).join('/')+
'/'+url2)
):url1;
}
function getVars(form,tag){
vars='';
fields=form.getElementsByTagName(
tag);
for(i=0;i<fields.length;i++)
vars=vars+(i?'&':'')+fields[i].name+
'='+fields[i].value;
return vars;
}
function submit(xhr,request,form){
request.url=getUrl(request.url,
form.action);
request.method=form.method;
vars1=getVars(form,'input');
vars2=getVars(form,'textarea');
request.vars=vars1||vars2?
(vars1?vars1:'')+
(vars1&&vars2?'&':'')+
(vars2?vars2:'')
:null;
getPage(request,processPage,xhr);
}
function processPage(xhr,request){
redir=xhr.getResponseHeader(
'Location');
if(redir){
if(redir==request.url)
finalize('Ошибка: циклическое '+
'перенаправление');
else{
log('Перенаправление\n\n');
getPage({'url':redir},processPage,
xhr);
}
} else {
forms=local('forms').split(',');
id=null;
for(i=0;i<forms.length;i++)
if(xhr.response.getElementById(
forms[i]))
id=forms[i];
if(id)submit(xhr,request,
xhr.response.getElementById(id));
else if(Number(local('debug'))){
log('Формы на странице:\n');
forms=xhr.response.
getElementsByTagName('form');
if(forms.length)
for(i=0;i<forms.length;i++)
log((i?', "':'"')+forms[i].id+'"');
else log('отсутствуют');
finalize();
} else finalize(
'Аутентификация успешна');
}
}
function checkConn(xhr,request){
redir=xhr.getResponseHeader(
'Location');
if(redir){
log('Перенаправление\n\n');
getPage({'url':redir},processPage,
xhr);
} else {
log('Аутентификация не '+
'требуется');
finalize();
}
}
function log(txt){logs=logs+(txt?txt:'');}
function requestToText(request){
return 'URL: '+request.url+
'\nMethod: '+request.method+
', Vars: '+request.vars+'\n\n';
}
function finalize(txt){
log(txt);
if(Number(local('debug'))) alert(logs);
else if(txt) flashLong(txt);
exit();
}
function getPage(request,func,xhr){
if(!request.method)
request.method='GET';
if(!request.vars)request.vars=null;
if(!xhr){
xhr=new XMLHttpRequest();
xhr.responseType="document";
xhr.timeout=20*1000;
}
xhr.open(request.method,
request.url,true);
xhr.onload=function(){
if(xhr.status==200 ||
xhr.status==401){
log (requestToText(request)+
'HTTP status: '+xhr.status+' '+
xhr.statusText+'\n');
func(xhr,request);
} else {
log(requestToText(request));
finalize('Ошибка HTTP: '+
xhr.status+' '+xhr.statusText);
}
}
xhr.onerror=function(){
log(requestToText(request));
finalize('Ошибка: отсутствует '+
'соединение');
}
xhr.ontimeout=function(){
log(requestToText(request));
finalize('Ошибка: таймаут '+
'соединения');
}
xhr.send(request.vars);
}
logs='';
getPage({'url':local('url')},checkConn);
Набор скрипта с телефонной клавиатуры, к сожалению, не располагает к комментариям, но я вкратце опишу алгоритм:
- Пытаемся загрузить страницу, указанную в переменной %url
- Если в ответе нет HTTP-заголовка Location, значит нас не перенаправляют, а значит в данный момент аутентификация не нужна, выход
- Загружаем страницу, на которую нас направили.
- Если есть заголовок Location, возвращаемся к п.3
- Если на странице есть форма из списка в переменной %forms, изображаем ее submit и возвращаемся к п.3
- В остальных случаях — мы успешно прошли аутентификацию
После завершения ввода скрипта, у нас получилась вот такая задача:

С помощью первой иконки в нижнем ряду можно попробовать ее запустить. Теперь самое время спускаться в метро, чтобы настроить ее!
В метро, подключившись к точке доступа, пробуем запустить задачу. Если нет явных проблем с доступностью серверов, то мы увидим сообщение, похожее на то, что изображено на следующем рисунке слева. Внизу мы видим идентификатор формы, которая есть на последней загруженной странице — auth-form. Эта форма — явно наш клиент, вносим ее название в переменную %forms и запускаем задачу еще раз, получаем примерно то, что изображено на следующем рисунке в центре. Новый идентификатор формы — hidden_form. Добавляем ее в переменную %forms, теперь ее значение будет 'auth-form,hidden_form'. Запускаем задачу еще раз и видим примерно то, что изображено на следующей рисунке справа — либо будет форма без идентификатора, либо пометка «отсутствуют» (зависит от ветки метро). Если теперь запустить браузер, будет понятно, что аутентификацию мы прошли. Присваиваем переменной %debug значение «0», и закрываем задачу — тут мы закончили.

Теперь дело за малым — настроить автоматический запуск задачи при подключении к нужной точке доступа. Переходим на закладку Profiles и создаем новый профиль, который будет активироваться после подключения к точке доступа московского метро. После того, как закончим формировать описание точки доступа, Tasker спросит нас с какой задачей связывать это профиль, выбираем, естественно, Metro Auth.

Еще одни нюанс: хотя и редко, но аутентификация все таки слетает, хотя отключения от точки и не происходило. Если не было отключения, не было и переподключения, а значит Tasker не запустит задачу повторно, поэтому мы настроим Tasker так, чтобы аутентификация автоматически проверялась каждые 2 минуты (минимальный возможный интервал), для этого нам надо долгим нажатием на уже ск��нфигурированное условие вызвать меню, в котором добавить временное условие, в котором установить интервал.

Ну вот и все. Отныне и до тех пор, пока не придется менять идентификаторы в переменной %forms, алгоритм ваших действий при заходе в вагон следующий:
- Включить Wi-Fi;
- Дождаться сообщения «Аутентификация завершена» на экране;
- Загадочно улыбнуться и заняться своими делами.
UPD: По совету Self_Perfection я экспортировал проект и выложил одним файлом. Этот файл нужно скачать и положить в папку /sdcard/Tasker/projects, потом запустить Tasker, долгим нажатием на иконку домика в нижнем левом углу вызвать меню и выбрать Import. В этой версии я вынес проверку раз в две минуты в отдельный профиль — так должно расторопнее работать.