Assembla home | Assembla project page
 

root/trunk/Phergie/Plugin/Drink.php

Revision 330, 14.8 kB (checked in by jlleblanc, 10 months ago)

Fixed the Drink plugin to check to make sure that the drink recipient is actually in the channel.

Line 
1 <?php
2
3 /**
4  * Parses incoming messages for issued commands indicating that the bot should
5  * serve a specified user with a random drink of a specific type, locates a
6  * drink of that type, and responds with an action indicating that the bot is
7  * serving that drink to that user.
8  *
9  * The gender configuration setting should be set to either M or F to indicate
10  * the gender of the bot so as to allow for substitution of the proper pronoun
11  * when a user requests that the bot serve itself a drink.
12  */
13 class Phergie_Plugin_Drink extends Phergie_Plugin_Abstract_Command
14 {
15     /**
16      * Indicates that a local directory is required for this plugin
17      *
18      * @var bool
19      */
20     protected $needsDir = true;
21
22     /**
23      * PDO resource for a SQLite database containing the drinks
24      *
25      * @var resource
26      */
27     protected $db = null;
28
29     /**
30      * List of profane words to filter from imported drinks
31      *
32      * @var array
33      */
34     protected $filter = array(
35         'shit',
36         'shitkicker',
37         'shitface',
38         'shittin',
39         'piss',
40         'pissed',
41         'pisser',
42         'pissbomb',
43         'fuck',
44         'fucks',
45         'motherfucker',
46         'motherfuckers',
47         'fucked',
48         'fucker',
49         'dumbfuck',
50         'mindfuck',
51         'stumblefuck',
52         'cunt',
53         'cock',
54         'cockteaser',
55         'cocksucker',
56         'ass',
57         'asshole',
58         'bitch',
59         'bitches',
60         'bitchin',
61         'tit',
62         'tits',
63         'titty',
64         'titties',
65         'penis',
66         'penisbutter',
67         'dick',
68         'dickhead',
69         'sex',
70         'buttsex',
71         'sexoholic',
72         'sexual',
73         'sexy',
74         'triplesex',
75         'panties',
76         'urine'
77     );
78
79     /**
80      * Connects to the database and populates tables where needed.
81      *
82      * @return void
83      */
84     public function onInit()
85     {
86         try {
87             // Initialize the database connection
88             $this->db = new PDO('sqlite:' . $this->dir . 'drink.db');
89             if (!$this->db) {
90                 return;
91             }
92
93             // Populate the database if necessary
94             if ($this->needTable('beer')) {
95                 $this->debug('Retrieving data for: Beer');
96                 $contents = @file_get_contents('http://beerme.com/beerlist.php');
97                 if ($contents !== false) {
98                     $this->debug('Parsing data for: Beer');
99                     preg_match_all('/brewery\.php\?[0-9]+#[0-9]+\'>([^<]+)/', $contents, $matches);
100                     $names = array();
101                     foreach($matches[1] as $key => $name) {
102                         $name = $this->decodeTranslit($name);
103                         if ($this->hasBadChars($name) || strpos($name, '(discontinued)') !== false) {
104                             continue;
105                         }
106                         $name = explode('/', preg_replace('/\([^)]+\)/', '', $name));
107                         $name = trim(array_shift($name));
108                         if (!empty($name)) {
109                             $name = html_entity_decode($name);
110                             $names[] = $name;
111                         }
112                     }
113                     $this->populateTable('beer', $names);
114                     unset($names);
115                 }
116             }
117
118             if ($this->needTable('cocktail')) {
119                 $limit = 2;
120                 $names = array();
121                 $this->debug('Retrieving data for: Cocktail');
122                 for ($i = 1; $i <= $limit; $i += 150) {
123                     $contents = @file_get_contents('http://www.webtender.com/db/browse?level=2&dir=drinks&char=%2A&start=' . $i);
124                     if ($contents === false) {
125                         break;
126                     }
127                     $this->debug('Parsing data for: Cocktail (' . $i . ' - ' . ($i + 150) . ')');
128                     if ($i == 1) {
129                         preg_match('/>([0-9]+) found\\.</', $contents, $match);
130                         $limit = $match[1]+(150-($match[1]%150));
131                     }
132                     preg_match_all('/db\\/drink\\/[0-9]+">([^<]+)/', $contents, $matches);
133                     foreach($matches[1] as $name) {
134                         $name = $this->decodeTranslit($name);
135                         if ($this->hasBadChars($name)) {
136                             continue;
137                         }
138                         $name = html_entity_decode(preg_replace('/ The$|^The |\s*\([^)]+\)\s*| #[0-9]+$/', '', $name));
139                         $names[] = $name;
140                     }
141                 }
142                 if ($contents) {
143                     $this->populateTable('cocktail', $names);
144                 }
145                 unset($names);
146             }
147
148             if ($this->needTable('coke')) {
149                 $this->debug('Retrieving data for: Coke');
150                 $contents = @file_get_contents('http://www.energyfiend.com/huge-caffeine-database/');
151                 if ($contents) {
152                     $this->debug('Parsing data for: Coke');
153                     // List of drinks to filter out
154                     $filter = array(
155                         'tea',
156                         'coffee',
157                         'starbucks'
158                     );
159                     $start = stripos($contents, 'id="caffeinedb"');
160                     $end = stripos($contents, '</table>', $start);
161                     $contents = substr($contents, $start, $end-$start);
162                     preg_match_all('/<tr[^>]*><td>(<[^>]+>)?([^<]+)/is', $contents, $matches);
163                     $names = array();
164                     foreach($matches[2] as $name) {
165                         $name = $this->decodeTranslit($name);
166                         if ($this->hasBadChars($name)) {
167                             continue;
168                         }
169                         $name = html_entity_decode(trim(preg_replace('/ \\([^)]+\\)| - .*$/', '', $name)));
170                         if (!preg_match('/(?:^|\s+)(?:' . implode('|', $filter) . ')(?:\s+|$)/i', $name)) {
171                             $names[] = $name;
172                         }
173                     }
174                     $this->populateTable('coke', $names);
175                     unset($names);
176                 }
177             }
178
179             if ($this->needTable('tea')) {
180                 $this->debug('Retrieving data for: Tea');
181                 $names = @file('http://www.midnight-labs.org/tea.txt');
182                 if ($names) {
183                     $this->debug('Parsing data for: Tea');
184                     foreach($names as $key => $value) {
185                         $value = $this->decodeTranslit($value);
186                         if ($this->hasBadChars($value)) {
187                             continue;
188                         }
189                         $names[$key] = ucwords(trim($value));
190                     }
191                     $this->populateTable('tea', $names);
192                     unset($names);
193                 }
194             }
195         } catch (PDOException $e) { }
196
197         unset($this->filter);
198     }
199
200     /**
201      * Returns whether or not the plugin's dependencies are met.
202      *
203      * @param Phergie_Driver_Abstract $client Client instance
204      * @param array $plugins List of short names for plugins that the
205      *                       bootstrap file intends to instantiate
206      * @see Phergie_Plugin_Abstract_Base::checkDependencies()
207      * @return bool TRUE if dependencies are met, FALSE otherwise
208      */
209     public static function checkDependencies(Phergie_Driver_Abstract $client, array $plugins)
210     {
211         $errors = array();
212
213         if (!extension_loaded('PDO')) {
214             $errors[] = 'PDO php extension is required';
215         }
216         if (!extension_loaded('pdo_sqlite')) {
217             $errors[] = 'pdo_sqlite php extension is required';
218         }
219
220         return empty($errors) ? true : $errors;
221     }
222
223     /**
224      * Determines if a table does not exist or is empty.
225      *
226      * @param string $name Table name
227      * @return bool TRUE if the table does not exist or is empty, FALSE
228      *              otherwise
229      */
230     private function needTable($name)
231     {
232         $table = $this->db->query('SELECT COUNT(*) FROM sqlite_master WHERE name = ' . $this->db->quote($name))->fetchColumn();
233         if (!$table) {
234             return true;
235         }
236
237         return !$this->db->query('SELECT COUNT(*) FROM ' . $name)->fetchColumn();
238     }
239
240     /**
241      * Populates a source database table with a given set of data.
242      *
243      * @param string $table Name of the table
244      * @param array $names List of drink names
245      * @return void
246      */
247     private function populateTable($table, $names)
248     {
249         $this->debug('Creating the database schema for: ' . ucfirst($table));
250         $this->db->exec('CREATE TABLE ' . $table . ' (name VARCHAR(255))');
251         $this->db->exec('CREATE UNIQUE INDEX ' . $table . '_name ON ' . $table . ' (name)');
252
253         $stmt = $this->db->prepare('INSERT INTO ' . $table . ' (name) VALUES (:name)');
254         $this->db->beginTransaction();
255         foreach(array_unique($names) as $name) {
256             if (preg_match('/(?:^|[^a-z])(' . implode('|', $this->filter) . ')(?:[^a-z]|$)/i', $name, $match)) {
257                 $this->debug('Filtered out ' . $name . ' because it contains ' . $match[1]);
258                 continue;
259             }
260             $this->debug('Inserted ' . ucfirst($table) . ': ' . $name);
261             $stmt->execute(array('name' => $name));
262         }
263         $this->db->commit();
264     }
265
266     /**
267      * Returns a random record value from a given table.
268      *
269      * @string $table Name of the table
270      * @return string Value of the name column for the selected record
271      */
272     private function getRandomRecord($table)
273     {
274         return $this->db->query('SELECT name FROM ' . $table . ' ORDER BY RANDOM() LIMIT 1')->fetchColumn();
275     }
276
277     /**
278      * Returns a random record value from a given search pattern.
279      *
280      * @string $table Name of the table
281      * @return string Value of the name column for the selected record
282      */
283     private function getSearchRecord($table, $search)
284     {
285         $search = $this->db->quote('%' . str_replace(array('\\', '%'), array('\\\\', '\\%'), $search) .'%');
286         return $this->db->query('SELECT name FROM ' . $table . ' WHERE name LIKE ' . $search . ' ESCAPE "\\" ORDER BY RANDOM() LIMIT 1')->fetchColumn();
287     }
288
289     /**
290      * Returns whether or not a given value has characters that may not be
291      * displayed correctly.
292      *
293      * @param string $name Value to check
294      */
295     private function hasBadChars($name)
296     {
297         return (max(array_map('ord', str_split($name))) > 126);
298     }
299
300     /**
301      * Resolves a target to the appropriate nick or pronoun and returns the
302      * result.
303      *
304      * @param string $target Original specified target
305      * @return string Resolved target
306      */
307     protected function resolveTarget($target)
308     {
309         $target = rtrim(trim($target), '.?!');
310
311         switch (trim(strtolower($target))) {
312             case 'me':
313                 $target = $this->event->getNick();
314             break;
315
316             case 'you':
317             case 'your self':
318             case 'yourself':
319             case strtolower($this->getIni('nick')):
320                 $gender = $this->getIni('gender');
321                 if (!$gender || $gender == 'F') {
322                     $target = 'herself';
323                 } else {
324                     $target = 'himself';
325                 }
326             break;
327         }
328
329         return $target;
330     }
331
332     /**
333      * Responds to a message requesting that the bot perform an action to
334      * serve the source with a random drink of a specific type.
335      *
336      * @param string $type Type of drink
337      * @param string $message Drink reuest message
338      * @return void
339      */
340     protected function handleDrink($type, $message)
341     {
342         if (!$this->db) {
343             return;
344         }
345
346         $message = preg_replace('/\s+/', ' ', trim($message));
347         preg_match('/^(.+?)(?:\s+an?(?:\s+(?:cuppa|cup\s+of))?\s+(.+?))?(\s+(?:for|because)\s+.+)?$/', $message, $m);
348         list(, $target, $drink, $action) = array_pad($m, 4, null);
349
350         $drink = trim($drink);
351         if ($type == 'tea' && strtolower(substr($drink, -3)) == 'tea') {
352             $drink = trim(substr($drink, 0, -3));
353         }
354
355         $action = trim($action);
356         $target = $this->resolveTarget($target);
357
358         if (!Phergie_Plugin_ServerInfo::isIn($target, $this->event->getSource())) {
359             return;
360         }
361
362         if (!empty($drink)) {
363             $drink = implode(' ', array_map('ucfirst', explode(' ', $drink)));
364             if ($search = $this->getSearchRecord($type, $drink)) {
365                 $drink = $search;
366             }
367         } else {
368             $drink = $this->getRandomRecord($type);
369         }
370
371         if ($drink) {
372             if ($type != 'tea') {
373                 $text = 'throws ' . $target . ' a';
374                 if (preg_match('/^[aeoiu]/i', $drink)) {
375                     $text .= 'n';
376                 }
377                 $text .= ' ' . $drink . ($action ? ' ' . $action : '');
378             } else {
379                 // One must be gentle with tea
380                 $text = 'pours ' . $target . ' a cup of ' . $drink . ' tea'  . ($action ? ' ' . $action : '');
381             }
382             $this->doAction($this->event->getSource(), $text . '.');
383         }
384     }
385
386     /**
387      * Handles beer requests.
388      *
389      * @param string $target Target for the request
390      * @return void
391      */
392     public function onDoBeer($message)
393     {
394         $this->handleDrink('beer', $message);
395     }
396
397     /**
398      * Alias to Beer
399      *
400      * @param string $target Target for the request
401      * @return void
402      */
403     public function onDoBooze($message)
404     {
405         $this->handleDrink('beer', $message);
406     }
407
408     /**
409      * Handles cocktail requests.
410      *
411      * @param string $target Target for the request
412      * @return void
413      */
414     public function onDoCocktail($message)
415     {
416         $this->handleDrink('cocktail', $message);
417     }
418
419     /**
420      * Handles coke requests.
421      *
422      * @param string $target Source of the request
423      * @return void
424      */
425     public function onDoCoke($message)
426     {
427         $this->handleDrink('coke', $message);
428     }
429
430     /**
431      * Provides a soda alias for coke requests.
432      *
433      * @param string $target Target for the request
434      * @return void
435      */
436     public function onDoSoda($message)
437     {
438         $this->handleDrink('coke', $message);
439     }
440
441     /**
442      * Handles tea requests.
443      *
444      * @param string $target Target for the request
445      * @return void
446      */
447     public function onDoTea($message)
448     {
449         $this->handleDrink('tea', $message);
450     }
451
452     /**
453      * Handles pop requests.
454      *
455      * @param string $target Target for the request
456      * @return void
457      */
458     public function onDoPop($target)
459     {
460         $target = $this->resolveTarget($target);
461
462         $this->doAction($this->event->getSource(), 'lays ' . $target . ' out flat.');
463     }
464 }
465
Note: See TracBrowser for help on using the browser.