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
 */

/**
 * Returns information on PHP functions as requested.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Php
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Php
 * @uses     extension pdo
 * @uses     extension pdo_sqlite
 * @uses     Phergie_Plugin_Command pear.phergie.org
 */
class Phergie_Plugin_Php extends Phergie_Plugin_Abstract
{
    /**
     * Data source to use
     *
     * @var Phergie_Plugin_Php_Source
     */
    protected $source;

    /**
     * Check for dependencies.
     *
     * @return void
     */
    public function onLoad()
    {
        // @todo find a way to move this to Phergie_Plugin_Php_Source_Local
        if (!extension_loaded('PDO') || !extension_loaded('pdo_sqlite')) {
            $this->fail('PDO and pdo_sqlite extensions must be installed');
        }

        $this->getPluginHandler()->getPlugin('Command');

        $this->source = new Phergie_Plugin_Php_Source_Local;
    }

    /**
     * Searches the data source for the requested function.
     *
     * @param string $functionName Name of the function to search for
     *
     * @return void
     */
    public function onCommandPhp($functionName)
    {
        $nick = $this->event->getNick();
        if ($function = $this->source->findFunction($functionName)) {
            $msg = $nick . ': ' . $function['description'];
            $this->doPrivmsg($this->event->getSource(), $msg);
        } else {
            $msg = 'Search for function ' . $functionName . ' returned no results.';
            $this->doNotice($nick, $msg);
        }
    }
}


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
 */

/**
 * Data source interface for the Php plugin.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Php
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Php
 * @uses     extension pdo
 * @uses     extension pdo_sqlite
 * @uses     Phergie_Plugin_Command pear.phergie.org
 */
interface Phergie_Plugin_Php_Source
{
    /**
     * Searches for a description of the function.
     *
     * @param string $function Search pattern to match against the function
     *        name, wildcards supported using %
     *
     * @return array|null Associative array containing the function name and
     *         description or NULL if no results are found
     */
    public function findFunction($function);
}

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
 */

/**
 * Data source for {@see Phergie_Plugin_Php}. This source reads function
 * descriptions from a file and stores them in a SQLite database. When a
 * function description is requested, the function is retrieved from the
 * local database.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Php
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Php
 * @uses     extension pdo
 * @uses     extension pdo_sqlite
 * @uses     Phergie_Plugin_Command pear.phergie.org
 */
class Phergie_Plugin_Php_Source_Local implements Phergie_Plugin_Php_Source
{
    /**
     * Local database for storage
     *
     * @var PDO
     */
    protected $database;

    /**
     * Source of the PHP function summary
     *
     * @var string
     */
    protected $url = 'http://cvs.php.net/viewvc.cgi/phpdoc/funcsummary.txt?revision=HEAD';

    /**
     * Constructor to initialize the data source.
     *
     * @return void
     */
    public function __construct()
    {
        $path = dirname(__FILE__);

        try {
            $this->database = new PDO('sqlite:' . $path . '/functions.db');
            $this->buildDatabase();
            // @todo Modify this to be rethrown as an appropriate
            //       Phergie_Plugin_Exception and handled in Phergie_Plugin_Php
        } catch (PDOException $e) {
            echo 'PDO failure: '.$e->getMessage();
        }
    }

    /**
     * Searches for a description of the function.
     *
     * @param string $function Search pattern to match against the function
     *        name, wildcards supported using %
     *
     * @return array|null Associative array containing the function name and
     *         description or NULL if no results are found
     */
    public function findFunction($function)
    {
        // Remove possible parentheses
        $split = preg_split('{\(|\)}', $function);
        $function = (count($split)) ? array_shift($split) : $function;

        // Prepare the database statement
        $stmt = $this->database->prepare('SELECT `name`, `description` FROM `functions` WHERE `name` LIKE :function');
        $stmt->execute(array(':function' => $function));

        // Check the results
        if (count($stmt) > 0) {
            $result = $stmt->fetch(PDO::FETCH_ASSOC);
            /**
             * @todo add class and function URLS
             * class methods: http://php.net/manual/en/classname.methodname.php
             * functions: http://php.net/manual/en/function.functionname.php
             * where '_' is replaced with '-'
             */
            return $result;
        }

        // No results found, return
        return null;
    }

    /**
     * Build the database and parses the function summary file into it.
     *
     * @param bool $rebuild TRUE to force a rebuild of the table used to
     *        house function information, FALSE otherwise, defaults to FALSE
     *
     * @return void
     */
    protected function buildDatabase($rebuild = false)
    {
        // Check to see if the functions table exists
        $checkstmt = $this->database->query("SELECT COUNT(*) FROM `sqlite_master` WHERE `name` = 'functions'");
        $checkstmt->execute();
        $result = $checkstmt->fetch(PDO::FETCH_ASSOC);
        unset( $checkstmt );
        $table = $result['COUNT(*)'];
        unset( $result );
        // If the table doesn't exist, create it
        if (!$table) {
                $this->database->exec('CREATE TABLE `functions` (`name` VARCHAR(255), `description` TEXT)');
                $this->database->exec('CREATE UNIQUE INDEX `functions_name` ON `functions` (`name`)');
        }

        // If we created a new table, fill it with data
        if (!$table || $rebuild) {
            // Get the contents of the source file
            // @todo Handle possible error cases better here; the @ operator
            //       shouldn't be needed
            $contents = @file($this->url, FILE_IGNORE_NEW_LINES + FILE_SKIP_EMPTY_LINES);

            if (!$contents) {
                return;
            }

            // Parse the contents
            $valid = array();
            $firstPart = '';
            $lineNumber = 0;
            foreach ($contents as $line) {
                // Clean the current line
                $line = trim($line);

                // Skip comment lines
                if (0 === strpos($line, '#')) {
                    // reset the line if the current line is odd
                    if (($lineNumber % 2) !== 0) {
                        $lineNumber--;
                    }
                    continue;
                }

                /*
                 * If the current line is even, it's the first part of the
                 * complete function description ...
                 */
                if (($lineNumber % 2) === 0) {
                    $firstPart = $line;
                } else {
                    // ... it's the last part of the complete function description
                    $completeLine = $firstPart . ' ' . $line;
                    $firstPart = '';
                    if (preg_match('{^([^\s]*)[\s]?([^)]*)\(([^\)]*)\)[\sU]+([\sa-zA-Z0-9\.,\-_()]*)$}', $completeLine, $matches)) {
                        $valid[] = $matches;
                    }
                }
                // Up the line number before going to the next line
                $lineNumber++;
            }
            // free up some memory
            unset($contents);

            // Process the valid matches
            if (count($valid) > 0) {
                // Clear the database
                $this->database->exec('DELETE * FROM `functions`');

                // Prepare the sql statement
                $stmt = $this->database->prepare('INSERT INTO `functions` (`name`, `description`) VALUES (:name, :description)');
                $this->database->beginTransaction();

                // Insert the data
                foreach ($valid as $function) {
                    // Extract function values
                    list( , $retval, $name, $params, $desc) = $function;
                    if (empty($name)) {
                        $name = $retval;
                        $retval = '';
                    }
                    // Reconstruct the complete function line
                    $line = trim($retval . ' ' . $name . '(' . $params . ') - ' . $desc);
                    // Execute the statement
                    $stmt->execute(array(':name' => $name, ':description' => $line));
                }

                // Commit the changes to the database
                $this->database->commit();
            }
            // free up some more memory
            unset($valid);
        }
    }
}


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_Ping
 * @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_Ping
 */

/**
 * Uses a self CTCP PING to ensure that the client connection has not been
 * dropped.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Ping
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Ping
 */
class Phergie_Plugin_Ping extends Phergie_Plugin_Abstract
{
    /**
     * Timestamp for the last instance in which an event was received
     *
     * @var int
     */
    protected $lastEvent;

    /**
     * Timestamp for the last instance in which a PING was sent
     *
     * @var int
     */
    protected $lastPing;

    /**
     * Initialize event timestamps upon connecting to the server.
     *
     * @return void
     */
    public function onConnect()
    {
        $this->lastEvent = time();
        $this->lastPing = null;
    }

    /**
     * Updates the timestamp since the last received event when a new event
     * arrives.
     *
     * @return void
     */
    public function preEvent()
    {
        $this->lastEvent = time();
    }

    /**
     * Clears the ping time if a reply is received.
     *
     * @return void
     */
    public function onPingResponse()
    {
        $this->lastPing = null;
    }

    /**
     * Performs a self ping if the event threshold has been exceeded or
     * issues a termination command if the ping threshold has been exceeded.
     *
     * @return void
     */
    public function onTick()
    {
        $time = time();
        if (!empty($this->lastPing)) {
            if ($time - $this->lastPing > $this->getConfig('ping.ping', 20)) {
                $this->doQuit();
            }
        } elseif (
            $time - $this->lastEvent > $this->getConfig('ping.event', 300)
        ) {
            $this->lastPing = $time;
            $this->doPing($this->getConnection()->getNick(), $this->lastPing);
        }
    }

    /**
     * Gets the last ping time
     * lastPing needs exposing for things such as unit testing
     *
     * @return int timestamp of last ping
     */
    public function getLastPing()
    {
        return $this->lastPing;
    }

    /**
     * Set the last ping time
     * lastPing needs to be exposed for unit testing
     *
     * @param int|null $ping timestamp of last ping
     *
     * @return self
     */
    public function setLastPing($ping = null)
    {
        if (null === $ping) {
            $ping = time();
        }
        if (!is_int($ping)) {
            throw new InvalidArgumentException('$ping must be an integer or null');
        }
        $this->lastPing = $ping;
        return $this;
    }

    /**
     * Gets the last event time
     * lastEvent needs exposing for things such as unit testing
     *
     * @return int timestamp of last ping
     */
    public function getLastEvent()
    {
        return $this->lastEvent;
    }

    /**
     * Set the last event time
     * lastEvent needs to be exposed for unit testing
     *
     * @param int|null $event timestamp of last ping
     *
     * @return self
     */
    public function setLastEvent($event = null)
    {
        if (null === $event) {
            $event = time();
        }
        if (!is_int($event)) {
            throw new InvalidArgumentException('$ping must be an integer or null');
        }
        $this->lastEvent = $event;
        return $this;
    }
}


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_Pong
 * @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_Pong
 */

/**
 * Responds to PING requests from the server.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Pong
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Pong
 * @link     http://irchelp.org/irchelp/rfc/chapter4.html#c4_6_2
 * @link     http://irchelp.org/irchelp/rfc/chapter4.html#c4_6_3
 */
class Phergie_Plugin_Pong extends Phergie_Plugin_Abstract
{
    /**
     * Sends a PONG response for each PING request received by the server.
     *
     * @return void
     */
    public function onPing()
    {
        $this->doPong($this->getEvent()->getArgument(0));
    }
}


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_Prioritize
 * @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_Prioritize
 */

/**
 * Prioritizes events such that they are executed in order from least to most
 * destructive.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Prioritize
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Prioritize
 */
class Phergie_Plugin_Prioritize extends Phergie_Plugin_Abstract
{
    /**
     * Event types ordered by priority of execution
     *
     * @var array
     */
    protected $priority = array(
        'raw',
        'pass',
        'user',
        'ping',
        'pong',
        'notice',
        'join',
        'list',
        'names',
        'version',
        'stats',
        'links',
        'time',
        'trace',
        'admin',
        'info',
        'who',
        'whois',
        'whowas',
        'mode',
        'privmsg',
        'action',
        'nick',
        'topic',
        'invite',
        'kill',
        'part',
        'quit'
    );

    /**
     * Prioritizes events from least to most destructive.
     *
     * @return void
     */
    public function preDispatch()
    {
        $events = $this->getEventHandler();

        // Categorize events by type
        $categorized = array();
        foreach ($events as $event) {
            $type = $event->getType();
            if (!isset($categorized[$type])) {
                $categorized[$type] = array();
            }
            $categorized[$type][] = $event;
        }

        // Order events by type from least to most destructive
        $types = array_intersect($this->priority, array_keys($categorized));
        $prioritized = array();
        foreach ($types as $type) {
            $prioritized = array_merge($prioritized, $categorized[$type]);
        }

        // Replace the original events array with the prioritized one
        $events->replaceEvents($prioritized);
    }
}


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_Puppet
 * @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_Puppet
 */

/**
 * Allows a user to effectively speak and act as the bot.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Puppet
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Puppet
 * @uses     Phergie_Plugin_Command pear.phergie.org
 */
class Phergie_Plugin_Puppet extends Phergie_Plugin_Abstract
{
    /**
     * Checks for dependencies.
     *
     * @return void
     */
    public function onLoad()
    {
        $this->getPluginHandler()->getPlugin('Command');
    }

    /**
     * Handles a request for the bot to repeat a given message in a specified
     * channel.
     *
     * say #chan message
     *
     * @param string $channel Name of the channel
     * @param string $message Message to repeat
     *
     * @return void
     */
    public function onCommandSay($channel, $message)
    {
        $this->doPrivmsg($channel, $message);
    }

    /**
     * Handles a request for the bot to repeat a given action in a specified
     * channel.
     *
     * act #chan action
     *
     * @param string $channel Name of the channel
     * @param string $action  Action to perform
     *
     * @return void
     */
    public function onCommandAct($channel, $action)
    {
        $this->doAction($channel, $action);
    }

    /**
     * Handles a request for the bot to send the server a raw message
     *
     * raw message
     *
     * @param string $message Message to send
     *
     * @return void
     */
    public function onCommandRaw($message)
    {
        $this->doRaw($message);
    }
}


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_Quit
 * @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_Quit
 */

/**
 * Terminates the current connection upon command.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Quit
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Quit
 * @uses     Phergie_Plugin_Command pear.phergie.org
 */
class Phergie_Plugin_Quit extends Phergie_Plugin_Abstract
{
    /**
     * Checks for dependencies.
     *
     * @return void
     */
    public function onLoad()
    {
        $this->getPluginHandler()->getPlugin('Command');
    }

    /**
     * Issues a quit command when a message is received requesting that the
     * bot terminate the current connection.
     *
     * @return void
     */
    public function onCommandQuit()
    {
        $this->doQuit('Requested by ' . $this->getEvent()->getNick());
    }
}


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_Reload
 * @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_Reload
 */

/**
 * Facilitates reloading of individual plugins for development purposes.
 * Note that, because existing class definitions cannot be removed from
 * memory, increased memory usage is an expected result of using this plugin.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Reload
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Reload
 * @uses     Phergie_Plugin_Command pear.phergie.org
 */
class Phergie_Plugin_Reload extends Phergie_Plugin_Abstract
{
    /**
     * Checks for dependencies.
     *
     * @return void
     */
    public function onLoad()
    {
        $this->getPluginHandler()->getPlugin('Command');
    }

    /**
     * Reloads a specified plugin.
     *
     * @param string $plugin Short name of the plugin to reload
     *
     * @return void
     */
    public function onCommandReload($plugin)
    {
        $plugin = ucfirst($plugin);

        $evalClass = true;
        if (strpos($plugin, ' ') !== false) {
            $args = explode(' ', $plugin);
            $plugin = $args[0];
            if (strtolower($args[1]) == 'force') {
                $evalClass = false;
            }
        }

        if (!$this->plugins->hasPlugin($plugin)) {
            echo 'DEBUG(Reload): ' . ucfirst($plugin) . ' is not loaded yet, loading', PHP_EOL;
            try {
                $this->plugins->getPlugin($plugin);
                $this->plugins->command->populateMethodCache();
            } catch (Phergie_Plugin_Exception $e) {
                if ($e->getCode() == Phergie_Plugin_Exception::ERR_CLASS_NOT_FOUND) {
                    echo 'DEBUG(Reload): ', $e->getMessage(), PHP_EOL;
                } else {
                    throw $e;
                }
            }
            return;
        }

        try {
            $info = $this->plugins->getPluginInfo($plugin);
        } catch (Phergie_Plugin_Exception $e) {
            $source = $this->event->getSource();
            $nick = $this->event->getNick();
            $this->doNotice($source, $nick . ': ' . $e->getMessage());
            return;
        }

        $class = $info['class'];
        $contents = file_get_contents($info['file']);
        $newClass = $class . '_' . sha1($contents);

        if (class_exists($newClass, false)) {
            if ($evalClass == true) {
                echo 'DEBUG(Reload): Class ', $class, ' has not changed since last reload', PHP_EOL;
                return;
            }
        } else {
            $contents = preg_replace(
                array('/^<\?(?:php)?/', '/class\s+' . $class . '/i'),
                array('', 'class ' . $newClass),
                $contents
            );
            eval($contents);
        }

        $instance = new $newClass;
        $instance->setName($plugin);
        $instance->setEvent($this->event);
        $this->plugins
            ->removePlugin($plugin)
            ->addPlugin($instance);

        $this->plugins->command->populateMethodCache();
        if ($this->plugins->hasPlugin('Help')) {
            $this->plugins->help->populateRegistry();
        }

        echo 'DEBUG(Reload): Reloaded ', $class, ' to ', $newClass, PHP_EOL;
    }
}


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_Remind
 * @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_Remind
 */

/**
 * Parses and logs messages that should be relayed to other users the next time
 * the recipient is active on the same channel.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Remind
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Remind
 * @uses     Phergie_Plugin_Command pear.phergie.org
 * @uses     Phergie_Plugin_Time pear.phergie.org
 * @uses     extension PDO
 * @uses     extension pdo_sqlite
 */
class Phergie_Plugin_Remind extends Phergie_Plugin_Abstract
{
    /**
     * Number of reminders to show in public.
     */
    protected $publicReminders = 3;

    /**
     * PDO resource for a SQLite database containing the reminders.
     *
     * @var resource
     */
    protected $db;

    /**
     * Flag that indicates whether or not to use an in-memory reminder list.
     *
     * @var bool
     */
    protected $keepListInMemory = true;

    /**
     * In-memory store for pending reminders.
     */
    protected $msgStorage = array();

    /**
     * Check for dependencies.
     *
     * @return void
     */
    public function onLoad()
    {
        $plugins = $this->getPluginHandler();
        $plugins->getPlugin('Command');
        $plugins->getPlugin('Time');

        if (!extension_loaded('PDO') || !extension_loaded('pdo_sqlite')) {
            $this->fail('PDO and pdo_sqlite extensions must be installed');
        }

        $dir = dirname(__FILE__) . '/' . $this->getName();
        $path = $dir . '/reminder.db';
        if (!file_exists($dir)) {
            mkdir($dir);
        }

        if (isset($this->config['remind.use_memory'])) {
            $this->keepListInMemory = (bool) $this->config['remind.use_memory'];
        }

        if (isset($this->config['remind.public_reminders'])) {
            $this->publicReminders = (int) $this->config['remind.public_reminders'];
            $this->publicReminders = max($this->publicReminders, 0);
        }

        try {
            $this->db = new PDO('sqlite:' . $path);
            $this->createTables();
        } catch (PDO_Exception $e) {
            throw new Phergie_Plugin_Exception($e->getMessage());
        }
    }

    /**
     * Intercepts a message and processes any contained recognized commands.
     *
     * @return void
     */
    public function onPrivmsg()
    {
        $source = $this->getEvent()->getSource();
        $nick = $this->getEvent()->getNick();

        $this->deliverReminders($source, $nick);
    }

    /**
     * Handle reminder requests
     *
     * @param string $recipient recipient of the message
     * @param string $message   message to tell the recipient
     *
     * @return void
     * @see handleRemind()
     */
    public function onCommandTell($recipient, $message)
    {
        $this->handleRemind($recipient, $message);
    }

    /**
     * Handle reminder requests
     *
     * @param string $recipient recipient of the message
     * @param string $message   message to tell the recipient
     *
     * @return void
     * @see handleRemind()
     */
    public function onCommandAsk($recipient, $message)
    {
        $this->handleRemind($recipient, $message);
    }

    /**
     * Handle reminder requests
     *
     * @param string $recipient recipient of the message
     * @param string $message   message to tell the recipient
     *
     * @return void
     * @see handleRemind()
     */
    public function onCommandRemind($recipient, $message)
    {
        $this->handleRemind($recipient, $message);
    }

    /**
     * Handles the tell/remind command (stores the message)
     *
     * @param string $recipient name of the recipient
     * @param string $message   message to store
     *
     * @return void
     */
    protected function handleRemind($recipient, $message)
    {
        $source = $this->getEvent()->getSource();
        $nick = $this->getEvent()->getNick();

        if (!$this->getEvent()->isInChannel()) {
            $this->doPrivmsg($source, 'Reminders must be requested in-channel.');
            return;
        }

        $q = $this->db->prepare(
            'INSERT INTO remind
                (
                    time,
                    channel,
                    recipient,
                    sender,
                    message
                )
            VALUES
                (
                    :time,
                    :channel,
                    :recipient,
                    :sender,
                    :message
               )'
        );
        try {
            $q->execute(
                array(
                    'time' => date(DATE_RFC822),
                    'channel' => $source,
                    'recipient' => strtolower($recipient),
                    'sender' => strtolower($nick),
                    'message' => $message
                )
            );
        } catch (PDOException $e) {
        }

        if ($rowid = $this->db->lastInsertId()) {
            $this->doPrivmsg($source, 'ok, ' . $nick . ', message stored');
        } else {
            $this->doPrivmsg(
                $source,
                $nick . ': bad things happened. Message not saved.'
            );
            return;
        }

        if ($this->keepListInMemory) {
            $this->msgStorage[$source][strtolower($recipient)] = $rowid;
        }
    }

    /**
     * Determines if the user has pending reminders, and if so, delivers them.
     *
     * @param string $channel channel to check
     * @param string $nick    nick to check
     *
     * @return void
     */
    protected function deliverReminders($channel, $nick)
    {
        if ($channel[0] != '#') {
            // private message, not a channel, so don't check
            return;
        }

        // short circuit if there's no message in memory (if allowed)
        if ($this->keepListInMemory
            && !isset($this->msgStorage[$channel][strtolower($nick)])
        ) {
            return;
        }

        // fetch and deliver messages
        $reminders = $this->fetchMessages($channel, $nick);
        if (count($reminders) > $this->publicReminders) {
            $msgs = array_slice($reminders, 0, $this->publicReminders);
            $privmsgs = array_slice($reminders, $this->publicReminders);
        } else {
            $msgs = $reminders;
            $privmsgs = false;
        }

        foreach ($msgs as $msg) {
            $ts = $this->plugins->time->getCountdown($msg['time']);
            $formatted = sprintf(
                '%s: (from %s, %s ago) %s',
                $nick, $msg['sender'], $ts, $msg['message']
            );
            $this->doPrivmsg($channel, $formatted);
            $this->deleteMessage($msg['rowid'], $channel, $nick);
        }

        if ($privmsgs) {
            foreach ($privmsgs as $msg) {
                $ts = $this->plugins->time->getCountdown($msg['time']);
                $formatted = sprintf(
                    'from %s, %s ago: %s',
                    $msg['sender'], $ts, $msg['message']
                );
                $this->doPrivmsg($nick, $formatted);
                $this->deleteMessage($msg['rowid'], $channel, $nick);
            }
            $formatted = sprintf(
                '%s: (%d more messages sent in private.)',
                $nick, count($privmsgs)
            );
            $this->doPrivmsg($channel, $formatted);
        }
    }

    /**
     * Get pending messages (for a specific channel/recipient)
     *
     * @param string $channel   channel on which to check for pending messages
     * @param string $recipient user for which to check pending messages
     *
     * @return array of records
     */
    protected function fetchMessages($channel = null, $recipient = null)
    {
        if ($channel) {
            $qClause = 'WHERE channel = :channel AND recipient LIKE :recipient';
            $params = compact('channel', 'recipient');
        } else {
            $qClause = '';
            $params = array();
        }
        $q = $this->db->prepare(
            'SELECT rowid, channel, sender, recipient, time, message
            FROM remind ' . $qClause
        );
        $q->execute($params);
        return $q->fetchAll();
    }

    /**
     * Deletes a delivered message
     *
     * @param int    $rowid   ID of the message to delete
     * @param string $channel message's channel
     * @param string $nick    message's recipient
     *
     * @return void
     */
    protected function deleteMessage($rowid, $channel, $nick)
    {
        $nick = strtolower($nick);
        $q = $this->db->prepare('DELETE FROM remind WHERE rowid = :rowid');
        $q->execute(array('rowid' => $rowid));

        if ($this->keepListInMemory) {
            if (isset($this->msgStorage[$channel][$nick])
                && $this->msgStorage[$channel][$nick] == $rowid
            ) {
                unset($this->msgStorage[$channel][$nick]);
            }
        }
    }

    /**
     * Determines if a table exists
     *
     * @param string $name Table name
     *
     * @return bool
     */
    protected function haveTable($name)
    {
        $sql = 'SELECT COUNT(*) FROM sqlite_master WHERE name = '
            . $this->db->quote($name);
        return (bool) $this->db->query($sql)->fetchColumn();
    }

    /**
     * Creates the database table(s) (if they don't exist)
     *
     * @return void
     */
    protected function createTables()
    {
        if (!$this->haveTable('remind')) {
            $this->db->exec(
                'CREATE TABLE
                    remind
                    (
                        time INTEGER,
                        channel TEXT,
                        recipient TEXT,
                        sender TEXT,
                        message TEXT
                    )'
            );
        }
    }

    /**
     * Populates the in-memory cache of pending reminders
     *
     * @return void
     */
    protected function populateMemory()
    {
        if (!$this->keepListInMemory) {
            return;
        }
        foreach ($this->fetchMessages() as $msg) {
            $this->msgStorage[$msg['channel']][$msg['recipient']] = $msg['rowid'];
        }
    }
}


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_Serve
 * @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_Serve
 */

/**
 * Processes requests to serve a user something from a database.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Serve
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Serve
 * @uses     extension pdo
 * @uses     extension pdo_sqlite
 */
class Phergie_Plugin_Serve extends Phergie_Plugin_Abstract
{
    /**
     * Checks for dependencies.
     *
     * @return void
     */
    public function onLoad()
    {
        if (!extension_loaded('PDO') || !extension_loaded('pdo_sqlite')) {
            $this->fail('PDO and pdo_sqlite extensions must be installed');
        }
    }

    /**
     * Retrieves a random item from the database table.
     *
     * @param string $database Path to the SQLite database file
     * @param string $table    Name of the database table
     * @param array  $request  Parsed request
     *
     * @return object Retrieved item
     */
    protected function getItem($database, $table, array $request)
    {
        $db = new PDO('sqlite:' . $database);
        if (!empty($request['suggestion'])) {
            $query = 'SELECT * FROM ' . $table . ' WHERE name LIKE ? ORDER BY RANDOM() LIMIT 1';
            $stmt = $db->prepare($query);
            $stmt->execute(array('%' . $request['suggestion'] . '%'));
            $item = $stmt->fetchObject();
            if (!$item) {
                $item = new stdClass;
                $item->name = $request['suggestion'];
                $item->link = null;
            }
        } else {
            $query = 'SELECT * FROM ' . $table . ' ORDER BY RANDOM() LIMIT 1';
            $stmt = $db->query($query);
            $item = $stmt->fetchObject();
        }
        return $item;
    }

    /**
     * Processes a request to serve a user something.
     *
     * @param string $database Path to the SQLite database file
     * @param string $table    Name of the database table
     * @param string $format   Format of the response where %target%,
     *        %item%, %article%', and %link will be replaced with their
     *        respective data
     * @param string $request  Request string including the target and an
     *        optional suggestion of the item to fetch
     * @param boolean $censor  TRUE to integrate with the Censor plugin,
     *        defaults to FALSE
     *
     * @return boolean TRUE if the request was processed successfully, FALSE
     *         otherwise
     */
    public function serve($database, $table, $format, $request, $censor = false)
    {
        // Parse the request
        $result = preg_match(
            '/(?P<target>[^\s]+)(\s+an?\s+)?(?P<suggestion>.*)?/',
            $request,
            $match
        );

        if (!$result) {
            return false;
        }

        // Resolve the target
        $target = $match['target'];
        if ($target == 'me') {
            $target = $this->event->getNick();
        }

        // Process the request
        $item = $this->getItem($database, $table, $match);

        // Reprocess the request for censorship if required
        if ($this->plugins->hasPlugin('Censor')) {
            $plugin = $this->plugins->getPlugin('Censor');
            $attempts = 0;
            while ($censor && $attempts < 3) {
                $clean = $plugin->cleanString($item->name);
                if ($item->name != $clean) {
                    $attempts++;
                    $item = $this->getItem($database, $table, $match);
                } else {
                    $censor = false;
                }
            }
            if ($censor && $attempts == 3) {
                $this->doAction($this->event->getSource(), 'shrugs.');
            }
        }

        // Derive the proper article for the item
        if (preg_match('/^[aeiou]/i', $item->name)) {
            $article = 'an';
        } else {
            $article = 'a';
        }

        // Format the message
        $replacements = array(
            'target' => $target,
            'item' => $item->name,
            'link' => $item->link,
            'article' => $article
        );

        $msg = $format;
        foreach ($replacements as $placeholder => $value) {
            $msg = str_replace(
                '%' . $placeholder . '%',
                $value,
                $msg
            );
        }

        // Send the message
        $this->doAction($this->event->getSource(), $msg);
    }
}


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_TerryChay
 * @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_TerryChay
 */

/**
 * Handles requests for checking spelling of specified words and returning
 * either confirmation of correctly spelled words or potential correct
 * spellings for misspelled words.
 *
 * @category Phergie
 * @package  Phergie_Plugin_SpellCheck
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_TerryChay
 * @uses     Phergie_Plugin_Command pear.phergie.org
 * @uses     extension pspell
 */
class Phergie_Plugin_SpellCheck extends Phergie_Plugin_Abstract
{
    /**
     * Spell check dictionary handler
     *
     * @var resource
     */
    protected $pspell;

    /**
     * Limit on the number of potential correct spellings returned
     *
     * @var int
     */
    protected $limit;

    /**
     * Check for dependencies.
     *
     * @return void
     */
    public function onLoad()
    {
        if (!extension_loaded('pspell')) {
            $this->fail('pspell php extension is required');
        }

        if (!$this->getConfig('spellcheck.lang')) {
            $this->fail('Setting spellcheck.lang must be filled-in');
        }

        $this->plugins->getPlugin('Command');

        set_error_handler(array($this, 'loadDictionaryError'));
        $this->pspell = pspell_new($this->getConfig('spellcheck.lang'));
        restore_error_handler();

        $this->limit = $this->getConfig('spellcheck.limit', 5);
    }

    /**
     * Intercepts and handles requests for spell checks.
     *
     * @param string $word the string to perform checks against
     *
     * @return void
     */
    public function onCommandSpell($word)
    {
        $source = $this->event->getSource();
        $target = $this->event->getNick();

        $message  = $target . ': The word "' . $word;
        $message .= '" seems to be spelled correctly.';
        if (!pspell_check($this->pspell, $word)) {
            $suggestions = pspell_suggest($this->pspell, $word);

            $message  = $target;
            $message .= ': I could not find any suggestions for "' . $word . '".';
            if (!empty($suggestions)) {
                $suggestions = array_splice($suggestions, 0, $this->limit);
                $message     = $target . ': Suggestions for "';
                $message    .= $word . '": ' . implode(', ', $suggestions) . '.';
            }
        }

        $this->doPrivmsg($source, $message);
    }

    /**
     * Handle any errors from loading dictionary
     *
     * @param integer $errno   Error code
     * @param string  $errstr  Error message
     * @param string  $errfile File that errored
     * @param integer $errline Line where the error happened
     *
     * @return void
     */
    protected function loadDictionaryError($errno, $errstr, $errfile, $errline)
    {
        $this->fail($errstr);
    }
}


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/>.
 *
 * Talks to the Statusnet IM architecture to enqueue incoming message messages
 * and notify result of nickname registration checks
 *
 * @category  Phergie
 * @package   Phergie_Plugin_Statusnet
 * @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_Plugin_Statusnet extends Phergie_Plugin_Abstract {
    /**
    * Message callback details
    *
    * @var array
    */
    protected $messageCallback;

    /**
    * Registration check callback details
    *
    * @var array
    */
    protected $regCallback;

    /**
    * Connection established callback details
    *
    * @var array
    */
    protected $connectedCallback;

    /**
    * Load callback from config
    */
    public function onLoad() {
        $messageCallback = $this->config['statusnet.messagecallback'];
        if (is_callable($messageCallback)) {
            $this->messageCallback = $messageCallback;
        } else {
            $this->messageCallback = NULL;
        }

        $regCallback = $this->config['statusnet.regcallback'];
        if (is_callable($regCallback)) {
            $this->regCallback = $regCallback;
        } else {
            $this->regCallback = NULL;
        }

        $connectedCallback = $this->config['statusnet.connectedcallback'];
        if (is_callable($connectedCallback)) {
            $this->connectedCallback = $connectedCallback;
        } else {
            $this->connectedCallback = NULL;
        }

        $this->unregRegexp = $this->getConfig('statusnet.unregregexp', '/\x02(.*?)\x02 (?:isn\'t|is not) registered/i');
        $this->regRegexp = $this->getConfig('statusnet.regregexp', '/(?:\A|\x02)(\w+?)\x02? (?:\(account|is \w+?\z)/i');
    }

    /**
     * Passes incoming messages to StatusNet
     *
     * @return void
     */
    public function onPrivmsg() {
        if ($this->messageCallback !== NULL) {
            $event = $this->getEvent();
            $source = $event->getSource();
            $sender = $event->getNick();
            $message = trim($event->getText());

            if (strpos($source, '#') === 0) {
                $botNick = $this->getConnection()->getNick();
                $nickPos = strpos($message, $botNick);
                $nickLen = strlen($botNick);
                $colonPos = strpos($message, ':', $nickLen);
                $commandStr = trim(substr($message, $colonPos+1));
                if ($nickPos === 0 && $colonPos == $nickLen && !empty($commandStr)) {
                    call_user_func($this->messageCallback, array('source' => $source, 'sender' => $sender, 'message' => $commandStr));
                }
            } else {
                call_user_func($this->messageCallback, array('source' => $source, 'sender' => $sender, 'message' => $message));
            }
        }
    }

    /**
     * Catches the response from NickServ
     *
     * @return void
     */
    public function onNotice() {
        if ($this->regCallback !== NULL) {
            $event = $this->getEvent();
            if ($event->getNick() == 'NickServ') {
                $message = $event->getArgument(1);
                if (preg_match($this->unregRegexp, $message, $groups)) {
                    $screenname = $groups[1];
                    call_user_func($this->regCallback, array('screenname' => $screenname, 'registered' => false));
                } elseif (preg_match($this->regRegexp, $message, $groups)) {
                    $screenname = $groups[1];
                    call_user_func($this->regCallback, array('screenname' => $screenname, 'registered' => true));
                }
            }
        }
    }

    /**
     * Intercepts the end of the "message of the day" response and tells
     * StatusNet we're connected
     *
     * @return void
     */
    public function onResponse() {
        switch ($this->getEvent()->getCode()) {
        case Phergie_Event_Response::RPL_ENDOFMOTD:
        case Phergie_Event_Response::ERR_NOMOTD:
            if ($this->connectedCallback !== NULL) {
                call_user_func($this->connectedCallback);
            }
        }
    }
}


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_Tea
 * @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_Tea
 */

/**
 * Processes requests to serve users tea.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Tea
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Tea
 * @uses     Phergie_Plugin_Command pear.phergie.org
 * @uses     Phergie_Plugin_Serve pear.phergie.org
 */
class Phergie_Plugin_Tea extends Phergie_Plugin_Abstract
{
    /**
     * Checks for dependencies.
     *
     * @return void
     */
    public function onLoad()
    {
        $plugins = $this->plugins;
        $plugins->getPlugin('Command');
        $plugins->getPlugin('Serve');
    }

    /**
     * Processes requests to serve a user tea.
     *
     * @param string $request Request including the target and an optional
     *        suggestion of what tea to serve
     *
     * @return void
     */
    public function onCommandTea($request)
    {
        $format = $this->getConfig(
            'tea.format',
            'serves %target% a cup of %item% tea.'
        );

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


code:
<?php

if (!defined('__DIR__')) {
    define('__DIR__', dirname(__FILE__));
}

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

// Get raw teacuppa.com data set
echo 'Downloading teacuppa.com data set', PHP_EOL;
$file = __DIR__ . '/tea-list.html';
if (!file_exists($file)) {
    copy('http://www.teacuppa.com/tea-list.asp', $file);
}
$contents = file_get_contents($file);

// Extract data from data set
echo 'Processing teacuppa.com data', PHP_EOL;
$contents = tidy_repair_string($contents);
libxml_use_internal_errors(true);
$doc = new DOMDocument;
$doc->loadHTML($contents);
libxml_clear_errors();
$xpath = new DOMXPath($doc);
$teas = $xpath->query('//p[@class="page_title"]/following-sibling::table//a');
$db->beginTransaction();
foreach ($teas as $tea) {
    $name = preg_replace(
        array('/\s*\v+\s*/', '/\s+tea\s*$/i'),
        array(' ', ''),
        $tea->textContent
    );
    $link = 'http://teacuppa.com/' . $tea->getAttribute('href');
    $insert->execute(array($name, $link));
}
$db->commit();

// Clean up
echo 'Cleaning up', PHP_EOL;
unlink($file);


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_Temperature
 * @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_Temperature
 */

/**
 * Performs temperature calculations for other plugins.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Temperature
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Temperature
 */
class Phergie_Plugin_Temperature extends Phergie_Plugin_Abstract
{
    /**
     * Converts a temperature in Celsius to Fahrenheit.
     *
     * @param int $temp Temperature in Celsius
     *
     * @return int Temperature converted to Fahrenheit
     */
    public function convertCelsiusToFahrenheit($temp)
    {
        return round(((((int) $temp * 9) / 5) + 32));
    }

    /**
     * Converts a temperature in Fahrenheit to Celsius.
     *
     * @param int $temp Temperature in Fahrenheit
     *
     * @return int Temperature converted to Celsius
     */
    public function convertFahrenheitToCelsius($temp)
    {
        return round(((((int) $temp - 32) * 5) / 9));
    }

    /**
     * Calculates the heat index (i.e. "feels like" temperature) based on
     * temperature and relative humidity.
     *
     * @param int $temperature Temperature in degrees Fahrenheit
     * @param int $humidity Relative humidity (ex: 68)
     * @return int Heat index in degrees Fahrenheit
     */
    public function getHeatIndex($temperature, $humidity)
    {
        $temperature2 = $temperature * $temperature;
        $humidity2 = $humidity * $humidity;
        return round(
            -42.379 +
            (2.04901523 * $temperature) +
            (10.14333127 * $humidity) -
            (0.22475541 * $temperature * $humidity) -
            (0.00683783 * $temperature2) -
            (0.05481717 * $humidity2) +
            (0.00122874 * $temperature2 * $humidity) +
            (0.00085282 * $temperature * $humidity2) -
            (0.00000199 * $temperature2 * $humidity2)
        );
    }
}


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_TerryChay
 * @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_TerryChay
 */

/**
 * Parses incoming messages for the words "Terry Chay" or tychay and responds
 * with a random Terry fact retrieved from the Chayism web service.
 *
 * @category Phergie
 * @package  Phergie_Plugin_TerryChay
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_TerryChay
 * @uses     Phergie_Plugin_Http pear.phergie.org
 */
class Phergie_Plugin_TerryChay extends Phergie_Plugin_Abstract
{
    /**
     * URL to the web service
     *
     * @const string
     */
    const URL = 'http://phpdoc.info/chayism/';

    /**
     * HTTP plugin
     *
     * @var Phergie_Plugin_Http
     */
    protected $http;

    /**
     * Checks for dependencies.
     *
     * @return void
     */
    public function onLoad()
    {
        $this->getPluginHandler()->getPlugin('Http');
    }

    /**
     * Fetches a chayism.
     *
     * @return string|bool Fetched chayism or FALSE if the operation failed
     */
    public function getChayism()
    {
        return $this
            ->getPluginHandler()
            ->getPlugin('Http')
            ->get(self::URL)
            ->getContent();
    }

    /**
     * Parses incoming messages for "Terry Chay" and related variations and
     * responds with a chayism.
     *
     * @return void
     */
    public function onPrivmsg()
    {
        $event = $this->getEvent();
        $source = $event->getSource();
        $message = $event->getText();
        $pattern
            = '{^(' . preg_quote($this->getConfig('command.prefix')) .
            '\s*)?.*(terry\s+chay|tychay)}ix';

        if (preg_match($pattern, $message)) {
            if($fact = $this->getChayism()) {
                $this->doPrivmsg($source, 'Fact: ' . $fact);
            }
        }
    }
}


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_TheFuckingWeather
 * @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_TheFuckingWeather
 */

/**
 * Detects and responds to requests for current weather conditions in a
 * particular location using data from a web service.
 *
 * @category Phergie
 * @package  Phergie_Plugin_TheFuckingWeather
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_TheFuckingWeather
 * @link     http://thefuckingweather.com
 * @uses     Phergie_Plugin_Command pear.phergie.org
 * @uses     Phergie_Plugin_Http pear.phergie.org
 */

class Phergie_Plugin_TheFuckingWeather extends Phergie_Plugin_Abstract
{
    /**
     * HTTP plugin
     *
     * @var Phergie_Plugin_Http
     */
    protected $http = null;

    /**
     * Base API URL
     *
     * @var string
     */
    protected $url = 'http://www.thefuckingweather.com/?zipcode=';

    /**
     * Checks for dependencies.
     *
     * @return void
     */
    public function onLoad()
    {
        $pluginHandler = $this->getPluginHandler();
        $pluginHandler->getPlugin('Command');
        $this->http = $pluginHandler->getPlugin('Http');
    }

    /**
     * Returns the weather from the specified location.
     *
     * @param string $location Location term
     *
     * @return void
     * @todo Implement use of URL shortening here
     */
    public function onCommandThefuckingweather($location)
    {
        $source = $this->getEvent()->getSource();
        $target = $this->getEvent()->getNick();
        $out = $this->getWeather($location);
        if (!$out) {
            $this->doNotice($source, $out);
        } else {
            $this->doPrivmsg($source, $target . ': ' . $out);
        }
    }

    /**
    * Alias for TheFuckingWeather command.
    *
    * @param string $location Location term
    *
    * @return void
    */
    public function onCommandTfw($location)
    {
        $this->onCommandThefuckingweather($location);
    }

    /**
     * Get the necessary content and returns the search result.
     *
     * @param string $location Location term
     *
     * @return string|bool Search result or FALSE if none is found
     * @todo Try to optimize pregs
     */
    protected function getWeather($location)
    {
        $url = $this->url . urlencode($location);
        $response = $this->http->get($url);
        $content = $response->getContent();

        preg_match_all(
            '#
(.*?)<\/span><\/div>#im',
            $content, $matches
        );
        $location = $matches[1][0];

        if (!empty($location)) {
            preg_match_all(
                '#
(.*?)
#im',
                $content, $matches
            );
            $temp_numb = (int) $matches[1][0];
            $temp_numb .= ' F / ' . round(($temp_numb - 32) / 1.8, 0) . ' C?!';

            preg_match_all(
                '#
(.*?)<\/div>

#im',
                $content, $matches
            );
            $temp_desc = $matches[1][0];

            preg_match_all(
                '#

\n(.*?)<\/span><\/div>#im',
                $content, $matches
            );
            $remark = $matches[1][0];

            $result = "{$location}: {$temp_numb} {$temp_desc} ({$remark})";
            $result = preg_replace('/</', ' <', $result);
            $result = strip_tags($result);
            return html_entity_decode($result);
        } else {
            return 'No fucking clue where that is.';
        }
    }
}


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_Time
 * @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_Time
 */

/**
 * Helper plugin to assist other plugins with time manipulation, display.
 *
 * Any shared time-related code should go into this class.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Time
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Time
 */
class Phergie_Plugin_Time extends Phergie_Plugin_Abstract
{
    /**
     * Returns the time interval between the current time and a given
     * timestamp.
     *
     * @param string $timestamp Timestamp compatible with strtotime()
     *
     * @return string
     */
    public function getCountdown($timestamp)
    {
        $time = time() - strtotime($timestamp);
        $return = array();

        $days = floor($time / 86400);
        if ($days > 0) {
            $return[] = $days . 'd';
            $time %= 86400;
        }

        $hours = floor($time / 3600);
        if ($hours > 0) {
            $return[] = $hours . 'h';
            $time %= 3600;
        }

        $minutes = floor($time / 60);
        if ($minutes > 0) {
            $return[] = $minutes . 'm';
            $time %= 60;
        }

        if ($time > 0 || count($return) <= 0) {
            $return[] = ($time > 0 ? $time : '0') . 's';
        }

        return implode(' ', $return);
    }
}


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
 */

/**
 * Responds to a request for a TLD (formatted as .tld where tld is the TLD to
 * be looked up) with its corresponding description.
 *
 * @category Phergie
 * @package  Phergie_Plugin_Tld
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Tld
 * @uses     extension PDO
 * @uses     extension pdo_sqlite
 */
class Phergie_Plugin_Tld extends Phergie_Plugin_Abstract
{
    /**
     * Connection to the database
     *
     * @var PDO
     */
    protected $db;

    /**
     * Prepared statement for selecting a single TLD
     *
     * @var PDOStatement
     */
    protected $select;

    /**
     * Prepared statement for selecting all TLDs
     *
     * @var PDOStatement
     */
    protected $selectAll;

    /**
     * Checks for dependencies and sets up the database and hard-coded values.
     *
     * @return void
     */
    public function onLoad()
    {
        if (!extension_loaded('PDO') || !extension_loaded('pdo_sqlite')) {
            $this->fail('PDO and pdo_sqlite extensions must be installed');
        }

        $dbFile = dirname(__FILE__) . '/Tld/tld.db';
        try {
            $this->db = new PDO('sqlite:' . $dbFile);

            $this->select = $this->db->prepare('
                SELECT type, description
                FROM tld
                WHERE LOWER(tld) = LOWER(:tld)
            ');

            $this->selectAll = $this->db->prepare('
                SELECT tld, type, description
                FROM btld
            ');
        } catch (PDOException $e) {
            $this->getPluginHandler()->removePlugin($this);
        }
    }

    /**
     * takes a tld in the format '.tld' and returns its related data
     *
     * @param string $tld tld to process
     *
     * @return null
     */
    public function onCommandTld($tld)
    {
        $tld = ltrim($tld, '.');
        $description = $this->getTld($tld);
        $this->doPrivmsg(
            $this->event->getSource(),
            "{$this->getEvent()->getNick()}: .{$tld} -> "
            . ($description ? $description : 'Unknown TLD')
        );
    }

    /**
     * Retrieves the definition for a given TLD if it exists
     *
     * @param string $tld TLD to search for
     *
     * @return mixed Definition of the given TLD as a string or false if unknown
     */
    public function getTld($tld)
    {
        $tld = trim(strtolower($tld));
        if ($this->select->execute(array('tld' => $tld))) {
            $tlds = $this->select->fetch();
            if (is_array($tlds)) {
                return '(' . $tlds['type'] . ') ' . $tlds['description'];
            }
        }
        return false;
    }

    /**
     * Retrieves a list of all the TLDs and their definitions
     *
     * @return mixed Array of all the TLDs and their definitions or FALSE on
      *        failure
     */
    public function getTlds()
    {
        if ($this->selectAll->execute()) {
            $tlds = $this->selectAll->fetchAll();
            if (is_array($tlds)) {
                $tldinfo = array();
                foreach ($tlds as $key => $tld) {
                    if (!empty($tld['tld'])) {
                        $tldinfo[$tld['tld']] = "({$tld['type']}) "
                        . $tld['description'];
                    }
                }
                return $tldinfo;
            }
        }
        return false;
    }
}


code:
<?php

$dbFile = 'tld.db';

if (file_exists($dbFile)) {
    exit;
}

$db = new PDO('sqlite:' . dirname(__FILE__) . '/' . $dbFile);

$query = '
    CREATE TABLE tld (
        tld VARCHAR(20),
        type VARCHAR(20),
        description VARCHAR(255)
    )
';
$db->exec($query);

$insert = $db->prepare('
    INSERT INTO tld (tld, type, description)
    VALUES (:tld, :type, :description)
');

$contents = file_get_contents(
    'http://www.iana.org/domains/root/db/'
);

libxml_use_internal_errors(true);
$doc = new DOMDocument;
$doc->loadHTML($contents);
libxml_clear_errors();

$descriptions = array(
    'com' => 'Commercial',
    'info' => 'Information',
    'net' => 'Network',
    'org' => 'Organization',
    'edu' => 'Educational',
    'name' => 'Individuals, by name'
);

$xpath = new DOMXPath($doc);
$rows = $xpath->query('//tr[contains(@class, "iana-group")]');
foreach (range(0, $rows->length - 1) as $index) {
    $row = $rows->item($index);
    $tld = strtolower(ltrim($row->childNodes->item(0)->textContent, '.'));
    $type = $row->childNodes->item(1)->nodeValue;
    if (isset($descriptions[$tld])) {
        $description = $descriptions[$tld];
    } else {
        $description = $row->childNodes->item(2)->textContent;
        $regex = '{(^(?:Reserved|Restricted)\s*(?:exclusively\s*)?'
         . '(?:for|to)\s*(?:members of\s*)?(?:the|support)?'
         . '\s*|\s*as advised.*$)}i';
        $description = preg_replace($regex, '', $description);
        $description = ucfirst(trim($description));
    }
    $data = array_map(
        'html_entity_decode',
        array(
            'tld' => $tld,
            'type' => $type,
            'description' => $description
        )
    );
    $insert->execute($data);
}


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_Twitter
 * @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_Twitter
 */

/**
 * These requires are for library code, so they don't fit Autoload's normal
 * conventions.
 *
 * @link http://github.com/scoates/simpletweet
 */
require dirname(__FILE__) . '/Twitter/twitter.class.php';
require dirname(__FILE__) . '/Twitter/laconica.class.php';

/**
 * Twitter plugin; Allows tweet (if configured) and twitter commands
 *
 * Usage:
 *   tweet text to tweet
 *    (sends a message to twitter and Phergie will give you the link)
 *   twitter username
 *    (fetches and displays the last tweet by @username)
 *   twitter username 3
 *    (fetches and displays the third last tweet by @username)
 *   twitter 1234567
 *    (fetches and displays tweet number 1234567)
 *   http://twitter.com/username/statuses/1234567
 *    (same as `twitter 1234567`)
 *
 * @category Phergie
 * @package  Phergie_Plugin_Twitter
 * @author   Phergie Development Team <team@phergie.org>
 * @license  http://phergie.org/license New BSD License
 * @link     http://pear.phergie.org/package/Phergie_Plugin_Twitter
 * @uses     Phergie_Plugin_Time pear.phergie.org
 */
class Phergie_Plugin_Twitter extends Phergie_Plugin_Abstract
{
    /**
     * Twitter object (from Simpletweet)
     */
    protected $twitter;

    /**
     * Twitter user
     */
    protected $twitteruser = null;

    /**
     * Password
     */
    protected $twitterpassword = null;

    /**
     * Register with the URL plugin, if possible
     *
     * @return void
     */
    public function onConnect()
    {
        if ($url = $this->getPluginHandler()->getPlugin('Url')) {
            $url->registerRenderer($this);
        }
    }

    /**
     * Initialize (set up configuration vars)
     *
     * @return void
     */
    public function onLoad()
    {
        if (!isset($this->config['twitter.class'])
            || !$twitterClass = $this->config['twitter.class']
        ) {
            $twitterClass = 'Twitter';
        }

        $this->twitteruser = $this->config['twitter.user'];
        $this->twitterpassword = $this->config['twitter.password'];
        $url = $this->config['twitter.url'];

        $this->twitter = new $twitterClass(
            $this->twitteruser,
            $this->twitterpassword,
            $url
        );

    }

    /**
     * Fetches the associated tweet and relays it to the channel
     *
     * @param string $tweeter if numeric the tweet number/id, otherwise the
     *  twitter user name (optionally prefixed with @)
     * @param int    $num     optional tweet number for this user (number of
     *  tweets ago)
     *
     * @return void
     */
    public function onCommandTwitter($tweeter = null, $num = 1)
    {
        $source = $this->getEvent()->getSource();
        if (is_numeric($tweeter)) {
            $tweet = $this->twitter->getTweetByNum($tweeter);
        } else if (is_null($tweeter) && $this->twitteruser) {
            $tweet = $this->twitter->getLastTweet($this->twitteruser, 1);
        } else {
            $tweet = $this->twitter->getLastTweet(ltrim($tweeter, '@'), $num);
        }
        if ($tweet) {
            $this->doPrivmsg($source, $this->formatTweet($tweet));
        }
    }

    /**
     * Sends a tweet to Twitter as the configured user
     *
     * @param string $txt the text to tweet
     *
     * @return void
     */
    public function onCommandTweet($txt)
    {
        $nick = $this->getEvent()->getNick();
        if (!$this->twitteruser) {
            return;
        }
        $source = $this->getEvent()->getSource();
        if ($tweet = $this->twitter->sendTweet($txt)) {
            $this->doPrivmsg(
                $source, 'Tweeted: '
                . $this->twitter->getUrlOutputStatus($tweet)
            );
        } else {
            $this->doNotice($nick, 'Tweet failed');
        }
    }

    /**
     * Formats a Tweet into a message suitable for output
     *
     * @param object $tweet      JSON-decoded tweet object from Twitter
     * @param bool   $includeUrl whether or not to include the URL in the
     *  formatted output
     *
     * @return string
     */
    protected function formatTweet(StdClass $tweet, $includeUrl = true)
    {
        $ts = $this->plugins->time->getCountDown($tweet->created_at);
        $out =  '<@' . $tweet->user->screen_name .'> '. $tweet->text
            . ' - ' . $ts . ' ago';
        if ($includeUrl) {
            $out .= ' (' . $this->twitter->getUrlOutputStatus($tweet) . ')';
        }
        return $out;
    }

    /**
     * Renders a URL
     *
     * @param array $parsed parse_url() output for the URL to render
     *
     * @return bool
     */
    public function renderUrl(array $parsed)
    {
        if ($parsed['host'] != 'twitter.com'
            && $parsed['host'] != 'www.twitter.com'
        ) {
            // unable to render non-twitter URLs
            return false;
        }

        $source = $this->getEvent()->getSource();

        if (preg_match('#^/(.*?)/status(es)?/([0-9]+)$#', $parsed['path'], $matches)
        ) {
            $tweet = $this->twitter->getTweetByNum($matches[3]);
            if ($tweet) {
                $this->doPrivmsg($source, $this->formatTweet($tweet, false));
            }
            return true;
        }

        // if we get this far, we haven't satisfied the URL, so bail:
        return false;
    }
}


code:
<?php
/**
 * Sean's Simple Twitter Library - Laconica extension
 *
 * Copyright 2008, Sean Coates
 * Usage of the works is permitted provided that this instrument is retained
 * with the works, so that any entity that uses the works is notified of this
 * instrument.
 * DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
 * ( Fair License - http://www.opensource.org/licenses/fair.php )
 * Short license: do whatever you like with this.
 *
 */
class Twitter_Laconica extends Twitter {

    /**
     * Constructor; sets up configuration.
     *
     * @param string $user Laconica user name; null for limited read-only access
     * @param string $pass Laconica password; null for limited read-only access
     * @param string $baseUrl Base URL of Laconica install. Defaults to identi.ca
     */
    public function __construct($user=null, $pass=null, $baseUrl = 'http://identi.ca/') {
        $this->baseUrl = $baseUrl;
        parent::__construct($user, $pass);
    }

    /**
     * Returns the base API URL
     */
    protected function getUrlApi() {
        return $this->baseUrlFull . 'api/';
    }

    /**
     * Output URL: status
     */
    public function getUrlOutputStatus(StdClass $tweet) {
        return $this->baseUrl . 'notice/' . urlencode($tweet->id);
    }
}


code:
<?php
/**
 * Sean's Simple Twitter Library
 *
 * Probably a little more or a little less than you need.
 *
 * Copyright 2008, Sean Coates
 * Usage of the works is permitted provided that this instrument is retained
 * with the works, so that any entity that uses the works is notified of this
 * instrument.
 * DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
 * ( Fair License - http://www.opensource.org/licenses/fair.php )
 * Short license: do whatever you like with this.
 *
 * komode: le=unix language=php codepage=utf8 tab=4 notabs indent=4
 */
class Twitter {

    /**
     * Base URL for Twitter API
     *
     * Do not specify user/password in URL
     */
    protected $baseUrl = 'http://twitter.com/';

    /**
     * Full base URL (includes user/pass)
     *
     * (created in Init)
     */
    protected $baseUrlFull = null;

    /**
     * Twitter API user
     */
    protected $user;

    /**
     * Twitter API password
     */
    protected $pass;

    /**
     * Constructor; sets up configuration.
     *
     * @param string $user Twitter user name; null for limited read-only access
     * @param string $pass Twitter password; null for limited read-only access
     */
    public function __construct($user=null, $pass=null) {
        $this->baseUrlFull = $this->baseUrl;
        if (null !== $user) {
            // user is defined, so use it in the URL
            $this->user = $user;
            $this->pass = $pass;
            $parsed = parse_url($this->baseUrl);
            $this->baseUrlFull = $parsed['scheme'] . '://' . $this->user . ':' .
                $this->pass . '@' . $parsed['host'];
            // port (optional)
            if (isset($parsed['port']) && is_numeric($parsed['port'])) {
                $this->baseUrlFull .= ':' . $parsed['port'];
            }
            // append path (default: /)
            if (isset($parsed['path'])) {
                $this->baseUrlFull .= $parsed['path'];
            } else {
                $this->baseUrlFull .= '/';
            }
        }
    }

    /**
     * Fetches a tweet by its number/id
     *
     * @param int $num the tweet id/number
     * @return string (null on failure)
     */
    public function getTweetByNum($num) {
        if (!is_numeric($num)) {
            return;
        }
        $tweet = json_decode(file_get_contents($this->getUrlStatus($num)));
        return $tweet;
    }

    /**
     * Reads [last] tweet from user
     *
     * @param string $tweeter the tweeter username
     * @param int $num this many tweets ago (1 = current tweet)
     * @return string (false on failure)
     */
    public function getLastTweet($tweeter, $num = 1)
    {
        $source = json_decode(file_get_contents($this->getUrlUserTimeline($tweeter)));
        if ($num > count($source)) {
            return false;
        }
        $tweet = $source[$num - 1];
        if (!isset($tweet->user->screen_name) || !$tweet->user->screen_name) {
            return false;
        }
        return $tweet;
    }

    /**
     * fetches mentions for a user
     */
    public function getMentions($sinceId=null, $count=20) {
        return json_decode(file_get_contents($this->getUrlMentions($sinceId, $count)));
    }

    /**
     * Fetches followers for a user
     */
    public function getFollowers($cursor=-1) {
        return json_decode(file_get_contents($this->getUrlFollowers($cursor)));
    }

    /**
     * Follow a userid
     */
    public function follow($userId) {
        $params = array(
            'http' => array(
                'method' => 'POST',
                'content' => array(),
                'header' => 'Content-type: application/x-www-form-urlencoded',
            )
        );
        $ctx = stream_context_create($params);
        $fp = fopen($this->getUrlFollow($userId), 'rb', false, $ctx);
        if (!$fp) {
            return false;
        }
        $response = stream_get_contents($fp);
        if ($response === false) {
            return false;
        }
        $response = json_decode($response);
        return $response;
    }

    /**
     * fetches DMs for a user
     */
    public function getDMs($sinceId=null, $count=20, $page=1) {
        return json_decode(file_get_contents($this->getUrlDMs($sinceId, $count, $page)));
    }

    /**
     * Send DM
     */
    public function sendDM($screenName, $text) {
        $data = http_build_query(array('screen_name'=>$screenName, 'text'=>$text));
        $params = array(
            'http' => array(
                'method' => 'POST',
                'content' => $data,
                'header' => 'Content-type: application/x-www-form-urlencoded',
            )
        );
        $ctx = stream_context_create($params);
        $fp = fopen($this->getUrlSendDM(), 'rb', false, $ctx);
        if (!$fp) {
            return false;
        }
        $response = stream_get_contents($fp);
        if ($response === false) {
            return false;
        }
        $response = json_decode($response);
        return $response;
    }

    /**
     * Sends a tweet
     *
     * @param string $txt the tweet text to send
     * @return string URL of tweet (or false on failure)
     */
    public function sendTweet($txt, $limit=true) {
        if ($limit) {
            $txt = substr($txt, 0, 140); // twitter message size limit
        }
        $data = 'status=' . urlencode($txt);
        $params = array(
            'http' => array(
                'method' => 'POST',
                'content' => $data,
                'header' => 'Content-type: application/x-www-form-urlencoded',
            )
        );
        $ctx = stream_context_create($params);
        $fp = fopen($this->getUrlTweetPost(), 'rb', false, $ctx);
        if (!$fp) {
            return false;
        }
        $response = stream_get_contents($fp);
        if ($response === false) {
            return false;
        }
        $response = json_decode($response);
        return $response;
    }

    /**
     * Returns the base API URL
     */
    protected function getUrlApi() {
        return $this->baseUrlFull;
    }

    /**
     * Returns the status URL
     *
     * @param int $num the tweet number
     */
    protected function getUrlStatus($num) {
        return $this->getUrlApi() . 'statuses/show/'. urlencode($num) .'.json';
    }

    /**
     * Returns the user timeline URL
     */
    protected function getUrlUserTimeline($user) {
        return $this->getUrlApi() . 'statuses/user_timeline/'. urlencode($user) .'.json';
    }

    /**
     * Returns the tweet posting URL
     */
    protected function getUrlTweetPost() {
        return $this->getUrlApi() . 'statuses/update.json';
    }

    /**
     * Output URL: status
     */
    public function getUrlOutputStatus(StdClass $tweet) {
        return $this->baseUrl . urlencode($tweet->user->screen_name) . '/statuses/' . urlencode($tweet->id);
    }

    /**
     * Return mentions URL
     */
    public function getUrlMentions($sinceId=null, $count=20) {
        $url = $this->baseUrlFull . 'statuses/mentions.json?count=' . urlencode($count);
        if ($sinceId !== null) {
            $url .= '&since_id=' . urlencode($sinceId);
        }
        return $url;
    }

    /**
     * Returns the followers URL
     */
    public function getUrlFollowers($cursor=-1) {
        return $this->baseUrlFull . 'statuses/followers.json?cursor=' . ((int)$cursor);
    }

    /**
     * Returns the follow-user URL
     */
    public function getUrlFollow($userid) {
        return $this->baseUrlFull . 'friendships/create/' . ((int) $userid) . '.json';
    }

    /**
     * Returns the get DMs URL
     */
    public function getUrlDMs($sinceId=null, $count=20, $page=1) {
        $url = $this->baseUrlFull . 'direct_messages.json?';
        if ($sinceId !== null) {
            $url .= 'since_id=' . urlencode($sinceId);
        }
        $url .= "&page={$page}";
        $url .= "&count={$count}";
        return $url;
    }

    /**
     * Returns the send DM URL
     */
    public function getURLSendDM() {
        return $this->baseUrlFull . 'direct_messages/new.json';
    }
}