448 lines
16 KiB
PHP
448 lines
16 KiB
PHP
|
<?php
|
||
|
/**
|
||
|
* Phergie
|
||
|
*
|
||
|
* PHP version 5
|
||
|
*
|
||
|
* LICENSE
|
||
|
*
|
||
|
* This source file is subject to the new BSD license that is bundled
|
||
|
* with this package in the file LICENSE.
|
||
|
* It is also available through the world-wide-web at this URL:
|
||
|
* http://phergie.org/license
|
||
|
*
|
||
|
* @category Phergie
|
||
|
* @package Phergie_Plugin_Karma
|
||
|
* @author Phergie Development Team <team@phergie.org>
|
||
|
* @copyright 2008-2010 Phergie Development Team (http://phergie.org)
|
||
|
* @license http://phergie.org/license New BSD License
|
||
|
* @link http://pear.phergie.org/package/Phergie_Plugin_Karma
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Handles requests for incrementation or decrementation of a maintained list
|
||
|
* of counters for specified terms and antithrottling to prevent extreme
|
||
|
* inflation or depression of counters by any single individual.
|
||
|
*
|
||
|
* @category Phergie
|
||
|
* @package Phergie_Plugin_Karma
|
||
|
* @author Phergie Development Team <team@phergie.org>
|
||
|
* @license http://phergie.org/license New BSD License
|
||
|
* @link http://pear.phergie.org/package/Phergie_Plugin_Karma
|
||
|
*/
|
||
|
class Phergie_Plugin_Karma extends Phergie_Plugin_Abstract
|
||
|
{
|
||
|
/**
|
||
|
* Stores the SQLite object
|
||
|
*
|
||
|
* @var resource
|
||
|
*/
|
||
|
protected $db = null;
|
||
|
|
||
|
/**
|
||
|
* Retains the last garbage collection date
|
||
|
*
|
||
|
* @var array
|
||
|
*/
|
||
|
protected $lastGc = null;
|
||
|
|
||
|
/**
|
||
|
* Logs the karma usages and limits users to one karma change per word
|
||
|
* and per day
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
protected $log = array();
|
||
|
|
||
|
/**
|
||
|
* Some fixed karma values, keys must be lowercase
|
||
|
*
|
||
|
* @var array
|
||
|
*/
|
||
|
protected $fixedKarma;
|
||
|
|
||
|
/**
|
||
|
* A list of blacklisted values
|
||
|
*
|
||
|
* @var array
|
||
|
*/
|
||
|
protected $karmaBlacklist;
|
||
|
|
||
|
/**
|
||
|
* Answers for correct assertions
|
||
|
*/
|
||
|
protected $positiveAnswers;
|
||
|
|
||
|
/**
|
||
|
* Answers for incorrect assertions
|
||
|
*/
|
||
|
protected $negativeAnswers;
|
||
|
|
||
|
/**
|
||
|
* Prepared PDO statements
|
||
|
*
|
||
|
* @var PDOStatement
|
||
|
*/
|
||
|
protected $insertKarma;
|
||
|
protected $updateKarma;
|
||
|
protected $fetchKarma;
|
||
|
protected $insertComment;
|
||
|
|
||
|
/**
|
||
|
* Connects to the database containing karma ratings and initializes
|
||
|
* class properties.
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
public function onLoad()
|
||
|
{
|
||
|
$this->db = null;
|
||
|
$this->lastGc = null;
|
||
|
$this->log = array();
|
||
|
|
||
|
if(!defined('M_EULER')) {
|
||
|
define('M_EULER', '0.57721566490153286061');
|
||
|
}
|
||
|
|
||
|
$this->fixedKarma = array(
|
||
|
'phergie' => '%s has karma of awesome',
|
||
|
'pi' => '%s has karma of ' . M_PI,
|
||
|
'Î ' => '%s has karma of ' . M_PI,
|
||
|
'Ï€' => '%s has karma of ' . M_PI,
|
||
|
'chucknorris' => '%s has karma of Warning: Integer out of range',
|
||
|
'chuck norris' => '%s has karma of Warning: Integer out of range',
|
||
|
'c' => '%s has karma of 299 792 458 m/s',
|
||
|
'e' => '%s has karma of ' . M_E,
|
||
|
'euler' => '%s has karma of ' . M_EULER,
|
||
|
'mole' => '%s has karma of 6.02214e23 molecules',
|
||
|
'avogadro' => '%s has karma of 6.02214e23 molecules',
|
||
|
'spoon' => '%s has no karma. There is no spoon',
|
||
|
'mc^2' => '%s has karma of E',
|
||
|
'mc2' => '%s has karma of E',
|
||
|
'mc²' => '%s has karma of E',
|
||
|
'i' => '%s haz big karma',
|
||
|
'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.'
|
||
|
);
|
||
|
|
||
|
$this->karmaBlacklist = array(
|
||
|
'*',
|
||
|
'all',
|
||
|
'everything'
|
||
|
);
|
||
|
|
||
|
$this->positiveAnswers = array(
|
||
|
'No kidding, %owner% totally kicks %owned%\'s ass !',
|
||
|
'True that.',
|
||
|
'I concur.',
|
||
|
'Yay, %owner% ftw !',
|
||
|
'%owner% is made of WIN!',
|
||
|
'Nothing can beat %owner%!',
|
||
|
);
|
||
|
|
||
|
$this->negativeAnswers = array(
|
||
|
'No sir, not at all.',
|
||
|
'You\'re wrong dude, %owner% wins.',
|
||
|
'I\'d say %owner% is better than %owned%.',
|
||
|
'You must be joking, %owner% ftw!',
|
||
|
'%owned% is made of LOSE!',
|
||
|
'%owned% = Epic Fail',
|
||
|
);
|
||
|
|
||
|
// Load or initialize the database
|
||
|
$class = new ReflectionClass(get_class($this));
|
||
|
$dir = dirname($class->getFileName() . '/' . $this->name);
|
||
|
$this->db = new PDO('sqlite:' . $dir . 'karma.db');
|
||
|
|
||
|
// Check to see if the table exists
|
||
|
$table = $this->db->query('
|
||
|
SELECT COUNT(*)
|
||
|
FROM sqlite_master
|
||
|
WHERE name = ' . $this->db->quote('karmas')
|
||
|
)->fetchColumn();
|
||
|
|
||
|
// Create database tables if necessary
|
||
|
if (!$table) {
|
||
|
$this->db->query('
|
||
|
CREATE TABLE karmas ( word VARCHAR ( 255 ), karma MEDIUMINT );
|
||
|
CREATE UNIQUE INDEX word ON karmas ( word );
|
||
|
CREATE INDEX karmaIndex ON karmas ( karma );
|
||
|
CREATE TABLE comments ( wordid INT , comment VARCHAR ( 255 ) );
|
||
|
CREATE INDEX wordidIndex ON comments ( wordid );
|
||
|
CREATE UNIQUE INDEX commentUnique ON comments ( comment );
|
||
|
');
|
||
|
}
|
||
|
|
||
|
$this->insertKarma = $this->db->prepare('
|
||
|
INSERT INTO karmas (
|
||
|
word,
|
||
|
karma
|
||
|
)
|
||
|
VALUES (
|
||
|
:word,
|
||
|
:karma
|
||
|
)
|
||
|
');
|
||
|
|
||
|
$this->insertComment = $this->db->prepare('
|
||
|
INSERT INTO comments (
|
||
|
wordid,
|
||
|
comment
|
||
|
)
|
||
|
VALUES (
|
||
|
:wordid,
|
||
|
:comment
|
||
|
)
|
||
|
');
|
||
|
|
||
|
$this->fetchKarma = $this->db->prepare('
|
||
|
SELECT karma, ROWID id FROM karmas WHERE LOWER(word) = LOWER(:word) LIMIT 1
|
||
|
');
|
||
|
|
||
|
$this->updateKarma = $this->db->prepare('
|
||
|
UPDATE karmas SET karma = :karma WHERE LOWER(word) = LOWER(:word)
|
||
|
');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Checks for dependencies.
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
public static function onLoad()
|
||
|
{
|
||
|
if (!extension_loaded('PDO') || !extension_loaded('pdo_sqlite')) {
|
||
|
$this->fail('PDO and pdo_sqlite extensions must be installed');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Handles requests for incrementation, decrementation, or lookup of karma
|
||
|
* ratings sent via messages from users.
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
public function onPrivmsg()
|
||
|
{
|
||
|
$source = $this->event->getSource();
|
||
|
$message = $this->event->getArgument(1);
|
||
|
$target = $this->event->getNick();
|
||
|
|
||
|
// Command prefix check
|
||
|
$prefix = preg_quote(trim($this->getConfig('command.prefix')));
|
||
|
$bot = preg_quote($this->getConfig('connections.nick'));
|
||
|
$exp = '(?:(?:' . $bot . '\s*[:,>]?\s+(?:' . $prefix . ')?)|(?:' . $prefix . '))';
|
||
|
|
||
|
// Karma status request
|
||
|
if (preg_match('#^' . $exp . 'karma\s+(.+)$#i', $message, $m)) {
|
||
|
// Return user's value if "me" is requested
|
||
|
if (strtolower($m[1]) === 'me') {
|
||
|
$m[1] = $target;
|
||
|
}
|
||
|
// Clean the term
|
||
|
$term = $this->doCleanWord($m[1]);
|
||
|
|
||
|
// Check the blacklist
|
||
|
if (is_array($this->karmaBlacklist) && in_array($term, $this->karmaBlacklist)) {
|
||
|
$this->doNotice($target, $term . ' is blacklisted');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Return fixed value if set
|
||
|
if (isset($this->fixedKarma[$term])) {
|
||
|
$this->doPrivmsg($source, $target . ': ' . sprintf($this->fixedKarma[$term], $m[1]) . '.');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Return current karma or neutral if not set yet
|
||
|
$this->fetchKarma->execute(array(':word'=>$term));
|
||
|
$res = $this->fetchKarma->fetch(PDO::FETCH_ASSOC);
|
||
|
|
||
|
// Sanity check if someone if someone prefixed their conversation with karma
|
||
|
if (!$res && substr_count($term, ' ') > 1 && !(substr($m[1], 0, 1) === '(' && substr($m[1], -1) === ')')) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Clean the raw term if it was contained within brackets
|
||
|
if (substr($m[1], 0, 1) === '(' && substr($m[1], -1) === ')') {
|
||
|
$m[1] = substr($m[1], 1, -1);
|
||
|
}
|
||
|
|
||
|
if ($res && $res['karma'] != 0) {
|
||
|
$this->doPrivmsg($source, $target . ': ' . $m[1] . ' has karma of ' . $res['karma'] . '.');
|
||
|
} else {
|
||
|
$this->doPrivmsg($source, $target . ': ' . $m[1] . ' has neutral karma.');
|
||
|
}
|
||
|
// Incrementation/decrementation request
|
||
|
} elseif (preg_match('{^' . $exp . '?(?:(\+{2,2}|-{2,2})(\S+?|\(.+?\)+)|(\S+?|\(.+?\)+)(\+{2,2}|-{2,2}))(?:\s+(.*))?$}ix', $message, $m)) {
|
||
|
if (!empty($m[4])) {
|
||
|
$m[1] = $m[4]; // Increment/Decrement
|
||
|
$m[2] = $m[3]; // Word
|
||
|
}
|
||
|
$m[3] = (isset($m[5]) ? $m[5] : null); // Comment
|
||
|
unset($m[4], $m[5]);
|
||
|
list(, $sign, $word, $comment) = array_pad($m, 4, null);
|
||
|
|
||
|
// Clean the word
|
||
|
$word = strtolower($this->doCleanWord($word));
|
||
|
if (empty($word)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Do nothing if the karma is fixed or blacklisted
|
||
|
if (isset($this->fixedKarma[$word]) ||
|
||
|
is_array($this->karmaBlacklist) && in_array($word, $this->karmaBlacklist)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Force a decrementation if someone tries to update his own karma
|
||
|
if ($word == strtolower($target) && $sign != '--' && !$this->fromAdmin(true)) {
|
||
|
$this->doNotice($target, 'Bad ' . $target . '! You can not modify your own Karma. Shame on you!');
|
||
|
$sign = '--';
|
||
|
}
|
||
|
|
||
|
// Antithrottling check
|
||
|
$host = $this->event->getHost();
|
||
|
$limit = $this->getConfig('karma.limit');
|
||
|
// This is waiting on the Acl plugin from Elazar, being bypassed for now
|
||
|
//if ($limit > 0 && !$this->fromAdmin()) {
|
||
|
if ($limit > 0) {
|
||
|
if (isset($this->log[$host][$word]) && $this->log[$host][$word] >= $limit) {
|
||
|
// Three strikes, you're out, so lets decrement their karma for spammage
|
||
|
if ($this->log[$host][$word] == ($limit+3)) {
|
||
|
$this->doNotice($target, 'Bad ' . $target . '! Didn\'t I tell you that you reached your limit already?');
|
||
|
$this->log[$host][$word] = $limit;
|
||
|
$word = $target;
|
||
|
$sign = '--';
|
||
|
// Toss a notice to the user if they reached their limit
|
||
|
} else {
|
||
|
$this->doNotice($target, 'You have currently reached your limit in modifying ' . $word . ' for this day, please wait a bit.');
|
||
|
$this->log[$host][$word]++;
|
||
|
return;
|
||
|
}
|
||
|
} else {
|
||
|
if (isset($this->log[$host][$word])) {
|
||
|
$this->log[$host][$word]++;
|
||
|
} else {
|
||
|
$this->log[$host][$word] = 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Get the current value then update or create entry
|
||
|
$this->fetchKarma->execute(array(':word'=>$word));
|
||
|
$res = $this->fetchKarma->fetch(PDO::FETCH_ASSOC);
|
||
|
if ($res) {
|
||
|
$karma = ($res['karma'] + ($sign == '++' ? 1 : -1));
|
||
|
$args = array(
|
||
|
':word' => $word,
|
||
|
':karma' => $karma
|
||
|
);
|
||
|
$this->updateKarma->execute($args);
|
||
|
} else {
|
||
|
$karma = ($sign == '++' ? '1' : '-1');
|
||
|
$args = array(
|
||
|
':word' => $word,
|
||
|
':karma' => $karma
|
||
|
);
|
||
|
$this->insertKarma->execute($args);
|
||
|
$this->fetchKarma->execute(array(':word'=>$word));
|
||
|
$res = $this->fetchKarma->fetch(PDO::FETCH_ASSOC);
|
||
|
}
|
||
|
$id = $res['id'];
|
||
|
// Add comment
|
||
|
$comment = preg_replace('{(?:^//(.*)|^#(.*)|^/\*(.*?)\*/$)}', '$1$2$3', $comment);
|
||
|
if (!empty($comment)) {
|
||
|
$this->insertComment->execute(array(':wordid' => $id, ':comment' => $comment));
|
||
|
}
|
||
|
// Perform garbage collection on the antithrottling log if needed
|
||
|
if (date('d') !== $this->lastGc) {
|
||
|
$this->doGc();
|
||
|
}
|
||
|
// Assertion request
|
||
|
} elseif (preg_match('#^' . $exp . '?([^><]+)(<|>)([^><]+)$#', $message, $m)) {
|
||
|
// Trim words
|
||
|
$word1 = strtolower($this->doCleanWord($m[1]));
|
||
|
$word2 = strtolower($this->doCleanWord($m[3]));
|
||
|
$operator = $m[2];
|
||
|
|
||
|
// Do nothing if the karma is fixed
|
||
|
if (isset($this->fixedKarma[$word1]) || isset($this->fixedKarma[$word2]) ||
|
||
|
empty($word1) || empty($word2)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Fetch first word
|
||
|
if ($word1 === '*' || $word1 === 'all' || $word1 === 'everything') {
|
||
|
$res = array('karma' => 0);
|
||
|
$word1 = 'everything';
|
||
|
} else {
|
||
|
$this->fetchKarma->execute(array(':word'=>$word1));
|
||
|
$res = $this->fetchKarma->fetch(PDO::FETCH_ASSOC);
|
||
|
}
|
||
|
// If it exists, fetch second word
|
||
|
if ($res) {
|
||
|
if ($word2 === '*' || $word2 === 'all' || $word2 === 'everything') {
|
||
|
$res2 = array('karma' => 0);
|
||
|
$word2 = 'everything';
|
||
|
} else {
|
||
|
$this->fetchKarma->execute(array(':word'=>$word2));
|
||
|
$res2 = $this->fetchKarma->fetch(PDO::FETCH_ASSOC);
|
||
|
}
|
||
|
// If it exists, compare and return value
|
||
|
if ($res2 && $res['karma'] != $res2['karma']) {
|
||
|
$assertion = ($operator === '<' && $res['karma'] < $res2['karma']) || ($operator === '>' && $res['karma'] > $res2['karma']);
|
||
|
// Switch arguments if they are in the wrong order
|
||
|
if ($operator === '<') {
|
||
|
$tmp = $word2;
|
||
|
$word2 = $word1;
|
||
|
$word1 = $tmp;
|
||
|
}
|
||
|
$this->doPrivmsg($source, $assertion ? $this->fetchPositiveAnswer($word1, $word2) : $this->fetchNegativeAnswer($word1, $word2));
|
||
|
// If someone asserts that something is greater or lesser than everything, we increment/decrement that something at the same time
|
||
|
if ($word2 === 'everything') {
|
||
|
$this->event = clone$this->event;
|
||
|
$this->event->setArguments(array($this->event->getArgument(0), '++'.$word1));
|
||
|
$this->onPrivmsg();
|
||
|
} elseif ($word1 === 'everything') {
|
||
|
$this->event = clone$this->event;
|
||
|
$this->event->setArguments(array($this->event->getArgument(0), '--'.$word2));
|
||
|
$this->onPrivmsg();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected function fetchPositiveAnswer($owner, $owned)
|
||
|
{
|
||
|
return str_replace(array('%owner%','%owned%'), array($owner, $owned), $this->positiveAnswers[array_rand($this->positiveAnswers,1)]);
|
||
|
}
|
||
|
|
||
|
protected function fetchNegativeAnswer($owned, $owner)
|
||
|
{
|
||
|
return str_replace(array('%owner%','%owned%'), array($owner, $owned), $this->negativeAnswers[array_rand($this->negativeAnswers,1)]);
|
||
|
}
|
||
|
|
||
|
protected function doCleanWord($word)
|
||
|
{
|
||
|
$word = trim($word);
|
||
|
if (substr($word, 0, 1) === '(' && substr($word, -1) === ')') {
|
||
|
$word = trim(substr($word, 1, -1));
|
||
|
}
|
||
|
$word = preg_replace('#\s+#', ' ', strtolower(trim($word)));
|
||
|
return $word;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Performs garbage collection on the antithrottling log.
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
public function doGc()
|
||
|
{
|
||
|
unset($this->log);
|
||
|
$this->log = array();
|
||
|
$this->lastGc = date('d');
|
||
|
}
|
||
|
}
|