root/trunk/Phergie/Plugin/Karma.php @ 136

Revision 136, 13.0 KB (checked in by Slynderdale, 5 years ago)
Line 
1<?php
2
3/**
4* @see Phergie_Plugin_Abstract_Base
5*/
6require_once 'Phergie/Plugin/Abstract/Base.php';
7
8/**
9* Handles requests for incrementation or decrementation of a maintained list
10* of counters for specified terms and antithrottling to prevent extreme
11* inflation or depression of counters by any single individual.
12*/
13class Phergie_Plugin_Karma extends Phergie_Plugin_Abstract_Base
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    * Stores the SQLite object
24    *
25    * @var resource
26    */
27    protected $db = null;
28
29    /**
30    * Retains the last garbage collection date
31    *
32    * @var array
33    */
34    protected $lastGc = null;
35
36    /**
37    * Logs the karma usages and limits users to one karma change per word
38    * and per day
39    *
40    * @return void
41    */
42    protected $log = array();
43
44    /**
45    * Some fixed karma values, keys must be lowercase
46    *
47    * @var array
48    */
49    protected $fixedKarma;
50
51    /**
52     * Answers for correct assertions
53     */
54    protected $positiveAnswers;
55
56    /**
57     * Answers for incorrect assertions
58     */
59    protected $negativeAnswers;
60
61    /**#@+
62     * Prepared PDO statements
63     *
64     * @var PDOStatement
65     */
66    protected $insertKarma;
67    protected $updateKarma;
68    protected $fetchKarma;
69    protected $insertComment;
70    /**#@-*/
71
72    /**
73    * Connects to the database containing karma ratings and initializes
74    * class properties.
75    *
76    * @return void
77    */
78    public function init()
79    {
80        $this->db = null;
81        $this->lastGc = null;
82        $this->log = null;
83
84        $this->fixedKarma = array
85        (
86            'pi' => 'pi has karma of ' . M_PI,
87            'chucknorris' => '%s has karma of Warning: Integer out of range',
88            'chuck norris' => '%s has karma of Warning: Integer out of range',
89            'c' => '%s has karma of 299 792 458 m/s',
90            'e' => '%s has karma of ' . M_E,
91            'euler' => '%s has karma of ' . M_EULER,
92            'mole' => '%s has karma of 6.02214e23 molecules',
93            'avogadro' => '%s has karma of 6.02214e23 molecules',
94            'spoon' => '%s has no karma. There is no spoon',
95            'mc^2' => '%s has karma of e',
96            'mc2' => '%s has karma of e',
97            'mc²' => '%s has karma of e',
98            'i' => 'I haz big karma',
99            'karma' => 'The karma law says that all living creatures are responsible for their karma - their actions and the effects of their actions. You should watch yours.',
100            'answer to life, the universe, and everything' => 'The answer is 42',
101            'the answer to life, the universe, and everything' => 'The answer is 42',
102        );
103
104        $this->positiveAnswers = array
105        (
106            'No kidding, %owner% totally kicks %owned%\'s ass !',
107            'True that.',
108            'I concur.',
109            'Yay, %owner% ftw !',
110            '%owner% is made of WIN!',
111            'Nothing can beat %owner%!',
112        );
113
114        $this->negativeAnswers = array
115        (
116            'No sir, not at all.',
117            'You\'re wrong dude, %owner% wins.',
118            'I\'d say %owner% is better than %owned%.',
119            'You must be joking, %owner% ftw!',
120            '%owned% is made of LOSE!',
121            '%owned% = Epic Fail',
122        );
123
124        $static = $this->getPluginIni('static');
125        if ($static) {
126            $this->fixedKarma[strtolower($this->getIni('nick'))] = $static;
127        }
128
129        // Load or initialize the database
130        $db = $this->dir . 'karma.db';
131        if (!file_exists($db)) {
132            $this->db = new PDO('sqlite:' . $db);
133            $this->db->query('
134                CREATE TABLE karmas ( word VARCHAR ( 255 ) , karma MEDIUMINT ) ;
135                CREATE UNIQUE INDEX word ON karmas ( word ) ;
136                CREATE INDEX karmaIndex ON karmas ( karma ) ;
137            ');
138            $this->db->query('
139                CREATE TABLE comments ( wordid INT , comment VARCHAR ( 255 ) ) ;
140                CREATE INDEX wordidIndex ON comments ( wordid ) ;
141                CREATE UNIQUE INDEX commentUnique ON comments ( comment ) ;
142            ');
143        } else {
144            $this->db = new PDO('sqlite:' . $db);
145        }
146
147        $this->insertKarma = $this->db->prepare('
148            INSERT INTO karmas (
149                word,
150                karma
151            )
152            VALUES (
153                :word,
154                :karma
155            )
156        ');
157
158        $this->insertComment = $this->db->prepare('
159            INSERT INTO comments (
160                wordid,
161                comment
162            )
163            VALUES (
164                :wordid,
165                :comment
166            )
167        ');
168
169        $this->fetchKarma = $this->db->prepare('
170            SELECT karma, ROWID id FROM karmas WHERE word = :word LIMIT 1
171        ');
172
173        $this->updateKarma = $this->db->prepare('
174            UPDATE karmas SET karma = :karma WHERE word = :word
175        ');
176    }
177
178    /**
179    * Returns whether or not the plugin's dependencies are met.
180    *
181    * @param Phergie_Driver_Abstract $client Client instance
182    * @param array $plugins List of short names for plugins that the
183    *                       bootstrap file intends to instantiate
184    * @see Phergie_Plugin_Abstract_Base::checkDependencies()
185    * @return bool TRUE if dependencies are met, FALSE otherwise
186    */
187    public static function checkDependencies(Phergie_Driver_Abstract $client, array $plugins)
188    {
189        if (!extension_loaded('PDO')
190            || !extension_loaded('pdo_sqlite')) {
191            return false;
192        }
193
194        return true;
195    }
196
197    /**
198    * Handles requests for incrementation, decrementation, or lookup of karma
199    * ratings sent via messages from users.
200    *
201    * @return void
202    */
203    public function onPrivmsg()
204    {
205        if ($this->db === null) {
206            return;
207        }
208        $source = $this->event->getSource();
209        $message = $this->event->getArgument(1);
210
211        // Command prefix check
212        $commandPrefix = trim($this->getIni('command_prefix'));
213        $prefix = preg_quote($commandPrefix ? $commandPrefix : '');
214
215        // Karma status request
216        if (preg_match('#^'.$prefix.'karma\s+(.+)$#i', $message, $m)) {
217            // Return user's value if "me" is requested
218            if (strtolower($m[1]) === 'me') {
219                $m[1] = $this->event->getNick();
220            }
221              // Clean the term
222            $term = $this->doCleanWord($m[1]);
223            // Return fixed value if set
224            if (isset ($this->fixedKarma[$term])) {
225                $this->doPrivmsg($source, sprintf($this->fixedKarma[$term], $m[1]).'.');
226                return;
227            }
228
229            // Return current karma or neutral if not set yet
230            $this->fetchKarma->execute(array(':word'=>$term));
231            $res = $this->fetchKarma->fetch(PDO::FETCH_ASSOC);
232
233            // Sanity check if someone if someone prefixed their conversation with karma
234            if (!$res && substr_count($term, ' ') > 1 && !(substr($m[1], 0, 1) === '(' && substr($m[1], -1) === ')'))
235                return;
236
237            // Clean the raw term if it was contained within brackets
238            if(substr($m[1], 0, 1) === '(' && substr($m[1], -1) === ')')
239                $m[1] = substr($m[1], 1, -1);
240
241            if ($res && $res['karma'] != 0) {
242                $this->doPrivmsg($source, $m[1].' has karma of '.$res['karma'].'.');
243            } else {
244                $this->doPrivmsg($source, $m[1].' has neutral karma.');
245            }
246        // Incrementation/decrementation request
247        } elseif (preg_match('{^(?:'.$prefix.')?(\S+?|\(.+?\)+)(\+{2,}|-{2,})(?:\s+(.*))?$}i', $message, $m)) {
248            $word = strtolower($m[1]);
249            // Strip parenthesis grouping multiple words
250            if(substr($word, 0, 1) === '(' && substr($word, -1) === ')') {
251                $word = substr($word, 1, -1);
252            } else { // Add trailing + or -'s
253                if(strlen($m[2]) > 2) {
254                    $word .= substr($m[2], 2);
255                    $m[2] = substr($m[2], -2);
256                }
257            }
258            // Replaces multiple spaces by one
259            $word = preg_replace('#\s+#', ' ', $word);
260            // Do nothing if the karma is fixed
261            if (isset($this->fixedKarma[$word])) {
262                return;
263            }
264            // Force a decrementation if someone tries to update his own karma
265            if ($word == strtolower($this->event->getNick())) {
266                $m[2] = '--';
267            }
268            // Antithrottling check
269            $host = $this->event->getHost();
270            $limit = $this->getPluginIni('limit');
271            if (isset ($this->log[$host][$word])
272                && $limit
273                && $this->log[$host][$word] > $limit) {
274                return;
275            } else {
276                $this->log[$host][$word] = 0;
277            }
278            $this->log[$host][$word]++;
279            // Get the current value then update or create entry
280            $this->fetchKarma->execute(array(':word'=>$word));
281
282            $res = $this->fetchKarma->fetch(PDO::FETCH_ASSOC);
283            if ($res) {
284                $args = array(
285                    ':karma' => ($res['karma'] + ($m[2]=='++' ? 1 : -1)),
286                    ':word' => $word
287                );
288                $this->updateKarma->execute($args);
289            } else {
290                $args = array(
291                    ':word' => $word,
292                    ':karma' => ($m[2]=='++' ? '1' : '-1')
293                );
294                $this->insertKarma->execute($args);
295                $this->fetchKarma->execute(array(':word'=>$word));
296                $res = $this->fetchKarma->fetch(PDO::FETCH_ASSOC);
297            }
298            $id = $res['id'];
299
300            // Add comment
301            $comment = preg_replace('{(?:^//(.*)|^#(.*)|^/\*(.*?)\*/$)}', '$1$2$3', $m[3]);
302            if (!empty($comment)) {
303                $this->insertComment->execute(array(':wordid' => $id, ':comment' => $comment));
304            }
305            // Perform garbage collection on the antithrottling log if needed
306            if (date('d') !== $this->lastGc) {
307                $this->doGc();
308            }
309        // Assertion request
310        } elseif (preg_match('#^([^><]+)(<|>)([^><]+)$#', $message, $m)) {
311            // Trim words
312            $word1 = strtolower(trim($m[1]));
313            $word2 = strtolower(trim($m[3]));
314            $operator = $m[2];
315
316            // Fetch first word
317            if ($word1 === '*' || $word1 === 'all' || $word1 === 'everything') {
318                   $res = array('karma' => 0);
319                   $word1 = 'everything';
320            } else {
321                $this->fetchKarma->execute(array(':word'=>$word1));
322                $res = $this->fetchKarma->fetch(PDO::FETCH_ASSOC);
323            }
324            // If it exists, fetch second word
325            if ($res) {
326                if ($word2 === '*' || $word2 === 'all' || $word2 === 'everything') {
327                    $res2 = array('karma' => 0);
328                    $word2 = 'everything';
329                } else {
330                    $this->fetchKarma->execute(array(':word'=>$word2));
331                    $res2 = $this->fetchKarma->fetch(PDO::FETCH_ASSOC);
332                }
333                // If it exists, compare and return value
334                if ($res2 && $res['karma'] != $res2['karma']) {
335                       $assertion = ($operator === '<' && $res['karma'] < $res2['karma']) || ($operator === '>' && $res['karma'] > $res2['karma']);
336                       // Switch arguments if they are in the wrong order
337                       if ($operator === '<') {
338                           $tmp = $word2; $word2 = $word1; $word1 = $tmp;
339                       }
340                       $this->doPrivmsg($source, $assertion ? $this->fetchPositiveAnswer($word1, $word2) : $this->fetchNegativeAnswer($word1, $word2) );
341                       // If someone asserts that something is greater or lesser than everything, we increment/decrement that something at the same time
342                       if ($word2 === 'everything') {
343                           $this->event = clone $this->event;
344                           $this->event->setArguments(array($this->event->getArgument(0), $word1.'++'));
345                           $this->onPrivmsg();
346                       } elseif ($word1 === 'everything') {
347                           $this->event = clone $this->event;
348                           $this->event->setArguments(array($this->event->getArgument(0), $word2.'--'));
349                           $this->onPrivmsg();
350                       }
351                }
352            }
353        }
354    }
355
356    protected function fetchPositiveAnswer($owner, $owned) {
357        return str_replace(array('%owner%','%owned%'), array($owner, $owned), $this->positiveAnswers[array_rand($this->positiveAnswers,1)]);
358    }
359
360    protected function fetchNegativeAnswer($owned, $owner) {
361        return str_replace(array('%owner%','%owned%'), array($owner, $owned), $this->negativeAnswers[array_rand($this->negativeAnswers,1)]);
362    }
363
364  protected function doCleanWord($word) {
365    if(substr($word, 0, 1) === '(' && substr($word, -1) === ')')
366       $word = substr($word, 1, -1);
367    $word = preg_replace('#\s+#', ' ', strtolower(trim($word)));
368    return $word;
369  }
370    /**
371     * Performs garbage collection on the antithrottling log.
372     *
373     * @return void
374     */
375    public function doGc()
376    {
377        unset ($this->log);
378        $this->log = array();
379        $this->lastGc = date('d');
380    }
381}
Note: See TracBrowser for help on using the browser.