diff --git a/plugins/Irc/extlib/.gitignore b/plugins/Irc/extlib/.gitignore new file mode 100644 index 0000000000..553fe8e258 --- /dev/null +++ b/plugins/Irc/extlib/.gitignore @@ -0,0 +1,2 @@ +Settings.php +*.db diff --git a/plugins/Irc/extlib/phergie/Phergie/Connection.php b/plugins/Irc/extlib/phergie/Phergie/Connection.php index d6c89ea801..b3f0acf83d 100755 --- a/plugins/Irc/extlib/phergie/Phergie/Connection.php +++ b/plugins/Irc/extlib/phergie/Phergie/Connection.php @@ -254,7 +254,7 @@ class Phergie_Connection if (!in_array($this->encoding, mb_list_encodings())) { throw new Phergie_Connection_Exception( 'Encoding ' . $this->encoding . ' is not supported', - Phergie_Connection_Exception::ENCODING_NOT_SUPPORTED + Phergie_Connection_Exception::ERR_ENCODING_NOT_SUPPORTED ); } diff --git a/plugins/Irc/extlib/phergie/Phergie/Connection/Exception.php b/plugins/Irc/extlib/phergie/Phergie/Connection/Exception.php index a750e1d860..aec1cd8e0f 100644 --- a/plugins/Irc/extlib/phergie/Phergie/Connection/Exception.php +++ b/plugins/Irc/extlib/phergie/Phergie/Connection/Exception.php @@ -41,4 +41,10 @@ class Phergie_Connection_Exception extends Phergie_Exception * but that transport is not supported by the current PHP installation */ const ERR_TRANSPORT_NOT_SUPPORTED = 2; + + /** + * Error indicating that a connection is configured to use an encoding, + * but that encoding is not supported by the current PHP installation + */ + const ERR_ENCODING_NOT_SUPPORTED = 3; } diff --git a/plugins/Irc/extlib/phergie/Phergie/Driver/Streams.php b/plugins/Irc/extlib/phergie/Phergie/Driver/Streams.php index 3f2ce052b1..73c0230c71 100755 --- a/plugins/Irc/extlib/phergie/Phergie/Driver/Streams.php +++ b/plugins/Irc/extlib/phergie/Phergie/Driver/Streams.php @@ -243,6 +243,7 @@ class Phergie_Driver_Streams extends Phergie_Driver_Abstract // Parse the command and arguments list($cmd, $args) = array_pad(explode(' ', $buffer, 2), 2, null); + $hostmask = new Phergie_Hostmask(null, null, $this->connection->getHost()); } else { // If the event could be from the server or a user... diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Abstract.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Abstract.php index d7e0d351af..b7105ecdb2 100755 --- a/plugins/Irc/extlib/phergie/Phergie/Plugin/Abstract.php +++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Abstract.php @@ -270,7 +270,7 @@ abstract class Phergie_Plugin_Abstract */ public function getEvent() { - if (empty($this->connection)) { + if (empty($this->event)) { throw new Phergie_Plugin_Exception( 'Event cannot be accessed before one is set', Phergie_Plugin_Exception::ERR_NO_EVENT diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Acl.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Acl.php index 9738df60e0..e209e32e14 100755 --- a/plugins/Irc/extlib/phergie/Phergie/Plugin/Acl.php +++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Acl.php @@ -1,6 +1,6 @@ * @copyright 2008-2010 Phergie Development Team (http://phergie.org) @@ -20,14 +20,48 @@ */ /** - * Provides an access control system to limit reponses to events based on + * Provides an access control system to limit reponses to events based on * the users who originate them. * - * @category Phergie + * Configuration settings: + * acl.whitelist - mapping of user hostmask patterns (optionally by host) to + * plugins and methods where those plugins and methods will + * only be accessible to those users (i.e. and inaccessible + * to other users) + * acl.blacklist - mapping of user hostmasks (optionally by host) to plugins + * and methods where where those plugins and methods will be + * inaccessible to those users but accessible to other users + * acl.ops - TRUE to automatically give access to whitelisted plugins + * and methods to users with ops for events they initiate in + * channels where they have ops + * + * The whitelist and blacklist settings are formatted like so: + * + * 'acl.whitelist' => array( + * 'hostname1' => array( + * 'pattern1' => array( + * 'plugins' => array( + * 'ShortPluginName' + * ), + * 'methods' => array( + * 'methodName' + * ) + * ), + * ) + * ), + * + * + * The hostname array dimension is optional; if not used, rules will be + * applied across all connections. The pattern is a user hostmask pattern + * where asterisks (*) are used for wildcards. Plugins and methods do not + * need to be set to empty arrays if they are not used; simply exclude them. + * + * @category Phergie * @package Phergie_Plugin_Acl * @author Phergie Development Team * @license http://phergie.org/license New BSD License * @link http://pear.phergie.org/package/Phergie_Plugin_Acl + * @uses Phergie_Plugin_UserInfo pear.phergie.org */ class Phergie_Plugin_Acl extends Phergie_Plugin_Abstract { @@ -38,6 +72,8 @@ class Phergie_Plugin_Acl extends Phergie_Plugin_Abstract */ public function onLoad() { + $this->plugins->getPlugin('UserInfo'); + if (!$this->getConfig('acl.blacklist') && !$this->getConfig('acl.whitelist') ) { @@ -46,47 +82,105 @@ class Phergie_Plugin_Acl extends Phergie_Plugin_Abstract } /** - * Checks permission settings and short-circuits event processing for + * Applies a set of rules to a plugin handler iterator. + * + * @param Phergie_Plugin_Iterator $iterator Iterator to receive rules + * @param array $rules Associate array containing + * either a 'plugins' key pointing to an array containing plugin + * short names to filter, a 'methods' key pointing to an array + * containing method names to filter, or both + * + * @return void + */ + protected function applyRules(Phergie_Plugin_Iterator $iterator, array $rules) + { + if (!empty($rules['plugins'])) { + $iterator->addPluginFilter($rules['plugins']); + } + if (!empty($rules['methods'])) { + $iterator->addMethodFilter($rules['methods']); + } + } + + /** + * Checks permission settings and short-circuits event processing for * blacklisted users. * - * @return bool FALSE to short-circuit event processing if the user is - * blacklisted, TRUE otherwise + * @return void */ public function preEvent() { // Ignore server responses if ($this->event instanceof Phergie_Event_Response) { - return true; + return; } // Ignore server-initiated events if (!$this->event->isFromUser()) { - return true; + return; } - // Determine whether a whitelist or blacklist is being used - $list = $this->getConfig('acl.whitelist'); - $matches = true; - if (!$list) { - $list = $this->getConfig('acl.blacklist'); - $matches = false; - } + // Get the iterator used to filter plugins when processing events + $iterator = $this->plugins->getIterator(); - // Support host-specific lists + // Get configuration setting values + $whitelist = $this->getConfig('acl.whitelist', array()); + $blacklist = $this->getConfig('acl.blacklist', array()); + $ops = $this->getConfig('acl.ops', false); + + // Support host-specific lists $host = $this->connection->getHost(); - if (isset($list[$host])) { - $list = $list[$host]; - } - - // Short-circuit event processing if appropriate - $hostmask = $this->event->getHostmask(); - foreach ($list as $pattern) { - if ($hostmask->matches($pattern)) { - return $matches; + foreach (array('whitelist', 'blacklist') as $var) { + foreach ($$var as $pattern => $rules) { + $regex = '/^' . str_replace('*', '.*', $pattern) . '$/i'; + if (preg_match($regex, $host)) { + ${$var} = ${$var}[$pattern]; + break; + } } } - // Allow event processing if appropriate - return !$matches; + // Get information on the user initiating the current event + $hostmask = $this->event->getHostmask(); + $isOp = $ops + && $this->event->isInChannel() + && $this->plugins->userInfo->isOp( + $this->event->getNick(), + $this->event->getSource() + ); + + // Filter whitelisted commands if the user is not on the whitelist + if (!$isOp) { + $whitelisted = false; + foreach ($whitelist as $pattern => $rules) { + if ($hostmask->matches($pattern)) { + $whitelisted = true; + } + } + if (!$whitelisted) { + foreach ($whitelist as $pattern => $rules) { + $this->applyRules($iterator, $rules); + } + } + } + + // Filter blacklisted commands if the user is on the blacklist + $blacklisted = false; + foreach ($blacklist as $pattern => $rules) { + if ($hostmask->matches($pattern)) { + $this->applyRules($iterator, $rules); + break; + } + } + } + + /** + * Clears filters on the plugin handler iterator. + * + * @return void + */ + public function postDispatch() + { + $this->plugins->getIterator()->clearFilters(); } } diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Beer/db.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Beer/db.php index 05ae064ed7..c7921e5193 100644 --- a/plugins/Irc/extlib/phergie/Phergie/Plugin/Beer/db.php +++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Beer/db.php @@ -35,6 +35,7 @@ $beers = $xpath->query('//table[@class="beerlist"]/tr/td[1]'); $db->beginTransaction(); foreach ($beers as $beer) { $name = iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $beer->textContent); + $name = preg_replace('/\h*\v+\h*/', '', $name); $link = 'http://beerme.com' . $beer->childNodes->item(1)->getAttribute('href'); $insert->execute(array($name, $link)); } @@ -66,6 +67,7 @@ $db->beginTransaction(); while ($line = fgetcsv($fp, 0, '|')) { $line = array_combine($columns, $line); $name = iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $line['name']); + $name = preg_replace('/\h*\v+\h*/', '', $name); $link = null; $insert->execute(array($name, $link)); } diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Caffeine.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Caffeine.php new file mode 100644 index 0000000000..2b76bd312f --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Caffeine.php @@ -0,0 +1,69 @@ + + * @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_Caffeine + */ + +/** + * Processes requests to serve users caffeinated beverages. + * + * @category Phergie + * @package Phergie_Plugin_Caffeine + * @author Phergie Development Team + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie_Plugin_Caffeine + * @uses Phergie_Plugin_Command pear.phergie.org + * @uses Phergie_Plugin_Serve pear.phergie.org + */ +class Phergie_Plugin_Caffeine extends Phergie_Plugin_Abstract +{ + /** + * Checks for dependencies. + * + * @return void + */ + public function onLoad() + { + $plugins = $this->plugins; + $plugins->getPlugin('Command'); + $plugins->getPlugin('Serve'); + } + + /** + * Processes requests to serve a user a caffeinated beverage. + * + * @param string $request Request including the target and an optional + * suggestion of what caffeinated beverage to serve + * + * @return void + */ + public function onCommandCaffeine($request) + { + $format = $this->getConfig( + 'beer.format', + 'throws %target% %article% %item%.' + ); + + $this->plugins->getPlugin('Serve')->serve( + dirname(__FILE__) . '/Caffeine/caffeine.db', + 'caffeine', + $format, + $request + ); + } +} diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Caffeine/db.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Caffeine/db.php new file mode 100644 index 0000000000..cdff52fd8d --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Caffeine/db.php @@ -0,0 +1,51 @@ +exec('CREATE TABLE caffeine (name VARCHAR(255), link VARCHAR(255))'); +$db->exec('CREATE UNIQUE INDEX caffeine_name ON caffeine (name)'); +$insert = $db->prepare('INSERT INTO caffeine (name, link) VALUES (:name, :link)'); + +// Get raw energyfiend.com data set +echo 'Downloading energyfiend.com data set', PHP_EOL; +$file = __DIR__ . '/the-caffeine-database.html'; +if (!file_exists($file)) { + copy('http://www.energyfiend.com/the-caffeine-database', $file); +} +$contents = file_get_contents($file); + +// Extract data from data set +echo 'Processing energyfiend.com data', PHP_EOL; +$contents = tidy_repair_string($contents); +libxml_use_internal_errors(true); +$doc = new DOMDocument; +$doc->loadHTML($contents); +libxml_clear_errors(); +$xpath = new DOMXPath($doc); +$caffeine = $xpath->query('//table[@id="caffeinedb"]//tr/td[1]'); +$db->beginTransaction(); +foreach ($caffeine as $drink) { + $name = iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $drink->textContent); + $name = preg_replace('/\s*\v+\s*/', ' ', $name); + if ($drink->firstChild->nodeName == 'a') { + $link = 'http://energyfiend.com' + . $drink->firstChild->getAttribute('href'); + } else { + $link = null; + } + $insert->execute(array($name, $link)); +} +$db->commit(); + +// Clean up +echo 'Cleaning up', PHP_EOL; +unlink($file); diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Command.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Command.php old mode 100755 new mode 100644 index 2ab5b69d1d..5c0a52e916 --- a/plugins/Irc/extlib/phergie/Phergie/Plugin/Command.php +++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Command.php @@ -1,6 +1,6 @@ * @copyright 2008-2010 Phergie Development Team (http://phergie.org) @@ -20,20 +20,28 @@ */ /** - * Handles parsing and execution of commands sent by users via messages sent + * Handles parsing and execution of commands sent by users via messages sent * to channels in which the bot is present or directly to the bot. * - * @category Phergie + * @category Phergie * @package Phergie_Plugin_Command * @author Phergie Development Team * @license http://phergie.org/license New BSD License * @link http://pear.phergie.org/package/Phergie_Plugin_Command * @uses extension reflection + * @uses Phergie_Plugin_Message pear.phergie.org */ class Phergie_Plugin_Command extends Phergie_Plugin_Abstract { /** - * Cache for command lookups used to confirm that methods exist and + * Prefix for command method names + * + * @var string + */ + const METHOD_PREFIX = 'onCommand'; + + /** + * Cache for command lookups used to confirm that methods exist and * parameter counts match * * @var array @@ -41,24 +49,28 @@ class Phergie_Plugin_Command extends Phergie_Plugin_Abstract protected $methods = array(); /** - * Prefix for command method names + * Load the Message plugin * - * @var string + * @return void */ - protected $methodPrefix = 'onCommand'; + public function onLoad() + { + $plugins = $this->getPluginHandler(); + $plugins->getPlugin('Message'); + } /** * Populates the methods cache. * * @return void */ - protected function populateMethodCache() + public function populateMethodCache() { - foreach ($this->getPluginHandler() as $plugin) { + foreach ($this->getPluginHandler()->getPlugins() as $plugin) { $reflector = new ReflectionClass($plugin); foreach ($reflector->getMethods() as $method) { $name = $method->getName(); - if (strpos($name, $this->methodPrefix) === 0 + if (strpos($name, self::METHOD_PREFIX) === 0 && !isset($this->methods[$name]) ) { $this->methods[$name] = array( @@ -84,26 +96,26 @@ class Phergie_Plugin_Command extends Phergie_Plugin_Abstract $this->populateMethodCache(); } - // Get the content of the message - $event = $this->getEvent(); - $msg = trim($event->getText()); - $prefix = $this->getConfig('command.prefix'); - - // Check for the command prefix if one is set and needed - if ($prefix && $event->isInChannel()) { - if (strpos($msg, $prefix) !== 0) { - return; - } else { - $msg = substr($msg, strlen($prefix)); - } + // Check for a prefixed message + $msg = $this->plugins->message->getMessage(); + if ($msg === false) { + return; } // Separate the command and arguments $parsed = preg_split('/\s+/', $msg, 2); - $method = $this->methodPrefix . ucfirst(strtolower(array_shift($parsed))); + $command = strtolower(array_shift($parsed)); $args = count($parsed) ? array_shift($parsed) : ''; + // Resolve aliases to their corresponding commands + $aliases = $this->getConfig('command.aliases', array()); + $result = preg_grep('/^' . $command . '$/i', array_keys($aliases)); + if ($result) { + $command = $aliases[array_shift($result)]; + } + // Check to ensure the command exists + $method = self::METHOD_PREFIX . ucfirst($command); if (empty($this->methods[$method])) { return; } @@ -122,7 +134,7 @@ class Phergie_Plugin_Command extends Phergie_Plugin_Abstract // Parse the arguments $args = preg_split('/\s+/', $args, $this->methods[$method]['total']); - // If the minimum arguments are passed, call the method + // If the minimum arguments are passed, call the method if ($this->methods[$method]['required'] <= count($args)) { call_user_func_array( array($this->getPluginHandler(), $method), diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Cookie/db.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Cookie/db.php index 91d375ca6d..27763159cc 100644 --- a/plugins/Irc/extlib/phergie/Phergie/Plugin/Cookie/db.php +++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Cookie/db.php @@ -36,9 +36,6 @@ $cookies = $xpath->query('//table[@width="90%"]/tr/td[1]/a'); foreach ($cookies as $cookie) { $name = $cookie->textContent; - foreach (range(0, mb_strlen($name) - 1) as $index) { - echo mb_strcut($name, $index, 1), PHP_EOL; - } $name = str_replace( array('(',')',"\n", 'cookies'), array('','', ' ', 'cookie'), diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Daddy.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Daddy.php index d47c3c41d3..ed258e10ff 100644 --- a/plugins/Irc/extlib/phergie/Phergie/Plugin/Daddy.php +++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Daddy.php @@ -1,6 +1,6 @@ * @copyright 2008-2010 Phergie Development Team (http://phergie.org) @@ -23,7 +23,7 @@ * Simply responds to messages addressed to the bot that contain the phrase * "Who's your daddy?" and related variations. * - * @category Phergie + * @category Phergie * @package Phergie_Plugin_Daddy * @author Phergie Development Team * @license http://phergie.org/license New BSD License @@ -45,15 +45,11 @@ class Phergie_Plugin_Daddy extends Phergie_Plugin_Abstract $text = $event->getArgument(1); $target = $event->getNick(); $source = $event->getSource(); - $pattern - = '/' . preg_quote($prefix) . + $pattern + = '/' . preg_quote($prefix) . '\s*?who\'?s y(?:our|a) ([^?]+)\??/iAD'; if (preg_match($pattern, $text, $m)) { - if ($config['daddy.curses'] && mt_rand(0, 5) === 5) { - $msg = $target . ': I am your ' . $m[1] . ', bitch!'; - } else { - $msg = 'You\'re my ' . $m[1] . ', ' . $target . '!'; - } + $msg = 'You\'re my ' . $m[1] . ', ' . $target . '!'; $this->doPrivmsg($source, $msg); } } diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Google.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Google.php index faa98f926a..d2a9d4dce9 100644 --- a/plugins/Irc/extlib/phergie/Phergie/Plugin/Google.php +++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Google.php @@ -32,24 +32,9 @@ * @uses Phergie_Plugin_Command pear.phergie.org * @uses Phergie_Plugin_Http pear.phergie.org * @uses Phergie_Plugin_Temperature pear.phergie.org - * - * @pluginDesc Provide access to some Google services */ class Phergie_Plugin_Google extends Phergie_Plugin_Abstract { - - /** - * HTTP plugin - * - * @var Phergie_Plugin_Http - */ - protected $http; - - /** - * Language for Google Services - */ - protected $lang; - /** * Checks for dependencies. * @@ -59,11 +44,8 @@ class Phergie_Plugin_Google extends Phergie_Plugin_Abstract { $plugins = $this->getPluginHandler(); $plugins->getPlugin('Command'); - $this->http = $plugins->getPlugin('Http'); - $plugins->getPlugin('Help')->register($this); + $plugins->getPlugin('Http'); $plugins->getPlugin('Weather'); - - $this->lang = $this->getConfig('google.lang', 'en'); } /** @@ -73,8 +55,6 @@ class Phergie_Plugin_Google extends Phergie_Plugin_Abstract * * @return void * @todo Implement use of URL shortening here - * - * @pluginCmd [query] do a search on google */ public function onCommandG($query) { @@ -83,7 +63,7 @@ class Phergie_Plugin_Google extends Phergie_Plugin_Abstract 'v' => '1.0', 'q' => $query ); - $response = $this->http->get($url, $params); + $response = $this->plugins->http->get($url, $params); $json = $response->getContent()->responseData; $event = $this->getEvent(); $source = $event->getSource(); @@ -110,8 +90,6 @@ class Phergie_Plugin_Google extends Phergie_Plugin_Abstract * @param string $query Search term * * @return void - * - * @pluginCmd [query] Do a search on Google and count the results */ public function onCommandGc($query) { @@ -120,7 +98,7 @@ class Phergie_Plugin_Google extends Phergie_Plugin_Abstract 'v' => '1.0', 'q' => $query ); - $response = $this->http->get($url, $params); + $response = $this->plugins->http->get($url, $params); $json = $response->getContent()->responseData->cursor; $count = $json->estimatedResultCount; $event = $this->getEvent(); @@ -146,8 +124,6 @@ class Phergie_Plugin_Google extends Phergie_Plugin_Abstract * @param string $query Term to translate * * @return void - * - * @pluginCmd [from language] [to language] [text to translate] Do a translation on Google */ public function onCommandGt($from, $to, $query) { @@ -157,7 +133,7 @@ class Phergie_Plugin_Google extends Phergie_Plugin_Abstract 'q' => $query, 'langpair' => $from . '|' . $to ); - $response = $this->http->get($url, $params); + $response = $this->plugins->http->get($url, $params); $json = $response->getContent(); $event = $this->getEvent(); $source = $event->getSource(); @@ -180,18 +156,16 @@ class Phergie_Plugin_Google extends Phergie_Plugin_Abstract * between 0 and 3 to get the forecast * * @return void - * - * @pluginCmd [location] Show the weather for the specified location */ public function onCommandGw($location, $offset = null) { $url = 'http://www.google.com/ig/api'; $params = array( 'weather' => $location, - 'hl' => $this->lang, + 'hl' => $this->getConfig('google.lang', 'en'), 'oe' => 'UTF-8' ); - $response = $this->http->get($url, $params); + $response = $this->plugins->http->get($url, $params); $xml = $response->getContent()->weather; $event = $this->getEvent(); @@ -280,8 +254,6 @@ class Phergie_Plugin_Google extends Phergie_Plugin_Abstract * @param string $location Location to search for * * @return void - * - * @pluginCmd [location] Get the location from Google Maps to the location specified */ public function onCommandGmap($location) { @@ -294,13 +266,13 @@ class Phergie_Plugin_Google extends Phergie_Plugin_Abstract $params = array( 'q' => $location, 'output' => 'json', - 'gl' => $this->lang, + 'gl' => $this->getConfig('google.lang', 'en'), 'sensor' => 'false', 'oe' => 'utf8', 'mrt' => 'all', 'key' => $this->getConfig('google.key') ); - $response = $this->http->get($url, $params); + $response = $this->plugins->http->get($url, $params); $json = $response->getContent(); if (!empty($json)) { $qtd = count($json->Placemark); @@ -345,8 +317,6 @@ class Phergie_Plugin_Google extends Phergie_Plugin_Abstract * @param string $to Destination metric * * @return void - * - * @pluginCmd [value] [currency from] [currency to] Converts a monetary value from one currency to another */ public function onCommandGconvert($value, $from, $to) { @@ -356,29 +326,21 @@ class Phergie_Plugin_Google extends Phergie_Plugin_Abstract 'from' => $from, 'to' => $to ); - $response = $this->http->get($url, $params); + $response = $this->plugins->http->get($url, $params); $contents = $response->getContent(); $event = $this->getEvent(); $source = $event->getSource(); $nick = $event->getNick(); if ($contents) { - preg_match( - '#.*? ' . $to . '#im', - $contents, - $matches - ); - if (!$matches[0]) { - $this->doPrivmsg($source, $nick . ', I can\'t do that.'); - } else { - $str = str_replace('', '', $matches[0]); - $str = str_replace($to . '', '', $str); - $text - = number_format($value, 2, ',', '.') . ' ' . $from . - ' => ' . number_format($str, 2, ',', '.') . ' ' . $to; - $this->doPrivmsg($source, $text); - } - } else { - $this->doPrivmsg($source, $nick . ', we had a problem.'); + libxml_use_internal_errors(true); + $doc = new DOMDocument; + $doc->loadHTML($contents); + libxml_clear_errors(); + $xpath = new DOMXPath($doc); + $result = $xpath->query('//div[@id="currency_converter_result"]'); + $div = $result->item(0); + $text = rtrim($div->textContent); + $this->doPrivmsg($source, $text); } } @@ -395,7 +357,7 @@ class Phergie_Plugin_Google extends Phergie_Plugin_Abstract public function onCommandConvert($query) { $url = 'http://www.google.com/search?q=' . urlencode($query); - $response = $this->http->get($url); + $response = $this->plugins->http->get($url); $contents = $response->getContent(); $event = $this->getEvent(); $source = $event->getSource(); @@ -434,49 +396,61 @@ class Phergie_Plugin_Google extends Phergie_Plugin_Abstract * * @return void * @todo Implement use of URL shortening here - * - * @pluginCmd [query] do a search of a definition on Google Dictionary */ public function onCommandDefine($query) { - $query = urlencode($query); - $url = 'http://www.google.com/dictionary/json?callback=result'. - '&q='.$query.'&sl='.$this->lang.'&tl='.$this->lang. - '&restrict=pr,de'; - $json = file_get_contents($url); + $lang = $this->getConfig('google.lang', 'en'); + $url = 'http://www.google.com/dictionary/json'; + $params = array( + 'callback' => 'result', + 'q' => $query, + 'sl' => $lang, + 'tl' => $lang, + 'restrict' => 'pr,de' + ); + $response = $this->plugins->http->get($url, $params); + $json = $response->getContent(); - //Remove some garbage from the json - $json = str_replace(array("result(", ",200,null)"), "", $json); - - //Awesome workaround to remove a lot of slashes from json + // Remove some garbage from the JSON and decode it + $json = str_replace(array('result(', ',200,null)'), '', $json); $json = str_replace('"', '¿?¿', $json); $json = strip_tags(stripcslashes($json)); $json = str_replace('"', "'", $json); $json = str_replace('¿?¿', '"', $json); - $json = json_decode($json); $event = $this->getEvent(); $source = $event->getSource(); $nick = $event->getNick(); - if (!empty($json->webDefinitions)){ - $results = count($json->webDefinitions[0]->entries); - $more = $results > 1 ? ($results-1).' ' : NULL; - $lang_code = substr($this->lang, 0, 2); - $msg = - $nick . ': ' . - $json->webDefinitions[0]->entries[0]->terms[0]->text . - ' - You can find more '.$more.'results at '. - 'http://www.google.com/dictionary?aq=f&langpair='. - $lang_code.'%7C'.$lang_code.'&q='.$query.'&hl='.$lang_code; + if (!empty($json->webDefinitions)) { + $results = 0; + foreach ($json->primaries[0]->entries as $entry) { + if ($entry->type == 'meaning') { + $results++; + if (empty($text)) { + foreach ($entry->terms as $term) { + if ($term->type == 'text') { + $text = trim($term->text); + } + } + } + } + } + $more = $results > 1 ? ($results - 1) . ' ' : ''; + $lang_code = substr($lang, 0, 2); + $msg = $nick . ': ' . $text + . ' - You can find ' . $more . 'more results at ' + . 'http://www.google.com/dictionary' + . '?aq=f' + . '&langpair=' . $lang_code . '%7C' . $lang_code + . '&q=' . $query + . '&hl=' . $lang_code; $this->doPrivmsg($source, $msg); - }else{ - if ($this->lang != 'en'){ - $temp = $this->lang; - $this->lang = 'en'; + } else { + if ($lang != 'en'){ + $lang = 'en'; $this->onCommandDefine($query); - $this->lang = $temp; - }else{ + } else { $msg = $nick . ': No results for this query.'; $this->doPrivmsg($source, $msg); } diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Handler.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Handler.php index b2ef089b44..13d2eac8fe 100755 --- a/plugins/Irc/extlib/phergie/Phergie/Plugin/Handler.php +++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Handler.php @@ -68,6 +68,14 @@ class Phergie_Plugin_Handler implements IteratorAggregate, Countable */ protected $events; + /** + * Iterator used for selectively proxying method calls to contained + * plugins + * + * @var Iterator + */ + protected $iterator; + /** * Constructor to initialize class properties and add the path for core * plugins. @@ -328,7 +336,7 @@ class Phergie_Plugin_Handler implements IteratorAggregate, Countable $plugins = array(); foreach ($names as $name) { - $plugins[$name] = $this->getPlugin($name); + $plugins[strtolower($name)] = $this->getPlugin($name); } return $plugins; } @@ -416,26 +424,39 @@ class Phergie_Plugin_Handler implements IteratorAggregate, Countable */ public function getIterator() { - return new ArrayIterator($this->plugins); + if (empty($this->iterator)) { + $this->iterator = new Phergie_Plugin_Iterator( + new ArrayIterator($this->plugins) + ); + } + return $this->iterator; } /** - * Proxies method calls to all plugins containing the called method. An - * individual plugin may short-circuit this process by explicitly - * returning FALSE. + * Sets the iterator for all currently loaded plugin instances. + * + * @param Iterator $iterator Plugin iterator + * + * @return Phergie_Plugin_Handler Provides a fluent interface + */ + public function setIterator(Iterator $iterator) + { + $this->iterator = $iterator; + return $this; + } + + /** + * Proxies method calls to all plugins containing the called method. * * @param string $name Name of the method called * @param array $args Arguments passed in the method call * - * @return bool FALSE if a plugin short-circuits processing by returning - * FALSE, TRUE otherwise + * @return void */ public function __call($name, array $args) { - foreach ($this->plugins as $plugin) { - if (call_user_func_array(array($plugin, $name), $args) === false) { - return false; - } + foreach ($this->getIterator() as $plugin) { + call_user_func_array(array($plugin, $name), $args); } return true; } diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Help.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Help.php index 7cc8349571..4c2c49b314 100644 --- a/plugins/Irc/extlib/phergie/Phergie/Plugin/Help.php +++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Help.php @@ -28,26 +28,16 @@ * @license http://phergie.org/license New BSD License * @link http://pear.phergie.org/package/Phergie_Plugin_Help * @uses Phergie_Plugin_Command pear.phergie.org - * - * @pluginDesc Provides access to plugin help information */ class Phergie_Plugin_Help extends Phergie_Plugin_Abstract { - /** - * Holds the registry of help data indexed by plugin name + * Registry of help data indexed by plugin name * * @var array */ protected $registry; - /** - * Whether the registry has been alpha sorted - * - * @var bool - */ - protected $registry_sorted = false; - /** * Checks for dependencies. * @@ -56,143 +46,175 @@ class Phergie_Plugin_Help extends Phergie_Plugin_Abstract public function onLoad() { $this->getPluginHandler()->getPlugin('Command'); - $this->register($this); + } + + /** + * Creates a registry of plugin metadata on connect. + * + * @return void + */ + public function onConnect() + { + $this->populateRegistry(); + } + + /** + * Creates a registry of plugin metadata. + * + * @return void + */ + public function populateRegistry() + { + $this->registry = array(); + + foreach ($this->plugins as $plugin) { + $class = new ReflectionClass($plugin); + $pluginName = strtolower($plugin->getName()); + + // Parse the plugin description + $docblock = $class->getDocComment(); + $annotations = $this->getAnnotations($docblock); + if (isset($annotations['pluginDesc'])) { + $pluginDesc = implode(' ', $annotations['pluginDesc']); + } else { + $pluginDesc = $this->parseShortDescription($docblock); + } + $this->registry[$pluginName] = array( + 'desc' => $pluginDesc, + 'cmds' => array() + ); + + // Parse command method descriptions + $methodPrefix = Phergie_Plugin_Command::METHOD_PREFIX; + $methodPrefixLength = strlen($methodPrefix); + foreach ($class->getMethods() as $method) { + if (strpos($method->getName(), $methodPrefix) !== 0) { + continue; + } + + $cmd = strtolower(substr($method->getName(), $methodPrefixLength)); + $docblock = $method->getDocComment(); + $annotations = $this->getAnnotations($docblock); + + if (isset($annotations['pluginCmd'])) { + $cmdDesc = implode(' ', $annotations['pluginCmd']); + } else { + $cmdDesc = $this->parseShortDescription($docblock); + } + + $cmdParams = array(); + if (!empty($annotations['param'])) { + foreach ($annotations['param'] as $param) { + $match = null; + if (preg_match('/\h+\$([^\h]+)\h+/', $param, $match)) { + $cmdParams[] = $match[1]; + } + } + } + + $this->registry[$pluginName]['cmds'][$cmd] = array( + 'desc' => $cmdDesc, + 'params' => $cmdParams + ); + } + + if (empty($this->registry[$pluginName]['cmds'])) { + unset($this->registry[$pluginName]); + } + } } /** * Displays a list of plugins with help information available or * commands available for a specific plugin. * - * @param string $plugin Short name of the plugin for which commands - * should be returned, else a list of plugins with help - * information available is returned + * @param string $query Optional short name of a plugin for which commands + * should be returned or a command; if unspecified, a list of + * plugins with help information available is returned * * @return void - * - * @pluginCmd Show all active plugins with help available - * @pluginCmd [plugin] Shows commands line for a specific plugin */ - public function onCommandHelp($plugin = null) + public function onCommandHelp($query = null) { + if ($query == 'refresh') { + $this->populateRegistry(); + } + $nick = $this->getEvent()->getNick(); + $delay = $this->getConfig('help.delay', 2); - if (!$plugin) { - // protect from sorting the registry each time help is called - if (!$this->registry_sorted) { - asort($this->registry); - $this->registry_sorted = true; - } + // Handle requests for a plugin list + if (!$query) { + $msg = 'These plugins have help information available: ' + . implode(', ', array_keys($this->registry)); + $this->doPrivmsg($nick, $msg); + return; + } - $msg = 'These plugins below have help information available.'; - $this->doPrivMsg($nick, $msg); + // Handle requests for plugin information + $query = strtolower($query); + if (isset($this->registry[$query]) + && empty($this->registry[$query]['cmds'][$query])) { + $msg = $query . ' - ' . $this->registry[$query]['desc']; + $this->doPrivmsg($nick, $msg); - foreach ($this->registry as $plugin => $data) { - $this->doPrivMsg($nick, "{$plugin} - {$data['desc']}"); - } - } else { - if (isset($this->getPluginHandler()->{$plugin}) - && isset($this->registry[strtolower($plugin)]['cmd']) - ) { + $msg = 'Available commands - ' + . implode(', ', array_keys($this->registry[$query]['cmds'])); + $this->doPrivmsg($nick, $msg); + + if ($this->getConfig('command.prefix')) { $msg - = 'The ' . - $plugin . - ' plugin exposes the commands shown below.'; - $this->doPrivMsg($nick, $msg); - if ($this->getConfig('command.prefix')) { - $msg - = 'Note that these commands must be prefixed with "' . - $this->getConfig('command.prefix') . - '" (without quotes) when issued in a public channel.'; - $this->doPrivMsg($nick, $msg); - } - - foreach ($this->registry[strtolower($plugin)]['cmd'] - as $cmd => $descs - ) { - foreach ($descs as $desc) { - $this->doPrivMsg($nick, "{$cmd} {$desc}"); - } - } - - } else { - $this->doPrivMsg($nick, 'That plugin is not loaded.'); + = 'Note that these commands must be prefixed with "' + . $this->getConfig('command.prefix') + . '" (without quotes) when issued in a public channel.'; + $this->doPrivmsg($nick, $msg); } + + return; + } + + // Handle requests for command information + foreach ($this->registry as $plugin => $data) { + if (empty($data['cmds'])) { + continue; + } + + $result = preg_grep('/^' . $query . '$/i', array_keys($data['cmds'])); + if (!$result) { + continue; + } + + $cmd = $data['cmds'][array_shift($result)]; + $msg = $query; + if (!empty($cmd['params'])) { + $msg .= ' [' . implode('] [', $cmd['params']) . ']'; + } + $msg .= ' - ' . $cmd['desc']; + $this->doPrivmsg($nick, $msg); } } /** - * Sets the description for the plugin instance + * Parses and returns the short description from a docblock. * - * @param Phergie_Plugin_Abstract $plugin plugin instance - * @param string $description plugin description + * @param string $docblock Docblock comment code * - * @return void + * @return string Short description (i.e. content from the start of the + * docblock up to the first double-newline) */ - public function setPluginDescription( - Phergie_Plugin_Abstract $plugin, - $description - ) { - $this->registry[strtolower($plugin->getName())] - ['desc'] = $description; - } - - /** - * Sets the description for the command on the plugin instance - * - * @param Phergie_Plugin_Abstract $plugin plugin instance - * @param string $command from onCommand method - * @param string $description command description - * - * @return void - */ - public function setCommandDescription( - Phergie_Plugin_Abstract $plugin, - $command, - array $description - ) { - $this->registry[strtolower($plugin->getName())] - ['cmd'][$command] = $description; - } - - /** - * registers the plugin with the help plugin. this will parse the docblocks - * for specific annotations that this plugin will respond with when - * queried. - * - * @param Phergie_Plugin_Abstract $plugin plugin instance - * - * @return void - */ - public function register(Phergie_Plugin_Abstract $plugin) + protected function parseShortDescription($docblock) { - $class = new ReflectionClass($plugin); - - $annotations = self::parseAnnotations($class->getDocComment()); - if (isset($annotations['pluginDesc'])) { - $this->setPluginDescription( - $plugin, - join(' ', $annotations['pluginDesc']) - ); - } - - foreach ($class->getMethods() as $method) { - if (strpos($method->getName(), 'onCommand') !== false) { - $annotations = self::parseAnnotations($method->getDocComment()); - if (isset($annotations['pluginCmd'])) { - $cmd = strtolower(substr($method->getName(), 9)); - $this->setCommandDescription( - $plugin, - $cmd, - $annotations['pluginCmd'] - ); - } - } - } + $desc = preg_replace( + array('#^\h*\*\h*#m', '#^/\*\*\h*\v+\h*#', '#(?:\r?\n){2,}.*#s', '#\s*\v+\s*#'), + array('', '', '', ' '), + $docblock + ); + return $desc; } /** - * Taken from PHPUnit/Util/Test.php:243 and modified to fix an issue - * with tag content spanning multiple lines. + * Taken from PHPUnit/Util/Test.php and modified to fix an issue with + * tag content spanning multiple lines. * * PHPUnit * @@ -232,7 +254,7 @@ class Phergie_Plugin_Help extends Phergie_Plugin_Abstract * * @return array */ - protected static function parseAnnotations($docblock) + protected function getAnnotations($docblock) { $annotations = array(); diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Http.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Http.php index 43c69eb879..7c5c8704c2 100644 --- a/plugins/Irc/extlib/phergie/Phergie/Plugin/Http.php +++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Http.php @@ -184,7 +184,10 @@ class Phergie_Plugin_Http extends Phergie_Plugin_Abstract $type = $this->response->getHeaders('content-type'); foreach ($this->handlers as $expr => $handler) { if (preg_match('#^' . $expr . '$#i', $type)) { - $body = call_user_func($handler, $body); + $handled = call_user_func($handler, $body); + if (!empty($handled)) { + $body = $handled; + } } } @@ -256,7 +259,7 @@ class Phergie_Plugin_Http extends Phergie_Plugin_Abstract public function post($url, array $query = array(), array $post = array(), array $context = array() ) { - if (!empty($params)) { + if (!empty($query)) { $url .= '?' . http_build_query($query); } @@ -278,4 +281,4 @@ class Phergie_Plugin_Http extends Phergie_Plugin_Abstract return $this->request($url, $context); } -} +} \ No newline at end of file diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Ideone.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Ideone.php new file mode 100644 index 0000000000..3af88dda94 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Ideone.php @@ -0,0 +1,180 @@ + + * @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_Ideone + */ + +/** + * Interfaces with ideone.com to execute code and return the result. + * + * @category Phergie + * @package Phergie_Plugin_Ideone + * @author Phergie Development Team + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie_Plugin_Ideone + * @uses Phergie_Plugin_Command pear.phergie.org + */ +class Phergie_Plugin_Ideone extends Phergie_Plugin_Abstract +{ + /** + * Checks for dependencies. + * + * @return void + */ + public function onLoad() + { + $this->plugins->getPlugin('Command'); + } + + /** + * Checks a service response for an error, sends a notice to the event + * source if an error has occurred, and returns whether an error was found. + * + * @param array $result Associative array representing the service response + * + * @return boolean TRUE if an error is found, FALSE otherwise + */ + protected function isError($result) + { + if ($result['error'] != 'OK') { + $this->doNotice($this->event->getNick(), 'ideone error: ' . $result['error']); + return true; + } + return false; + } + + /** + * Executes a source code sequence in a specified language and returns + * the result. + * + * @param string $language Programming language the source code is in + * @param string $code Source code to execute + * + * @return void + */ + public function onCommandIdeone($language, $code) + { + $source = $this->event->getSource(); + $nick = $this->event->getNick(); + + // Get authentication credentials + $user = $this->getConfig('ideone.user', 'test'); + $pass = $this->getConfig('ideone.pass', 'test'); + + // Normalize the command parameters + $language = strtolower($language); + + // Massage PHP code to allow for convenient shorthand + if ($language == 'php') { + if (!preg_match('/^<\?(?:php)?/', $code)) { + $code = 'getLanguages($user, $pass); + if ($this->isError($response)) { + return; + } + $languageLength = strlen($language); + foreach ($response['languages'] as $languageId => $languageName) { + if (strncasecmp($language, $languageName, $languageLength) == 0) { + break; + } + } + + // Send the paste data + $response = $client->createSubmission( + $user, + $pass, + $code, + $languageId, + null, // string input - data from stdin + true, // boolean run - TRUE to execute the code + false // boolean private - FALSE to make the paste public + ); + if ($this->isError($response)) { + return; + } + $link = $response['link']; + + // Wait until the paste data is processed or the service fails + $attempts = $this->getConfig('ideone.attempts', 10); + foreach (range(1, $attempts) as $attempt) { + $response = $client->getSubmissionStatus($user, $pass, $link); + if ($this->isError($response)) { + return; + } + if ($response['status'] == 0) { + $result = $response['result']; + break; + } else { + $result = null; + sleep(1); + } + } + if ($result == null) { + $this->doNotice($nick, 'ideone error: Timed out'); + return; + } + if ($result != 15) { + $this->doNotice($nick, 'ideone error: Status code ' . $result); + return; + } + + // Get details for the created paste + $response = $client->getSubmissionDetails( + $user, + $pass, + $link, + false, // boolean withSource - FALSE to not return the source code + false, // boolean withInput - FALSE to not return stdin data + true, // boolean withOutput - TRUE to include output + true, // boolean withStderr - TRUE to return stderr data + false // boolean withCmpinfo - TRUE to return compilation info + ); + if ($this->isError($response)) { + return; + } + + // Replace the output if it exceeds a specified maximum length + $outputLimit = $this->getConfig('ideone.output_limit', 100); + var_dump($response); + if ($outputLimit && strlen($response['output']) > $outputLimit) { + $response['output'] = 'Output is too long to post'; + } + + // Format the message + $msg = $this->getConfig('ideone.format', '%nick%: [ %link% ] %output%'); + $response['nick'] = $nick; + $response['link'] = 'http://ideone.com/' . $link; + foreach ($response as $key => $value) { + $msg = str_replace('%' . $key . '%', $value, $msg); + } + $this->doPrivmsg($source, $msg); + } +} diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Iterator.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Iterator.php new file mode 100644 index 0000000000..5963300713 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Iterator.php @@ -0,0 +1,126 @@ + + * @copyright 2008-2010 Phergie Development Team (http://phergie.org) + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ + +/** + * Implements a filtering iterator for limiting executing of methods across + * a group of plugins. + * + * @category Phergie + * @package Phergie + * @author Phergie Development Team + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ +class Phergie_Plugin_Iterator extends FilterIterator +{ + /** + * List of short names of plugins to exclude when iterating + * + * @var array + */ + protected $plugins = array(); + + /** + * List of method names where plugins with these methods will be + * excluded when iterating + * + * @var array + */ + protected $methods = array(); + + /** + * Adds to a list of plugins to exclude when iterating. + * + * @param mixed $plugins String containing the short name of a single + * plugin to exclude or an array of short names of multiple + * plugins to exclude + * + * @return Phergie_Plugin_Iterator Provides a fluent interface + */ + public function addPluginFilter($plugins) + { + if (is_array($plugins)) { + $this->plugins = array_unique( + array_merge($this->plugins, $plugins) + ); + } else { + $this->plugins[] = $plugins; + } + return $this; + } + + /** + * Adds to a list of method names where plugins defining these methods + * will be excluded when iterating. + * + * @param mixed $methods String containing the name of a single method + * or an array containing the name of multiple methods + * + * @return Phergie_Plugin_Iterator Provides a fluent interface + */ + public function addMethodFilter($methods) + { + if (is_array($methods)) { + $this->methods = array_merge($this->methods, $methods); + } else { + $this->methods[]= $methods; + } + return $this; + } + + /** + * Clears any existing plugin and methods filters. + * + * @return Phergie_Plugin_Iterator Provides a fluent interface + */ + public function clearFilters() + { + $this->plugins = array(); + $this->methods = array(); + } + + /** + * Implements FilterIterator::accept(). + * + * @return boolean TRUE to include the current item in those by returned + * during iteration, FALSE otherwise + */ + public function accept() + { + if (!$this->plugins && !$this->methods) { + return true; + } + + $current = $this->current(); + + if (in_array($current->getName(), $this->plugins)) { + return false; + } + + foreach ($this->methods as $method) { + if (method_exists($current, $method)) { + return false; + } + } + + return true; + } +} diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Karma.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Karma.php index b7ce48685a..f55f8d6edf 100644 --- a/plugins/Irc/extlib/phergie/Phergie/Plugin/Karma.php +++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Karma.php @@ -21,427 +21,407 @@ /** * 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. + * of counters for specified terms. * * @category Phergie * @package Phergie_Plugin_Karma * @author Phergie Development Team * @license http://phergie.org/license New BSD License * @link http://pear.phergie.org/package/Phergie_Plugin_Karma + * @uses extension PDO + * @uses extension pdo_sqlite + * @uses Phergie_Plugin_Command pear.phergie.org + * @uses Phergie_Plugin_Message pear.phergie.org */ class Phergie_Plugin_Karma extends Phergie_Plugin_Abstract { /** - * Stores the SQLite object + * 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 + * Prepared statement to add a new karma record * * @var PDOStatement */ protected $insertKarma; - protected $updateKarma; - protected $fetchKarma; - protected $insertComment; /** - * Connects to the database containing karma ratings and initializes - * class properties. + * Prepared statement to update an existing karma record + * + * @var PDOStatement + */ + protected $updateKarma; + + /** + * Retrieves an existing karma record + * + * @var PDOStatement + */ + protected $fetchKarma; + + /** + * Retrieves an existing fixed karma record + * + * @var PDOStatement + */ + protected $fetchFixedKarma; + + /** + * Retrieves a positive answer for a karma comparison + * + * @var PDOStatement + */ + protected $fetchPositiveAnswer; + + /** + * Retrieves a negative answer for a karma comparison + * + * @var PDOStatement + */ + protected $fetchNegativeAnswer; + + /** + * Check for dependencies and initializes a database connection and + * prepared statements. * * @return void */ public function onLoad() { - $this->db = null; - $this->lastGc = null; - $this->log = array(); + $plugins = $this->getPluginHandler(); + $plugins->getPlugin('Command'); + $plugins->getPlugin('Message'); - 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 - ) - '); + $file = dirname(__FILE__) . '/Karma/karma.db'; + $this->db = new PDO('sqlite:' . $file); $this->fetchKarma = $this->db->prepare(' - SELECT karma, ROWID id FROM karmas WHERE LOWER(word) = LOWER(:word) LIMIT 1 + SELECT karma + FROM karmas + WHERE term = :term + LIMIT 1 + '); + + $this->insertKarma = $this->db->prepare(' + INSERT INTO karmas (term, karma) + VALUES (:term, :karma) '); $this->updateKarma = $this->db->prepare(' - UPDATE karmas SET karma = :karma WHERE LOWER(word) = LOWER(:word) + UPDATE karmas + SET karma = :karma + WHERE term = :term + '); + + $this->fetchFixedKarma = $this->db->prepare(' + SELECT karma + FROM fixed_karmas + WHERE term = :term + LIMIT 1 + '); + + $this->fetchPositiveAnswer = $this->db->prepare(' + SELECT answer + FROM positive_answers + ORDER BY RANDOM() + LIMIT 1 + '); + + $this->fetchNegativeAnswer = $this->db->prepare(' + SELECT answer + FROM negative_answers + ORDER BY RANDOM() + LIMIT 1 '); } /** - * Checks for dependencies. + * Get the canonical form of a given term. * - * @return void + * In the canonical form all sequences of whitespace + * are replaced by a single space and all characters + * are lowercased. + * + * @param string $term Term for which a canonical form is required + * + * @return string Canonical term */ - public static function onLoad() + protected function getCanonicalTerm($term) { - if (!extension_loaded('PDO') || !extension_loaded('pdo_sqlite')) { - $this->fail('PDO and pdo_sqlite extensions must be installed'); - } + $canonicalTerm = strtolower(preg_replace('|\s+|', ' ', trim($term, '()'))); + switch ($canonicalTerm) { + case 'me': + $canonicalTerm = strtolower($this->event->getNick()); + break; + case 'all': + case '*': + case 'everything': + $canonicalTerm = 'everything'; + break; + } + return $canonicalTerm; } /** - * Handles requests for incrementation, decrementation, or lookup of karma - * ratings sent via messages from users. + * Intercepts a message and processes any contained recognized commands. * * @return void */ public function onPrivmsg() { - $source = $this->event->getSource(); - $message = $this->event->getArgument(1); - $target = $this->event->getNick(); + $message = $this->getEvent()->getText(); - // Command prefix check - $prefix = preg_quote(trim($this->getConfig('command.prefix'))); - $bot = preg_quote($this->getConfig('connections.nick')); - $exp = '(?:(?:' . $bot . '\s*[:,>]?\s+(?:' . $prefix . ')?)|(?:' . $prefix . '))'; + $termPattern = '\S+?|\([^<>]+?\)+'; + $actionPattern = '(?P\+\+|--)'; - // 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]); + $modifyPattern = <<karmaBlacklist) && in_array($term, $this->karmaBlacklist)) { - $this->doNotice($target, $term . ' is blacklisted'); - return; - } + (?: # start with ++ or -- before the term + $actionPattern + (?P$termPattern) + | # follow the term with ++ or -- + (?P$termPattern) + $actionPattern # allow no whitespace between the term and the action + ) + $}ix +REGEX; - // Return fixed value if set - if (isset($this->fixedKarma[$term])) { - $this->doPrivmsg($source, $target . ': ' . sprintf($this->fixedKarma[$term], $m[1]) . '.'); - return; - } + $versusPattern = <<$termPattern) + \s+(?P<|>)\s+ + (?P$termPattern)$# + $}ix +REGEX; - // Return current karma or neutral if not set yet - $this->fetchKarma->execute(array(':word'=>$term)); - $res = $this->fetchKarma->fetch(PDO::FETCH_ASSOC); + $match = null; - // 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(); - } - } - } + if (preg_match($modifyPattern, $message, $match)) { + $action = $match['action']; + $term = $this->getCanonicalTerm($match['term']); + $this->modifyKarma($term, $action); + } elseif (preg_match($versusPattern, $message, $match)) { + $term0 = trim($match['term0']); + $term1 = trim($match['term1']); + $method = $match['method']; + $this->compareKarma($term0, $term1, $method); } } - 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. + * Get the karma rating for a given term. + * + * @param string $term Term for which the karma rating needs to be + * retrieved * * @return void */ - public function doGc() + public function onCommandKarma($term) { - unset($this->log); - $this->log = array(); - $this->lastGc = date('d'); + $source = $this->getEvent()->getSource(); + $nick = $this->getEvent()->getNick(); + + if (empty($term)) { + return; + } + + $canonicalTerm = $this->getCanonicalTerm($term); + + $fixedKarma = $this->fetchFixedKarma($canonicalTerm); + if ($fixedKarma) { + $message = $nick . ': ' . $term . $fixedKarma . '.'; + $this->doPrivmsg($source, $message); + return; + } + + $karma = $this->fetchKarma($canonicalTerm); + + $message = $nick . ': '; + + if ($term == 'me') { + $message .= 'You have'; + } else { + $message .= $term . ' has'; + } + + $message .= ' '; + + if ($karma) { + $message .= 'karma of ' . $karma; + } else { + $message .= 'neutral karma'; + } + + $message .= '.'; + + $this->doPrivmsg($source, $message); + } + + /** + * Resets the karma for a term to 0. + * + * @param string $term Term for which to reset the karma rating + * + * @return void + */ + public function onCommandReincarnate($term) + { + $data = array( + ':term' => $term, + ':karma' => 0 + ); + $this->updateKarma->execute($data); + } + + /** + * Compares the karma between two terms. Optionally increases/decreases + * the karma of either term. + * + * @param string $term0 First term + * @param string $term1 Second term + * @param string $method Comparison method (< or >) + * + * @return void + */ + protected function compareKarma($term0, $term1, $method) + { + $event = $this->getEvent(); + $nick = $event->getNick(); + $source = $event->getSource(); + + $canonicalTerm0 = $this->getCanonicalTerm($term0); + $canonicalTerm1 = $this->getCanonicalTerm($term1); + + $fixedKarma0 = $this->fetchFixedKarma($canonicalTerm0); + $fixedKarma1 = $this->fetchFixedKarma($canonicalTerm1); + + if ($fixedKarma0 + || $fixedKarma1 + || empty($canonicalTerm0) + || empty($canonicalTerm1) + ) { + return; + } + + if ($canonicalTerm0 == 'everything') { + $change = $method == '<' ? '++' : '--'; + $this->modifyKarma($canonicalTerm1, $change); + $karma0 = 0; + $karma1 = $this->fetchKarma($canonicalTerm1); + } elseif ($canonicalTerm1 == 'everything') { + $change = $method == '<' ? '--' : '++'; + $this->modifyKarma($canonicalTerm0, $change); + $karma0 = $this->fetchKarma($canonicalTerm1); + $karma1 = 0; + } else { + $karma0 = $this->fetchKarma($canonicalTerm0); + $karma1 = $this->fetchKarma($canonicalTerm1); + } + + if (($method == '<' + && $karma0 < $karma1) + || ($method == '>' + && $karma0 > $karma1)) { + $replies = $this->fetchPositiveAnswer; + } else { + $replies = $this->fetchNegativeAnswer; + } + $replies->execute(); + $reply = $replies->fetchColumn(); + + if (max($karma0, $karma1) == $karma1) { + list($canonicalTerm0, $canonicalTerm1) = + array($canonicalTerm1, $canonicalTerm0); + } + + $message = str_replace( + array('%owner%','%owned%'), + array($canonicalTerm0, $canonicalTerm1), + $reply + ); + + $this->doPrivmsg($source, $message); + } + + /** + * Modifes a term's karma. + * + * @param string $term Term to modify + * @param string $action Karma action (either ++ or --) + * + * @return void + */ + protected function modifyKarma($term, $action) + { + if (empty($term)) { + return; + } + + $karma = $this->fetchKarma($term); + if ($karma !== false) { + $statement = $this->updateKarma; + } else { + $statement = $this->insertKarma; + } + + $karma += ($action == '++') ? 1 : -1; + + $args = array( + ':term' => $term, + ':karma' => $karma + ); + $statement->execute($args); + } + + /** + * Returns the karma rating for a specified term for which the karma + * rating can be modified. + * + * @param string $term Term for which to fetch the corresponding karma + * rating + * + * @return integer|boolean Integer value denoting the term's karma or + * FALSE if there is the specified term has no associated karma + * rating + */ + protected function fetchKarma($term) + { + $this->fetchKarma->execute(array(':term' => $term)); + $result = $this->fetchKarma->fetch(PDO::FETCH_ASSOC); + + if ($result === false) { + return false; + } + + return (int) $result['karma']; + } + + /** + * Returns a phrase describing the karma rating for a specified term for + * which the karma rating is fixed. + * + * @param string $term Term for which to fetch the corresponding karma + * rating + * + * @return string Phrase describing the karma rating, which may be append + * to the term to form a complete response + */ + protected function fetchFixedKarma($term) + { + $this->fetchFixedKarma->execute(array(':term' => $term)); + $result = $this->fetchFixedKarma->fetch(PDO::FETCH_ASSOC); + + if ($result === false) { + return false; + } + + return $result['karma']; } } diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Message.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Message.php new file mode 100644 index 0000000000..4dfbb30dee --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Message.php @@ -0,0 +1,112 @@ + + * @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_Message + */ + +/** + * Generalized plugin providing utility methods for + * prefix and bot named based message extraction. + * + * @category Phergie + * @package Phergie_Plugin_Message + * @author Phergie Development Team + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie_Plugin_Message + */ +class Phergie_Plugin_Message extends Phergie_Plugin_Abstract +{ + + /** + * Check whether a message is specifically targeted at the bot. + * This is the case when the message starts with the bot's name + * followed by [,:>] or when it is a private message. + * + * @return boolean true when the message is specifically targeted at the bot, + * false otherwise. + */ + public function isTargetedMessage() + { + $event = $this->getEvent(); + + $self = preg_quote($this->connection->getNick()); + + $targetPattern = <<,].* # expect the bots name, followed by a [:>,] + $}ix +REGEX; + + return !$event->isInChannel() + || preg_match($targetPattern, $event->getText()) > 0; + } + + /** + * Allow for prefix and bot name aware extraction of a message + * + * @return string|bool $message The message, which is possibly targeted at the + * bot or false if a prefix requirement failed + */ + public function getMessage() + { + $event = $this->getEvent(); + + $prefix = preg_quote($this->getConfig('command.prefix')); + $self = preg_quote($this->connection->getNick()); + $message = $event->getText(); + + // $prefixPattern matches : Phergie, do command + // where $prefix = 'do' : do command + // : Phergie, command + $prefixPattern = <<,]\s* # start with bot name + (?:{$prefix})? # which is optionally followed by the prefix + | + \s*{$prefix} # or start with the prefix + ) + \s*(.*) # always end with the message + $}ix +REGEX; + + // $noPrefixPattern matches : Phergie, command + // : command + $noPrefixPattern = <<,]\s*)? # optionally start with the bot name + (.*?) # always end with the message + $}ix +REGEX; + + $pattern = $noPrefixPattern; + + // If a prefix is set, force it as a requirement + if ($prefix && $event->isInChannel()) { + $pattern = $prefixPattern; + } + + $match = null; + + if (!preg_match($pattern, $message, $match)) { + return false; + } + + return $match[1]; + } +} \ No newline at end of file diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Ping.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Ping.php index 29ac69f464..021670d97d 100755 --- a/plugins/Irc/extlib/phergie/Phergie/Plugin/Ping.php +++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Ping.php @@ -87,7 +87,7 @@ class Phergie_Plugin_Ping extends Phergie_Plugin_Abstract { $time = time(); if (!empty($this->lastPing)) { - if ($time - $this->lastPing > $this->getConfig('ping.ping', 10)) { + if ($time - $this->lastPing > $this->getConfig('ping.ping', 20)) { $this->doQuit(); } } elseif ( @@ -112,7 +112,7 @@ class Phergie_Plugin_Ping extends Phergie_Plugin_Abstract /** * Set the last ping time * lastPing needs to be exposed for unit testing - * + * * @param int|null $ping timestamp of last ping * * @return self diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Quit.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Quit.php index cacd77d3f3..eca22a98e3 100755 --- a/plugins/Irc/extlib/phergie/Phergie/Plugin/Quit.php +++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Quit.php @@ -1,6 +1,6 @@ * @copyright 2008-2010 Phergie Development Team (http://phergie.org) @@ -22,14 +22,12 @@ /** * Terminates the current connection upon command. * - * @category Phergie + * @category Phergie * @package Phergie_Plugin_Quit * @author Phergie Development Team * @license http://phergie.org/license New BSD License * @link http://pear.phergie.org/package/Phergie_Plugin_Quit * @uses Phergie_Plugin_Command pear.phergie.org - * - * @pluginDesc Terminates the current connection upon command. */ class Phergie_Plugin_Quit extends Phergie_Plugin_Abstract { @@ -41,17 +39,13 @@ class Phergie_Plugin_Quit extends Phergie_Plugin_Abstract public function onLoad() { $this->getPluginHandler()->getPlugin('Command'); - $help = $this->getPluginHandler()->getPlugin('Help'); - $help->register($this); } /** - * Issues a quit command when a message is received requesting that the + * Issues a quit command when a message is received requesting that the * bot terminate the current connection. * * @return void - * - * @pluginCmd terminates the connection */ public function onCommandQuit() { diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Reload.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Reload.php index a2d680d7aa..90e3adcd52 100755 --- a/plugins/Irc/extlib/phergie/Phergie/Plugin/Reload.php +++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Reload.php @@ -57,6 +57,7 @@ class Phergie_Plugin_Reload extends Phergie_Plugin_Abstract if (!$this->plugins->hasPlugin($plugin)) { echo 'DEBUG(Reload): ' . ucfirst($plugin) . ' is not loaded yet, loading', PHP_EOL; $this->plugins->getPlugin($plugin); + $this->plugins->command->populateMethodCache(); return; } @@ -79,7 +80,7 @@ class Phergie_Plugin_Reload extends Phergie_Plugin_Abstract } $contents = preg_replace( - array('/<\?(?:php)?/', '/class\s+' . $class . '/i'), + array('/^<\?(?:php)?/', '/class\s+' . $class . '/i'), array('', 'class ' . $newClass), $contents ); @@ -87,10 +88,16 @@ class Phergie_Plugin_Reload extends Phergie_Plugin_Abstract $instance = new $newClass; $instance->setName($plugin); + $instance->setEvent($this->event); $this->plugins ->removePlugin($plugin) ->addPlugin($instance); + $this->plugins->command->populateMethodCache(); + if ($this->plugins->hasPlugin('Help')) { + $this->plugins->help->populateRegistry(); + } + echo 'DEBUG(Reload): Reloaded ', $class, ' to ', $newClass, PHP_EOL; } } diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Serve.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Serve.php index 19ecb0c7e4..cdb8f7f919 100755 --- a/plugins/Irc/extlib/phergie/Phergie/Plugin/Serve.php +++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Serve.php @@ -113,20 +113,21 @@ class Phergie_Plugin_Serve extends Phergie_Plugin_Abstract $item = $this->getItem($database, $table, $match); // Reprocess the request for censorship if required - $attempts = 0; - while ($censor && $attempts < 3) { + if ($this->plugins->hasPlugin('Censor')) { $plugin = $this->plugins->getPlugin('Censor'); - $clean = $plugin->cleanString($item->name); - if ($item->name != $clean) { - $attempts++; - $item = $this->getItem($database, $table, $match); - } else { - $censor = false; + $attempts = 0; + while ($censor && $attempts < 3) { + $clean = $plugin->cleanString($item->name); + if ($item->name != $clean) { + $attempts++; + $item = $this->getItem($database, $table, $match); + } else { + $censor = false; + } + } + if ($censor && $attempts == 3) { + $this->doAction($this->event->getSource(), 'shrugs.'); } - } - - if ($censor && $attempts == 3) { - $this->doAction($this->event->getSource(), 'shrugs.'); } // Derive the proper article for the item diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Tea.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Tea.php new file mode 100644 index 0000000000..c6453566c7 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Tea.php @@ -0,0 +1,69 @@ + + * @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_Tea + */ + +/** + * Processes requests to serve users tea. + * + * @category Phergie + * @package Phergie_Plugin_Tea + * @author Phergie Development Team + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie_Plugin_Tea + * @uses Phergie_Plugin_Command pear.phergie.org + * @uses Phergie_Plugin_Serve pear.phergie.org + */ +class Phergie_Plugin_Tea extends Phergie_Plugin_Abstract +{ + /** + * Checks for dependencies. + * + * @return void + */ + public function onLoad() + { + $plugins = $this->plugins; + $plugins->getPlugin('Command'); + $plugins->getPlugin('Serve'); + } + + /** + * Processes requests to serve a user tea. + * + * @param string $request Request including the target and an optional + * suggestion of what tea to serve + * + * @return void + */ + public function onCommandTea($request) + { + $format = $this->getConfig( + 'tea.format', + 'serves %target% a cup of %item% tea.' + ); + + $this->plugins->getPlugin('Serve')->serve( + dirname(__FILE__) . '/Tea/tea.db', + 'tea', + $format, + $request + ); + } +} diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Tea/db.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Tea/db.php new file mode 100644 index 0000000000..21fe1950d1 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Tea/db.php @@ -0,0 +1,49 @@ +exec('CREATE TABLE tea (name VARCHAR(255), link VARCHAR(255))'); +$db->exec('CREATE UNIQUE INDEX tea_name ON tea (name)'); +$insert = $db->prepare('INSERT INTO tea (name, link) VALUES (:name, :link)'); + +// Get raw teacuppa.com data set +echo 'Downloading teacuppa.com data set', PHP_EOL; +$file = __DIR__ . '/tea-list.html'; +if (!file_exists($file)) { + copy('http://www.teacuppa.com/tea-list.asp', $file); +} +$contents = file_get_contents($file); + +// Extract data from data set +echo 'Processing teacuppa.com data', PHP_EOL; +$contents = tidy_repair_string($contents); +libxml_use_internal_errors(true); +$doc = new DOMDocument; +$doc->loadHTML($contents); +libxml_clear_errors(); +$xpath = new DOMXPath($doc); +$teas = $xpath->query('//p[@class="page_title"]/following-sibling::table//a'); +$db->beginTransaction(); +foreach ($teas as $tea) { + $name = preg_replace( + array('/\s*\v+\s*/', '/\s+tea\s*$/i'), + array(' ', ''), + $tea->textContent + ); + $link = 'http://teacuppa.com/' . $tea->getAttribute('href'); + $insert->execute(array($name, $link)); +} +$db->commit(); + +// Clean up +echo 'Cleaning up', PHP_EOL; +unlink($file); diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/TheFuckingWeather.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/TheFuckingWeather.php index e8a02860b2..8559426b61 100644 --- a/plugins/Irc/extlib/phergie/Phergie/Plugin/TheFuckingWeather.php +++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/TheFuckingWeather.php @@ -31,9 +31,6 @@ * @link http://thefuckingweather.com * @uses Phergie_Plugin_Command pear.phergie.org * @uses Phergie_Plugin_Http pear.phergie.org - * - * @pluginDesc Detects and responds to requests for current weather - * conditions in a particular location. */ class Phergie_Plugin_TheFuckingWeather extends Phergie_Plugin_Abstract @@ -71,8 +68,6 @@ class Phergie_Plugin_TheFuckingWeather extends Phergie_Plugin_Abstract * * @return void * @todo Implement use of URL shortening here - * @pluginCmd [location] Detects and responds to requests for current - * weather conditions in a particular location. */ public function onCommandThefuckingweather($location) { @@ -92,7 +87,6 @@ class Phergie_Plugin_TheFuckingWeather extends Phergie_Plugin_Abstract * @param string $location Location term * * @return void - * @pluginCmd [location] Alias for thefuckingweather command. */ public function onCommandTfw($location) { diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Tld.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Tld.php index 21993f73c5..d7d64a4717 100644 --- a/plugins/Irc/extlib/phergie/Phergie/Plugin/Tld.php +++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Tld.php @@ -31,31 +31,32 @@ * @link http://pear.phergie.org/package/Phergie_Plugin_Tld * @uses extension PDO * @uses extension pdo_sqlite - * - * @pluginDesc Provides information for a top level domain. */ class Phergie_Plugin_Tld extends Phergie_Plugin_Abstract { /** - * connection to the database + * Connection to the database + * * @var PDO */ protected $db; /** - * Prepared statement for selecting a single tld + * Prepared statement for selecting a single TLD + * * @var PDOStatement */ protected $select; /** - * Prepared statement for selecting all tlds + * Prepared statement for selecting all TLDs + * * @var PDOStatement */ protected $selectAll; /** - * Checks for dependencies, sets up database and hard coded values + * Checks for dependencies and sets up the database and hard-coded values. * * @return void */ @@ -65,9 +66,6 @@ class Phergie_Plugin_Tld extends Phergie_Plugin_Abstract $this->fail('PDO and pdo_sqlite extensions must be installed'); } - $help = $this->getPluginHandler()->getPlugin('Help'); - $help->register($this); - $dbFile = dirname(__FILE__) . '/Tld/tld.db'; try { $this->db = new PDO('sqlite:' . $dbFile); @@ -93,8 +91,6 @@ class Phergie_Plugin_Tld extends Phergie_Plugin_Abstract * @param string $tld tld to process * * @return null - * - * @pluginCmd .[tld] request details about the tld */ public function onCommandTld($tld) { diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Tld/db.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Tld/db.php index 364d9a09f8..28f963ad10 100644 Binary files a/plugins/Irc/extlib/phergie/Phergie/Plugin/Tld/db.php and b/plugins/Irc/extlib/phergie/Phergie/Plugin/Tld/db.php differ diff --git a/plugins/Irc/extlib/phergie/Phergie/Plugin/Youtube.php b/plugins/Irc/extlib/phergie/Phergie/Plugin/Youtube.php index b19e9d5aef..e06ea5640b 100644 --- a/plugins/Irc/extlib/phergie/Phergie/Plugin/Youtube.php +++ b/plugins/Irc/extlib/phergie/Phergie/Plugin/Youtube.php @@ -148,7 +148,7 @@ class Phergie_Plugin_Youtube extends Phergie_Plugin_Abstract { switch ($parsed['host']) { case 'youtu.be': - $v = ltrim('/', $parsed['path']); + $v = ltrim($parsed['path'], '/'); break; case 'youtube.com': case 'www.youtube.com': diff --git a/plugins/Irc/extlib/phergie/Phergie/Process/Abstract.php b/plugins/Irc/extlib/phergie/Phergie/Process/Abstract.php index 68d45289e8..0ec3569feb 100755 --- a/plugins/Irc/extlib/phergie/Phergie/Process/Abstract.php +++ b/plugins/Irc/extlib/phergie/Phergie/Process/Abstract.php @@ -1,6 +1,6 @@ * @copyright 2008-2010 Phergie Development Team (http://phergie.org) @@ -22,7 +22,7 @@ /** * Base class for obtaining and processing incoming events. * - * @category Phergie + * @category Phergie * @package Phergie * @author Phergie Development Team * @license http://phergie.org/license New BSD License @@ -38,9 +38,9 @@ abstract class Phergie_Process_Abstract protected $driver; /** - * Current connection handler instance + * Current connection handler instance * - * @var Phergie_Connection_Handler + * @var Phergie_Connection_Handler */ protected $connections; @@ -75,9 +75,9 @@ abstract class Phergie_Process_Abstract /** * Gets the required class refences from Phergie_Bot. * - * @param Phergie_Bot $bot Current bot instance in use + * @param Phergie_Bot $bot Current bot instance in use * @param array $options Optional processor arguments - * + * * @return void */ public function __construct(Phergie_Bot $bot, array $options = array()) @@ -99,8 +99,8 @@ abstract class Phergie_Process_Abstract */ protected function processEvents(Phergie_Connection $connection) { + $this->plugins->preDispatch(); if (count($this->events)) { - $this->plugins->preDispatch(); foreach ($this->events as $event) { $this->ui->onCommand($event, $connection); @@ -110,15 +110,15 @@ abstract class Phergie_Process_Abstract $event->getArguments() ); } - $this->plugins->postDispatch(); - - if ($this->events->hasEventOfType(Phergie_Event_Request::TYPE_QUIT)) { - $this->ui->onQuit($connection); - $this->connections->removeConnection($connection); - } - - $this->events->clearEvents(); } + $this->plugins->postDispatch(); + + if ($this->events->hasEventOfType(Phergie_Event_Request::TYPE_QUIT)) { + $this->ui->onQuit($connection); + $this->connections->removeConnection($connection); + } + + $this->events->clearEvents(); } /** diff --git a/plugins/Irc/extlib/phergie/Phergie/Process/Async.php b/plugins/Irc/extlib/phergie/Phergie/Process/Async.php index e553ad5a82..78d5959144 100644 --- a/plugins/Irc/extlib/phergie/Phergie/Process/Async.php +++ b/plugins/Irc/extlib/phergie/Phergie/Process/Async.php @@ -117,11 +117,7 @@ class Phergie_Process_Async extends Phergie_Process_Abstract if ($event = $this->driver->getEvent()) { $this->ui->onEvent($event, $connection); $this->plugins->setEvent($event); - - if (!$this->plugins->preEvent()) { - continue; - } - + $this->plugins->preEvent(); $this->plugins->{'on' . ucfirst($event->getType())}(); } diff --git a/plugins/Irc/extlib/phergie/Phergie/Process/Standard.php b/plugins/Irc/extlib/phergie/Phergie/Process/Standard.php index 385c65fa22..24adbaf991 100644 --- a/plugins/Irc/extlib/phergie/Phergie/Process/Standard.php +++ b/plugins/Irc/extlib/phergie/Phergie/Process/Standard.php @@ -1,6 +1,6 @@ * @copyright 2008-2010 Phergie Development Team (http://phergie.org) @@ -23,7 +23,7 @@ * Connection data processor which reads all connections looking * for a response. * - * @category Phergie + * @category Phergie * @package Phergie * @author Phergie Development Team * @license http://phergie.org/license New BSD License @@ -32,7 +32,7 @@ class Phergie_Process_Standard extends Phergie_Process_Abstract { /** - * Obtains and processes incoming events, then sends resulting outgoing + * Obtains and processes incoming events, then sends resulting outgoing * events. * * @return void @@ -47,11 +47,7 @@ class Phergie_Process_Standard extends Phergie_Process_Abstract if ($event = $this->driver->getEvent()) { $this->ui->onEvent($event, $connection); $this->plugins->setEvent($event); - - if (!$this->plugins->preEvent()) { - continue; - } - + $this->plugins->preEvent(); $this->plugins->{'on' . ucfirst($event->getType())}(); } diff --git a/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/HandlerTest.php b/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/HandlerTest.php index 0d6ac454a8..dcf52a65a1 100644 --- a/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/HandlerTest.php +++ b/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/HandlerTest.php @@ -178,35 +178,11 @@ class Phergie_Plugin_HandlerTest extends PHPUnit_Framework_TestCase /** * Tests the plugin handler executing a callback on all contained - * plugins where one plugin short-circuits the process. + * plugins. * * @return void */ - public function testImplementsCallWithShortCircuit() - { - $plugin1 = $this->getMockPlugin('TestPlugin1', array('callback')); - $plugin1 - ->expects($this->once()) - ->method('callback') - ->will($this->returnValue(false)); - $this->handler->addPlugin($plugin1); - - $plugin2 = $this->getMockPlugin('TestPlugin2', array('callback')); - $plugin2 - ->expects($this->exactly(0)) - ->method('callback'); - $this->handler->addPlugin($plugin2); - - $this->assertFalse($this->handler->callback()); - } - - /** - * Tests the plugin handler executing a callback on all contained - * plugins where no plugins short-circuit the process. - * - * @return void - */ - public function testImplementsCallWithoutShortCircuit() + public function testImplementsCall() { foreach (range(1, 2) as $index) { $plugin = $this->getMockPlugin('TestPlugin' . $index, array('callback')); @@ -736,4 +712,25 @@ class Phergie_Plugin_HandlerTest extends PHPUnit_Framework_TestCase $actual = $this->handler->getPlugins(array('testplugin1', 'testplugin2')); $this->assertEquals($expected, $actual); } + + /** + * Tests the plugin receiving and using a predefined iterator instance. + * + * @depends testGetPlugins + * @return void + */ + public function testSetIterator() + { + $plugin = $this->getMockPlugin('TestPlugin'); + $this->handler->addPlugin($plugin); + $plugins = $this->handler->getPlugins(); + $iterator = new ArrayIterator($plugins); + $this->handler->setIterator($iterator); + $this->assertSame($this->handler->getIterator(), $iterator); + $iterated = array(); + foreach ($this->handler as $plugin) { + $iterated[strtolower($plugin->getName())] = $plugin; + } + $this->assertEquals($iterated, $plugins); + } } diff --git a/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/IteratorTest.php b/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/IteratorTest.php new file mode 100644 index 0000000000..336b25d355 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/IteratorTest.php @@ -0,0 +1,151 @@ + + * @copyright 2008-2010 Phergie Development Team (http://phergie.org) + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie_Tests + */ + +/** + * Unit test suite for Pherge_Plugin_Iterator. + * + * @category Phergie + * @package Phergie_Tests + * @author Phergie Development Team + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie_Tests + */ +class Phergie_Plugin_IteratorTest extends PHPUnit_Framework_TestCase +{ + /** + * Iterator instance being tested + * + * @var Phergie_Plugin_Iterator + */ + protected $iterator; + + /** + * List of mock plugin instances to be iterated + * + * @var array + */ + protected $plugins; + + /** + * Initializes the iterator instance being tested. + * + * @return void + */ + public function setUp() + { + $this->plugins = array(); + foreach (range(0, 4) as $index) { + $plugin = $this->getMock('Phergie_Plugin_Abstract'); + $plugin + ->expects($this->any()) + ->method('getName') + ->will($this->returnValue($index)); + $this->plugins[] = $plugin; + } + + $this->iterator = new Phergie_Plugin_Iterator( + new ArrayIterator($this->plugins) + ); + } + + /** + * Tests that all plugins are iterated when no filters are applied. + */ + public function testIteratesAllPluginsWithNoFilters() + { + $expected = range(0, 4); + $actual = array(); + foreach ($this->iterator as $plugin) { + $actual[] = $plugin->getName(); + } + $this->assertEquals($expected, $actual); + } + + /** + * Tests that appropriate plugins are iterated when plugin name filters + * are applied. + */ + public function testIteratesPluginsWithNameFilters() + { + // Test acceptance of strings and fluent interface implementation + $returned = $this->iterator->addPluginFilter('0'); + $this->assertSame($this->iterator, $returned); + + // Test acceptance of arrays + $this->iterator->addPluginFilter(array('1', '3')); + + // Test application of filters to iteration + $expected = array('2', '4'); + $actual = array(); + foreach ($this->iterator as $plugin) { + $actual[] = $plugin->getName(); + } + $this->assertEquals($expected, $actual); + } + + /** + * Tests that appropriate plugins are iterated when method name filters + * are applied. + * + * The same method name is used in all cases here because mocked methods + * of mock objects do not appear to be detected by method_exists() or + * ReflectionClass, so filtering by a method defined in the base plugin + * class seems the easiest way to test that method filtering really + * works. + */ + public function testIteratesPluginsWithMethodFilters() + { + // Tests acceptance of strings and fluent interface implementation + $returned = $this->iterator->addMethodFilter('getName'); + $this->assertSame($this->iterator, $returned); + + // Test acceptance of arrays + $this->iterator->addMethodFilter(array('getName', 'getName')); + + // Test application of filters to iteration + $expected = array(); + $actual = array(); + foreach ($this->iterator as $plugin) { + $actual[] = $plugin->getName(); + } + $this->assertEquals($expected, $actual); + } + + /** + * Tests that all plugins are iterated after filters are cleared. + * + * @depends testIteratesPluginsWithNameFilters + * @depends testIteratesPluginsWithMethodFilters + */ + public function testIteratesPluginsAfterClearingFilters() + { + $this->iterator->addPluginFilter('0'); + $this->iterator->addMethodFilter('method1'); + $this->iterator->clearFilters(); + + $expected = range(0, 4); + $actual = array(); + foreach ($this->iterator as $plugin) { + $actual[] = $plugin->getName(); + } + $this->assertEquals($expected, $actual); + } +}