Привет!
Хотелось бы рассказать вам историю создания одного незамысловатого развлекательного сервиса чат ботов.
О себе: Хабр читаю давно (лет этак 5), а вот зарегистрировался месяц назад, так как очень хотелось поделиться. Работаю в IT компании инженером по тестированию. Но меня всегда тянуло к разработке. И лет 7 назад занялся freelance параллельно основной работе. Начинал, как и все с малого, разбирался при помощи google. Писал всякие web непотребства. Все это ради достижения неких финансовых целей. Да и вообще вся жизнь состояла из цикла: поиск цели – достижение любыми средствами – удовлетворение – поиск новой цели. Так было и с freelance.
Шла зима 2012/2013 года. Работал я на freelance над проектом онлайн-консультанта, и мне надо было сделать интеграцию с jabber и skype, то есть чтобы операторы могли работать с клиентом печатая в свой im клиент, а пользователь сайта получал это все на web. После поиска в google, оказалось, что с jabber все просто, любой xmpp сервер на выбор (я выбрал ejabbered) и библиотечка для работы с ним JAXL (взял на github), тогда еще версии 2.х. А вот со Skype были проблемы: на сайте в разделе Developers предлагался какой-то kit за деньги (и лицензия, запрещающая серверное использование), с ненужными мне активациями и с упоминанием про какой-то api. А хотелось чего-то более простого. И тут я наткнулся на этот api в «Доступ к Skype API используя PHP на *nix системах», сейчас на сайте skype упоминания о работе с api отсутствуют уже, хотя тогда было, и скачал pdf по работе с ним через dbus. Там было все то, что я искал.
Возникло сразу несколько проблем, не описанных в прочитанной мною статье, если система x64, то skype требовал некоторые x86 зависимости:
Прием сообщений на стороне сервера:
— работает не совсем правильно, так как реагирует на все сообщения
— да и вообще лучше не использовать такую конструкцию, так как часть сообщений исчезает в недрах dbus, а php до них не добирается, то есть скрипт за цикл работы while() очень часто не успевает получить все сообщения или не получает сообщения вообще. Методом проб и ошибок был найден способ 100% отлавливающий все сообщения и даже после простоя скрипта или его перезагрузки, вот он:
Который в dbus шлет все не обработанные сообщения (на которых не стоит флаг SEEN):
и в основном цикле их отлавливаем с помощью:
То же самое сделано и для авторизаций от пользователей боту (будущий пользователь может зарегистрироваться просто послав авторизацию в skype), а именно:
а не ждать:
которое тоже иногда давало осечку. С этим связан еще один нюанс, если нам надо знать, что пользователь удалил бота из листа контактов, то инициируется:
Но надо быть внимательным и отслеживать ситуацию, когда аналогичная проблема возникает при отправке пользователю авторизации от бота:
Но я так и не смог решить проблему того что боты skype уходят в away после того как пользователи ОС лочаться после бездействия. Хотя в настройках skype стоит away_mode=off и эта строка не помогает:
Только после этого, skype стал работать стабильно.
И вот приступил я к настройке на … freebsd тогда еще 9.х, как и все мои разработки, ох и нравилась мне эта система, боролся я с ней долго, но так и не завел skype через dbus, уперся я тогда в сессии х-менеджера и dbus и их несоответствие и:
не всегда помогало, пришлось все ставить на linux, как было указано в статье на Хабре, я выбрал debian. Хотя backend web и остался на freebsd и общались они между собой через memcache. Да, да, знатный «велосипед», но об этом ниже.
Код чат ботов был связан с кодом веба, хотелось его сделать поскорее и поэтому вместо того чтобы взять хорошие решения по «Очередям Сообщений», которые я ранее не использовал, и не особо разбирался, я начал лепить свою очередь из php+memcache со своей логикой lock'ов для того чтобы сделать регистрацию, и рассылку авторизаций и сообщений в im клиенты операторов асинхронными. Это была ужасная «машина» в которой можно было сломать глаза – но она работала и работает до сих пор, насколько я знаю. Представьте себе: 6 ботов/ОС пользователей, 6 поднятых виртуальных рабочих столов отдельно на каждого пользователя debian, 6 vnc окон, в каждом из них по 4 скрипта (skype и jabber бот, воркеры мною написанной очереди). Каждая перезагрузка сервера превращалась в кропотливую работу, зайти в систему шестью юзерами, открыть для каждого vnc, открыть терминал и там запустить 4 php скрипта. Боты то и дело вылетали с какими-то странными ошибками, в частности JAXL, при использовании VNC падал с:
лечилось это изменением/удалением полей в месте инициализации «new JAXL()», поиск которых в google в рамках очень сжатых сроков, не позволял тщательно решить их.
Весной 2013 я решил потешить свой перфекционизм и подумал, что надо бы написать интересное и нужное в плане идеи, да и красивое в плане кода, а не как обычно. И занялся поиском идеи. А она, как оказалась, была у меня под самым носом. Я, например, часто вызываю калькулятор в windows7 или в macos или смотрю погоду в своем телефоне. И почти весь день, пока бодрствую, я провожу за компьютером. Вывод просился сам собой, надо часто используемые операции перенести в постоянно открытые приложения и сократить количество выполняемых операций для существующих задач на компьютере. Бинго. Я судорожно полез в google искать готовые решения, а именно всевозможные приложения, реализованные как чат боты через популярные im протоколы. И на тот момент я ни одного не нашел (плохо искал?). Надо писать свой, он будет бесплатным и самое главное я им сам буду пользоваться. Мне предстояло написать чат ботов, которые по USSD командам могли бы взаимодействовать с юзерами через skype и jabber (позже и whatsapp), только написать получше чем до этого и сделать полностью готовое решение, выбрать доменное имя, логотип, дизайн сайта, сам сайт и все остальное что для этого надо, то есть полный цикл разработки и деплоя в prod.
Задача непростая, да и раньше я никогда так не делал. Но стоит только сделать 1й шаг…. А он уже был сделан. Если честно я сразу подумал, как было бы круто мониторить сервер и иметь эмуляцию ssh консоли в своем skype/jabber/whatsapp для мониторинга сервера (пока эта задача отложена на реализацию, хотя частичные наработки уже есть).
И очень хотелось иметь аналог сервиса «email на 10 минут», написав владельцу популярного сервиса об интеграции в мой, я не ждал ничего хорошего, интуиция не подвела, разработчик этой системы вежливо отказался, сказав, что сервис бесплатный, а сам разработчик едва покрывает свое затраченное время рекламой на этом сервисе. А я им так часто пользуюсь. Ну да ладно, я начал мастерить свой велосипед на эту тему, но пока не дописал, так все опробованные идеи сделать быстро – были не очень годными.
Не заладилось с freelance (но я не сильно расстроился, цель его и была финансовая, freelance помог ее решить, других целей не было, ах да, я взял себе спорт-байк и вот теперь точно, целей больше не было) и появилось свободное время (хотя я и продолжал постоянно работать в прежней IT компании, но уже на более интересных проектах, но все еще таких далеких от того, что мне нравилось). Два месяца я пилил код, все равно это был какой-то монстр в плане php кода в разрезе чат бота. А вот дизайн сайта до сих пор остался почти не тронутым, я сразу начал делать flat ui – мне казалось за ним успех в ближайшее время. Конец весны я переживаю личные проблемы различного характера, и забываю про этот сервис и к его разработке я возвращаюсь в ноябре 2013 и первое, что я делаю удаляю весь код чат ботов. И начинаю писать его снова и без очереди на memcache.
Написал текстом будущую архитектуру, структуры модулей (web, api, bot). Составил документацию по структуре хранения в memcache — в дальнейшем все это сильно выручало, дабы не держать большие объемы логики в своем «дворце памяти».
Начало было в голове такое:
Web:
Chatbot:
Архитектура для каждого приложения: бот-listner для skype, бот-listner для jabber, бот-listner для whatsapp, user worker (rabbitMQ) для обработки add/delete user и add/delete service, api worker (rabbitMQ) для вызова api метдов, spamer worker (rabbitMQ) для рассылки сообщений и трех хелперов (для всех протоколов) — простейшие боты-sender'ы, которые получают команды от user worker и spamer worker, а также вызываются в фоне и после работы закрываются и непосредственно реализуют все активности ботов (отозвать авторизацию, выслать авторизацию с сообщением «X», и отправить сообщение).
Пока я пилил и тестировал, коллега с работы, который посмотрел мои наработки, предложил:
И я снова обратился к своему незаменимому помощнику – google. Там не оказалось ничего об открытых api, но я нашел варианты использования whatsapp и прикрутил его к сервису (взял на github код библиотеки «WhatsProt»). Но стоп, ведь whatsapp в основном для не русскоговорящих, надо делать переводы и для ботов, и на сайте. Почитал «На какие языки переводить проект в первую очередь?» и выбрал английский, русский, китайский упрощенный, немецкий и испанский, от китайского потом отказался, из моих знакомых никто в нем не понимал, чтобы внести изменения художественного характера.
Взял ZF 2.x за основу для веб. Планировал реализовать все приложения как единый бот, а потом каждое приложение — как отдельный бот. Первая схема была очень нестабильной (так как я все еще опирался на memcache в качестве никак недокументированного кеша и в качестве «очереди сообщений»), и я от нее отказался, но потом вернулся частично. А вторая уже после 8 ботов начала захламлять мой контакт лист в skype и jabber, а также отъедала ресурсы на моей локальной виртуальной машине с невероятной скоростью, особенно сам skype, так как для каждого бота приходилось запускать отдельного пользователя со своим рабочим столом и своим skype (хотя и есть возможность запускать несколько разных skype в одном окружении, и привязываться к api каждого по id dbus сессий — но я пока не пробовал), и я отказался от этой схемы, позже я объединил их, теперь каждый бот реализует несколько приложений, сгруппированных по смыслу. Начинал я с такого набора приложений:
За ноябрь и декабрь и январь я написал почти стабильную версию и, что самое главное, годную для теста на моих знакомых.
Но тут на глаза мне попалась статья «Google отказывается от поддержки XMPP», и я немного расстроился. Но через некоторое время, после статьи «Без паники! Про то, что сделал Google с XMPP», я понял, что все просто хорошо и сам постоянно использую google xmpp. Пришло время выливать все на хостинг. Моя тестовая ВМ на 7 ботов (по три скрипта на каждого бота/приложение (skype-jabber-whatsapp) и 3 воркера для каждого приложения: user worker, api worker (я написал все взаимодействие с бд через api – только зачем, до сих пор не понимаю, зато модульно и с одной точкой входа для web и ботов вышло, проще стало тестировать), и spam worker, все воркеры работали через зайцаMQ) потребляла 3,5ГБ RAM, а у всех хостеров были тарифы 2 и 4 ГБ со стоимостью в 2 раза больше за 4Гб, что мне не подходило, так все это из своего кармана и сервис бесплатен, написал в «цифровой океан» (о котором тоже прочитал на Хабре) о просьбе под нужды такого то сервиса дать ВМ с другим количеством оперативной памяти, но с более слабым процессором, он был не критичен. Мне отказали. Ожидаемо. Нашел сравнение хостеров на «comparevps» и за деньги те же что и для 2Гб, взял систему с 4Гб, хотя опыт реальный работы с этим хостером оказался чуть хуже ожидаемого, все стоит своих денег (в этом случае к сожалению). И написал две пошаговые инструкции по установке freebsd и debian сервера до этапа полностью работающего сервиса, чтобы быстро завести купленные VPS.
Но брать два сервера (freebsd для web + debian для чат ботов) было не с руки, я начал ставить все на один. И это был debian, я неприятно удивился что не могу поставить из пакетов последние версии того что мне надо как я мог сделать это на freebsd сборкой и портов. Потом я разобрался, как и что и престал быть хейтером linux и debian в частности, а стал фанатиком в хорошем смысле (теперь у меня в «must have» еще и Red Hat).
Я добавил по «наводке» друзей новые приложения:
Ботов стало 11, на каждого 6 скриптов (1й –skype, 2й –jabber, 3й – whatsapp и три воркера), системе не хватало и 4Гб. Пришло время для понимания того что надо объединять приложения в тематические боты, как я говорил ранее, сказано – сделано. Появились Base, Office (калькулятор, курсы валют, мировые часы), Lifestyle (погода, гороскоп, 8ка), Developer (habra, hash). Теперь 2.5Гб и все хорошо. Тут я немного устал, читал неспешно «Джаббер переходит на полное шифрование» и решил протестировать свой сервер на xmpp.net и получил оценку A-, что могло не радовать. Кстати я долго не мог заставить свой ejabbered сервер стать публичным (а равно и добавить межсерверное взаимодействие) пока не сделал в DNS это

Но у меня все еще не было ssl сертификатов ни для jabber ни для сайта. Два по 60$ мне не улыбалось, и тут я нашел статью «Получаем бесплатный SSL сертификат» и понеслась. Все просто и понятно.
Была постоянно одна проблема, которая меня вконец допекла: я очень устал восстанавливать все сервисы руками, когда сервер по причине хостера падал или после перезагрузки просто стоял болванкой, надо было решить как-то проблему того, что я об этом узнавал только в понедельник, если это происходило в субботу. Я пробовал много разного, в конечном итоге, система после перезагрузки, полностью автоматом логинит всех *nix пользователей (для skype), под ними стартует terminal в графической оболочке, открывает шесть вкладок и в каждом запускает нужный php скрипт чтобы я мог зайти и посмотреть его вывод в любое время (для debug возникающих иногда мелких проблем):
А чтобы получать email при shutdown и/или startup использовал этот скрипт. Теперь я всегда знал, что происходит с сервером и знал, что он поднимется полностью сам и все боты будут работать, даже после неожиданностей. Плюс к этому настроил бекап бд с ротацией и складированием на почту дампов (они пока небольшие).
Начитался на Хабре про heartbleed, и как, наверное, все, проверил свой сервер – все было ОК, но никак не мог включить TLS 1.2 и все время получал F, заодно и exploit нашел для heartbleed, чтобы протестировать различные сервисы, так как никогда таким не занимался, было занимательно попробовать себя в качестве иженера по безопасности, заодно попробовал «nikto.pl» и вдохновился «nessus» сканерами. Но вернусь к чат ботам, я и libssl переставил и openssl который «g» и проверяя зависимости nginx, постоянно видел, что он склинкован со старой lib, оказалось я просто поставил nginx для squeeze вместо wheezy. Хотя это и было причиной старой libssl слинкованной с nginx из-за которой у меня и не было уязвимости. «Случайность», — скажете вы. «Случайности не случайны», – отвечу я.
Кстати для whatsapp, как я понял свободное получение пароля от аккаунта, прикроют через какое-то время, сейчас его получить очень просто:
Но есть проблема, что каждое новое соединение (в моей схеме, один скрипт слушает команды, а другой отправляет ответы, полученные через очередь) обрывает текущее. Пришлось для whatsapp отказаться частично от очередей и сделать автоматический рестарт самого php скрипта.
Вот интересный хинт для jabber бота, чтобы он показывал статус «typing», когда пользователь ему что-нибудь пишет:
Кстати, для whatsapp и skype ботов, выйти из spamer воркера, когда тот отработает сообщение из очереди в фоне и совершт действие с пользователем, позволила только строка
а для jabber просто
В планах следующие приложения:
Да и много других приложений вертится в голове, только не хватает теперь на все сразу времени. Даже не столько времени, сколько понимания того, какая ЦА (целевая аудитория) у этого сервиса.
Для того, кто задумает написать что-то похожее и не знает с чего начать, весь код был взят по приведенным выше ссылкам и из примеров использования к проектам на github. Единственное что я там не нашел это то, что я упомянул выше и то, как получать сообщения пользователя из xml для whatsapp бота и не реагировать на «typing» статус, когда юзер набирает сообщение:
Вывод: при помощи в основном только Хабрахабра простой «тестировщик» сделал то, что ему нравилось, спасибо! И стал чуточку счастливее.
PS 1: Когда я заканчивал описывать процесс кропотливой работы над своим сервисом, я увидел на Хабре и не мог не прочитать вот эту статью «Джон Резиг: Пишите код каждый день». В ней четко и просто сформулировано все то, что я никак не мог оформить в своей голове. Я действовал точно также при написании своего сервиса, стал писать код каждый день. И это очень оправдало себя, так как ушла ненужная тревога о том, что я не успеваю сделать достаточно.
Бонус: Free Object Motion Tracking Plugin at Final Cut Pro X, Adobe Premier and DaVinci Resolve.
Хотелось бы рассказать вам историю создания одного незамысловатого развлекательного сервиса чат ботов.
Вступление
О себе: Хабр читаю давно (лет этак 5), а вот зарегистрировался месяц назад, так как очень хотелось поделиться. Работаю в IT компании инженером по тестированию. Но меня всегда тянуло к разработке. И лет 7 назад занялся freelance параллельно основной работе. Начинал, как и все с малого, разбирался при помощи google. Писал всякие web непотребства. Все это ради достижения неких финансовых целей. Да и вообще вся жизнь состояла из цикла: поиск цели – достижение любыми средствами – удовлетворение – поиск новой цели. Так было и с freelance.
Переломный момент
Шла зима 2012/2013 года. Работал я на freelance над проектом онлайн-консультанта, и мне надо было сделать интеграцию с jabber и skype, то есть чтобы операторы могли работать с клиентом печатая в свой im клиент, а пользователь сайта получал это все на web. После поиска в google, оказалось, что с jabber все просто, любой xmpp сервер на выбор (я выбрал ejabbered) и библиотечка для работы с ним JAXL (взял на github), тогда еще версии 2.х. А вот со Skype были проблемы: на сайте в разделе Developers предлагался какой-то kit за деньги (и лицензия, запрещающая серверное использование), с ненужными мне активациями и с упоминанием про какой-то api. А хотелось чего-то более простого. И тут я наткнулся на этот api в «Доступ к Skype API используя PHP на *nix системах», сейчас на сайте skype упоминания о работе с api отсутствуют уже, хотя тогда было, и скачал pdf по работе с ним через dbus. Там было все то, что я искал.
Возникло сразу несколько проблем, не описанных в прочитанной мною статье, если система x64, то skype требовал некоторые x86 зависимости:
sudo dpkg --add-architecture i386
sudo aptitude update
Прием сообщений на стороне сервера:
preg_match('#RECEIVED|SENT#Uis')
— работает не совсем правильно, так как реагирует на все сообщения
— да и вообще лучше не использовать такую конструкцию, так как часть сообщений исчезает в недрах dbus, а php до них не добирается, то есть скрипт за цикл работы while() очень часто не успевает получить все сообщения или не получает сообщения вообще. Методом проб и ошибок был найден способ 100% отлавливающий все сообщения и даже после простоя скрипта или его перезагрузки, вот он:
$oSkype->Invoke('SEARCH MISSEDMESSAGES')
Подробнее
class phpSkype {
private static $iLastId;
public static function notify($sNotify) {
//....
//....
//....
/* Message serving 4 */
if (preg_match('/^CHATMESSAGES/', $sNotify)) {
$sNotify = str_replace('CHATMESSAGES ', '', $sNotify);
if ($sNotify != '') {
$aMessages = explode(', ', $sNotify);
if (sizeof($aMessages)) {
foreach ($aMessages as $iMessageId) {
$sAuthRaw = $oSkype->Invoke('GET CHATMESSAGE ' . $iMessageId . ' FROM_HANDLE');
$sMessageRaw = $oSkype->Invoke('GET CHATMESSAGE ' . $iMessageId . ' BODY');
$aMessage = explode('BODY ', $sMessageRaw);
$aUsernameSkype = explode('FROM_HANDLE ', $sAuthRaw);
$aChatHash = explode('CHATNAME ', $oSkype->Invoke('GET CHATMESSAGE ' . $iMessageId . ' CHATNAME'));
verifyUserSkypeChatId($aUsernameSkype[1], $aChatHash[1]);
if (self::$iLastId < $iMessageId) {
self::$iLastId = $iMessageId;
addLog($aUsernameSkype[1], $aMessage[1]);
self::received($aUsernameSkype[1], trim(strtolower($aMessage[1])), $iMessageId);
} else {
$oSkype->Invoke('SET CHATMESSAGE ' . $iMessageId . ' SEEN');
}
}
}
}
}
//....
//....
//....
}
public static function received($sUid, $sMessage, $iLastMsgId) {
$oSkype = Zend_Registry::get('$oSkype');
// Mark received message as read
$oSkype->Invoke('SET CHATMESSAGE ' . $iLastMsgId . ' SEEN');
/* Prepare */
$sChatIdRaw = $oSkype->Invoke('GET CHATMESSAGE ' . $iLastMsgId . ' CHATNAME');
$aChatId = explode('CHATNAME ', $sChatIdRaw);
$sMembers = $oSkype->Invoke("GET CHAT " . $aChatId[1] . " ACTIVEMEMBERS");
$sMembers = str_replace('CHAT ' . $aChatId[1] . ' ACTIVEMEMBERS ', '', $sMembers);
$aMembers = explode(' ', $sMembers);
if (sizeof($aMembers) > 2) {
$oSkype->Invoke("ALTER CHAT " . $aChatId[1] . " CLEARRECENTMESSAGES");
$oSkype->Invoke("ALTER CHAT " . $aChatId[1] . " DISBAND");
$oSkype->Invoke("ALTER CHAT " . $aChatId[1] . " LEAVE");
return false;
}
//....
//....
//....
}
}
$oDbus->registerObject('/com/Skype/Client', 'com.Skype.API.Client', 'phpSkype');
$bSMLastUpdate = 0;
while (true) {
$s = $oDbus->waitLoop(1);
/* Get new skype messages */
global $bSMLastUpdate;
if (time() - $bSMLastUpdate >= $aConfig['skype_get_new_message']) {
$bSMLastUpdate = time();
$oSkype->Invoke('SET USERSTATUS ONLINE');
$oSkype->Invoke('SEARCH MISSEDMESSAGES');
}
}
Который в dbus шлет все не обработанные сообщения (на которых не стоит флаг SEEN):
$oSkype->Invoke('SET CHATMESSAGE ' . $iMessageId . ' SEEN');
и в основном цикле их отлавливаем с помощью:
preg_match('/^CHATMESSAGES/', $sNotify)
То же самое сделано и для авторизаций от пользователей боту (будущий пользователь может зарегистрироваться просто послав авторизацию в skype), а именно:
$oSkype->Invoke("SEARCH USERSWAITINGMYAUTHORIZATION")
а не ждать:
preg_match('/^USER ([a-zA-Z0-9_\.]+) RECEIVEDAUTHREQUEST (.)+/', $sNotify, $aMatches)
которое тоже иногда давало осечку. С этим связан еще один нюанс, если нам надо знать, что пользователь удалил бота из листа контактов, то инициируется:
/* Client unlink */
if (preg_match('/BUDDYSTATUS 2/', $sNotify)) {
//...
}
Но надо быть внимательным и отслеживать ситуацию, когда аналогичная проблема возникает при отправке пользователю авторизации от бота:
$oSkype->Invoke("SET USER [USER_NAME] BUDDYSTATUS 2 [AUTH_MESSAGE]");
Но я так и не смог решить проблему того что боты skype уходят в away после того как пользователи ОС лочаться после бездействия. Хотя в настройках skype стоит away_mode=off и эта строка не помогает:
$oSkype->Invoke('SET USERSTATUS ONLINE');
Только после этого, skype стал работать стабильно.
И вот приступил я к настройке на … freebsd тогда еще 9.х, как и все мои разработки, ох и нравилась мне эта система, боролся я с ней долго, но так и не завел skype через dbus, уперся я тогда в сессии х-менеджера и dbus и их несоответствие и:
найти в /root/.dbus/session-bus/
id сессии dbus и добавить в окружение
например export DBUS_SESSION_BUS_ADDRESS=8c9916eb531f5f9a0458961c000033bc-1
не всегда помогало, пришлось все ставить на linux, как было указано в статье на Хабре, я выбрал debian. Хотя backend web и остался на freebsd и общались они между собой через memcache. Да, да, знатный «велосипед», но об этом ниже.
Код чат ботов был связан с кодом веба, хотелось его сделать поскорее и поэтому вместо того чтобы взять хорошие решения по «Очередям Сообщений», которые я ранее не использовал, и не особо разбирался, я начал лепить свою очередь из php+memcache со своей логикой lock'ов для того чтобы сделать регистрацию, и рассылку авторизаций и сообщений в im клиенты операторов асинхронными. Это была ужасная «машина» в которой можно было сломать глаза – но она работала и работает до сих пор, насколько я знаю. Представьте себе: 6 ботов/ОС пользователей, 6 поднятых виртуальных рабочих столов отдельно на каждого пользователя debian, 6 vnc окон, в каждом из них по 4 скрипта (skype и jabber бот, воркеры мною написанной очереди). Каждая перезагрузка сервера превращалась в кропотливую работу, зайти в систему шестью юзерами, открыть для каждого vnc, открыть терминал и там запустить 4 php скрипта. Боты то и дело вылетали с какими-то странными ошибками, в частности JAXL, при использовании VNC падал с:
Interrupted system call в jaxl_loop.php
лечилось это изменением/удалением полей в месте инициализации «new JAXL()», поиск которых в google в рамках очень сжатых сроков, не позволял тщательно решить их.
Ready, steady, go
Весной 2013 я решил потешить свой перфекционизм и подумал, что надо бы написать интересное и нужное в плане идеи, да и красивое в плане кода, а не как обычно. И занялся поиском идеи. А она, как оказалась, была у меня под самым носом. Я, например, часто вызываю калькулятор в windows7 или в macos или смотрю погоду в своем телефоне. И почти весь день, пока бодрствую, я провожу за компьютером. Вывод просился сам собой, надо часто используемые операции перенести в постоянно открытые приложения и сократить количество выполняемых операций для существующих задач на компьютере. Бинго. Я судорожно полез в google искать готовые решения, а именно всевозможные приложения, реализованные как чат боты через популярные im протоколы. И на тот момент я ни одного не нашел (плохо искал?). Надо писать свой, он будет бесплатным и самое главное я им сам буду пользоваться. Мне предстояло написать чат ботов, которые по USSD командам могли бы взаимодействовать с юзерами через skype и jabber (позже и whatsapp), только написать получше чем до этого и сделать полностью готовое решение, выбрать доменное имя, логотип, дизайн сайта, сам сайт и все остальное что для этого надо, то есть полный цикл разработки и деплоя в prod.
Задача непростая, да и раньше я никогда так не делал. Но стоит только сделать 1й шаг…. А он уже был сделан. Если честно я сразу подумал, как было бы круто мониторить сервер и иметь эмуляцию ssh консоли в своем skype/jabber/whatsapp для мониторинга сервера (пока эта задача отложена на реализацию, хотя частичные наработки уже есть).
И очень хотелось иметь аналог сервиса «email на 10 минут», написав владельцу популярного сервиса об интеграции в мой, я не ждал ничего хорошего, интуиция не подвела, разработчик этой системы вежливо отказался, сказав, что сервис бесплатный, а сам разработчик едва покрывает свое затраченное время рекламой на этом сервисе. А я им так часто пользуюсь. Ну да ладно, я начал мастерить свой велосипед на эту тему, но пока не дописал, так все опробованные идеи сделать быстро – были не очень годными.
Не заладилось с freelance (но я не сильно расстроился, цель его и была финансовая, freelance помог ее решить, других целей не было, ах да, я взял себе спорт-байк и вот теперь точно, целей больше не было) и появилось свободное время (хотя я и продолжал постоянно работать в прежней IT компании, но уже на более интересных проектах, но все еще таких далеких от того, что мне нравилось). Два месяца я пилил код, все равно это был какой-то монстр в плане php кода в разрезе чат бота. А вот дизайн сайта до сих пор остался почти не тронутым, я сразу начал делать flat ui – мне казалось за ним успех в ближайшее время. Конец весны я переживаю личные проблемы различного характера, и забываю про этот сервис и к его разработке я возвращаюсь в ноябре 2013 и первое, что я делаю удаляю весь код чат ботов. И начинаю писать его снова и без очереди на memcache.
From the scratch
Написал текстом будущую архитектуру, структуры модулей (web, api, bot). Составил документацию по структуре хранения в memcache — в дальнейшем все это сильно выручало, дабы не держать большие объемы логики в своем «дворце памяти».
Начало было в голове такое:
Web:
freebsd 10 + php 5.x + apache + mysql 5.x + apc
, во втором чтенииphp 5.x стало nginx + php-fpm, и без apc, а с нативным opcache.
Chatbot:
debian wheezy с gnome + php 5.x + skype + memcache (для кэширования всех результатов ботов, их меню и всевозможных данных приложений) + ejabbered с JAXL 2.x (теперь и 3.x)+ rabbitMQ (о котором тоже прочитал на Хабре) с PhpAmqpLib (замена моему велосипеду с реализацией очереди в memcache взята с github).
Архитектура для каждого приложения: бот-listner для skype, бот-listner для jabber, бот-listner для whatsapp, user worker (rabbitMQ) для обработки add/delete user и add/delete service, api worker (rabbitMQ) для вызова api метдов, spamer worker (rabbitMQ) для рассылки сообщений и трех хелперов (для всех протоколов) — простейшие боты-sender'ы, которые получают команды от user worker и spamer worker, а также вызываются в фоне и после работы закрываются и непосредственно реализуют все активности ботов (отозвать авторизацию, выслать авторизацию с сообщением «X», и отправить сообщение).
Пока я пилил и тестировал, коллега с работы, который посмотрел мои наработки, предложил:
А давай еще и viber!
И я снова обратился к своему незаменимому помощнику – google. Там не оказалось ничего об открытых api, но я нашел варианты использования whatsapp и прикрутил его к сервису (взял на github код библиотеки «WhatsProt»). Но стоп, ведь whatsapp в основном для не русскоговорящих, надо делать переводы и для ботов, и на сайте. Почитал «На какие языки переводить проект в первую очередь?» и выбрал английский, русский, китайский упрощенный, немецкий и испанский, от китайского потом отказался, из моих знакомых никто в нем не понимал, чтобы внести изменения художественного характера.
Взял ZF 2.x за основу для веб. Планировал реализовать все приложения как единый бот, а потом каждое приложение — как отдельный бот. Первая схема была очень нестабильной (так как я все еще опирался на memcache в качестве никак недокументированного кеша и в качестве «очереди сообщений»), и я от нее отказался, но потом вернулся частично. А вторая уже после 8 ботов начала захламлять мой контакт лист в skype и jabber, а также отъедала ресурсы на моей локальной виртуальной машине с невероятной скоростью, особенно сам skype, так как для каждого бота приходилось запускать отдельного пользователя со своим рабочим столом и своим skype (хотя и есть возможность запускать несколько разных skype в одном окружении, и привязываться к api каждого по id dbus сессий — но я пока не пробовал), и я отказался от этой схемы, позже я объединил их, теперь каждый бот реализует несколько приложений, сгруппированных по смыслу. Начинал я с такого набора приложений:
- Базовый (различные операции и статистика по юзеру)
- Калькулятор
- Погода
- Игра с механикой «Башня Ханоя» (я ее пока не выпустил в prod, так как пока не придумал как обрисовывать пирамидки ASCII art'ом без расползания, в skype шрифт играет со мной в игры)
- Волшебный шар 8
- Возможность послать сообщение (даже не зарегистрированному) пользователю из любого клиента в любой из трех доступных (из самих ботов и с web сайта без авторизации)
- Курсы валют
- Курс конверсии
- Мировой источник
- Европейский Центробанк
- OCBC Банк (Сингапур)
- Центробанк России
- Национальный Банк Украины
- Национальный Банк Беларуси
- Банк Китая
- Банк Канады
- Курсы Bitcoin
За ноябрь и декабрь и январь я написал почти стабильную версию и, что самое главное, годную для теста на моих знакомых.
Но тут на глаза мне попалась статья «Google отказывается от поддержки XMPP», и я немного расстроился. Но через некоторое время, после статьи «Без паники! Про то, что сделал Google с XMPP», я понял, что все просто хорошо и сам постоянно использую google xmpp. Пришло время выливать все на хостинг. Моя тестовая ВМ на 7 ботов (по три скрипта на каждого бота/приложение (skype-jabber-whatsapp) и 3 воркера для каждого приложения: user worker, api worker (я написал все взаимодействие с бд через api – только зачем, до сих пор не понимаю, зато модульно и с одной точкой входа для web и ботов вышло, проще стало тестировать), и spam worker, все воркеры работали через зайцаMQ) потребляла 3,5ГБ RAM, а у всех хостеров были тарифы 2 и 4 ГБ со стоимостью в 2 раза больше за 4Гб, что мне не подходило, так все это из своего кармана и сервис бесплатен, написал в «цифровой океан» (о котором тоже прочитал на Хабре) о просьбе под нужды такого то сервиса дать ВМ с другим количеством оперативной памяти, но с более слабым процессором, он был не критичен. Мне отказали. Ожидаемо. Нашел сравнение хостеров на «comparevps» и за деньги те же что и для 2Гб, взял систему с 4Гб, хотя опыт реальный работы с этим хостером оказался чуть хуже ожидаемого, все стоит своих денег (в этом случае к сожалению). И написал две пошаговые инструкции по установке freebsd и debian сервера до этапа полностью работающего сервиса, чтобы быстро завести купленные VPS.
Но брать два сервера (freebsd для web + debian для чат ботов) было не с руки, я начал ставить все на один. И это был debian, я неприятно удивился что не могу поставить из пакетов последние версии того что мне надо как я мог сделать это на freebsd сборкой и портов. Потом я разобрался, как и что и престал быть хейтером linux и debian в частности, а стал фанатиком в хорошем смысле (теперь у меня в «must have» еще и Red Hat).
Я добавил по «наводке» друзей новые приложения:
- Мировые часы (смещения, время, закат, восход и часовой пояс)
- Hash генератор
- Хабра парсер (бот мне скидывает все ссылки на лучшие за сутки статьи с кратким описанием)
- Гороскоп (сложность была в том для всех поддерживаемых языков надо было искать оригинальные гороскопы, так как переводить их на лету плохо)
Ботов стало 11, на каждого 6 скриптов (1й –skype, 2й –jabber, 3й – whatsapp и три воркера), системе не хватало и 4Гб. Пришло время для понимания того что надо объединять приложения в тематические боты, как я говорил ранее, сказано – сделано. Появились Base, Office (калькулятор, курсы валют, мировые часы), Lifestyle (погода, гороскоп, 8ка), Developer (habra, hash). Теперь 2.5Гб и все хорошо. Тут я немного устал, читал неспешно «Джаббер переходит на полное шифрование» и решил протестировать свой сервер на xmpp.net и получил оценку A-, что могло не радовать. Кстати я долго не мог заставить свой ejabbered сервер стать публичным (а равно и добавить межсерверное взаимодействие) пока не сделал в DNS это

Но у меня все еще не было ssl сертификатов ни для jabber ни для сайта. Два по 60$ мне не улыбалось, и тут я нашел статью «Получаем бесплатный SSL сертификат» и понеслась. Все просто и понятно.
Была постоянно одна проблема, которая меня вконец допекла: я очень устал восстанавливать все сервисы руками, когда сервер по причине хостера падал или после перезагрузки просто стоял болванкой, надо было решить как-то проблему того, что я об этом узнавал только в понедельник, если это происходило в субботу. Я пробовал много разного, в конечном итоге, система после перезагрузки, полностью автоматом логинит всех *nix пользователей (для skype), под ними стартует terminal в графической оболочке, открывает шесть вкладок и в каждом запускает нужный php скрипт чтобы я мог зайти и посмотреть его вывод в любое время (для debug возникающих иногда мелких проблем):
Debian автологин всех юзеров и запуск скриптов в terminal в графическом режиме
«sleep» нужен для того чтобы все успело стартовать, включая skype.
А в автозагрузку gnome каждого пользователя ОС (который предназначен для выполнения скриптов бота) добавить skype и скрипты по обслуживанию бота в:
Для перезагрузки всего бота я использую:
>>nano /etc/inittab
8:2345:respawn:/bin/login -f ИМЯ_СЕРВИСА tty8 </dev/tty8 >/dev/tty8 2>&1
9:2345:respawn:/bin/login -f lifestyle tty9 </dev/tty9 >/dev/tty9 2>&1
10:2345:respawn:/bin/login -f developer tty10 </dev/tty10 >/dev/tty10 2>&1
11:2345:respawn:/bin/login -f xmess tty11 </dev/tty11 >/dev/tty11 2>&1
12:2345:respawn:/bin/login -f office tty12 </dev/tty12 >/dev/tty12 2>&1
>>nano /etc/profile
sleep 8
[ `tty` == '/dev/tty8' ] && startx -- :1
sleep 8
[ `tty` == '/dev/tty9' ] && startx -- :2
sleep 8
[ `tty` == '/dev/tty10' ] && startx -- :3
sleep 8
[ `tty` == '/dev/tty11' ] && startx -- :4
sleep 8
[ `tty` == '/dev/tty12' ] && startx -- :5
«sleep» нужен для того чтобы все успело стартовать, включая skype.
А в автозагрузку gnome каждого пользователя ОС (который предназначен для выполнения скриптов бота) добавить skype и скрипты по обслуживанию бота в:
/home/USERNAME/.config/autostart/
[Desktop Entry]
Type=Application
Exec=skype
Hidden=false
X-GNOME-Autostart-enabled=true
Name[en_US]=skype
Name=skype
Comment[en_US]=
Comment=
[Desktop Entry]
Type=Application
Exec=gnome-terminal --tab --title="skype" -e "/usr/local/bin/php /home/ИМЯ_СЕРВИСА/utils/assistant/_skype_assistant/base_assistant.php" --tab --title="jabber" -e "/usr/local/bin/php /home/ИМЯ_СЕРВИСА/utils/assistant/_jabber_assistant/base_assistant.php" --tab --title="whatsapp" -e "/usr/local/bin/php /home/ИМЯ_СЕРВИСА/utils/assistant/_whatsapp_assistant/base_assistant.php" --tab --title="user_op" -e "/usr/local/bin/php /home/ИМЯ_СЕРВИСА/utils/assistant/lib/worker/user_operations_worker.php base_assistant" --tab --title="api" -e "/usr/local/bin/php /home/ИМЯ_СЕРВИСА/utils/assistant/lib/worker/api_worker.php base_assistant" --tab --title="spamer" -e "/usr/local/bin/php /home/ИМЯ_СЕРВИСА/utils/assistant/lib/worker/spamer_worker.php base_assistant"
Hidden=false
X-GNOME-Autostart-enabled=true
Name[en_US]=all_tabs
Name=all_tabs
Comment[en_US]=
Comment=
Для перезагрузки всего бота я использую:
killall -u lifestyle
и в background запускаю
/bin/login -f lifestyle tty9 </dev/tty9 >/dev/tty9 2>&1 &
А чтобы получать email при shutdown и/или startup использовал этот скрипт. Теперь я всегда знал, что происходит с сервером и знал, что он поднимется полностью сам и все боты будут работать, даже после неожиданностей. Плюс к этому настроил бекап бд с ротацией и складированием на почту дампов (они пока небольшие).
Начитался на Хабре про heartbleed, и как, наверное, все, проверил свой сервер – все было ОК, но никак не мог включить TLS 1.2 и все время получал F, заодно и exploit нашел для heartbleed, чтобы протестировать различные сервисы, так как никогда таким не занимался, было занимательно попробовать себя в качестве иженера по безопасности, заодно попробовал «nikto.pl» и вдохновился «nessus» сканерами. Но вернусь к чат ботам, я и libssl переставил и openssl который «g» и проверяя зависимости nginx, постоянно видел, что он склинкован со старой lib, оказалось я просто поставил nginx для squeeze вместо wheezy. Хотя это и было причиной старой libssl слинкованной с nginx из-за которой у меня и не было уязвимости. «Случайность», — скажете вы. «Случайности не случайны», – отвечу я.
Кстати для whatsapp, как я понял свободное получение пароля от аккаунта, прикроют через какое-то время, сейчас его получить очень просто:
получить пароль для своего whatsapp аккаунта
Потом
/* Request sms code */
$username = "ТЕЛЕФОН";
$token = md5($username);
$nickname = "ИМЯ_СОСБТВЕННОЕ_ЛЮБОЕ";
$w = new WhatsProt($username, $token, $nickname, true);
$w->codeRequest();
Потом
/* Get password */
$username = "ТЕЛЕФОН";
$token = md5($username);
echo $token;
$nickname = "ИМЯ_СОСБТВЕННОЕ_ЛЮБОЕ";
$w = new WhatsProt($username, $token, $nickname, true);
$result = $w->codeRegister("SMS_КОД_ПОЛУЧЕННЫЙ_РАНЕЕ");
$password = $result->pw;
echo "Password is $password";
Но есть проблема, что каждое новое соединение (в моей схеме, один скрипт слушает команды, а другой отправляет ответы, полученные через очередь) обрывает текущее. Пришлось для whatsapp отказаться частично от очередей и сделать автоматический рестарт самого php скрипта.
Вот интересный хинт для jabber бота, чтобы он показывал статус «typing», когда пользователь ему что-нибудь пишет:
JAXL bot typing status
$oJabber->add_cb('on_chat_message', function($oStanza) {
sMessage = trim(strtolower($oStanza->body));
if (!$sMessage) {
$oStanza->to = $oStanza->from;
$oStanza->from = $oJabber->full_jid->to_string();
$oJabber->send($oStanza);
}
//….
}
Кстати, для whatsapp и skype ботов, выйти из spamer воркера, когда тот отработает сообщение из очереди в фоне и совершт действие с пользователем, позволила только строка
posix_kill(getmypid(), 9);
а для jabber просто
$oJabber->send_end_stream();
В планах следующие приложения:
- чат рулетка
- email на 10 минут
- мониторинг сервера через im
- приложение для интернет магазинов чтобы со стороны продавца все skype и/или jabber были одним для клиента и наоборот
Да и много других приложений вертится в голове, только не хватает теперь на все сразу времени. Даже не столько времени, сколько понимания того, какая ЦА (целевая аудитория) у этого сервиса.
Для того, кто задумает написать что-то похожее и не знает с чего начать, весь код был взят по приведенным выше ссылкам и из примеров использования к проектам на github. Единственное что я там не нашел это то, что я упомянул выше и то, как получать сообщения пользователя из xml для whatsapp бота и не реагировать на «typing» статус, когда юзер набирает сообщение:
whatsapp get user message
$oWa->pollMessages();
$mData = $oWa->getMessages();
if (!empty($mData)) {
if ('message' === $mData[count($mData) - 1]->getTag()) {
if ('notify' === $mData[count($mData) - 1]->getChildren()[0]->getTag()) {
$oDbAdapter->getConnection();
received($oWa->parseJID($mData[count($mData) - 1]->getAttributes()['from']), $mData[count($mData) - 1]->getChildren()[2]->getData());//$sUid, $sMessage
$oDbAdapter->closeConnection();
}
}
}
Вывод: при помощи в основном только Хабрахабра простой «тестировщик» сделал то, что ему нравилось, спасибо! И стал чуточку счастливее.
PS 1: Когда я заканчивал описывать процесс кропотливой работы над своим сервисом, я увидел на Хабре и не мог не прочитать вот эту статью «Джон Резиг: Пишите код каждый день». В ней четко и просто сформулировано все то, что я никак не мог оформить в своей голове. Я действовал точно также при написании своего сервиса, стал писать код каждый день. И это очень оправдало себя, так как ушла ненужная тревога о том, что я не успеваю сделать достаточно.
Бонус: Free Object Motion Tracking Plugin at Final Cut Pro X, Adobe Premier and DaVinci Resolve.