root/trunk/Phergie/Plugin/Remind.php @ 308

Revision 308, 10.0 KB (checked in by sean@…, 5 years ago)

changes 'too many messages in public' behaviour. fixes #61

Line 
1<?php
2
3/**
4 * Parses and logs commands messages that should be relayed to other users the
5 * next time the recipient is active on the same channel
6 *
7 * (adapted from the Drink plugin)
8 */
9class Phergie_Plugin_Remind extends Phergie_Plugin_Abstract_Command
10{
11    /**
12     * Number of reminders to show in public
13     */
14    const PUBLIC_REMINDERS = 3;
15   
16    /**
17     * Indicates that a local directory is required for this plugin
18     *
19     * @var bool
20     */
21    protected $needsDir = true;
22
23    /**
24     * PDO resource for a SQLite database containing the reminders
25     *
26     * @var resource
27     */
28    protected $db = null;
29
30    /**
31     * Flag that indicates whether or not to use and in-memory reminder list
32     *
33     * @var bool
34     */
35    protected $keepListInMemory = true;
36
37    /**
38     * In-memory store for pending reminders
39     *
40     * Form: $msgStore[channel][recipient] set if a pending reminder exists
41     */
42    protected $msgStore = array();
43
44    /**
45     * Connects to the database and populates tables where needed.
46     *
47     * @return void
48     */
49    public function onInit()
50    {
51        // Initialize the database connection
52        $this->db = new PDO('sqlite:' . $this->dir . 'remind.db');
53        if (!$this->db) {
54            return;
55        }
56        $this->createTables();
57        $this->populateMemory();
58    }
59
60    /**
61     * Returns whether or not the plugin's dependencies are met.
62     *
63     * @param Phergie_Driver_Abstract $client Client instance
64     * @param array $plugins List of short names for plugins that the
65     *                       bootstrap file intends to instantiate
66     * @see Phergie_Plugin_Abstract_Base::checkDependencies()
67     * @return bool TRUE if dependencies are met, FALSE otherwise
68     */
69    public static function checkDependencies(Phergie_Driver_Abstract $client, array $plugins)
70    {
71        $errors = array();
72
73        if (!extension_loaded('PDO')) {
74            $errors[] = 'PDO php extension is required';
75        }
76        if (!extension_loaded('pdo_sqlite')) {
77            $errors[] = 'pdo_sqlite php extension is required';
78        }
79        return empty($errors) ? true : $errors;
80    }
81
82    /**
83     * Determines if a table exists
84     *
85     * @param string $name Table name
86     * @return bool
87     */
88    protected function haveTable($name)
89    {
90        return (bool) $this->db->query(
91            'SELECT COUNT(*) FROM sqlite_master WHERE name = ' . $this->db->quote($name)
92            )->fetchColumn();
93    }
94
95    /**
96     * Creates the database table(s) (if they don't exist)
97     *
98     * @return void
99     */
100    protected function createTables()
101    {
102        if (!$this->haveTable('remind')) {
103            $this->debug('Creating the database schema for: remind');
104            $this->db->exec('
105                CREATE TABLE
106                    remind
107                    (
108                        time INTEGER,
109                        channel TEXT,
110                        recipient TEXT,
111                        sender TEXT,
112                        message TEXT
113                    )
114            ');
115        }
116    }
117
118    /**
119     * Populates the in-memory cache of pending reminders
120     *
121     * @return void
122     */
123    protected function populateMemory()
124    {
125        if (!$this->keepListInMemory) {
126            return;
127        }
128        $storeCounter = 0;
129        foreach ($this->fetchMessages() as $msg) {
130            $this->msgStore[$msg['channel']][$msg['recipient']] = $msg['rowid'];
131            ++$storeCounter;
132        }
133        $this->debug("Found {$storeCounter} messages", true);
134    }
135
136    /**
137     * Gets pending messages (for a specific channel/recipient)
138     *
139     * @param string $channel   channel on which to check pending messages
140     * @param string $recipient user for which to check pending messages
141     * @return array of records
142     */
143    protected function fetchMessages($channel = null, $recipient = null)
144    {
145        if ($channel) {
146            $qClause = 'WHERE channel = :channel AND recipient = :recipient';
147            $params = array(
148                'channel' => $channel,
149                'recipient' => strtolower($recipient)
150            );
151        } else {
152            $qClause = '';
153            $params = array();
154        }
155        $q = $this->db->prepare(
156            'SELECT rowid, channel, sender, recipient, time, message
157            FROM remind ' . $qClause
158        );
159        $q->execute($params);
160        return $q->fetchAll();
161    }
162
163    /**
164     * Deletes a delivered message
165     *
166     * @param int    $rowid     ID of the message to delete
167     * @param string $channel   message's channel
168     * @param string $recipient message's recipient
169     * @return void
170     */
171    protected function deleteMessage($rowid, $channel, $nick)
172    {
173        $q = $this->db->prepare('DELETE FROM remind WHERE rowid = :rowid');
174        $q->execute(array('rowid' => $rowid));
175
176        if ($this->keepListInMemory) {
177            if (isset($this->msgStore[$channel][$nick])
178                && $this->msgStore[$channel][$nick] == $rowid) {
179                unset($this->msgStore[$channel][$nick]);
180            }
181        }
182    }
183
184    /**
185     * Get data for a specific message
186     *
187     * @param int $rowid row ID of the message to get
188     */
189    protected function getMessage($rowid)
190    {
191        $q = $this->db->prepare('
192            SELECT rowid, time, channel, recipient, sender, message
193            FROM remind
194            WHERE rowid = :rowid
195        ');
196        $q->execute(array('rowid' => $rowid));
197        return $q->fetch(PDO::FETCH_ASSOC);
198    }
199
200    /**
201     * Handles the tell/remind command (stores the message)
202     *
203     * @param string $recipient name of the recipient
204     * @param string $essage    message to store
205     * @return void
206     */
207    protected function handleRemind($recipient, $message)
208    {
209        $source = $this->event->getSource();
210        $nick = $this->event->getNick();
211        if (!$this->event->isInChannel()) {
212            $this->doPrivmsg($source, 'Reminders must be requested in-channel.');
213            return;
214        }
215        $q = $this->db->prepare('
216            INSERT INTO remind
217                (
218                    time,
219                    channel,
220                    recipient,
221                    sender,
222                    message
223                )
224            VALUES
225                (
226                    :time,
227                    :channel,
228                    :recipient,
229                    :sender,
230                    :message
231                )
232        ');
233        try {
234            $q->execute(array(
235                'time' => date(DATE_RFC822),
236                'channel' => $source,
237                'recipient' => strtolower($recipient),
238                'sender' => strtolower($nick),
239                'message' => $message
240            ));
241        } catch (PDOException $e) { }
242
243        if ($rowid = $this->db->lastInsertId()) {
244            $this->doPrivmsg($source, "ok, $nick, message stored");
245        } else {
246            $this->doPrivmsg($source, "$nick: bad things happened. Message not saved.");
247            return;
248        }
249
250        if ($this->keepListInMemory) {
251            $this->msgStore[$source][$recipient] = $rowid;
252        }
253    }
254
255    /**
256     * Determines if the user has pending reminders, and if so, delivers them
257     *
258     * @param string $channel   channel to check
259     * @param string $recipient recipient to check
260     * @return Bool
261     */
262    protected function deliverReminders($channel, $nick)
263    {
264        if ($channel[0] != '#') {
265            // private message, not a channel, so don't check
266            return;
267        }
268
269        // short circuit if there's no message in memory (if allowed)
270        if ($this->keepListInMemory
271            && !isset($this->msgStore[$channel][strtolower($nick)])) {
272            return;
273        }
274       
275        // fetch and deliver messages
276        $reminders = $this->fetchMessages($channel, $nick);
277        if (count($reminders) > self::PUBLIC_REMINDERS) {
278            $msgs = array_slice($reminders, 0, self::PUBLIC_REMINDERS);
279            $privmsgs = array_slice($reminders, self::PUBLIC_REMINDERS);
280        } else {
281            $msgs = $reminders;
282            $privmsgs = false;
283        }
284        foreach ($msgs as $msg) {
285            $this->doPrivmsg(
286                $channel,
287                "{$nick}: (from {$msg['sender']}, {$msg['time']}) " .
288                    $msg['message']
289            );
290            $this->deleteMessage($msg['rowid'], $channel, $nick);
291        }
292        if ($privmsgs) {
293            foreach ($privmsgs as $msg) {
294                $this->doPrivmsg(
295                    $nick,
296                    "{$nick}: (from {$msg['sender']}, {$msg['time']}) " .
297                        $msg['message']
298                );
299                $this->deleteMessage($msg['rowid'], $channel, $nick);
300            }
301            $this->doPrivmsg(
302                $channel,
303                "{$nick}: (" . count($privmsgs) . " more messages sent in private.)"
304            );
305        }
306    }
307
308    /**
309     * Intercepts a message and processes any contained recognized commands.
310     *
311     * Overrides parent to force bot nick prefix (3rd param of processCommand())
312     * Also checks to see if the message's nick has any pending message and
313     * delivers them if so
314     *
315     * @return void
316     */
317    public function onPrivmsg()
318    {
319        $this->deliverReminders($this->event->getSource(), $this->event->getNick());
320        $this->processCommand($this->event->getArgument(1), false, true);
321    }
322
323    /**
324     * Handles reminder requests
325     *
326     * @param string $message
327     * @return void
328     */
329    public function onDoTell($recipient, $message)
330    {
331        $this->handleRemind($recipient, $message);
332    }
333   
334    /**
335     * Handles reminder requests
336     *
337     * @param string $message
338     * @return void
339     */
340    public function onDoAsk($recipient, $message)
341    {
342        $this->handleRemind($recipient, $message);
343    }
344
345    /**
346     * Handles reminder requests
347     *
348     * @param string $message
349     * @return void
350     */
351    public function onDoRemind($recipient, $message)
352    {
353        $this->handleRemind($recipient, $message);
354    }
355}
Note: See TracBrowser for help on using the browser.