root/trunk/Phergie/Plugin/Logging.php @ 134

Revision 134, 14.3 KB (checked in by tobias382, 5 years ago)

Logging: Removed responses when the user is online or no result is found from the seen and search commands and removed the heard command

Line 
1<?php
2
3/**
4* @see Phergie_Plugin_Abstract_Command
5*/
6require_once 'Phergie/Plugin/Abstract/Command.php';
7
8/**
9* @see Phergie_Plugin_Users
10*/
11require_once 'Phergie/Plugin/Users.php';
12
13/**
14* Logs channel events and handles requests for logged data pertaining to the
15* last actions taken or messages sent by a given user or posts sent
16* containing a given search phrase.
17*/
18class Phergie_Plugin_Logging extends Phergie_Plugin_Abstract_Command
19{
20    /**
21    * Indicates that a local directory is required for this plugin
22    *
23    * @var bool
24    */
25    protected $needsDir = true;
26
27    /**
28    * Indicates a JOIN event in the type column of the logs table
29    *
30    * @const int
31    */
32    const JOIN = 1;
33
34    /**
35    * Indicates a PART event in the type column of the logs table
36    *
37    * @const int
38    */
39    const PART = 2;
40
41    /**
42    * Indicates a QUIT event in the type column of the logs table
43    *
44    * @const int
45    */
46    const QUIT = 3;
47
48    /**
49    * Indicates a PRIVMSG event in the type column of the logs table
50    *
51    * @const int
52    */
53    const PRIVMSG = 4;
54
55    /**
56    * Indicates a CTCP ACTION event in the type column of the logs table
57    *
58    * @const int
59    */
60    const ACTION = 5;
61
62    /**
63    * Indicates a NICK event in the type column of the logs table
64    *
65    * @const int
66    */
67    const NICK = 6;
68
69    /**
70    * Indicates a KICK event in the type column of the logs table
71    *
72    * @const int
73    */
74    const KICK = 7;
75
76    /**
77    * Indicates a MODE event in the type column of the logs table
78    *
79    * @const int
80    */
81    const MODE = 8;
82
83    /**
84    * Indicates a TOPIC event in the type column of the logs table
85    *
86    * @const int
87    */
88    const TOPIC = 9;
89
90    /**
91    * PDO instance for the database
92    *
93    * @var PDO
94    */
95    protected $db = null;
96
97    /**
98    * Prepared statement for searching for the last logged action in which a
99    * particular user was mentioned
100    *
101    * @var PDOStatement
102    */
103    protected $search;
104
105    /**
106    * Prepared statement for searching for the last logged action originating
107    * from a particular user
108    *
109    * @var PDOStatement
110    */
111    protected $seen;
112
113    /**
114    * Prepared statement for searching for the last logged PRIVMSG action or
115    * CTCP ACTION command originating from a particular user
116    *
117    * @var PDOStatement
118    */
119    protected $heard;
120
121    /**
122    * PRepared statement for searching for one or more time ranges during
123    * which a particular user is most likely to be present in a particular
124    * channel
125    *
126    * @var PDOStatement
127    */
128    protected $willsee;
129
130    /**
131    * Prepared statement for inserting new log entries
132    *
133    * @var PDOStatement
134    */
135    protected $insert;
136
137    /**
138    * Action descriptions corresponding to event constants
139    *
140    * @var array
141    */
142    protected $actions = array
143    (
144        self::JOIN => 'joining this channel',
145        self::PART => 'leaving this channel because',
146        self::QUIT => 'quitting',
147        self::PRIVMSG => 'saying',
148        self::ACTION => 'saying',
149        self::NICK => 'changing nick to',
150        self::KICK => 'being kicked off because',
151    );
152
153    /**
154    * Initializes the database.
155    *
156    * @return void
157    */
158    public function init()
159    {
160        try {
161            // Initialize the database connection
162            $db = $this->dir . 'logging.db';
163            $create = !file_exists($db);
164            $this->db = new PDO('sqlite:' . $db);
165
166            // Create database tables if necessary
167            if ($create) {
168                $result = $this->db->exec('
169                    CREATE TABLE logs (
170                        tstamp VARCHAR(19),
171                        type SHORTINT,
172                        chan VARCHAR(45),
173                        nick VARCHAR(25),
174                        message VARCHAR(255)
175                    );
176                    CREATE INDEX channicktype ON logs (tstamp, type, chan, nick);
177                    CREATE INDEX channick ON logs (tstamp, chan, nick);
178                ');
179            }
180
181            // Initialize prepared statements for common operations
182            $this->search = $this->db->prepare('
183                SELECT tstamp, type, chan, nick, message
184                FROM logs
185                WHERE nick LIKE :phrase
186                OR message LIKE :phrase
187                ORDER BY tstamp DESC
188                LIMIT :limit
189            ');
190
191            $this->seen = $this->db->prepare('
192                SELECT tstamp, type, message
193                FROM logs
194                WHERE nick = :name
195                AND chan = :chan
196                ORDER BY tstamp DESC
197                LIMIT 1
198            ');
199
200            $this->willsee = $this->db->prepare('
201                SELECT strftime("%H", tstamp) post_hour, COUNT(*) post_count
202                FROM logs
203                WHERE type IN (' . self::PRIVMSG . ', ' . self::ACTION . ')
204                AND nick = :nick
205                AND chan = :chan
206                GROUP BY strftime("%H", tstamp)
207                ORDER BY 2 DESC, 1
208                LIMIT 1
209            ');
210
211            $this->insert = $this->db->prepare('
212                INSERT INTO logs (
213                    tstamp,
214                    type,
215                    chan,
216                    nick,
217                    message
218                )
219                VALUES (
220                    :tstamp,
221                    :type,
222                    :chan,
223                    :nick,
224                    :message
225                )
226            ');
227        } catch (PDOException $e) { }
228    }
229
230    /**
231    * Returns whether or not the plugin's dependencies are met.
232    *
233    * @param Phergie_Driver_Abstract $client Client instance
234    * @param array $plugins List of short names for plugins that the
235    *                       bootstrap file intends to instantiate
236    * @see Phergie_Plugin_Abstract_Base::checkDependencies()
237    * @return bool TRUE if dependencies are met, FALSE otherwise
238    */
239    public static function checkDependencies(Phergie_Driver_Abstract $client, array $plugins)
240    {
241        if (! in_array('Users', $plugins)
242            || ! Phergie_Plugin_Users::checkDependencies($client, $plugins)) {
243            return false;
244        }
245
246        if (!extension_loaded('PDO')
247            || !extension_loaded('pdo_sqlite')) {
248            return false;
249        }
250
251        return true;
252    }
253
254    /**
255    * Formats a timestamp for display purposes.
256    *
257    * @param string $timestamp Timestamp to format
258    * @return string Formatted timestamp
259    */
260    protected function formatTimestamp($timestamp)
261    {
262        return preg_replace(
263            '#^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$#',
264            '$1-$2-$3 @ $4:$5:$6',
265            $timestamp
266        );
267    }
268
269    /**
270    * Inserts a new entry in the log database.
271    *
272    * @param int $type Class constant representing the event type
273    * @param string $chan Name of the channel in which the event occurs
274    * @param string $nick Nick of the user from which the event originates
275    * @param string $message Message associated with the event if applicable
276    *                        (optional)
277    * @return void
278    */
279    protected function insertEvent($type, $chan, $nick, $message = null)
280    {
281        if (!$this->db) {
282            return;
283        }
284
285        $params = array(
286            ':tstamp' => date('Y-m-d H:i:s'),
287            ':type' => $type,
288            ':chan' => $chan,
289            ':nick' => $nick,
290            ':message' => trim($message)
291        );
292
293        $result = $this->insert->execute($params);
294    }
295
296    /**
297    * Logs incoming messages.
298    *
299    * @return void
300    */
301    public function onPrivmsg()
302    {
303        // Allow the Command plugin to process command calls
304        parent::onPrivmsg();
305
306        if ($this->event->isInChannel()) {
307            $this->insertEvent(
308                self::PRIVMSG,
309                $this->event->getSource(),
310                $this->event->getNick(),
311                $this->event->getArgument(1)
312            );
313        }
314    }
315
316    /**
317    * Logics incoming actions.
318    *
319    * @return void
320    */
321    public function onAction()
322    {
323        if ($this->event->isInChannel()) {
324            $this->insertEvent(
325                self::ACTION,
326                $this->event->getSource(),
327                $this->event->getNick(),
328                $this->event->getArgument(1)
329            );
330        }
331    }
332
333    /**
334    * Tracks users joining.
335    *
336    * @return void
337    */
338    public function onJoin()
339    {
340        $this->insertEvent(
341            self::JOIN,
342            $this->event->getSource(),
343            $this->event->getNick()
344        );
345    }
346
347    /**
348    * Tracks users parting.
349    *
350    * @return void
351    */
352    public function onPart()
353    {
354        $this->insertEvent(
355            self::PART,
356            $this->event->getSource(),
357            $this->event->getNick(),
358            $this->event->getArgument(1)
359        );
360    }
361
362    /**
363    * Tracks users being kicked.
364    *
365    * @return void
366    */
367    public function onKick()
368    {
369        $this->insertEvent(
370            self::KICK,
371            $this->event->getSource(),
372            $this->event->getNick(),
373            $this->event->getArgument(1)
374        );
375    }
376
377    /**
378    * Tracks users changing modes.
379    *
380    * @return void
381    */
382    public function onMode()
383    {
384        $this->insertEvent(
385            self::MODE,
386            $this->event->getSource(),
387            $this->event->getNick(),
388            implode(' ', array_slice($this->event->getArguments(), 1))
389        );
390    }
391
392    /**
393    * Tracks channel topic changes.
394    *
395    * @return void
396    */
397    public function onTopic()
398    {
399        $this->insertEvent(
400            self::TOPIC,
401            $this->event->getSource(),
402            $this->event->getNick(),
403            $this->event->getArgument(1)
404        );
405    }
406
407    /**
408    * Tracks users quitting.
409    *
410    * @return void
411    */
412    public function onQuit()
413    {
414        $nick = $this->event->getNick();
415
416        foreach (Phergie_Plugin_Users::getChannels($nick) as $chan) {
417            $this->insertEvent(
418                self::QUIT,
419                $chan,
420                $this->event->getNick(),
421                $this->event->getArgument(0)
422            );
423        }
424    }
425
426    /**
427    * Tracks users changing nicks.
428    *
429    * @return void
430    */
431    public function onNick()
432    {
433        $nick = $this->event->getNick();
434
435        foreach (Phergie_Plugin_Users::getChannels($nick) as $chan) {
436            $this->insertEvent(
437                self::NICK,
438                $chan,
439                $this->event->getNick(),
440                $this->event->getArgument(0)
441            );
442        }
443    }
444
445    /**
446    * Responds to requests for logged messages containing a particular search
447    * phrase.
448    *
449    * @param string $phrase Phrase to search for
450    * @return void
451    */
452    public function onDoSearch($phrase)
453    {
454        if (!$this->db) {
455            return;
456        }
457
458        $source = $this->event->getSource();
459
460        $params = array(
461            ':phrase' => '%' . $phrase . '%',
462            ':limit' => ($source[0] == '#' ? 1 : 6)
463        );
464
465        $this->search->execute($params);
466
467        foreach($this->search as $row) {
468            $this->doPrivmsg(
469                $source,
470                sprintf(
471                    '%s was seen %s: %s on %s (on %s)',
472                    $row['nick'],
473                    $this->actions[$row['type']],
474                    $row['message'],
475                    $row['chan'],
476                    $this->formatTimestamp($row['tstamp'])
477                )
478            );
479        }
480    }
481
482    /**
483    * Responds to requests for the last logged action originating from a
484    * particular user.
485    *
486    * @param string $user Nick of the user to search for
487    * @return void
488    */
489    public function onDoSeen($user)
490    {
491        if (!$this->db) {
492            return;
493        }
494
495        // Don't match if user has a space (obviously it's not a nick)
496        if (strpos($user, ' ') !== false) {
497            return;
498        }
499
500        $source = $this->event->getSource();
501        $target = $this->event->getNick();
502
503        // Handle 'me' alias
504        if ($user == 'me') {
505            $user = $target;
506        }
507
508        // Get the last event from the specified user
509        $params = array(
510            ':name' => $user,
511            ':chan' => $source
512        );
513
514        $this->seen->execute($params);
515        $row = $this->seen->fetch(PDO::FETCH_ASSOC);
516
517        // Send the last action if available
518        if ($row) {
519            $this->doPrivmsg(
520                $source,
521                sprintf(
522                    '%s: %s was last seen %s: %s (on %s)',
523                    $target,
524                    $user,
525                    $this->actions[$row['type']],
526                    $row['message'],
527                    $this->formatTimestamp($row['tstamp'])
528                )
529            );
530        }
531    }
532
533    /**
534    * Responds to requests for a time range during which a particular user
535    * is most likely to be present in the channel from which the request
536    * originates.
537    *
538    * @param string $user Nick of the user to search for
539    * @return void
540    */
541    public function onDoWillsee($user)
542    {
543        if (!$this->db) {
544            return;
545        }
546       
547        // Check to make sure the request came from a channel
548        $source = $this->event->getSource();
549        if ($source[0] != '#') {
550            return;
551        }
552
553        // Handle cases where the bot is the subject
554        if ($user == $this->getIni('nick')) {
555            $this->doPrivmsg($source, 'What are you talking about? I\'m always here!');
556            return;
557        }
558
559        // Handle 'me' alias
560        if ($user == 'me') {
561            $user = $this->event->getNick();
562        }
563
564        // Perform the search
565        $params = array(
566            ':nick' => $user,
567            ':chan' => $source
568        );
569
570        $this->willsee->execute($params);
571        $prediction = $this->willsee->fetchColumn();
572
573        // Return if no results are found
574        if ($prediction === false) {
575            return;
576        }
577
578        // Calculate a predicted time of arrival
579        $hour = date('H');
580        if ($hour > $prediction) {
581            $prediction = 24 - ($hour - $prediction);
582        } else {
583            $prediction = $prediction - $hour;
584        }
585
586        // Return with a message including the prediction
587        $message = $target . ': ' . $user . ' is most likely to be online ';
588        if ($prediction == 0) {
589            $message .= 'now!';
590        } elseif ($prediction == 1) {
591            $message .= 'in 1 hour.';
592        } else {
593            $message .= 'in ' . $prediction . ' hours.';
594        }
595        $this->doPrivmsg($source, $message);
596    }
597}
Note: See TracBrowser for help on using the browser.