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

Revision 161, 13.1 KB (checked in by Slynderdale, 5 years ago)

Some tiny fixes and additions. Fixed a sorting bug in ModuleList, added support for %nick% in URL's format and added Phergie to karma's fixed karma list.

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