Jabber-бот на PHP
Существует множество задач, где требуется уведомление о наступлении некоторого события (например, на сайте любимой компании появилось уведомление о срочном получении зарплаты в бухгалтерии). К сожалению, когда объектов наблюдения становится значительное количество, постоянный мониторинг всего и вся становится весьма обременительным и даже раздражающим (не в случае с зарплатой, конечно же). Существует множество программных решений, автоматизирующих данное занятие, но универсальнее кода, написанного на коленке, сложно что-то придумать (да-да, изобретем велосипед).
Собственно, код можно разделить на две независимые части — демон, который сидит в фоне и периодически проверяет нужные нам странички, выдергивает оттуда данные и сравнивает с предыдущими, и Jabber-бот, который, в случае появления свежих данных, выскажет вам все, что об этом думает.
Код демона не содержт ничего примечательного:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | #!/usr/bin/php5 <? define("ROOT", dirname(__FILE__)); define("SLEEP_TIME", 1800); define("PID_FILE", __FILE__ . ".pid"); include(ROOT."/daemon_process.php"); $g_signals = array( 'SIGTERM' => 0, 'OTHER' => 0, ); // Test if we already running $pl = array(); exec("ps ax", $pl); $z = false; foreach($pl as $p) { if(strpos($p, basename(__FILE__))) { if($z) die("Process already running"); else $z = true; } } // Make daemon if(-1 == ($g_pid = pcntl_fork())) die("Could not fork"); if($g_pid != 0) { $f = @fopen(PID_FILE, "w+"); @fputs($f, $g_pid); @fclose($f); exit(0); } if(!pcntl_signal(SIGTERM, "_sig_handler")) die("Could not setup SIGTERM handler"); if(-1 == posix_setsid()) die("Could not detach from terminal"); init(); while(true) { main(); if($g_signals['SIGTERM'] == 1) { deinit(); exit(); } sleep(SLEEP_TIME); } // // signal handler // function _sig_handler($signo) { global $g_signals, $db; switch($signo) { case SIGTERM: { $g_signals['SIGTERM'] = 1; if(is_object($db)) $db->disconnect(); @unlink(PID_FILE); exit(); break; } default: $g_signals['OTHER'] = $signo; } } ?> |
Теперь собственно к процедурам init(), deinit() и main(), которые определены в daemon_process.php, и, собственно, отвечают за процедуру изъятия данных и нотификацию. Приведу сокращенно код (выборку данных, проверку и сравнение оставим на собственное изучение).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | <? require_once(ROOT . "/jabber/class_Jabber.php"); $js = null; // // Init-shminit // function init() { global $js; // Инициализируем объект класса Jabber-клиента $js = new JabberSender("логин", "пароль", "сервер"); } function deinit() {} // // Main processing // function main() { global $js; $n = array(); // тут выбираем данные из нужных источников и формируем массив сообщений if(count($n) > 0) $js->send("ваш Jabber-контакт", $n); } // // Класс Jabber-клиента // class JabberSender { var $messages; var $username, $passw, $resource, $server; function __construct($username, $passw, $server, $resource = NULL) { $this->username = $username; $this->passw = $passw; $this->resource = $resource; $this->server = $server; $this->jab = new Jabber(true); $this->first_roster_update = true; // здесь мы определяем функции, которые будут реагировать на сообщения от // Jabber-клиента. Нам нужно два события - когда подключились к серверу и // когда прошли авторизацию $this->jab->set_handler("connected", $this, "handleConnected"); $this->jab->set_handler("authenticated", $this, "handleAuthenticated"); } function handleConnected() { // подсоединились? авторизуемся! $this->jab->login($this->username, $this->passw); } function handleAuthenticated() { $b = true; // проталкиваем в поток сообщения один за другим с задержкой в три секунды // чтобы нас не сочли за спамеров foreach($this->messages as $mi) { $b = $b && $this->jab->message($this->sendto, "normal", NULL, $mi, NULL, NULL); sleep(3); } // если все сообщения прошли - отключаем клиента, в противном случае // он сам отключится по истечении времени функционирования if($b) $this->jab->terminated = true; } // функция, которая и будет использоваться для отправки пачки сообщений // со стороны остального function send($sendto, $messages){ // если соединение с сервером не удалось - делать нечего if (!$this->jab->connect($this->server)) return; $this->messages = $messages; $this->sendto = $sendto; // запускаем основной цикл работы клиента с временем жизни 100 секунд $this->jab->execute(1, 100); $this->jab->disconnect(); } } ?> |
В чем сложность в вышеприведенном коде — класс Jabber-клиента, инкапсулирующий Jabber Client Library, построен по асинхронному принципу. Есть объект $this->jab, которому передается полное управление, и который асинхронно вызывает методы инкапсулирующего его класса. Т.е. процесс работы клиента можно представить в качестве последовательности «подключаемся — авторизируемся — пропихиваем свои сообщения — отключаемся». Иными словами, бот не будет постоянно висеть в онлайне, а будет только плевать сообщений и тут же возвращаться в оффлайн. Впрочем, библиотека весьма интересная, так что написание на ней полноценного бота, который все время будет в сети и реагировать на ваши команды, так же вполне возможно.
Дело за малым — создать новый аккаунт, взаимно авторизовать его с вашим Jabber-аккаунтом и протестировать его работу.

[...] Начнем с отвлеченного. Как следить за выходом серий? Тут есть два очевидных варианта: а) воспользоваться сервисом для отслеживания сериалов; б) написать небольшого робота, который будет самостоятельно отслеживать изменения на страницах трекера и присылать вам уведомление об изменении (можно, конечно, заставить торрент-клиента скачивать по RSS, но мы же настоящие комсомольцы, правда?). Для себя я сделал такого робота на базе Jabber-клиента, уже упоминавшегося в данном блоге. [...]