Создаем чат бота с командами

Image

В одной из прошлых статей мы создавали простой скрипт для переписки. Были рассмотрены базовые возможности. На этот раз мы напишем чат-бот с командами. Реализуем такие команды:

  • город [название города]. Данной командой задается город собеседника, бот запомнит его, сохранит в файл. Эти данные будут доступны даже после перезапуска программы.
  • погода. Бот отправляет текущую температуру в городе собеседника.
  • пробки. Бот получает скриншот Яндекс.Карт с состоянием пробок и отправляет картинку.
  • события. Бот отправляет список событий в городе собеседника.
  • пришли на email [email]. Бот сохраняет email собеседника в файл.

Город

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

  
  var cityFile = profile + "__" + contact;
  if (content.startsWith("город ")) {
    var city = content.substring(6);
    log.info("Saving city for contact " + contact);
    tools.writeToFile(cityFile, city);
    return "Теперь я знаю твой город.";
  } 

В переменную cityFile мы сохраняем имя файла. Далее с помощью вызова метода tools.writeToFile(file, data) мы сохраняем город в файл. Обратите внимание, что функцией writeToFile пользоваться небезопасно в случае если несколько анкет выполняют один и тот же скрипт и обращаются к одному и тому же файлу. Поэтому мы используем id анкеты в имени файла, чтобы исключить такие конфликты.

Для чтения из файла используется метод tools.readFromFile(file). Данная функция читает весь файл и возвращает содержимое. Если файла не существует, функция возвращает пустую строку.

    var city = tools.readFromFile(cityFile);
    if (city.isEmpty()) {
      return "Я не знаю твой город :(";
    }

Выбор имени файла

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

  var profile = hist.localContact().getID().toString();
  var contact = hist.externalContact().getID().toString();
  var cityFile = profile + "__" + contact;

Здесь мы получаем данные об анкете в боте через метод hist.localContact(). Далее методом getID() получаем внутренний идентификатор анкеты (выглядит примерно так profile_www.vk.com_+79129192508). Аналогично получаем идентификатор собеседника (id361493719). В результате в переменной cityFile получаем profile_www.vk.com_+79129192508__id361493719. Поскольку имя содержит идентификатор анкеты, добавленной в бот, другая анкета не сможет сгенерировать такое же имя файла и конфликтов не будет.

Погода

Для получения данных о погоде воспользуемся сервисом openweathermap.org. Нужно зарегистрироваться и получить бесплатный ключ для доступа к API. Далее отправить HTTP-запрос с названием города и получить в ответ JSON с данными.

function getWeather(city, tools) {
  city = city.replace(" ", "%20");
  var apiKey = "43599b515694631087a103907284116d";
  var url = "http://api.openweathermap.org/data/2.5/weather?q=" + city + "&appid=" + apiKey + "&units=metric";
  return JSON.parse(tools.httpGet(url));
}

Для выполнения HTTP-запроса используем метод tools.httpGet(url). Метод возвращает тело HTTP-ответа. Обратите внимание, что пробелы в названии города мы заменяем на специальный код.

Пробки

Получать данные о пробках будет с Яндекс.Карт также через выполнение HTTP-запроса.

function getTraffic(city, file, log, tools) {
  log.info("Getting coordinates for city " + city);
  city = city.replace(" ", "%20");
  var url = "https://geocode-maps.yandex.ru/1.x/?geocode=" + city + "&format=json";
  var resp = JSON.parse(tools.httpGet(url));
  var coord = resp["response"]["GeoObjectCollection"]["featureMember"][0]["GeoObject"]["Point"]["pos"].replace(" ", ",");
  log.info("Coordinates are: " + coord + ", getting traffic pic");
  var url = "https://static-maps.yandex.ru/1.x/?ll=" + coord + "&spn=0.1,0.1&l=map,trf";
  return tools.download(url, file);
}

Сервис Яндекс.Карт возвращает данные о пробках по географической координате. У нас же есть только строковое название города. Поэтому сначала мы отправляем HTTP-запрос на получение данных о городе на другой сервис. Среди прочего в ответе есть данные о координате. Координату используем для другого HTTP-запроса. Обратите внимание, что для получения картинки с пробками мы используем метод tools.download(url, file). Этот метод также выполняет HTTP GET запрос, но сохраняет бинарный результат в файл. Метод получает URL и имя файла для сохранения ответа. Обратите внимание, что в имени файла должно быть только имя, без путей. Метод возвращает полный путь к этому файлу.

События

Для получения событий в конкретном городе воспользуемся API timepad.ru.

function getEvents(city, tools) {
  city = city.replace(" ", "%20");
  var url = "https://api.timepad.ru/v1/events.json?limit=10&skip=0&cities=" + city + "&fields=location&sort=+starts_at";
  return JSON.parse(tools.httpGet(url));
}

Пришли на email

Воспользуемся методом tools.appendToFile(file, line). Метод добавляет строку line в файл file (только имя файла, без путей). Данный метод является безопасным для использования из нескольких анкет с одним и тем же именем файла. Таким образом удобно собирать какие-то сообщения или фрагменты сообщений от пользователей в одном файле. В данном скрипте мы будем собирать email адреса пользователей.

  if (content.startsWith("пришли на email ")) {
    var email = content.substring(16);
    tools.appendToFile("emails", email);
  }

Весь скрипт

Приведем весь код скрипта.

function getTemplates() {
  return [];
}
function getWeather(city, tools) {
  city = city.replace(" ", "%20");
  var apiKey = "43599b515694631087a103907284116d";
  var url = "http://api.openweathermap.org/data/2.5/weather?q=" + city + "&appid=" + apiKey + "&units=metric";
  return JSON.parse(tools.httpGet(url));
}
function getTraffic(city, file, log, tools) {
  log.info("Getting coordinates for city " + city);
  city = city.replace(" ", "%20");
  var url = "https://geocode-maps.yandex.ru/1.x/?geocode=" + city + "&format=json";
  var resp = JSON.parse(tools.httpGet(url));
  var coord = resp["response"]["GeoObjectCollection"]["featureMember"][0]["GeoObject"]["Point"]["pos"].replace(" ", ",");
  log.info("Coordinates are: " + coord + ", getting traffic pic");
  var url = "https://static-maps.yandex.ru/1.x/?ll=" + coord + "&spn=0.1,0.1&l=map,trf";
  return tools.download(url, file);
}
function getEvents(city, tools) {
  city = city.replace(" ", "%20");
  var url = "https://api.timepad.ru/v1/events.json?limit=10&skip=0&cities=" + city + "&fields=location&sort=+starts_at";
  return JSON.parse(tools.httpGet(url));
}
function handle(content, profile, contact, log, tools) {
  log.info("Got message: " + content + " from " + contact + " to " + profile);
  var cityFile = profile + "__" + contact;
  if (content.startsWith("город ")) {
    var city = content.substring(6);
    log.info("Saving city for contact " + contact);
    tools.writeToFile(cityFile, city);
    return "Теперь я знаю твой город.";
  } else if (content.startsWith("погода")) {
    var city = tools.readFromFile(cityFile);
    if (city.isEmpty()) {
      return "Я не знаю твой город :(";
    }
    log.info("Getting weather for city " + city);
    var weather = getWeather(city, tools);
    return "Температура: " + weather["main"]["temp"];
  } else if (content.startsWith("пробки")) {
    var city = tools.readFromFile(cityFile);
    if (city.isEmpty()) {
      return "Я не знаю твой город :(";
    }
    var trafficFile = profile + "__" + contact + ".png";
    var fullFileName = getTraffic(city, trafficFile, log, tools);
    return "такие пробки [image:" + fullFileName + "]";
  } else if (content.startsWith("пришли на email ")) {
    var email = content.substring(16);
    tools.appendToFile("emails", email);
    return "Спасибо! Я напишу.";
  } else if (content.equals("события")) {
    var city = tools.readFromFile(cityFile);
    if (city.isEmpty()) {
      return "Я не знаю твой город :(";
    }
    log.info("Getting events for city " + city);
    var events = getEvents(city, tools)["values"];
    log.info("Got " + events.length + " events");
    var result = "";
    for (var i = 0; i < events.length; ++i) {
      var dateTime = events[i]["starts_at"];
      var dateTimeStr = dateTime.substring(0, 10) + " " + dateTime.substring(11, 17);
      var name = events[i]["name"];
      result += (i + 1) + ". " + dateTimeStr + " " + name + "\n";
    }
    return result;
  }
  return "";
}
function getAnswer(hist, log, tools) {
  var profile = hist.localContact().getID().toString();
  var contact = hist.externalContact().getID().toString();
  var toReply = hist.unansweredMessages();
  var result = "";
  for(var i = 0; i < toReply.size() ; ++i) {
    var message = toReply.get(i);
    var content = message.content().toLowerCase();
    var dup = false;
    for(var j = 0; j < i && !dup; ++j) {
      dup = content.equals(toReply.get(j).content().toLowerCase());
    }
    if (!dup) {
      result += handle(content, profile, contact, log, tools) + " ";
    }
  }
  return result;
}

Пример работы

2016-11-12_20-43-04

Что еще можно получить из истории

Из истории переписки в скрипте можно получить некоторую дополнительную информацию об анкете и собеседнике. А именно:

hist.localContact().getLogin()логин анкеты в боте
hist.localContact().getPassword()пароль анкеты в боте
hist.localContact().getHomePageUrl()ссылка на анкету в боте (например, https://vk.com/id381559011)
hist.localContact().getProxy()прокси анкеты в боте или пустая строка, если прокси не задан
hist.localContact().getUserAgent()User-Agent анкеты в боте
hist.externalContact().getAlias()имя собеседника (например, Иван Иванов)

Эта статья была полезна для 5 людей. Эта статья помогла вам?

Сервис поддержки клиентов работает на платформе UserEcho