29
Окт

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-аккаунтом и протестировать его работу.

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


Прокомментировать

Мой Круг — Максим Крентовский

Рекомендую

Автомобильные видеорегистраторы с GPS на gadgetz.ru