Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Чат-бот приложения через skype, jabber и whatsapp

Anna | 29.05.2014 | нет комментариев
Привет!
Хотелось бы рассказать вам историю создания одного бесхитростного развлекательного обслуживания чат ботов.

Введение

О себе: Прогр читаю давным-давно (лет этак 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 — в последующем все это крепко спасало, чтобы не удерживать крупные объемы логики в своем «дворце памяти».
Предисловие было в голове такое:
Webfreebsd 10 php 5.x apache mysql 5.x apc, во втором чтении
php 5.x стало nginx php-fpm, и без apc, а с нативным opcache.
Chatbotdebian 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) для рассылки сообщений и 3 хелперов (для всех протоколов) — простейшие боты-sender’ы, которые получают команды от user worker и spamer worker, а также вызываются в фоне и позже работы закрываются и непринужденно реализуют все активности ботов (отозвать авторизацию, выслать авторизацию с сообщением «X», и отправить сообщение).

Пока я пилил и тестировал, сотрудник с работы, тот, что посмотрел мои наработки, предложил:

А давай еще и viber!

И я вновь обратился к своему необходимому помощнику – google. Там не оказалось ничего об открытых api, но я обнаружил варианты применения whatsapp и прикрутил его к сервису (взял на github код библиотеки «WhatsProt»). Но стоп, чай whatsapp в основном для не русскоговорящих, нужно делат/li>

  • Hash генератор
  • Програ парсер (бот мне скидывает все ссылки на лучшие за сутки статьи с коротким изложением)
  • Гороскоп (трудность была в том для всех поддерживаемых языков нужно было искать подлинные гороскопы, так как переводить их на лету нехорошо)

Ботов стало 11, на всякого 6 скриптов (1й –skype, 2й –jabber, 3й – whatsapp и три воркера), системе не хватало и 4Гб. Пришло время для понимания того что нужно объединять приложения в тематические боты, как я говорил ранее, сказано – сделано. Возникли Base, Office (калькулятор, курсы валют, мировые часы), Lifestyle (погода, гороскоп, 8ка), Developer (habra, hash). Сейчас 2.5Гб и все отлично. Здесь я немножко утомился, читал неспешно «Джаббер переходит на полное шифрование» и решил протестировать свой сервер на xmpp.net и получил оценку A-, что могло не радовать. Кстати я длинно не мог принудить свой ejabbered сервер стать публичным (а равно и добавить межсерверное взаимодействие) пока не сделал в DNS это
image

Но у меня все еще не было ssl сертификатов ни для jabber ни для сайта. Два по 60$ мне не улыбалось, и здесь я обнаружил статью «Получаем безвозмездный SSL сертификат» и понеслась. Все легко и ясно.

Была непрерывно одна задача, которая меня вконец допекла: я дюже утомился восстанавливать все сервисы руками, когда сервер по причине хостера падал либо позже перезагрузки легко стоял болванкой, нужно было решить как-то задачу того, что я об этом узнавал только в понедельник, если это происходило в субботу. Я пробовал много различного, в финальном результате, система позже перезагрузки, всецело автоматом логинит всех *nix пользователей (для skype), под ними стартует terminal в графической оболочке, открывает шесть вкладок и в всяком запускает необходимый php скрипт Дабы я мог зайти и посмотреть его итог в всякое время (для debug возникающих изредка мелких задач):

Debian автологин всех юзеров и запуск скриптов в terminal в графическом режиме

>>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 смс 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: Когда я заканчивал описывать процесс заботливой работы над своим сервисом, я увидел на Прогре и не мог не прочитать вот эту статью «Джон Резиг: Пишите код всякий день». В ней Отчетливо и легко сформулировано все то, что я никак не мог оформить в своей голове. Я действовал верно также при написании своего обслуживания, стал писать код всякий день. И это дюже оправдало себя, так как ушла непотребная тревога о том, что я не поспеваю сделать довольно.

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB