JavaScript
Programming
Google API
14 July 2013

Автоматическое оповещение об изменениях статуса почтовых посылок через SMS

From Sandbox Tutorial
В последнее время я стал делать много покупок в интернет-магазинах, и нередко ловлю себя на том, что очень часто проверяю статус своих посылок (с нашей почтой не беспокоиться не получится).
Поиск готовых решений приводил только к платным сервисам. В результате было принято решение сообразить что-нибудь свое.
Под катом список используемых сервисов и подробная инструкция.

Выбор вспомогательных сервисов


Следующий шаг поисков был посвящен сервисам, предоставляющим возможность периодического выполнения различных скриптов за неимением (ах, какая досада) личного сервера. Выбор пал на Google Apps Script. В качестве ЯП используется модификация хорошо знакомого мне Javascript.
В качестве SMS-гейта был выбран SMS.ru, предоставляющий возможность отправки неограниченного количества бесплатных SMS стандартной длины (180 символов латиницы, 60 юникода) на личный номер телефона.
Функцию трекинга было решено позаимствовать у [не знаю, насколько] универсального китайского сервиса 17track.

Если у вашего оператора есть email2sms-гейт, то все упоминания SMS.ru можно пропускать.

Приготовления


До того, как приступить к написанию… кода, нам нужны:
  • аккаунты на Google (вдруг у кого нет) и SMS.ru
  • API ID от SMS.ru
  • созданный пустой проект на Google Apps Script
  • хеш, без которого 17track откажется нам выдавать статус, для каждого трек-номера


Подробно расписывать процесс регистрации, надеюсь, не нужно.

API ID для SMS.ru можно получить здесь: online.sms.ru/?panel=settings&subpanel=api


Для создания скрипта в Google Apps Script требуется, как ни странно, пройти на сайт этого сервиса.
Там нас встретит следующее окно (если оно не было отключено ранее):

Нам нужен «Пустой проект».

Хеш для 17track не придется получать, генерация теперь производится силами Google Apps Script. Готовый код для вставки в скрипт можно получить здесь.

Все нужные ингредиенты получены, можно приступать к коду.

Код


var user = ["%API ID%", "%TELNO%"];

var num = [
  ["%TRACKCODE1%", "%NAME1%"],
  ["%TRACKCODE2%", "%NAME2%"],
  ...
  ["%TRACKCODEn%", "%NAMEn%"],
];

var errors = {
  "hsErr": "Wrong hash",
  "unAllow": "You've changed parameter \"lo\" in query URL. Set it to \"www.17track.net\"",
  "hsNon": "There's no hash"
}
var success = false;

function sendSMS(text){
  UrlFetchApp.fetch("http://sms.ru/sms/send?api_id="+user[0]+"&to="+user[1]+"&text="+encodeURI(text));
}

function digest2str(digest){
  var str = '';
  var i = 0;
  for (i=0; i<digest.length; i++) {
    byte = digest[i];
    if (byte < 0)
      byte += 256;
    byteStr = byte.toString(16);
    // Ensure we have 2 chars in our byte, pad with 0
    if (byteStr.length == 1) byteStr = '0'+byteStr;
    str += byteStr;
  }   
  return str;
}

function checkStatus(){
  var i = 0;
  for(i=0; i<num.length; i++){
    var response = UrlFetchApp.fetch("http://www.17track.net/r/handlertrack.ashx?callback=&num="+num[i][0]+"&pt=0&cm=0&cc=0&_="+Math.random());
    UserProperties.setProperty("q", response.getContentText());
    var result = Utilities.jsonParse(response.getContentText());
    var sendstring = num[i][1]+": ";
    if(result["ret"] == 1){
      if(result["dat"]["f"] == "0"){
        sendstring += "Track code not found";
      }else{
        success = true;
      }
    }else{
      sendstring += errors[result["msg"]] ? errors[result["msg"]] : "Please, leave comment on habrahabr, error message: "+result["msg"];
    }
    if(success){
      if(UserProperties.getProperty(num[i][0]) != result["dat"]["z"]["b"]){
        translit = UrlFetchApp.fetch("http://translate.google.com/translate_a/t?client=t&q="+encodeURI(result["dat"]["z"]["b"]));
        translitobj = Utilities.jsonParse(translit.getContentText());
        if(translitobj[0][0][3]){
          for(z in translitobj[0]){
            sendstring += translitobj[0][z][3]+" ";
          }
        }else{
          sendstring += result["dat"]["z"]["b"];
        }
        sendSMS(sendstring);
        UserProperties.setProperty(num[i][0], result["dat"]["z"]["b"]);
        success = false;
      }
    }else{
      sendSMS(sendstring);
    }
  }
}

Также можно не возиться с sms.ru, если у вашего оператора есть email2sms-гейт. Например, инструкции для Мегафон Удмуртия. Если есть, обнаружить на сайте оператора можно поиском по слову «e-mail». Убрана транслитерация (если у кого-то гейт не умеет клеить сообщения, сообщите, сделаю версию с транслитерацией). Название кода складывается в тему сообщения.
Код
var user = [
  "%EMAIL1%",
  "%EMAIL2%",
  ...
  "%EMAILn%"
];
var num = [
  ["%TRACKCODE1%", "%NAME1%"],
  ["%TRACKCODE2%", "%NAME2%"],
  ...
  ["%TRACKCODEn%", "%NAMEn%"],
];
var errors = {
  "hsErr": "Wrong hash",
  "unAllow": "You've changed parameter \"lo\" in query URL. Set it to \"www.17track.net\"",
  "hsNon": "There's no hash"
}
var success = false;

function sendSMS(code, text){
  var i = 0;
  for(i=0; i<user.length; i++){
    MailApp.sendEmail(user[i],
                   code,
                   text);
  }
}

function digest2str(digest){
  var str = '';
  var i = 0;
  for (i=0; i<digest.length; i++) {
    byte = digest[i];
    if (byte < 0)
      byte += 256;
    byteStr = byte.toString(16);
    if (byteStr.length == 1) byteStr = '0'+byteStr;
    str += byteStr;
  }   
  return str;
}

function checkStatus(){
  var i = 0;
  for(i=0; i<num.length; i++){
    hashstr = num[i][0]+"{EDFCE98B-1CE6-4D87-8C4A-870D140B62BA}0{EDFCE98B-1CE6-4D87-8C4A-870D140B62BA}www.17track.net";
    dig = Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, hashstr);
    hs = digest2str(dig);
    var response = UrlFetchApp.fetch("http://s1.17track.net/Rest/HandlerTrackPost.ashx?lo=www.17track.net&num="+num[i][0]+"&hs="+hs);
    UserProperties.setProperty("q", response.getContentText());
    var result = Utilities.jsonParse(response.getContentText().replace(/^\((.*)\)$/, "$1"));
    var code = num[i][1]
    var sendstring = "";
    if(result["ret"] == 1){
      if(result["dat"]["f"] == "0"){
        sendstring += "Track code not found";
      }else{
        success = true;
      }
    }else{
      sendstring += errors[result["msg"]] ? errors[result["msg"]] : "Please, leave comment on habrahabr, error message: "+result["msg"];
    }
    if(success){
      if(UserProperties.getProperty(num[i][0]) != result["dat"]["z"]["b"]){
        sendstring += result["dat"]["z"]["b"];
        sendSMS(code, sendstring);
        UserProperties.setProperty(num[i][0], result["dat"]["z"]["b"]);
        success = false;
      }
    }else{
      sendSMS(code, sendstring);
    }
  }
}


Оговорюсь только о том, что в качестве флага, было оповещение по этому статусу или нет, используется дата последнего статуса, которая сохраняется в User Properties и сравнивается каждый раз.

В код остается подставить API ID, номер телефона и пары трекинг-номер/хеш. Теперь нужно настроить периодическое выполнение этого скрипта. Для этого идем в «Ресурсы->Триггеры текущего проекта», нажимаем «Добавить триггер» и настраиваем по образу и подобию (промежуток можно выбрать свой):


Результатом будут такие сообщения на мобильный телефон при обнаружении изменений в статусе:


Вопросы и предложения с альтернативами, исправлениями и оптимизациями принимаются.

P.S.: благодарю неизвестного (или это я просто не нашел?) хабраюзера за предоставленный инвайт.

UPD:
  1. Функциональные изменения: добавлено присвоение своих имен посылкам (отображаются вместо трек-кодов) и отлов ошибок (известны только две ошибки, если ошибка неизвестна, ее код придет в SMS, сообщите его здесь, мне в личку или через skype/twitter, ID в профиле).
  2. «Внутренние» изменения: обработка ответа сервиса сделана как нормальный JSON, а не костыльными RegExp.

Если изменить параметр ol из запроса на «17track.net» (вместо «www.17track.net»), почему-то приходит ошибка по поводу хеша. Странные они, эти китайцы.
Оказалось, при генерации хеша еще и домен используется.

UPD2:
У меня сломался скрипт, потому что наша (российская) почта вдруг стала выдавать результаты на русском (недавно только на английском писала, EMS пишет статус по-русски (в результате не влазит в одно сообщение). Теперь идет транслитерация при помощи Google Translate (опять нелегально пользуем ресурсы)

UPD3:
Теперь у нас есть страничка генерации готового кода для вставки в скрипт. Все благодарности z0rg.

UPD4:
Генерация хеша производится скриптом

UPD5:
Если статус латиницей, Google Translate отдавал пустую строку с транслитерацией. fix.

UPD6:
Если трек-код не существует или еще не зарегистрирован, скрипт падал с ошибкой и сообщения не приходило. fix.
Спасибо за сообщение об ошибке -=INFINITY=- с 4pda.ru

UPD7:
Более изящная проверка кода на существование, проверка на новизну статуса по его тексту (иногда дата не отдается, спасибо Dudka), исправление проблем с транслитерацией (при наличии в тексте статуса нескольких предложений получалось забирать только первое, спасибо почте Беларуси и kakawajazz).

UPD8:
Версия для оповещения на E-Mail (включая email@sms-гейты)

UPD9:
Починил скрипт для работы с обновленным 17track, спасибо rocket за указание.

+29
23.6k 232
Comments 89
Top of the day