code:
<?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_Url
 * @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_Url
 */

/**
 * Monitors incoming messages for instances of URLs and responds with messages
 * containing relevant information about detected URLs.
 *
 * Has an utility method accessible via
 * $this->getPlugin('Url')->getTitle('http://foo..').
 *
 * @category Phergie
 * @package  Phergie_Plugin_Url
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Url
 * @uses     Phergie_Plugin_Encoding pear.phergie.org
 * @uses     Phergie_Plugin_Http pear.phergie.org
 * @uses     Phergie_Plugin_Tld pear.phergie.org
 */
class Phergie_Plugin_Url extends Phergie_Plugin_Abstract
{
    /**
     * Links output format
     *
     * Can use the variables %nick%, %title% and %link% in it to display
     * page titles and links
     *
     * @var string
     */
    protected $baseFormat = '%message%';
    protected $messageFormat = '[ %link% ] %title%';

    /**
     * Flag indicating whether a single response should be sent for a single
     * message containing multiple links
     *
     * @var bool
     */
    protected $mergeLinks = true;

    /**
     * Max length of the fetched URL title
     *
     * @var int
     */
    protected $titleLength = 40;

    /**
     * Url cache to prevent spamming, especially with multiple bots on the
     * same channel
     *
     * @var array
     */
    protected $urlCache = array();
    protected $shortCache = array();

    /**
     * Time in seconds to store the cached entries
     *
     * Setting it to 0 or below disables the cache expiration
     *
     * @var int
     */
    protected $expire = 1800;

    /**
     * Number of entries to keep in the cache at one time per channel
     *
     * Setting it to 0 or below disables the cache limit
     *
     * @var int
     */
    protected $limit = 10;

    /**
     * Flag that determines if the plugin will fall back to using an HTTP
     * stream when a URL using SSL is detected and OpenSSL support isn't
     * available in the PHP installation in use
     *
     * @var bool
     */
    protected $sslFallback = true;

    /**
     * Flag that is set to true by the custom error handler if an HTTP error
     * code has been received
     *
     * @var boolean
     */
    protected $errorStatus = false;
    protected $errorMessage = null;

    /**
     * Flag indicating whether or not to display error messages as the title
     * if a link posted encounters an error
     *
     * @var boolean
     */
    protected $showErrors = true;

    /**
     * Flag indicating whether to detect schemeless URLS (i.e. "example.com")
     *
     * @var boolean
     */
    protected $detectSchemeless = false;

    /**
     * Shortener object
     */
    protected $shortener;

    /**
     * Array of renderers
     */
    protected $renderers = array();

    /**
     * Checks for dependencies.
     *
     * @return void
     */
    public function onLoad()
    {
        $plugins = $this->plugins;
        $plugins->getPlugin('Encoding');
        $plugins->getPlugin('Http');
        $plugins->getPlugin('Tld');

        // make the shortener configurable
        $shortener = $this->getConfig('url.shortener', 'Trim');
        $shortener = "Phergie_Plugin_Url_Shorten_{$shortener}";
        $this->shortener = new $shortener($this->plugins->getPlugin('Http'));

        if (!$this->shortener instanceof Phergie_Plugin_Url_Shorten_Abstract) {
            $this->fail("Declared shortener class {$shortener} is not of proper ancestry");
        }

        // load config (a bit ugly, but focusing on porting):
        foreach (
            array(
                'detect_schemeless' => 'detectSchemeless',
                'base_format' => 'baseFormat',
                'message_format' => 'messageFormat',
                'merge_links' => 'mergeLinks',
                'title_length' => 'titleLength',
                'show_errors' => 'showErrors',
                'expire' => 'expire',
            ) as $config => $local) {
            if (isset($this->config["url.{$config}"])) {
                $this->$local = $this->config["uri.{$config}"];
            }
        }
    }

    /**
     * Checks an incoming message for the presence of a URL and, if one is
     * found, responds with its title if it is an HTML document and the
     * shortened equivalent of its original URL if it meets length requirements.
     *
     * @todo Update this to pull configuration settings from $this->config
     *       rather than caching them as class properties
     * @return void
     */
    public function onPrivmsg()
    {
        $this->handleMsg();
    }

    /**
     * Checks an incoming message for the presence of a URL and, if one is
     * found, responds with its title if it is an HTML document and the
     * shortened equivalent of its original URL if it meets length requirements.
     *
     * @todo Update this to pull configuration settings from $this->config
     *       rather than caching them as class properties
     * @return void
     */
    public function onAction()
    {
        $this->handleMsg();
    }

    /**
     * Handles message events and responds with url titles.
     *
     * @return void
     */
    protected function handleMsg()
    {
        $source = $this->getEvent()->getSource();
        $user = $this->getEvent()->getNick();

        $responses = array();
        $urls = $this->findUrls($this->getEvent()->getArgument(1));

        foreach ($urls as $parsed) {
            $url = $parsed['glued'];

            // allow out-of-class renderers to handle this URL
            foreach ($this->renderers as $renderer) {
                if ($renderer->renderUrl($parsed) === true) {
                    // renderers should return true if they've fully
                    // rendered the passed URL (they're responsible
                    // for their own output)
                    $this->debug('Handled by renderer: ' . get_class($renderer));
                    continue 2;
                }
            }

            // Convert url
            $shortenedUrl = $this->shortener->shorten($url);
            if (!$shortenedUrl) {
                $this->debug('Invalid Url: Unable to shorten. (' . $url . ')');
                $shortenedUrl = $url;
            }

            // Prevent spamfest
            if ($this->checkUrlCache($url, $shortenedUrl)) {
                $this->debug('Invalid Url: URL is in the cache. (' . $url . ')');
                continue;
            }

            $title = $this->getTitle($url);
            if (!empty($title)) {
                $responses[] = str_replace(
                    array(
                        '%title%',
                        '%link%',
                        '%nick%'
                    ), array(
                        $title,
                        $shortenedUrl,
                        $user
                    ), $this->messageFormat
                );
            }

            // Update cache
            $this->updateUrlCache($url, $shortenedUrl);
            unset($title, $shortenedUrl, $title);
        }

        // Check to see if there were any URL responses, format them and handle if they
        // get merged into one message or not
        if (count($responses) > 0) {
            if ($this->mergeLinks) {
                $message = str_replace(
                    array(
                        '%message%',
                        '%nick%'
                    ), array(
                        implode('; ', $responses),
                        $user
                    ), $this->baseFormat
                );
                $this->doPrivmsg($source, $message);
            } else {
                foreach ($responses as $response) {
                    $message = str_replace(
                        array(
                            '%message%',
                            '%nick%'
                        ), array(
                            implode('; ', $responses),
                            $user
                        ), $this->baseFormat
                    );
                    $this->doPrivmsg($source, $message);
                }
            }
        }
    }

    /**
     * Detect URLs in a given string.
     *
     * @param string $message the string to detect urls in
     *
     * @return array the array of urls found
     */
    public function findUrls($message)
    {
        $pattern = '#'.($this->detectSchemeless ? '' : 'https?://').'(?:([0-9]{1,3}(?:\.[0-9]{1,3}){3})(?![^/]) | ('
            .($this->detectSchemeless ? '(?<!http:/|https:/)[@/\\\]' : '').')?(?:(?:[a-z0-9_-]+\.?)+\.[a-z0-9]{1,6}))[^\s]*#xis';
        $urls = array();

        // URL Match
        if (preg_match_all($pattern, $message, $matches, PREG_SET_ORDER)) {
            foreach ($matches as $m) {
                $url = trim(rtrim($m[0], ', ].?!;'));

                // Check to see if the URL was from an email address, is a directory, etc
                if (!empty($m[2])) {
                    $this->debug('Invalid Url: URL is either an email or a directory path. (' . $url . ')');
                    continue;
                }

                // Parse the given URL
                if (!$parsed = $this->parseUrl($url)) {
                    $this->debug('Invalid Url: Could not parse the URL. (' . $url . ')');
                    continue;
                }

                // Check to see if the given IP/Host is valid
                if (!empty($m[1]) and !$this->checkValidIP($m[1])) {
                    $this->debug('Invalid Url: ' . $m[1] . ' is not a valid IP address. (' . $url . ')');
                    continue;
                }

                // Process TLD if it's not an IP
                if (empty($m[1])) {
                    // Get the TLD from the host
                    $pos = strrpos($parsed['host'], '.');
                    $parsed['tld'] = ($pos !== false ? substr($parsed['host'], ($pos+1)) : '');

                    // Check to see if the URL has a valid TLD
                    if ($this->plugins->tld->getTld($parsed['tld']) === false) {
                        $this->debug('Invalid Url: ' . $parsed['tld'] . ' is not a supported TLD. (' . $url . ')');
                        continue;
                    }
                }

                // Check to see if the URL is to a secured site or not and handle it accordingly
                if ($parsed['scheme'] == 'https' && !extension_loaded('openssl')) {
                    if (!$this->sslFallback) {
                        $this->debug('Invalid Url: HTTPS is an invalid scheme, OpenSSL isn\'t available. (' . $url . ')');
                        continue;
                    } else {
                        $parsed['scheme'] = 'http';
                    }
                }

                if (!in_array($parsed['scheme'], array('http', 'https'))) {
                    $this->debug('Invalid Url: ' . $parsed['scheme'] . ' is not a supported scheme. (' . $url . ')');
                    continue;
                }

                $urls[] = $parsed + array('glued' => $this->glueURL($parsed));
            }
        }

        return $urls;
    }

    /**
     * Checks a given URL (+shortened) against the cache to verify if they were
     * previously posted on the channel.
     *
     * @param string $url          The URL to check against
     * @param string $shortenedUrl The shortened URL to check against
     *
     * @return bool
     */
    protected function checkUrlCache($url, $shortenedUrl)
    {
        $source = $this->getEvent()->getSource();

        /**
         * Transform the URL (+shortened) into a HEX CRC32 checksum to prevent potential problems
         * and minimize the size of the cache for less cache bloat.
         */
        $url = $this->getUrlChecksum($url);
        $shortenedUrl = $this->getUrlChecksum($shortenedUrl);

        $cache = array(
            'url' => isset($this->urlCache[$source][$url]) ? $this->urlCache[$source][$url] : null,
            'shortened' => isset($this->shortCache[$source][$shortenedUrl]) ? $this->shortCache[$source][$shortenedUrl] : null
        );

        $expire = $this->expire;
        $this->debug("Cache expire: {$expire}");
        /**
         * If cache expiration is enabled, check to see if the given url has expired in the cache
         * If expire is disabled, simply check to see if the url is listed
         */
        if (($expire > 0 && (($cache['url'] + $expire) > time() || ($cache['shortened'] + $expire) > time()))
            || ($expire <= 0 && (isset($cache['url']) || isset($cache['shortened'])))
        ) {
            unset($cache, $url, $shortenedUrl, $expire);
            return true;
        }
        unset($cache, $url, $shortenedUrl, $expire);
        return false;
    }

    /**
     * Updates the cache and adds the given URL (+shortened) to the cache. It
     * also handles cleaning the cache of old entries as well.
     *
     * @param string $url          The URL to add to the cache
     * @param string $shortenedUrl The shortened to add to the cache
     *
     * @return bool
     */
    protected function updateUrlCache($url, $shortenedUrl)
    {
        $source = $this->getEvent()->getSource();

        /**
         * Transform the URL (+shortened) into a HEX CRC32 checksum to prevent potential problems
         * and minimize the size of the cache for less cache bloat.
         */
        $url = $this->getUrlChecksum($url);
        $shortenedUrl = $this->getUrlChecksum($shortenedUrl);
        $time = time();

        // Handle the URL cache and remove old entries that surpass the limit if enabled
        $this->urlCache[$source][$url] = $time;
        if ($this->limit > 0 && count($this->urlCache[$source]) > $this->limit) {
            asort($this->urlCache[$source], SORT_NUMERIC);
            array_shift($this->urlCache[$source]);
        }

        // Handle the shortened cache and remove old entries that surpass the limit if enabled
        $this->shortCache[$source][$shortenedUrl] = $time;
        if ($this->limit > 0 && count($this->shortCache[$source]) > $this->limit) {
            asort($this->shortCache[$source], SORT_NUMERIC);
            array_shift($this->shortCache[$source]);
        }
        unset($url, $shortenedUrl, $time);
    }

    /**
     * Transliterates a UTF-8 string into corresponding ASCII characters and
     * truncates and appends an ellipsis to the string if it exceeds a given
     * length.
     *
     * @param string $str  String to decode
     * @param int    $trim Maximum string length, optional
     *
     * @return string
     */
    protected function decode($str, $trim = null)
    {
        $out = $this->plugins->encoding->transliterate($str);
        if ($trim > 0) {
            $out = substr($out, 0, $trim) . (strlen($out) > $trim ? '...' : '');
        }
        return $out;
    }

    /**
     * Takes a url, parses and cleans the URL without of all the junk
     * and then return the hex checksum of the url.
     *
     * @param string $url url to checksum
     *
     * @return string the hex checksum of the cleaned url
     */
    protected function getUrlChecksum($url)
    {
        $checksum = strtolower(urldecode($this->glueUrl($url, true)));
        $checksum = preg_replace('#\s#', '', $this->plugins->encoding->transliterate($checksum));
        return dechex(crc32($checksum));
    }

    /**
     * Parses a given URI and procceses the output to remove redundant
     * or missing values.
     *
     * @param string $url the url to parse
     *
     * @return array the url components
     */
    protected function parseUrl($url)
    {
        if (is_array($url)) return $url;

        $url = trim(ltrim($url, ' /@\\'));
        if (!preg_match('&^(?:([a-z][-+.a-z0-9]*):)&xis', $url, $matches)) {
            $url = 'http://' . $url;
        }
        $parsed = parse_url($url);

        if (!isset($parsed['scheme'])) {
            $parsed['scheme'] = 'http';
        }
        $parsed['scheme'] = strtolower($parsed['scheme']);

        if (isset($parsed['path']) && !isset($parsed['host'])) {
            $host = $parsed['path'];
            $path = '';
            if (strpos($parsed['path'], '/') !== false) {
                list($host, $path) = array_pad(explode('/', $parsed['path'], 2), 2, null);
            }
            $parsed['host'] = $host;
            $parsed['path'] = $path;
        }

        return $parsed;
    }

    /**
     * Parses a given URI and then glues it back together in the proper format.
     * If base is set, then it chops off the scheme, user and pass and fragment
     * information to return a more unique base URI.
     *
     * @param string $uri  uri to rebuild
     * @param string $base set to true to only return the base components
     *
     * @return string the rebuilt uri
     */
    protected function glueUrl($uri, $base = false)
    {
        $parsed = $uri;
        if (!is_array($parsed)) {
            $parsed = $this->parseUrl($parsed);
        }

        if (is_array($parsed)) {
            $uri = '';
            if (!$base) {
                $uri .= (!empty($parsed['scheme']) ? $parsed['scheme'] . ':' .
                        ((strtolower($parsed['scheme']) == 'mailto') ? '' : '//') : '');
                $uri .= (!empty($parsed['user']) ? $parsed['user'] .
                        (!empty($parsed['pass']) ? ':' . $parsed['pass'] : '') . '@' : '');
            }
            if ($base && !empty($parsed['host'])) {
                $parsed['host'] = trim($parsed['host']);
                if (substr($parsed['host'], 0, 4) == 'www.') {
                    $parsed['host'] = substr($parsed['host'], 4);
                }
            }
            $uri .= (!empty($parsed['host']) ? $parsed['host'] : '');
            if (!empty($parsed['port'])
                && (($parsed['scheme'] == 'http' && $parsed['port'] == 80)
                || ($parsed['scheme'] == 'https' && $parsed['port'] == 443))
            ) {
                unset($parsed['port']);
            }
            $uri .= (!empty($parsed['port']) ? ':' . $parsed['port'] : '');
            if (!empty($parsed['path']) && (!$base || $base && $parsed['path'] != '/')) {
                $uri .= (substr($parsed['path'], 0, 1) == '/') ? $parsed['path'] : ('/' . $parsed['path']);
            }
            $uri .= (!empty($parsed['query']) ? '?' . $parsed['query'] : '');
            if (!$base) {
                $uri .= (!empty($parsed['fragment']) ? '#' . $parsed['fragment'] : '');
            }
        }
        return $uri;
    }

    /**
     * Checks the given string to see if its a valid IP4 address
     *
     * @param string $ip the ip to validate
     *
     * @return bool
     */
    protected function checkValidIP($ip)
    {
        return long2ip(ip2long($ip)) === $ip;
    }

    /**
     * Returns the title of the given page
     *
     * @param string $url url to the page
     *
     * @return string title
     */
    public function getTitle($url)
    {
        $http = $this->plugins->getPlugin('Http');
        $options = array(
            'timeout' => 3.5,
            'user_agent' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.12) Gecko/20080201 Firefox/2.0.0.12'
        );

        $response = $http->head($url, array(), $options);
        $header = $response->getHeaders('Content-Type');

        if (!preg_match('#^(text/x?html|application/xhtml+xml)(?:;.*)?$#', $header)) {
            $title = $header;
        } else {
            $response = $http->get($url, array(), $options);
            $content = $response->getContent();
            if (preg_match('#<title[^>]*>(.*?)</title>#is', $content, $match)) {
                $title = preg_replace('/[\s\v]+/', ' ', trim($match[1]));
            }
        }
        $encoding = $this->plugins->getPlugin('Encoding');
        $title = $encoding->decodeEntities($title);

        if (empty($title)) {
            if ($response->isError()) {
                $title = $response->getCodeAsString();
            } else {
                $title = 'No Title';
            }
        }

        return $title;
    }

    /**
     * Output a debug message
     *
     * @param string $msg the message to output
     *
     * @return void
     */
    protected function debug($msg)
    {
        echo "(DEBUG:Url) $msg\n";
    }

    /**
     * Add a renderer to the stack
     *
     * @param object $obj the renderer to add
     *
     * @return void
     */
    public function registerRenderer($obj)
    {
        $this->renderers[spl_object_hash($obj)] = $obj;
    }
}


code:
<?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_Php
 * @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_Php
 */

/**
 * URL shortener abstract class
 *
 * @category Phergie
 * @package  Phergie_Plugin_Url
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Url
 * @uses     Phergie_Plugin_Http pear.phergie.org
 */
abstract class Phergie_Plugin_Url_Shorten_Abstract
{
    protected $http;

    /**
     * Constructor
     *
     * @param Phergie_Plugin_Http $http instance of the http plugin
     */
    public function __construct(Phergie_Plugin_Http $http)
    {
        $this->http = $http;
    }

    /**
     * Returns an array of request parameters given a url to shorten. The
     * following keys are valid request parameters:
     *
     *  * 'uri': the URI for the request (required)
     *  * 'query': an array of key-value pairs sent in a GET request
     *  * 'post': an array of key-value pairs sent in a POST request
     *  * 'callback': to be called after the request is finished. Should accept
     *    a Phergie_Plugin_Http_Response object and return either the shortened
     *    url or false if an error has occured.
     *
     * If the 'post' key is present a POST request shall be made; otherwise
     * a GET request will be made. The 'post' key can be an empty array and
     * a post request will still be made.
     *
     * If no callback is provided the contents of the response will be returned.
     *
     * @param string $url the url to shorten
     *
     * @return array the request parameters
     */
    protected abstract function getRequestParams($url);

    /**
     * Shortens a given url.
     *
     * @param string $url the url to shorten
     *
     * @return string the shortened url or false on a failure
     */
    public function shorten($url)
    {
        $defaults = array('get' => array(), 'post' => array(), 'callback' => null);
        $options = array('timeout' => 2);
        $params = $this->getRequestParams($url) + $defaults;

        // Should some kind of notice be thrown? Maybe just if getRequestParams does not return an array?
        if (!is_array($params) || empty($params['uri'])) {
            return $url;
        }

        if (!empty($params['post'])) {
            $response = $this->http->post($params['uri'], $params['get'], $params['post'], $options);
        } else {
            $response = $this->http->get($params['uri'], $params['get'], $options);
        }

        if (is_callable($params['callback'])) {
            return call_user_func($params['callback'], $response);
        }

        $code = $response->getCode();
        $content = trim($response->getContent);
        if ($code < 200 || $code >= 300 || empty($content)) {
            return false;
        }

        return $response->getContent();
    }
}

license: mit


code:
<?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_Php
 * @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_Php
 */

/**
 * Shortens urls via the tr.im service
 *
 * @category Phergie
 * @package  Phergie_Plugin_Url
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Url
 */
class Phergie_Plugin_Url_Shorten_Trim extends Phergie_Plugin_Url_Shorten_Abstract
{
    /**
     * Returns an array of request parameters given a url to shorten. The
     * following keys are valid request parameters:
     *
     * @param string $url the url to shorten
     *
     * @return array the request parameters
     */
    protected function getRequestParams($url)
    {
        return array(
            'uri' => 'http://api.tr.im/v1/trim_simple?url=' . rawurlencode($url),
            'callback' => array($this, 'onComplete')
        );
    }

    /**
     * Callback for when the URL has been shortened. Checks for error messages.
     *
     * @param Phergie_Plugin_Http_Response $response the response object
     *
     * @return string|bool the shortened url or false on failure
     */
    protected function onComplete($response)
    {
        if (strpos($response->getContent(), 'Error: ') === 0) {
            return false;
        }

        return $response->getContent();
    }
}


code:
<?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_UserInfo
 * @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_UserInfo
 */

/**
 * Provides an API for querying information on users.
 *
 * @category Phergie
 * @package  Phergie_Plugin_UserInfo
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_UserInfo
 */
class Phergie_Plugin_UserInfo extends Phergie_Plugin_Abstract
{
    const REGULAR = 1;
    const VOICE   = 2;
    const HALFOP  = 4;
    const OP      = 8;
    const ADMIN   = 16;
    const OWNER   = 32;

    /**
     * An array containing all the user information for a given channel
     *
     * @var array
     */
    protected $store = array();

    /**
     * Tracks mode changes
     *
     * @return void
     */
    public function onMode()
    {
        $args = $this->event->getArguments();

        if (count($args) != 3) {
            return;
        }

        list($chan, $modes, $nicks) = $args;

        if (!preg_match('/(?:\+|-)[hovaq+-]+/i', $modes)) {
            return;
        }

        $chan = trim(strtolower($chan));
        $modes = str_split(trim(strtolower($modes)), 1);
        $nicks = explode(' ', trim(strtolower($nicks)));
        $operation = array_shift($modes); // + or -

        while ($char = array_shift($modes)) {
            $nick = array_shift($nicks);
            $mode = null;

            switch ($char) {
            case 'q':
                $mode = self::OWNER;
                break;
            case 'a':
                $mode = self::ADMIN;
                break;
            case 'o':
                $mode = self::OP;
                break;
            case 'h':
                $mode = self::HALFOP;
                break;
            case 'v':
                $mode = self::VOICE;
                break;
            }

            if (!empty($mode)) {
                if ($operation == '+') {
                    $this->store[$chan][$nick] |= $mode;
                } else if ($operation == '-') {
                    $this->store[$chan][$nick] ^= $mode;
                }
            }
        }
    }

    /**
     * Tracks users joining a channel
     *
     * @return void
     */
    public function onJoin()
    {
        $chan = trim(strtolower($this->event->getArgument(0)));
        $nick = trim(strtolower($this->event->getNick()));

        $this->store[$chan][$nick] = self::REGULAR;
    }

    /**
     * Tracks users leaving a channel
     *
     * @return void
     */
    public function onPart()
    {
        $chan = trim(strtolower($this->event->getArgument(0)));
        $nick = trim(strtolower($this->event->getNick()));

        if (isset($this->store[$chan][$nick])) {
            unset($this->store[$chan][$nick]);
        }
    }

    /**
     * Tracks users quitting a server
     *
     * @return void
     */
    public function onQuit()
    {
        $nick = trim(strtolower($this->event->getNick()));

        foreach ($this->store as $chan => $store) {
            if (isset($store[$nick])) {
                unset($this->store[$chan][$nick]);
            }
        }
    }

    /**
     * Tracks users changing nicks
     *
     * @return void
     */
    public function onNick()
    {
        $nick = trim(strtolower($this->event->getNick()));
        $newNick = trim(strtolower($this->event->getArgument(0)));

        foreach ($this->store as $chan => $store) {
            if (isset($store[$nick])) {
                $this->store[$chan][$newNick] = $store[$nick];
                unset($this->store[$chan][$nick]);
            }
        }
    }

    /**
     * Populates the internal user listing for a channel when the bot joins it.
     *
     * @return void
     */
    public function onResponse()
    {
        if ($this->event->getCode() != Phergie_Event_Response::RPL_NAMREPLY) {
            return;
        }

        $desc = preg_split('/[@*=]\s*/', $this->event->getDescription(), 2);
        list($chan, $users) = array_pad(explode(' :', trim($desc[1])), 2, null);
        $users = explode(' ', trim($users));

        $chan = trim(strtolower($chan));

        foreach ($users as $user) {
            if (empty($user)) {
                continue;
            }

            $user = trim(strtolower($user));
            $flag = self::REGULAR;

            if ($user[0] == '~') {
                $flag |= self::OWNER;
            } else if ($user[0] == '&') {
                $flag |= self::ADMIN;
            } else if ($user[0] == '@') {
                $flag |= self::OP;
            } else if ($user[0] == '%') {
                $flag |= self::HALFOP;
            } else if ($user[0] == '+') {
                $flag |= self::VOICE;
            }

            if ($flag != self::REGULAR) {
                $user = substr($user, 1);
            }

            $this->store[$chan][$user] = $flag;
        }
    }

    /**
     * Debugging function
     *
     * @return void
     */
    public function onPrivmsg()
    {
        if ($this->getConfig('debug', false) == false) {
            return;
        }

        list($target, $msg) = array_pad($this->event->getArguments(), 2, null);

        if (preg_match('#^ishere (\S+)$#', $msg, $m)) {
            $this->doPrivmsg($target, $this->isIn($m[1], $target) ? 'true' : 'false');
        } elseif (preg_match('#^isowner (\S+)$#', $msg, $m)) {
            $this->doPrivmsg($target, $this->isOwner($m[1], $target) ? 'true' : 'false');
        } elseif (preg_match('#^isadmin (\S+)$#', $msg, $m)) {
            $this->doPrivmsg($target, $this->isAdmin($m[1], $target) ? 'true' : 'false');
        } elseif (preg_match('#^isop (\S+)$#', $msg, $m)) {
            $this->doPrivmsg($target, $this->isOp($m[1], $target) ? 'true' : 'false');
        } elseif (preg_match('#^ishop (\S+)$#', $msg, $m)) {
            $this->doPrivmsg($target, $this->isHalfop($m[1], $target) ? 'true' : 'false');
        } elseif (preg_match('#^isvoice (\S+)$#', $msg, $m)) {
            $this->doPrivmsg($target, $this->isVoice($m[1], $target) ? 'true' : 'false');
        } elseif (preg_match('#^channels (\S+)$#', $msg, $m)) {
            $channels = $this->getChannels($m[1]);
            $this->doPrivmsg($target, $channels ? join(', ', $channels) : 'unable to find nick');
        } elseif (preg_match('#^users (\S+)$#', $msg, $m)) {
            $nicks = $this->getUsers($m[1]);
            $this->doPrivmsg($target, $nicks ? join(', ', $nicks) : 'unable to find channel');
        } elseif (preg_match('#^random (\S+)$#', $msg, $m)) {
            $nick = $this->getrandomuser($m[1]);
            $this->doPrivmsg($target, $nick ? $nick : 'unable to  find channel');
        }
    }

    /**
     * Checks whether or not a given user has a mode
     *
     * @param int    $mode A numeric mode (identified by the class constants)
     * @param string $nick The nick to check
     * @param string $chan The channel to check in
     *
     * @return bool
     */
    public function is($mode, $nick, $chan)
    {
        $chan = trim(strtolower($chan));
        $nick = trim(strtolower($nick));

        if (!isset($this->store[$chan][$nick])) {
            return false;
        }

        return ($this->store[$chan][$nick] & $mode) != 0;
    }

    /**
     * Checks whether or not a given user has owner (~) status
     *
     * @param string $nick The nick to check
     * @param string $chan The channel to check in
     *
     * @return bool
     */
    public function isOwner($nick, $chan)
    {
        return $this->is(self::OWNER, $nick, $chan);
    }

    /**
     * Checks whether or not a given user has admin (&) status
     *
     * @param string $nick The nick to check
     * @param string $chan The channel to check in
     *
     * @return bool
     */
    public function isAdmin($nick, $chan)
    {
        return $this->is(self::ADMIN, $nick, $chan);
    }

    /**
     * Checks whether or not a given user has operator (@) status
     *
     * @param string $nick The nick to check
     * @param string $chan The channel to check in
     *
     * @return bool
     */
    public function isOp($nick, $chan)
    {
        return $this->is(self::OP, $nick, $chan);
    }

    /**
     * Checks whether or not a given user has halfop (%) status
     *
     * @param string $nick The nick to check
     * @param string $chan The channel to check in
     *
     * @return bool
     */
    public function isHalfop($nick, $chan)
    {
        return $this->is(self::HALFOP, $nick, $chan);
    }

    /**
     * Checks whether or not a given user has voice (+) status
     *
     * @param string $nick The nick to check
     * @param string $chan The channel to check in
     *
     * @return bool
     */
    public function isVoice($nick, $chan)
    {
        return $this->is(self::VOICE, $nick, $chan);
    }

    /**
     * Checks whether or not a given user is in a channel
     *
     * @param string $nick The nick to check
     * @param string $chan The channel to check in
     *
     * @return bool
     */
    public function isIn($nick, $chan)
    {
        return $this->is(self::REGULAR, $nick, $chan);
    }

    /**
     * Returns the entire user list for a channel or false if the bot is not
     * in the channel.
     *
     * @param string $chan The channel name
     *
     * @return array|bool
     */
    public function getUsers($chan)
    {
        $chan = trim(strtolower($chan));
        if (isset($this->store[$chan])) {
            return array_keys($this->store[$chan]);
        }
        return false;
    }

    /**
     * Returns the nick of a random user present in a given channel or false
     * if the bot is not present in the channel.
     *
     * @param string $chan The channel name
     *
     * @return array|bool
     */
    public function getRandomUser($chan)
    {
        $chan = trim(strtolower($chan));

        if (isset($this->store[$chan])) {
            $ignore = array('chanserv', 'q', 'l', 's');

            do {
                $nick = array_rand($this->store[$chan], 1);
            } while (in_array($nick, $ignore));

            return $nick;
        }

        return false;
    }

    /**
     * Returns a list of channels in which a given user is present.
     *
     * @param string $nick Nick of the user (optional, defaults to the bot's
     *               nick)
     *
     * @return array|bool
     */
    public function getChannels($nick = null)
    {
        if (empty($nick)) {
            $nick = $this->connection->getNick();
        }

        $nick = trim(strtolower($nick));
        $channels = array();

        foreach ($this->store as $chan => $store) {
            if (isset($store[$nick])) {
                $channels[] = $chan;
            }
        }

        return $channels;
    }
}


code:
<?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_Weather
 * @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_Weather
 */

/**
 * Detects and responds to requests for current weather conditions in a
 * particular location using data from a web service. Requires registering
 * with weather.com to obtain authentication credentials, which must be
 * stored in the configuration settings weather.partner_id and
 * weather.license_key for the plugin to function.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Weather
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Weather
 * @link     http://www.weather.com/services/xmloap.html
 * @uses     Phergie_Plugin_Command pear.phergie.org
 * @uses     Phergie_Plugin_Http pear.phergie.org
 * @uses     Phergie_Plugin_Temperature pear.phergie.org
 * @uses     extension SimpleXML
 */
class Phergie_Plugin_Weather extends Phergie_Plugin_Abstract
{
    /**
     * Checks for dependencies.
     *
     * @return void
     */
    public function onLoad()
    {
        $plugins = $this->getPluginHandler();
        $plugins->getPlugin('Command');
        $plugins->getPlugin('Http');
        $plugins->getPlugin('Temperature');

        if (empty($this->config['weather.partner_id'])
            || empty($this->config['weather.license_key'])) {
            $this->fail('weather.partner_id and weather.license_key must be specified');
        }
    }

    /**
     * Returns a weather report for a specified location.
     *
     * @param string $location Zip code or city/state/country specification
     *
     * @return void
     */
    public function onCommandWeather($location)
    {
        $response = $this->plugins->http->get(
            'http://xoap.weather.com/search/search',
            array('where' => $location)
        );

        if ($response->isError()) {
            $this->doNotice(
                $this->event->getNick(),
                'ERROR: ' . $response->getCode() . ' ' . $response->getMessage()
            );
            return;
        }

        $nick = $this->event->getNick();

        $xml = $response->getContent();
        if (count($xml->loc) == 0) {
            $this->doNotice($nick, 'No results for that location.');
            return;
        }

        $where = (string) $xml->loc[0]['id'];
        $response = $this->plugins->http->get(
            'http://xoap.weather.com/weather/local/' . $where,
            array(
                'cc' => '*',
                'link' => 'xoap',
                'prod' => 'xoap',
                'par' => $this->config['weather.partner_id'],
                'key' => $this->config['weather.license_key'],
            )
        );

        if ($response->isError()) {
            $this->doNotice(
                $this->event->getNick(),
                'ERROR: ' . $response->getCode() . ' ' . $response->getMessage()
            );
            return;
        }

        $temperature = $this->plugins->getPlugin('Temperature');
        $xml = $response->getContent();
        $weather = 'Weather for ' . (string) $xml->loc->dnam . ' - ';
        switch ($xml->head->ut) {
            case 'F':
                $tempF = $xml->cc->tmp;
                $tempC = $temperature->convertFahrenheitToCelsius($tempF);
                break;
            case 'C':
                $tempC = $xml->cc->tmp;
                $tempF = $temperature->convertCelsiusToFahrenheit($tempC);
                break;
            default:
                $this->doNotice(
                    $this->event->getNick(),
                    'ERROR: No scale information given.');
                break;
        }
        $r = $xml->cc->hmid;
        $hiF = $temperature->getHeatIndex($tempF, $r);
        $hiC = $temperature->convertFahrenheitToCelsius($hiF);
        $weather .= 'Temperature: ' . $tempF . 'F/' . $tempC . 'C';
        $weather .= ', Humidity: ' . (string) $xml->cc->hmid . '%';
        if ($hiF > $tempF || $hiC > $tempC) {
            $weather .= ', Heat Index: ' . $hiF . 'F/' . $hiC . 'C';
        }
        $weather .=
            ', Conditions: ' . (string) $xml->cc->t .
            ', Updated: ' . (string) $xml->cc->lsup .
            ' [ http://weather.com/weather/today/' .
            str_replace(
                array('(', ')', ',', ' '),
                array('', '', '', '+'),
                (string) $xml->loc->dnam
            ) .
            ' ]';

        $this->doPrivmsg($this->event->getSource(), $nick . ': ' . $weather);
    }
}


code:
<?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_Wine
 * @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_Wine
 */

/**
 * Processes requests to serve users wine.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Wine
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Wine
 * @uses     Phergie_Plugin_Command pear.phergie.org
 * @uses     Phergie_Plugin_Serve pear.phergie.org
 */
class Phergie_Plugin_Wine 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 wine.
     *
     * @param string $request Request including the target and an optional
     *        suggestion of what wine to serve
     *
     * @return void
     */
    public function onCommandWine($request)
    {
        $format = $this->getConfig(
            'wine.format',
            'serves %target% a glass of %item%.'
        );

        $this->plugins->getPlugin('Serve')->serve(
            dirname(__FILE__) . '/Wine/wine.db',
            'wine',
            $format,
            $request
        );
    }
}


code:
<?php

// Create database schema
echo 'Creating database', PHP_EOL;
$file = __DIR__ . '/wine.db';
if (file_exists($file)) {
    unlink($file);
}
$db = new PDO('sqlite:' . $file);
$db->exec('CREATE TABLE wine (name VARCHAR(255), link VARCHAR(255))');
$db->exec('CREATE UNIQUE INDEX wine_name ON wine (name)');
$insert = $db->prepare('INSERT INTO wine (name, link) VALUES (:name, :link)');

// Get and decompress lcboapi.com data set
$outer = __DIR__ . '/current.zip';
if (!file_exists($outer)) {
    echo 'Downloading lcboapi.com data set', PHP_EOL;
    copy('http://lcboapi.com/download/current.zip', $outer);
}

echo 'Decompressing lcboapi.com data set', PHP_EOL;
$zip = new ZipArchive;
$zip->open($outer);
$stat = $zip->statIndex(0);
$inner = __DIR__ . '/' . $stat['name'];
$zip->extractTo(__DIR__);
$zip->close();
$zip = new ZipArchive;
$zip->open($inner);
$stat = $zip->statIndex(0);
$file = __DIR__ . '/' . $stat['name'];
$zip->extractTo(__DIR__);
$zip->close();

// Aggregate data set into the database
$lcbo = new PDO('sqlite:' . $file);
$result = $lcbo->query('SELECT product_no, name FROM products WHERE primary_category = "Wine"');
$wines = $result->fetchAll();
echo 'Processing lcboapi.com data - ', number_format(count($wines), 0), ' records', PHP_EOL;
$db->beginTransaction();
foreach ($wines as $wine) {
    $name = $wine['name'];
    $link = 'http://lcboapi.com/products/' . $wine['product_no'];
    $insert->execute(array($name, $link));
}
$db->commit();

// Clean up
echo 'Cleaning up', PHP_EOL;
unset($lcbo);
unlink($outer);
unlink($inner);
unlink($file);


code:
<?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_Youtube
 * @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_Youtube
 */

/**
 * Provides commands used to access several services offered by Google
 * including search, translation, weather, maps, and currency and general
 * value unit conversion.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Youtube
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Youtube
 * @uses     Phergie_Plugin_Command pear.phergie.org
 * @uses     Phergie_Plugin_Http pear.phergie.org
 */
class Phergie_Plugin_Youtube extends Phergie_Plugin_Abstract
{
    /**
     * Checks for dependencies.
     *
     * @return void
     */
    public function onLoad()
    {
        $plugins = $this->getPluginHandler();
        $plugins->getPlugin('Command');
        $plugins->getPlugin('Http');
        if ($url = $plugins->getPlugin('Url')) {
            $url->registerRenderer($this);
        }
    }

    /**
     * Queries the YouTube video search web service, processes the first
     * result, and sends a message back to the current event source.
     *
     * @param string $query Search term
     *
     * @return object YouTube result object
     */
    protected function queryYoutube($query)
    {
        $url = 'http://gdata.youtube.com/feeds/api/videos';
        $params = array(
            'max-results' => '1',
            'alt' => 'json',
            'q' => $query
        );
        $http = $this->plugins->getPlugin('Http');
        $response = $http->get($url, $params);
        $json = $response->getContent();

        $entries = $json->feed->entry;
        if (!$entries) {
            $this->doNotice($this->event->getNick(), 'Query returned no results');
            return;
        }
        $entry = reset($entries);

        $nick = $this->event->getNick();
        $link = $entry->link[0]->href;
        $title = $entry->title->{'$t'};
        $author = $entry->author[0]->name->{'$t'};
        $seconds = $entry->{'media$group'}->{'yt$duration'}->seconds;
        $published = $entry->published->{'$t'};
        $views = $entry->{'yt$statistics'}->viewCount;
        $rating = $entry->{'gd$rating'}->average;

        $minutes = floor($seconds / 60);
        $seconds = str_pad($seconds % 60, 2, '0', STR_PAD_LEFT);
        $parsed_link = parse_url($link);
        parse_str($parsed_link['query'], $parsed_query);
        $link = 'http://youtu.be/' . $parsed_query['v'];
        $published = date('n/j/y g:i A', strtotime($published));
        $views = number_format($views, 0);
        $rating = round($rating, 2);

        $format = $this->getConfig('youtube.format');
        if (!$format) {
            $format = '%nick%:'
                . ' [ %link% ]'
                . ' "%title%" by %author%,'
                . ' Length %minutes%:%seconds%,'
                . ' Published %published%,'
                . ' Views %views%,'
                . ' Rating %rating%';
        }

        $replacements = array(
            'nick' => $nick,
            'link' => $link,
            'title' => $title,
            'author' => $author,
            'minutes' => $minutes,
            'seconds' => $seconds,
            'published' => $published,
            'views' => $views,
            'rating' => $rating
        );

        $msg = $format;
        foreach ($replacements as $from => $to) {
            $msg = str_replace('%' . $from . '%', $to, $msg);
        }
        $this->doPrivmsg($this->event->getSource(), $msg);
    }

    /**
     * Returns the first result of a YouTube search.
     *
     * @param string $query Search query
     *
     * @return void
     */
    public function onCommandYoutube($query)
    {
        $this->queryYoutube($query);
    }

    /**
     * Renders YouTube URLs.
     *
     * @param array $parsed parse_url() output for the URL to render
     *
     * @return boolean TRUE if the URL was rendered successfully, FALSE
     *         otherwise
     */
    public function renderUrl(array $parsed)
    {
        switch ($parsed['host']) {
            case 'youtu.be':
                $v = ltrim($parsed['path'], '/');
                break;
            case 'youtube.com':
            case 'www.youtube.com':
                parse_str($parsed['query'], $parsed_query);
                if (!empty($parsed_query['v'])) {
                    $v = $parsed_query['v'];
                    break;
                }
            default:
                return false;
        }

        $this->queryYoutube($v);

        return true;
    }
}


code:
<?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
 * @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
 */

/**
 * Base class for obtaining and processing incoming events.
 *
 * @category Phergie
 * @package  Phergie
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie
 */
abstract class Phergie_Process_Abstract
{
    /**
     * Current driver instance
     *
     * @var Phergie_Driver_Abstract
     */
    protected $driver;

    /**
     * Current connection handler instance
     *
     * @var Phergie_Connection_Handler
     */
    protected $connections;

    /**
     * Current plugin handler instance
     *
     * @var Phergie_Plugin_Handler
     */
    protected $plugins;

    /**
     * Current event handler instance
     *
     * @var Phergie_Event_Handler
     */
    protected $events;

    /**
     * Current end-user interface instance
     *
     * @var Phergie_Ui_Abstract
     */
    protected $ui;

    /**
     * List of arguments for use within the instance
     *
     * @var array
     */
    protected $options = array();

    /**
     * Gets the required class refences from Phergie_Bot.
     *
     * @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())
    {
        $this->driver = $bot->getDriver();
        $this->plugins = $bot->getPluginHandler();
        $this->connections = $bot->getConnectionHandler();
        $this->events = $bot->getEventHandler();
        $this->ui = $bot->getUi();
        $this->options = $options;
    }

    /**
     * Sends resulting outgoing events from ealier processing in handleEvents.
     *
     * @param Phergie_Connection $connection Active connection
     *
     * @return void
     */
    protected function processEvents(Phergie_Connection $connection)
    {
        $this->plugins->preDispatch();
        if (count($this->events)) {
            foreach ($this->events as $event) {
                $this->ui->onCommand($event, $connection);

                $method = 'do' . ucfirst(strtolower($event->getType()));
                call_user_func_array(
                    array($this->driver, $method),
                    $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();
    }

    /**
     * Obtains and processes incoming events.
     *
     * @return void
     */
    public abstract function handleEvents();
}


code:
<?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
 * @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
 */

/**
 * Connection data processor which polls to handle input in an
 * asynchronous manner. Will also cause the application tick at
 * the user-defined wait time.
 *
 * @category Phergie
 * @package  Phergie
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie
 */
class Phergie_Process_Async extends Phergie_Process_Abstract
{
    /**
     * Length of time to poll for stream activity (seconds)
     *
     * @var int
     */
    protected $sec = 0;

    /**
     * Length of time to poll for stream activity (microseconds)
     *
     * @var int
     */
    protected $usec = 200000;

    /**
     * Length of time to wait between ticks.
     *
     * @var int
     */
    protected $wait = 0;

    /**
     * Records when the application last performed a tick
     *
     * @var int
     */
    protected $lastTick = 0;

    /**
     * Overrides the parent class to set the poll time.
     *
     * @param Phergie_Bot $bot     Main bot class
     * @param array       $options Processor arguments
     *
     * @return void
     */
    public function __construct(Phergie_Bot $bot, array $options)
    {
        if (!$bot->getDriver() instanceof Phergie_Driver_Streams) {
            throw new Phergie_Process_Exception(
                'The Async event processor requires the Streams driver'
            );
        }

        foreach (array('sec', 'usec') as $var) {
            if (isset($options[$var])) {
                if (!is_int($options[$var])) {
                     throw new Phergie_Process_Exception(
                        'Processor option "' . $var . '" must be an integer'
                     );
                }
                $this->$var = $options[$var];
            }
        }

        if (!isset($this->sec) && !isset($this->usec)) {
            throw new Phergie_Process_Exception(
                'One of the processor options "sec" or "usec" must be specified'
            );
        }

        parent::__construct($bot, $options);
    }

    /**
     * Waits for stream activity and performs event processing on
     * connections with data to read.
     *
     * @return void
     */
    protected function handleEventsAsync()
    {
        $hostmasks = $this->driver->getActiveReadSockets($this->sec, $this->usec);
        if (!$hostmasks) {
            return;
        }
        $connections = $this->connections->getConnections($hostmasks);
        foreach ($connections as $connection) {
            $this->driver->setConnection($connection);
            $this->plugins->setConnection($connection);
            $this->plugins->onTick();

            if ($event = $this->driver->getEvent()) {
                $this->ui->onEvent($event, $connection);
                $this->plugins->setEvent($event);
                $this->plugins->preEvent();
                $this->plugins->{'on' . ucfirst($event->getType())}();
            }

            $this->processEvents($connection);
        }
    }

    /**
     * Perform application tick event on all plugins and connections.
     *
     * @return void
     */
    protected function doTick()
    {
        foreach ($this->connections as $connection) {
            $this->plugins->setConnection($connection);
            $this->plugins->onTick();
            $this->processEvents($connection);
        }
    }

    /**
     * Obtains and processes incoming events, then sends resulting outgoing
     * events.
     *
     * @return void
     */
    public function handleEvents()
    {
        $time = time();
        if ($this->lastTick == 0 || ($this->lastTick + $this->wait <= $time)) {
            $this->doTick();
            $this->lastTick = $time;
        }
        $this->handleEventsAsync();
    }
}


code:
<?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
 * @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
 */

/**
 * Exception related to event processor operations.
 *
 * @category Phergie
 * @package  Phergie
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie
 */
class Phergie_Process_Exception extends Phergie_Exception
{
}


code:
<?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
 * @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
 */

/**
 * Connection data processor which reads all connections looking
 * for a response.
 *
 * @category Phergie
 * @package  Phergie
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie
 */
class Phergie_Process_Standard extends Phergie_Process_Abstract
{
    /**
     * Obtains and processes incoming events, then sends resulting outgoing
     * events.
     *
     * @return void
     */
    public function handleEvents()
    {
        foreach ($this->connections as $connection) {
            $this->driver->setConnection($connection);
            $this->plugins->setConnection($connection);
            $this->plugins->onTick();

            if ($event = $this->driver->getEvent()) {
                $this->ui->onEvent($event, $connection);
                $this->plugins->setEvent($event);
                $this->plugins->preEvent();
                $this->plugins->{'on' . ucfirst($event->getType())}();
            }

            $this->processEvents($connection);
        }
    }
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Extends the bot class (Phergie_Bot) to allow connection and access to
 * sockets and to allow StatusNet to 'drive' the bot
 *
 * @category  Phergie
 * @package   Phergie_StatusnetBot
 * @author    Luke Fitzgerald <lw.fitzgerald@googlemail.com>
 * @copyright 2010 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */
class Phergie_StatusnetBot extends Phergie_Bot {
    /**
    * Set up bot and connect to servers
    *
    * @return void
    */
    public function connect() {
        $ui = $this->getUi();
        $ui->setEnabled($this->getConfig('ui.enabled'));

        $this->loadPlugins();
        $this->loadConnections();
    }

    /**
    * Transmit raw command to server using driver
    *
    * Handles construction of command strings and their transmission to the
    * server.
    *
    * @param string       $command Command to send
    * @param string|array $args    Optional string or array of sequential
    *        arguments
    *
    * @return string Command string that was sent
    * @throws Phergie_Driver_Exception
    */
    public function send($command, $args = '') {
        return $this->getDriver()->send($command, $args);
    }

    /**
    * Handle incoming data on the socket using the handleEvents
    * method of the Processor
    *
    * @return void
    */
    public function handleEvents() {
        $this->getProcessor()->handleEvents();
    }

    /**
    * Close the current connection and reconnect to the server
    *
    * @return void
    */
    public function reconnect() {
        $driver = $this->getDriver();
        $sockets = $driver->getSockets();

        // Close any existing connections
        try {
            $driver->forceQuit();
        } catch (Phergie_Driver_Exception $e){}
        try {
            $driver->doConnect();
        } catch (Phergie_Driver_Exception $e){
            $driver->forceQuit();
            throw $e;
        }
    }

    /**
    * Get the sockets used by the bot
    *
    * @return array Array of socket resources
    */
    public function getSockets() {
        return $this->getDriver()->getSockets();
    }
}


code:
<?php
// Phergie Log Viewer ... Currently designed as a single PHP file in order to make it easy to
//  'install' this.  Just drop the index.php (or whatever name you wish to rename it to) wherever
//  you wish, and it will simply work.  Sure, it would be nice to structure some of this stuff into
//  various include files/etc.  But right now this is simple enough of a quick log viewer, that it's
//  just one file.

/********** SETUP **********/

// (Change any of these if/as needed for your setup)
ini_set('default_charset', 'UTF-8');
date_default_timezone_set('UTC');
$log = "/PATH/AND/FILENAME/TO/YOUR/LOGFILE/PLEASE.db";

/********** PREPARATION **********/

$db = new PDO('sqlite:' . $log);
if (!is_object($db)) {
    // Failure, can't access Phergie Log.  Bail with an error message, not pretty, but works:
    echo "ERROR: Cannot access Phergie Log File, please check the configuration & access privileges";
    exit();
}

/********** DETECTION **********/

// Determine the mode of the application and call the appropriate handler function
$mode = empty($_GET['m']) ? '' : $_GET['m'];
switch ($mode) {
    case 'channel':
        show_days($db);
        break;
    case 'day':
        show_log($db);
        break;
    default:
        show_channels($db);
}

// Exit not really needed here, but reminds us that everything below is support functions:
exit();

/********** MODES **********/

/**
 * show_channels
 *
 * Provide a list of all channel's that we are logging information for:
 *
 * @param PDO A PDO object referring to the database
 * @return void
 * @author Eli White <eli@eliw.com>
 **/
function show_channels(PDO $db) {
    // Begin the HTML page:
    template_header('Channels');
    echo "\nChannels:\n
\n";

    // Loop through the database reading in each channel, and echoing out a
 for it.
    // only grab actual channels that start with # ... also pre-lowercase everything.
    // this allows us to 'deal' with variable caps in how the channels were logged.
    $channels = $db->query("
        select distinct lower(chan) as c
        from logs
        where chan like '#%'
        ");
    foreach ($channels as $row) {
        $html = utf8specialchars($row['c']);
        $url = urlencode($row['c']);
        echo "
{$html}
\n";
    }

    // Finish off the page:
    echo "\n
\n";
    template_footer();
}

/**
 * show_days
 *
 * Create a calendar view of all days available for this particular channel
 *
 * NOTE: May get unwieldy if large log files.  Perhaps consider in the future
 *  making a paginated version of this?  by year?  Or a separate 'which year' page
 *  before this?  Not to worry about now.
 *
 * @param PDO A PDO object referring to the database
 * @return void
 * @author Eli White <eli@eliw.com>
 **/
function show_days(PDO $db) {
    $channel = $_GET['w'];
    $url = urlencode($channel);

    // Begin the HTML page:
    template_header('Daily Logs for Channel: ' . utf8specialchars($channel));
    echo "\n
\n";

    // Query the database to discover all days that are available for this channel:
    $data = array();
    $prepared = $db->prepare("
        select distinct date(tstamp) as day
        from logs
        where lower(chan) = :chan
        ");
    $prepared->execute(array(':chan' => $channel));
    foreach ($prepared as $row) {
        list($y, $m, $d) = explode('-', $row['day']);
        $data[$y][$m][$d] = "{$y}-{$m}-{$d}";
    }

    // For now, just loop over them all and provide a list:
    ksort($data);
    foreach ($data as $year => $months) {
        ksort($months);
        foreach ($months as $month => $days) {
            // Figure out a few facts about this month:
            $stamp = mktime(0, 0, 0, $month, 1, $year);
            $first_weekday = idate('w', $stamp);
            $days_in_month = idate('t', $stamp);
            $name = date('F', $stamp);

            // We have a month ... start a new table:
            echo <<<EOTABLE

{$name} {$year}

Sun	Mon	Tue	Wed	Thu	Fri	Sat

EOTABLE;
            // Now we need to start looping through the days in this month:
            echo '
';
            $rowmod = 0;
            // Loop through all day entries, no matter how many blanks we need:
            for ($d = (-$first_weekday + 1); $d < $days_in_month + 1; $d++) {
                if (!($rowmod++ % 7)) {
                    // Stop/start a new row:
                    echo '

';
                }
                echo '';
                // If this day is pre or post actual month days, make it blank:
                if (($d < 1) || ($d > $days_in_month)) {
                    echo ' ';
                } elseif (isset($days[$d])) {
                    // Make a link to the day's log:
                    echo "{$d}";
                } else {
                    // Just a dead number:
                    echo $d;
                }
                echo '	';
            }
            // Finish off any blanks needed for a complete table row:
            while ($rowmod++ % 7) {
                echo ' 	';
            }
            echo "

\n";
        }
    }

    // Finish off the page:
    echo "\n
\n";
    template_footer();
}

/**
 * show_log
 *
 * Actually show the log for this specific day
 *
 * @param PDO A PDO object referring to the database
 * @return void
 * @author Eli White <eli@eliw.com>
 **/
function show_log(PDO $db) {
    $channel = $_GET['w'];
    $day = $_GET['d'];
    $parts = explode('-', $day);
    $formatted_date = "{$parts[0]}-{$parts[1]}-{$parts[2]}";

    // Begin the HTML page:
    template_header('Date: ' . utf8specialchars($formatted_date) .
        ' - Channel: ' . utf8specialchars($channel));

    // Query the database to get all log lines for this date:
    $prepared = $db->prepare("
        select time(tstamp) as t, type, nick, message
        from logs
        where lower(chan) = :chan and date(tstamp) = :day
        order by tstamp asc
        ");
    $prepared->execute(array(
        ':chan' => $channel,
        ':day' => $day,
        ));

    // Loop through each line,
    foreach ($prepared as $row) {
        // Prepare some basic details for output:
        $color = nick_color($row['nick']);
        $time = utf8specialchars($row['t']);
        $msg = utf8specialchars($row['message']);
        $nick = utf8specialchars($row['nick']);
        $type = false;

        // Now change the format of the line based upon the type:
        switch ($row['type']) {
            case 4: // PRIVMSG (A Regular Message)
                echo "[$time] <{$nick}> {$msg}
\n";
                break;
            case 5: // ACTION (emote)
                echo "[$time] *{$nick} {$msg}
\n";
                break;
            case 1: // JOIN
                echo "[$time] -> {$nick} joined the room.
\n";
                break;
            case 2: // PART (leaves channel)
                echo "[$time] -> {$nick} left the room: {$msg}
\n";
                break;
            case 3: // QUIT (quits the server)
                echo "[$time] -> {$nick} left the server: {$msg}
\n";
                break;
            case 6: // NICK (changes their nickname)
                echo "[$time] -> {$nick} is now known as: {$msg}
\n";
                break;
            case 7: // KICK (booted)
                echo "[$time] -> {$nick} boots {$msg} from the room.
\n";
                break;
            case 8: // MODE (changed their mode)
                $type = 'MODE';
            case 9: // TOPIC (changed the topic)
                $type = $type ? $type : 'TOPIC';
                echo "[$time] -> {$nick}: :{$type}: {$msg}
\n";
        }
    }

    // Finish up the page:
    template_footer();
}

/**
 * nick_color
 *
 * Uses a silly little algorithm to pick a consistent but unique(ish) color for
 *  any given username.  NOTE: Augment this in the future to make it not generate
 *  'close to white' ones, also maybe to ensure uniqueness?  (Not allow two to have
 *  colors that are close to each other?)
 *
 * @return string A CSS valid hex color string
 * @author Eli White <eli@eliw.com>
 **/
function nick_color($user) {
    static $colors = array();

    if (!isset($colors[$user])) {
        $colors[$user] = substr(md5($user), 0, 6);
    }

    return $colors[$user];
}

/**
 * utf8specialchars
 *
 * Just a quick wrapper around htmlspecialchars
 *
 * @param string The UTF8 string to escape
 * @return string An escaped and ready for HTML use string
 * @author Eli White <eli@eliw.com>
 **/
function utf8specialchars($string) {
    return htmlspecialchars($string, ENT_COMPAT, 'UTF-8');
}

/********** TEMPLATES **********/

/**
 * template_header
 *
 * Echo out the header for each HTML page
 *
 * @param $title string The title to be used for this page.
 * @return void
 * @author Eli White <eli@eliw.com>
 **/
function template_header($title) {
    $css = template_css();
    echo <<<EOHTML
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Phergie LogViewer - {$title}</title>

Phergie LogViewer - {$title}

EOHTML;
}

/**
 * template_footer
 *
 * Echo out the bottom of each HTML page
 *
 * @return void
 * @author Eli White <eli@eliw.com>
 **/
function template_footer() {
    echo <<<EOHTML

EOHTML;
}

/**
 * template_css
 *
 * Generate the CSS used by these HTML pages & return it.
 *
 * @return string The CSS in question:
 * @author Eli White <eli@eliw.com>
 **/
function template_css() {
    return <<<EOCSS
    div.month {
        float: left;
        height: 15em;
    }

    div.month table {
        border-collapse: collapse;
        border: 2px solid black;
        margin-right: 2em;
    }

    div.month td, div.month th {
        text-align: center;
        vertical-align: bottom;
        border: 1px solid gray;
        width: 2em;
        height: 1.7em;
        padding: 1px;
        margin: 0px;
    }

    div.month th {
        text-decoration: bold;
        border: 2px solid black;
    }

    div.month a {
        text-decoration: none;
    }

    a:visited, a:link {
        color: blue;
    }

    a:active, a:hover {
        color: red;
    }
EOCSS;
}


code:
This directory holds any assorted tools that work with Phergie but that are
not part of Phergie itself.  Such as tools to examine data that Phergie is
capturing/logging.

Each should live inside of it's own directory, and be completely self-contained
with instructions inside on how to use it.


code:
<?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
 * @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
 */

/**
 * Base class for end-user interfaces.
 *
 * @category Phergie
 * @package  Phergie
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie
 */
abstract class Phergie_Ui_Abstract
{
    /**
     * Handler for when a server connection is attempted.
     *
     * @param string $host Server hostname
     *
     * @return void
     */
    public function onConnect($host)
    {
    }

    /**
     * Handler for when an attempt is made to load a plugin.
     *
     * @param string $plugin Short name of the plugin
     *
     * @return void
     */
    public function onPluginLoad($plugin)
    {
    }

    /**
     * Handler for when a plugin fails to load.
     *
     * @param string $plugin  Short name of the plugin
     * @param string $message Message describing the reason for the failure
     *
     * @return void
     */
    public function onPluginFailure($plugin, $message)
    {
    }

    /**
     * Handler for when the bot receives an IRC event.
     *
     * @param Phergie_Event_Abstract $event      Received event
     * @param Phergie_Connection     $connection Connection on which the event
     *        was received
     *
     * @return void
     */
    public function onEvent(Phergie_Event_Abstract $event,
        Phergie_Connection $connection
    ) {
    }

    /**
     * Handler for when the bot sends a command to a server.
     *
     * @param Phergie_Event_Command $event      Event representing the command
     *        being sent
     * @param Phergie_Connection    $connection Connection on which the command
     *        is being sent
     *
     * @return void
     */
    public function onCommand(Phergie_Event_Command $event,
        Phergie_Connection $connection
    ) {
    }

    /**
     * Handler for when the bot terminates a connection to a server.
     *
     * @param Phergie_Connection $connection Terminated connection
     *
     * @return void
     */
    public function onQuit(Phergie_Connection $connection)
    {
    }

    /**
     * Handler for when the bot shuts down after terminating all server
     * connections.
     *
     * @return void
     */
    public function onShutdown()
    {
    }
}


code:
<?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
 * @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
 */

/**
 * End-user interface that produces console output when running the bot from
 * a shell.
 *
 * @category Phergie
 * @package  Phergie
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie
 */
class Phergie_Ui_Console extends Phergie_Ui_Abstract
{
    /**
     * Flag that toggles all console output
     *
     * @var bool
     */
    protected $enabled;

    /**
     * Format for timestamps included in console output
     *
     * @var string
     * @link http://php.net/date
     */
    protected $format;

    /**
     * Constructor to initialize object properties.
     *
     * @return void
     */
    public function __construct()
    {
        $this->enabled = true;
        $this->format = 'H:i:s';
    }

    /**
     * Outputs a timestamped line to the console if console output is enabled.
     *
     * @param string $line Line to output
     *
     * @return void
     */
    protected function console($line)
    {
        if ($this->enabled) {
            echo date($this->format), ' ', $line, PHP_EOL;
        }
    }

    /**
     * Returns whether console output is enabled.
     *
     * @return bool TRUE if console output is enabled, FALSE otherwise
     */
    public function isEnabled()
    {
        return $this->enabled;
    }

    /**
     * Sets whether console output is enabled.
     *
     * @param bool $enabled TRUE to enable console output, FALSE otherwise,
     *        defaults to TRUE
     *
     * @return Phergie_Ui_Console Provides a fluent interface
     */
    public function setEnabled($enabled = true)
    {
        $this->enabled = (bool) $enabled;
        return $this;
    }

    /**
     * Returns the format used for timestamps in console output.
     *
     * @return string
     * @link http://php.net/date
     */
    public function getFormat()
    {
        return $this->format;
    }

    /**
     * Sets the format used for timestamps in console output, overwriting
     * any previous format used.
     *
     * @param string $format Timestamp format
     *
     * @return Phergie_Ui_Console Provides a fluent interface
     * @link http://php.net/date
     */
    public function setFormat($format)
    {
        $this->format = (string) $format;
        return $this;
    }

    /**
     * Outputs a prompt when a server connection is attempted.
     *
     * @param string $host Server hostname
     *
     * @return void
     */
    public function onConnect($host)
    {
        $this->console('Connecting to ' . $host);
    }

    /**
     * Outputs a prompt when a plugin is loaded successfully.
     *
     * @param string $plugin Short name of the plugin
     *
     * @return void
     */
    public function onPluginLoad($plugin)
    {
        $this->console('Loaded plugin ' . $plugin);
    }

    /**
     * Outputs a prompt when a plugin fails to load.
     *
     * @param string $plugin  Short name of the plugin
     * @param string $message Message describing the reason for the failure
     *
     * @return void
     */
    public function onPluginFailure($plugin, $message)
    {
        $this->console('Unable to load plugin ' . $plugin . ' - ' . $message);
    }

    /**
     * Outputs a prompt when the bot receives an IRC event.
     *
     * @param Phergie_Event_Abstract $event      Received event
     * @param Phergie_Connection     $connection Connection on which the
     *        event was received
     *
     * @return void
     */
    public function onEvent(Phergie_Event_Abstract $event,
        Phergie_Connection $connection
    ) {
        $host = $connection->getHostmask()->getHost();
        $this->console($host . ' <- ' . $event->getRawData());
    }

    /**
     * Outputs a prompt when the bot sends a command to a server.
     *
     * @param Phergie_Event_Command $event      Event representing the
     *        command being sent
     * @param Phergie_Connection    $connection Connection on which the
     *        command is being sent
     *
     * @return void
     */
    public function onCommand(Phergie_Event_Command $event,
        Phergie_Connection $connection
    ) {
        $plugin = $event->getPlugin()->getName();
        $host = $connection->getHostmask()->getHost();
        $type = strtoupper($event->getType());
        $args = implode(' ', $event->getArguments());
        $this->console(
            $plugin . ' plugin: ' .
            $host . ' -> ' . $type . ' ' . $args
        );
    }

    /**
     * Outputs a prompt when the bot terminates a connection to a server.
     *
     * @param Phergie_Connection $connection Terminated connection
     *
     * @return void
     */
    public function onQuit(Phergie_Connection $connection)
    {
        $host = $connection->getHostmask()->getHost();
        $this->console('Disconnecting from ' . $host);
    }

    /**
     * Outputs a prompt when the bot shuts down after terminating all server
     * connections.
     *
     * @return void
     */
    public function onShutdown()
    {
        $this->console('Shutting down');
    }
}


code:
<?php

require_once 'phing/tasks/ext/PearPackage2Task.php';

class PhergiePackageTask extends PearPackage2Task
{
    protected function setOptions()
    {
        $this->pkg->addMaintainer('lead', 'team', 'Phergie Development Team', 'team@phergie.org');

        $path = str_replace('_', '/', $this->package) . '.php';
        if (file_exists($path)) {
            $contents = file_get_contents($path);
            preg_match_all('#/\*\*(.*)\*/#Ums', $contents, $matches, PREG_SET_ORDER);
            $doc = $matches[1][1];

            $have_summary = false;
            $have_description = false;
            foreach ($this->options as $option) {
                switch ($option->getName()) {
                    case 'summary':
                        $have_summary = true;
                        break;
                    case 'description':
                        $have_descripion = true;
                        break;
                }
            }

            if (!$have_summary || !$have_description) {
                $description = substr($doc, 0, strpos($doc, '@'));
                $description = trim(preg_replace(array('#^[\h*]*|[\h*]*$#m', '#[\h]+#m'), array('', ' '), $description));
                $split = preg_split('/\v\v+/', $description);
                $summary = trim(array_shift($split));
                if (!$have_summary) {
                    $this->pkg->setSummary(htmlentities($summary, ENT_QUOTES));
                }
                if (!$have_description) {
                    $this->pkg->setDescription(htmlentities($description, ENT_QUOTES));
                }
            }

            $doc = preg_split('/\v+/', $doc);
            $doc = preg_grep('/@uses/', $doc);
            $doc = preg_replace('/\s*\* @uses\s+|\s+$/', '', $doc);
            foreach ($doc as $line) {
                if (strpos($line, 'extension') === 0) {
                    $line = explode(' ', $line);
                    $name = $line[1];
                    $optional = 'required';
                    if (isset($line[2])) {
                        $optional = $line[2];
                    }
                    $this->pkg->addExtensionDep(
                        $optional,
                        $name
                    );
                } else {
                    $line = explode(' ', $line);
                    $name = $line[0];
                    $channel = $line[1];
                    $optional = 'required';
                    if (isset($line[2])) {
                        $optional = $line[2];
                    }
                    $this->pkg->addPackageDepWithChannel(
                        $optional,
                        $name,
                        $channel
                    );
                }
            }
        }

        $newmap = array();
        foreach ($this->mappings as $key => $map) {
            switch ($map->getName()) {
                case 'releases':
                    $releases = $map->getValue();
                    foreach ($releases as $release) {
                        $this->pkg->addRelease();
                        if (isset($release['installconditions'])) {
                            if (isset($release['installconditions']['os'])) {
                                $this->pkg->setOsInstallCondition($release['installconditions']['os']);
                            }
                        }
                        if (isset($release['filelist'])) {
                            if (isset($release['filelist']['install'])) {
                                foreach ($release['filelist']['install'] as $file => $as) {
                                    $this->pkg->addInstallAs($file, $as);
                                }
                            }
                            if (isset($release['filelist']['ignore'])) {
                                foreach ($release['filelist']['ignore'] as $file) {
                                    $this->pkg->addIgnoreToRelease($file);
                                }
                            }
                        }
                    }
                    break;

                default:
                    $newmap[] = $map;
            }
        }
        $this->mappings = $newmap;

        parent::setOptions();
    }
}


code:
Phergie is an IRC bot written for PHP 5.2.

Main project web site: http://phergie.org

Instructions for running your own instance of Phergie: http://phergie.org/users/

Architectural overview for plugin developers: http://phergie.org/developers/

Support: http://phergie.org/support/

Bug reports/feature requests: http://github.com/elazar/phergie/issues

path: plugins/Irc/extlib/phergie/README


code:
<?php

return array(

    // One array per connection, pretty self-explanatory
    'connections' => array(
        // Ex: All connection info for the Freenode network
        array(
            'host' => 'irc.freenode.net',
            'port' => 6667,
            'username' => 'Elazar',
            'realname' => 'Matthew Turland',
            'nick' => 'Phergie2',
            // 'password' => 'password goes here if needed',
            // 'transport' => 'ssl', // uncomment to connect using SSL
            // 'encoding' => 'UTF8', // uncomment if using UTF8
        )
    ),

    'processor' => 'async',
    'processor.options' => array('usec' => 200000),
    // Time zone. See: http://www.php.net/manual/en/timezones.php
    'timezone' => 'UTC',

    // Whitelist of plugins to load
    'plugins' => array(
        // To enable a plugin, simply add a string to this array containing
        // the short name of the plugin as shown below.

        // 'ShortPluginName',

        // Below is an example of enabling the AutoJoin plugin, for which
        // the corresponding PEAR package is Phergie_Plugin_AutoJoin. This
        // plugin allows you to set a list of channels in this configuration
        // file that the bot will automatically join when it connects to a
        // server. If you'd like to enable this plugin, simply install it,
        // uncomment the line below, and set a value for the setting
        // autojoin.channels (examples for which are located further down in
        // this file).

        // 'AutoJoin',

        // A few other recommended plugins:

        // Servers randomly send PING events to clients to ensure that
        // they're still connected and will eventually terminate the

        // connection if a PONG response is not received. The Pong plugin
        // handles sending these responses.

        // 'Pong',

        // It's sometimes difficult to distinguish between a lack of
        // activity on a server and the client not receiving data even
        // though a connection remains open. The Ping plugin performs a self
        // CTCP PING sporadically to ensure that its connection is still
        // functioning and, if not, terminates the bot.

        // 'Ping',

        // Sometimes it's desirable to have the bot disconnect gracefully
        // when issued a command to do so via a PRIVMSG event. The Quit
        // plugin implements this using the Command plugin to intercept the
        // command.

        // 'Quit',
    ),

    // If set to true, this allows any plugin dependencies for plugins
    // listed in the 'plugins' option to be loaded even if they are not
    // explicitly included in that list
    'plugins.autoload' => true,

    // Enables shell output describing bot events via Phergie_Ui_Console
    'ui.enabled' => true,

    // Examples of supported values for autojoins.channel:
    // 'autojoin.channels' => '#channel1,#channel2',
    // 'autojoin.channels' => array('#channel1', '#channel2'),
    // 'autojoin.channels' => array(
    //                            'host1' => '#channel1,#channel2',
    //                            'host2' => array('#channel3', '#channel4')
    //                        ),

    // Examples of setting values for Ping plugin settings

    // This is the amount of time in seconds that the Ping plugin will wait
    // to receive an event from the server before it initiates a self-ping

    // 'ping.event' => 300, // 5 minutes

    // This is the amount of time in seconds that the Ping plugin will wait
    // following a self-ping attempt before it assumes that a response will
    // never be received and terminates the connection

    // 'ping.ping' => 10, // 10 seconds

);