9 * This source file is subject to the new BSD license that is bundled
10 * with this package in the file LICENSE.
11 * It is also available through the world-wide-web at this URL:
12 * http://phergie.org/license
15 * @package Phergie_Plugin_Karma
16 * @author Phergie Development Team <team@phergie.org>
17 * @copyright 2008-2010 Phergie Development Team (http://phergie.org)
18 * @license http://phergie.org/license New BSD License
19 * @link http://pear.phergie.org/package/Phergie_Plugin_Karma
23 * Handles requests for incrementation or decrementation of a maintained list
24 * of counters for specified terms and antithrottling to prevent extreme
25 * inflation or depression of counters by any single individual.
28 * @package Phergie_Plugin_Karma
29 * @author Phergie Development Team <team@phergie.org>
30 * @license http://phergie.org/license New BSD License
31 * @link http://pear.phergie.org/package/Phergie_Plugin_Karma
33 class Phergie_Plugin_Karma extends Phergie_Plugin_Abstract
36 * Stores the SQLite object
43 * Retains the last garbage collection date
47 protected $lastGc = null;
50 * Logs the karma usages and limits users to one karma change per word
55 protected $log = array();
58 * Some fixed karma values, keys must be lowercase
62 protected $fixedKarma;
65 * A list of blacklisted values
69 protected $karmaBlacklist;
72 * Answers for correct assertions
74 protected $positiveAnswers;
77 * Answers for incorrect assertions
79 protected $negativeAnswers;
82 * Prepared PDO statements
86 protected $insertKarma;
87 protected $updateKarma;
88 protected $fetchKarma;
89 protected $insertComment;
92 * Connects to the database containing karma ratings and initializes
97 public function onLoad()
100 $this->lastGc = null;
101 $this->log = array();
103 if(!defined('M_EULER')) {
104 define('M_EULER', '0.57721566490153286061');
107 $this->fixedKarma = array(
108 'phergie' => '%s has karma of awesome',
109 'pi' => '%s has karma of ' . M_PI,
110 'Î ' => '%s has karma of ' . M_PI,
111 'Ï€' => '%s has karma of ' . M_PI,
112 'chucknorris' => '%s has karma of Warning: Integer out of range',
113 'chuck norris' => '%s has karma of Warning: Integer out of range',
114 'c' => '%s has karma of 299 792 458 m/s',
115 'e' => '%s has karma of ' . M_E,
116 'euler' => '%s has karma of ' . M_EULER,
117 'mole' => '%s has karma of 6.02214e23 molecules',
118 'avogadro' => '%s has karma of 6.02214e23 molecules',
119 'spoon' => '%s has no karma. There is no spoon',
120 'mc^2' => '%s has karma of E',
121 'mc2' => '%s has karma of E',
122 'mc²' => '%s has karma of E',
123 'i' => '%s haz big karma',
124 '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.'
127 $this->karmaBlacklist = array(
133 $this->positiveAnswers = array(
134 'No kidding, %owner% totally kicks %owned%\'s ass !',
137 'Yay, %owner% ftw !',
138 '%owner% is made of WIN!',
139 'Nothing can beat %owner%!',
142 $this->negativeAnswers = array(
143 'No sir, not at all.',
144 'You\'re wrong dude, %owner% wins.',
145 'I\'d say %owner% is better than %owned%.',
146 'You must be joking, %owner% ftw!',
147 '%owned% is made of LOSE!',
148 '%owned% = Epic Fail',
151 // Load or initialize the database
152 $class = new ReflectionClass(get_class($this));
153 $dir = dirname($class->getFileName() . '/' . $this->name);
154 $this->db = new PDO('sqlite:' . $dir . 'karma.db');
156 // Check to see if the table exists
157 $table = $this->db->query('
160 WHERE name = ' . $this->db->quote('karmas')
163 // Create database tables if necessary
166 CREATE TABLE karmas ( word VARCHAR ( 255 ), karma MEDIUMINT );
167 CREATE UNIQUE INDEX word ON karmas ( word );
168 CREATE INDEX karmaIndex ON karmas ( karma );
169 CREATE TABLE comments ( wordid INT , comment VARCHAR ( 255 ) );
170 CREATE INDEX wordidIndex ON comments ( wordid );
171 CREATE UNIQUE INDEX commentUnique ON comments ( comment );
175 $this->insertKarma = $this->db->prepare('
186 $this->insertComment = $this->db->prepare('
187 INSERT INTO comments (
197 $this->fetchKarma = $this->db->prepare('
198 SELECT karma, ROWID id FROM karmas WHERE LOWER(word) = LOWER(:word) LIMIT 1
201 $this->updateKarma = $this->db->prepare('
202 UPDATE karmas SET karma = :karma WHERE LOWER(word) = LOWER(:word)
207 * Checks for dependencies.
211 public static function onLoad()
213 if (!extension_loaded('PDO') || !extension_loaded('pdo_sqlite')) {
214 $this->fail('PDO and pdo_sqlite extensions must be installed');
219 * Handles requests for incrementation, decrementation, or lookup of karma
220 * ratings sent via messages from users.
224 public function onPrivmsg()
226 $source = $this->event->getSource();
227 $message = $this->event->getArgument(1);
228 $target = $this->event->getNick();
230 // Command prefix check
231 $prefix = preg_quote(trim($this->getConfig('command.prefix')));
232 $bot = preg_quote($this->getConfig('connections.nick'));
233 $exp = '(?:(?:' . $bot . '\s*[:,>]?\s+(?:' . $prefix . ')?)|(?:' . $prefix . '))';
235 // Karma status request
236 if (preg_match('#^' . $exp . 'karma\s+(.+)$#i', $message, $m)) {
237 // Return user's value if "me" is requested
238 if (strtolower($m[1]) === 'me') {
242 $term = $this->doCleanWord($m[1]);
244 // Check the blacklist
245 if (is_array($this->karmaBlacklist) && in_array($term, $this->karmaBlacklist)) {
246 $this->doNotice($target, $term . ' is blacklisted');
250 // Return fixed value if set
251 if (isset($this->fixedKarma[$term])) {
252 $this->doPrivmsg($source, $target . ': ' . sprintf($this->fixedKarma[$term], $m[1]) . '.');
256 // Return current karma or neutral if not set yet
257 $this->fetchKarma->execute(array(':word'=>$term));
258 $res = $this->fetchKarma->fetch(PDO::FETCH_ASSOC);
260 // Sanity check if someone if someone prefixed their conversation with karma
261 if (!$res && substr_count($term, ' ') > 1 && !(substr($m[1], 0, 1) === '(' && substr($m[1], -1) === ')')) {
265 // Clean the raw term if it was contained within brackets
266 if (substr($m[1], 0, 1) === '(' && substr($m[1], -1) === ')') {
267 $m[1] = substr($m[1], 1, -1);
270 if ($res && $res['karma'] != 0) {
271 $this->doPrivmsg($source, $target . ': ' . $m[1] . ' has karma of ' . $res['karma'] . '.');
273 $this->doPrivmsg($source, $target . ': ' . $m[1] . ' has neutral karma.');
275 // Incrementation/decrementation request
276 } elseif (preg_match('{^' . $exp . '?(?:(\+{2,2}|-{2,2})(\S+?|\(.+?\)+)|(\S+?|\(.+?\)+)(\+{2,2}|-{2,2}))(?:\s+(.*))?$}ix', $message, $m)) {
278 $m[1] = $m[4]; // Increment/Decrement
279 $m[2] = $m[3]; // Word
281 $m[3] = (isset($m[5]) ? $m[5] : null); // Comment
283 list(, $sign, $word, $comment) = array_pad($m, 4, null);
286 $word = strtolower($this->doCleanWord($word));
291 // Do nothing if the karma is fixed or blacklisted
292 if (isset($this->fixedKarma[$word]) ||
293 is_array($this->karmaBlacklist) && in_array($word, $this->karmaBlacklist)) {
297 // Force a decrementation if someone tries to update his own karma
298 if ($word == strtolower($target) && $sign != '--' && !$this->fromAdmin(true)) {
299 $this->doNotice($target, 'Bad ' . $target . '! You can not modify your own Karma. Shame on you!');
303 // Antithrottling check
304 $host = $this->event->getHost();
305 $limit = $this->getConfig('karma.limit');
306 // This is waiting on the Acl plugin from Elazar, being bypassed for now
307 //if ($limit > 0 && !$this->fromAdmin()) {
309 if (isset($this->log[$host][$word]) && $this->log[$host][$word] >= $limit) {
310 // Three strikes, you're out, so lets decrement their karma for spammage
311 if ($this->log[$host][$word] == ($limit+3)) {
312 $this->doNotice($target, 'Bad ' . $target . '! Didn\'t I tell you that you reached your limit already?');
313 $this->log[$host][$word] = $limit;
316 // Toss a notice to the user if they reached their limit
318 $this->doNotice($target, 'You have currently reached your limit in modifying ' . $word . ' for this day, please wait a bit.');
319 $this->log[$host][$word]++;
323 if (isset($this->log[$host][$word])) {
324 $this->log[$host][$word]++;
326 $this->log[$host][$word] = 1;
331 // Get the current value then update or create entry
332 $this->fetchKarma->execute(array(':word'=>$word));
333 $res = $this->fetchKarma->fetch(PDO::FETCH_ASSOC);
335 $karma = ($res['karma'] + ($sign == '++' ? 1 : -1));
340 $this->updateKarma->execute($args);
342 $karma = ($sign == '++' ? '1' : '-1');
347 $this->insertKarma->execute($args);
348 $this->fetchKarma->execute(array(':word'=>$word));
349 $res = $this->fetchKarma->fetch(PDO::FETCH_ASSOC);
353 $comment = preg_replace('{(?:^//(.*)|^#(.*)|^/\*(.*?)\*/$)}', '$1$2$3', $comment);
354 if (!empty($comment)) {
355 $this->insertComment->execute(array(':wordid' => $id, ':comment' => $comment));
357 // Perform garbage collection on the antithrottling log if needed
358 if (date('d') !== $this->lastGc) {
362 } elseif (preg_match('#^' . $exp . '?([^><]+)(<|>)([^><]+)$#', $message, $m)) {
364 $word1 = strtolower($this->doCleanWord($m[1]));
365 $word2 = strtolower($this->doCleanWord($m[3]));
368 // Do nothing if the karma is fixed
369 if (isset($this->fixedKarma[$word1]) || isset($this->fixedKarma[$word2]) ||
370 empty($word1) || empty($word2)) {
375 if ($word1 === '*' || $word1 === 'all' || $word1 === 'everything') {
376 $res = array('karma' => 0);
377 $word1 = 'everything';
379 $this->fetchKarma->execute(array(':word'=>$word1));
380 $res = $this->fetchKarma->fetch(PDO::FETCH_ASSOC);
382 // If it exists, fetch second word
384 if ($word2 === '*' || $word2 === 'all' || $word2 === 'everything') {
385 $res2 = array('karma' => 0);
386 $word2 = 'everything';
388 $this->fetchKarma->execute(array(':word'=>$word2));
389 $res2 = $this->fetchKarma->fetch(PDO::FETCH_ASSOC);
391 // If it exists, compare and return value
392 if ($res2 && $res['karma'] != $res2['karma']) {
393 $assertion = ($operator === '<' && $res['karma'] < $res2['karma']) || ($operator === '>' && $res['karma'] > $res2['karma']);
394 // Switch arguments if they are in the wrong order
395 if ($operator === '<') {
400 $this->doPrivmsg($source, $assertion ? $this->fetchPositiveAnswer($word1, $word2) : $this->fetchNegativeAnswer($word1, $word2));
401 // If someone asserts that something is greater or lesser than everything, we increment/decrement that something at the same time
402 if ($word2 === 'everything') {
403 $this->event = clone$this->event;
404 $this->event->setArguments(array($this->event->getArgument(0), '++'.$word1));
406 } elseif ($word1 === 'everything') {
407 $this->event = clone$this->event;
408 $this->event->setArguments(array($this->event->getArgument(0), '--'.$word2));
416 protected function fetchPositiveAnswer($owner, $owned)
418 return str_replace(array('%owner%','%owned%'), array($owner, $owned), $this->positiveAnswers[array_rand($this->positiveAnswers,1)]);
421 protected function fetchNegativeAnswer($owned, $owner)
423 return str_replace(array('%owner%','%owned%'), array($owner, $owned), $this->negativeAnswers[array_rand($this->negativeAnswers,1)]);
426 protected function doCleanWord($word)
429 if (substr($word, 0, 1) === '(' && substr($word, -1) === ')') {
430 $word = trim(substr($word, 1, -1));
432 $word = preg_replace('#\s+#', ' ', strtolower(trim($word)));
437 * Performs garbage collection on the antithrottling log.
441 public function doGc()
444 $this->log = array();
445 $this->lastGc = date('d');