Merge remote branch 'chat-interface-plugins/irc-plugin' into 1.0.x

This commit is contained in:
Craig Andrews 2010-08-18 14:13:15 -04:00
commit 7cd0706aef
126 changed files with 20826 additions and 0 deletions

View File

@ -0,0 +1,61 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Extend the IMChannel class to allow commands to send messages
* to a channel instead of PMing a user
*
* PHP version 5
*
* LICENCE: 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/>.
*
* @category Network
* @package StatusNet
* @author Luke Fitzgerald <lw.fitzgerald@googlemail.com>
* @copyright 2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
class ChannelResponseChannel extends IMChannel {
protected $ircChannel;
/**
* Construct a ChannelResponseChannel
*
* @param IMplugin $imPlugin IMPlugin
* @param string $ircChannel IRC Channel to reply to
* @return ChannelResponseChannel
*/
public function __construct($imPlugin, $ircChannel) {
$this->ircChannel = $ircChannel;
parent::__construct($imPlugin);
}
/**
* Send a message using the plugin
*
* @param User $user User
* @param string $text Message text
* @return void
*/
public function output($user, $text) {
$text = $user->nickname.': ['.common_config('site', 'name') . '] ' . $text;
$this->imPlugin->send_message($this->ircChannel, $text);
}
}

47
plugins/Irc/Fake_Irc.php Normal file
View File

@ -0,0 +1,47 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Instead of sending IRC messages, retrieve the raw data that would be sent
*
* PHP version 5
*
* LICENCE: 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/>.
*
* @category Network
* @package StatusNet
* @author Luke Fitzgerald <lw.fitzgerald@googlemail.com>
* @copyright 2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
class Fake_Irc extends Phergie_Driver_Streams {
public $would_be_sent = null;
/**
* Store the components for sending a command
*
* @param string $command Command
* @param array $args Arguments
* @return void
*/
protected function send($command, $args = '') {
$this->would_be_sent = array('command' => $command, 'args' => $args);
}
}

396
plugins/Irc/IrcPlugin.php Normal file
View File

@ -0,0 +1,396 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2010, StatusNet, Inc.
*
* Send and receive notices using an IRC network
*
* PHP version 5
*
* 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/>.
*
* @category IM
* @package 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/
*/
if (!defined('STATUSNET')) {
// This check helps protect against security problems;
// your code file can't be executed directly from the web.
exit(1);
}
// We bundle the Phergie library...
set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . '/extlib/phergie');
/**
* Plugin for IRC
*
* @category Plugin
* @package 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 IrcPlugin extends ImPlugin {
public $host = null;
public $port = null;
public $username = null;
public $realname = null;
public $nick = null;
public $password = null;
public $nickservidentifyregexp = null;
public $nickservpassword = null;
public $channels = null;
public $transporttype = null;
public $encoding = null;
public $pinginterval = null;
public $regcheck = null;
public $unregregexp = null;
public $regregexp = null;
public $transport = 'irc';
protected $whiteList;
protected $fake_irc;
/**
* Get the internationalized/translated display name of this IM service
*
* @return string Name of service
*/
public function getDisplayName() {
return _m('IRC');
}
/**
* Normalize a screenname for comparison
*
* @param string $screenname Screenname to normalize
* @return string An equivalent screenname in normalized form
*/
public function normalize($screenname) {
$screenname = str_replace(" ","", $screenname);
return strtolower($screenname);
}
/**
* Get the screenname of the daemon that sends and receives messages
*
* @return string Screenname
*/
public function daemon_screenname() {
return $this->nick;
}
/**
* Validate (ensure the validity of) a screenname
*
* @param string $screenname Screenname to validate
* @return boolean true if screenname is valid
*/
public function validate($screenname) {
if (preg_match('/\A[a-z0-9\-_]{1,1000}\z/i', $screenname)) {
return true;
} else {
return false;
}
}
/**
* Load related modules when needed
*
* @param string $cls Name of the class to be loaded
* @return boolean hook value; true means continue processing, false means stop.
*/
public function onAutoload($cls) {
$dir = dirname(__FILE__);
switch ($cls) {
case 'IrcManager':
include_once $dir . '/'.strtolower($cls).'.php';
return false;
case 'Fake_Irc':
case 'Irc_waiting_message':
case 'ChannelResponseChannel':
include_once $dir . '/'. $cls .'.php';
return false;
default:
if (substr($cls, 0, 7) == 'Phergie') {
include_once str_replace('_', DIRECTORY_SEPARATOR, $cls) . '.php';
return false;
}
return true;
}
}
/*
* Start manager on daemon start
*
* @param array &$versions Array to insert manager into
* @return boolean
*/
public function onStartImDaemonIoManagers(&$classes) {
parent::onStartImDaemonIoManagers(&$classes);
$classes[] = new IrcManager($this); // handles sending/receiving
return true;
}
/**
* Ensure the database table is present
*
*/
public function onCheckSchema() {
$schema = Schema::get();
// For storing messages while sessions become ready
$schema->ensureTable('irc_waiting_message',
array(new ColumnDef('id', 'integer', null,
false, 'PRI', null, null, true),
new ColumnDef('data', 'blob', null, false),
new ColumnDef('prioritise', 'tinyint', 1, false),
new ColumnDef('attempts', 'integer', null, false),
new ColumnDef('created', 'datetime', null, false),
new ColumnDef('claimed', 'datetime')));
return true;
}
/**
* Get a microid URI for the given screenname
*
* @param string $screenname Screenname
* @return string microid URI
*/
public function microiduri($screenname) {
return 'irc:' . $screenname;
}
/**
* Send a message to a given screenname
*
* @param string $screenname Screenname to send to
* @param string $body Text to send
* @return boolean true on success
*/
public function send_message($screenname, $body) {
$lines = explode("\n", $body);
foreach ($lines as $line) {
$this->fake_irc->doPrivmsg($screenname, $line);
$this->enqueue_outgoing_raw(array('type' => 'message', 'prioritise' => 0, 'data' => $this->fake_irc->would_be_sent));
}
return true;
}
/**
* Accept a queued input message.
*
* @return boolean true if processing completed, false if message should be reprocessed
*/
public function receive_raw_message($data) {
if (strpos($data['source'], '#') === 0) {
$message = $data['message'];
$parts = explode(' ', $message, 2);
$command = $parts[0];
if (in_array($command, $this->whiteList)) {
$this->handle_channel_incoming($data['sender'], $data['source'], $message);
} else {
$this->handle_incoming($data['sender'], $message);
}
} else {
$this->handle_incoming($data['sender'], $data['message']);
}
return true;
}
/**
* Helper for handling incoming messages from a channel requiring response
* to the channel instead of via PM
*
* @param string $nick Screenname the message was sent from
* @param string $channel Channel the message originated from
* @param string $message Message text
* @param boolean true on success
*/
protected function handle_channel_incoming($nick, $channel, $notice_text) {
$user = $this->get_user($nick);
// For common_current_user to work
global $_cur;
$_cur = $user;
if (!$user) {
$this->send_from_site($nick, 'Unknown user; go to ' .
common_local_url('imsettings') .
' to add your address to your account');
common_log(LOG_WARNING, 'Message from unknown user ' . $nick);
return;
}
if ($this->handle_channel_command($user, $channel, $notice_text)) {
common_log(LOG_INFO, "Command message by $nick handled.");
return;
} else if ($this->is_autoreply($notice_text)) {
common_log(LOG_INFO, 'Ignoring auto reply from ' . $nick);
return;
} else if ($this->is_otr($notice_text)) {
common_log(LOG_INFO, 'Ignoring OTR from ' . $nick);
return;
} else {
common_log(LOG_INFO, 'Posting a notice from ' . $user->nickname);
$this->add_notice($nick, $user, $notice_text);
}
$user->free();
unset($user);
unset($_cur);
unset($message);
}
/**
* Attempt to handle a message from a channel as a command
*
* @param User $user User the message is from
* @param string $channel Channel the message originated from
* @param string $body Message text
* @return boolean true if the message was a command and was executed, false if it was not a command
*/
protected function handle_channel_command($user, $channel, $body) {
$inter = new CommandInterpreter();
$cmd = $inter->handle_command($user, $body);
if ($cmd) {
$chan = new ChannelResponseChannel($this, $channel);
$cmd->execute($chan);
return true;
} else {
return false;
}
}
/**
* Send a confirmation code to a user
*
* @param string $screenname screenname sending to
* @param string $code the confirmation code
* @param User $user user sending to
* @return boolean success value
*/
public function send_confirmation_code($screenname, $code, $user, $checked = false) {
$body = sprintf(_('User "%s" on %s has said that your %s screenname belongs to them. ' .
'If that\'s true, you can confirm by clicking on this URL: ' .
'%s' .
' . (If you cannot click it, copy-and-paste it into the ' .
'address bar of your browser). If that user isn\'t you, ' .
'or if you didn\'t request this confirmation, just ignore this message.'),
$user->nickname, common_config('site', 'name'), $this->getDisplayName(), common_local_url('confirmaddress', array('code' => $code)));
if ($this->regcheck && !$checked) {
return $this->checked_send_confirmation_code($screenname, $code, $user);
} else {
return $this->send_message($screenname, $body);
}
}
/**
* Only sends the confirmation message if the nick is
* registered
*
* @param string $screenname Screenname sending to
* @param string $code The confirmation code
* @param User $user User sending to
* @return boolean true on succes
*/
public function checked_send_confirmation_code($screenname, $code, $user) {
$this->fake_irc->doPrivmsg('NickServ', 'INFO '.$screenname);
$this->enqueue_outgoing_raw(
array(
'type' => 'nickcheck',
'prioritise' => 1,
'data' => $this->fake_irc->would_be_sent,
'nickdata' =>
array(
'screenname' => $screenname,
'code' => $code,
'user' => $user
)
)
);
return true;
}
/**
* Initialize plugin
*
* @return boolean
*/
public function initialize() {
if (!isset($this->host)) {
throw new Exception('must specify a host');
}
if (!isset($this->username)) {
throw new Exception('must specify a username');
}
if (!isset($this->realname)) {
throw new Exception('must specify a "real name"');
}
if (!isset($this->nick)) {
throw new Exception('must specify a nickname');
}
if (!isset($this->port)) {
$this->port = 6667;
}
if (!isset($this->transporttype)) {
$this->transporttype = 'tcp';
}
if (!isset($this->encoding)) {
$this->encoding = 'UTF-8';
}
if (!isset($this->pinginterval)) {
$this->pinginterval = 120;
}
if (!isset($this->regcheck)) {
$this->regcheck = true;
}
$this->fake_irc = new Fake_Irc;
/*
* Commands allowed to return output to a channel
*/
$this->whiteList = array('stats', 'last', 'get');
return true;
}
/**
* Get plugin information
*
* @param array $versions Array to insert information into
* @return void
*/
public function onPluginVersion(&$versions) {
$versions[] = array('name' => 'IRC',
'version' => STATUSNET_VERSION,
'author' => 'Luke Fitzgerald',
'homepage' => 'http://status.net/wiki/Plugin:IRC',
'rawdescription' =>
_m('The IRC plugin allows users to send and receive notices over an IRC network.'));
return true;
}
}

View File

@ -0,0 +1,142 @@
<?php
/**
* Table Definition for irc_waiting_message
*/
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
class Irc_waiting_message extends Memcached_DataObject {
public $__table = 'irc_waiting_message'; // table name
public $id; // int primary_key not_null auto_increment
public $data; // blob not_null
public $prioritise; // tinyint(1) not_null
public $attempts; // int not_null
public $created; // datetime() not_null
public $claimed; // datetime()
/* Static get */
public function staticGet($k, $v = null) {
return Memcached_DataObject::staticGet('Irc_waiting_message', $k, $v);
}
/**
* return table definition for DB_DataObject
*
* DB_DataObject needs to know something about the table to manipulate
* instances. This method provides all the DB_DataObject needs to know.
*
* @return array array of column definitions
*/
public function table() {
return array('id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
'data' => DB_DATAOBJECT_BLOB + DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
'prioritise' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
'created' => DB_DATAOBJECT_TIME + DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
'claimed' => DB_DATAOBJECT_TIME + DB_DATAOBJECT_STR);
}
/**
* return key definitions for DB_DataObject
*
* DB_DataObject needs to know about keys that the table has, since it
* won't appear in StatusNet's own keys list. In most cases, this will
* simply reference your keyTypes() function.
*
* @return array list of key field names
*/
public function keys() {
return array_keys($this->keyTypes());
}
/**
* return key definitions for Memcached_DataObject
*
* Our caching system uses the same key definitions, but uses a different
* method to get them. This key information is used to store and clear
* cached data, so be sure to list any key that will be used for static
* lookups.
*
* @return array associative array of key definitions, field name to type:
* 'K' for primary key: for compound keys, add an entry for each component;
* 'U' for unique keys: compound keys are not well supported here.
*/
public function keyTypes() {
return array('id' => 'K');
}
/**
* Magic formula for non-autoincrementing integer primary keys
*
* If a table has a single integer column as its primary key, DB_DataObject
* assumes that the column is auto-incrementing and makes a sequence table
* to do this incrementation. Since we don't need this for our class, we
* overload this method and return the magic formula that DB_DataObject needs.
*
* @return array magic three-false array that stops auto-incrementing.
*/
public function sequenceKey() {
return array(false, false, false);
}
/**
* Get the next item in the queue
*
* @return Irc_waiting_message Next message if there is one
*/
public static function top() {
$wm = new Irc_waiting_message();
$wm->orderBy('prioritise DESC, created');
$wm->whereAdd('claimed is null');
$wm->limit(1);
$cnt = $wm->find(true);
if ($cnt) {
# XXX: potential race condition
# can we force it to only update if claimed is still null
# (or old)?
common_log(LOG_INFO, 'claiming IRC waiting message id = ' . $wm->id);
$orig = clone($wm);
$wm->claimed = common_sql_now();
$result = $wm->update($orig);
if ($result) {
common_log(LOG_INFO, 'claim succeeded.');
return $wm;
} else {
common_log(LOG_INFO, 'claim failed.');
}
}
$wm = null;
return null;
}
/**
* Increment the attempts count
*
* @return void
* @throws Exception
*/
public function incAttempts() {
$orig = clone($this);
$this->attempts++;
$result = $this->update($orig);
if (!$result) {
throw Exception(sprintf(_m("Could not increment attempts count for %d"), $this->id));
}
}
/**
* Release a claimed item.
*/
public function releaseClaim() {
// DB_DataObject doesn't let us save nulls right now
$sql = sprintf("UPDATE irc_waiting_message SET claimed=NULL WHERE id=%d", $this->id);
$this->query($sql);
$this->claimed = null;
$this->encache();
}
}

45
plugins/Irc/README Normal file
View File

@ -0,0 +1,45 @@
The IRC plugin allows users to send and receive notices over an IRC network.
Installation
============
add "addPlugin('irc',
array('setting'=>'value', 'setting2'=>'value2', ...);"
to the bottom of your config.php
scripts/imdaemon.php included with StatusNet must be running. It will be started by
the plugin along with their other daemons when you run scripts/startdaemons.sh.
See the StatusNet README for more about queuing and daemons.
Settings
========
host*: Hostname of IRC server
port: Port of IRC server (defaults to 6667)
username*: Username of bot
realname*: Real name of bot
nick*: Nickname of bot
password: Password
nickservpassword: NickServ password for identification
nickservidentifyregexp: Override existing regexp matching request for identification from NickServ
channels: Channels for bot to idle in
transporttype: Set to 'ssl' to enable SSL
encoding: Set to change encoding
pinginterval: Set to change the number of seconds between pings (helps keep the connection open)
Defaults to 120 seconds
regcheck: Check user's nicknames are registered, enabled by default, set to false to disable
regregexp: Override existing regexp matching response from NickServ if nick checked is registered.
Must contain a capturing group catching the nick
unregregexp: Override existing regexp matching response from NickServ if nick checked is unregistered
Must contain a capturing group catching the nick
* required
Example
=======
addPlugin('irc', array(
'host' => '...',
'username' => '...',
'realname' => '...',
'nick' => '...',
'channels' => array('#channel1', '#channel2')
));

2
plugins/Irc/extlib/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
Settings.php
*.db

2
plugins/Irc/extlib/phergie/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
Settings.php
*.db

View File

@ -0,0 +1,27 @@
Copyright (c) 2010, Phergie Development Team
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
Neither the name of the Phergie Development Team nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,81 @@
<?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
*/
/**
* Autoloader for Phergie classes.
*
* @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_Autoload
{
/**
* Constructor to add the base Phergie path to the include_path.
*
* @return void
*/
public function __construct()
{
$path = realpath(dirname(__FILE__) . '/..');
$includePath = get_include_path();
$includePathList = explode(PATH_SEPARATOR, $includePath);
if (!in_array($path, $includePathList)) {
self::addPath($path);
}
}
/**
* Autoload callback for loading class files.
*
* @param string $class Class to load
*
* @return void
*/
public function load($class)
{
include str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php';
}
/**
* Registers an instance of this class as an autoloader.
*
* @return void
*/
public static function registerAutoloader()
{
spl_autoload_register(array(new self, 'load'));
}
/**
* Add a path to the include path.
*
* @param string $path Path to add
*
* @return void
*/
public static function addPath($path)
{
set_include_path($path . PATH_SEPARATOR . get_include_path());
}
}

View File

@ -0,0 +1,390 @@
<?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
*/
/**
* Composite class for other components to represent the bot.
*
* @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_Bot
{
/**
* Current version of Phergie
*/
const VERSION = '2.0.1';
/**
* Current driver instance
*
* @var Phergie_Driver_Abstract
*/
protected $driver;
/**
* Current configuration instance
*
* @var Phergie_Config
*/
protected $config;
/**
* 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;
/**
* Current processor instance
*
* @var Phergie_Process_Abstract
*/
protected $processor;
/**
* Returns a driver instance, creating one of the default class if
* none has been set.
*
* @return Phergie_Driver_Abstract
*/
public function getDriver()
{
if (empty($this->driver)) {
// Check if a driver has been defined in the configuration to use
// as the default
$config = $this->getConfig();
if (isset($config['driver'])) {
$class = 'Phergie_Driver_' . ucfirst($config['driver']);
} else {
// Otherwise default to the Streams driver.
$class = 'Phergie_Driver_Streams';
}
$this->driver = new $class;
}
return $this->driver;
}
/**
* Sets the driver instance to use.
*
* @param Phergie_Driver_Abstract $driver Driver instance
*
* @return Phergie_Bot Provides a fluent interface
*/
public function setDriver(Phergie_Driver_Abstract $driver)
{
$this->driver = $driver;
return $this;
}
/**
* Sets the configuration to use.
*
* @param Phergie_Config $config Configuration instance
*
* @return Phergie_Runner_Abstract Provides a fluent interface
*/
public function setConfig(Phergie_Config $config)
{
$this->config = $config;
return $this;
}
/**
* Returns the entire configuration in use or the value of a specific
* configuration setting.
*
* @param string $index Optional index of a specific configuration
* setting for which the corresponding value should be returned
* @param mixed $default Value to return if no match is found for $index
*
* @return mixed Value corresponding to $index or the entire
* configuration if $index is not specified
*/
public function getConfig($index = null, $default = null)
{
if (empty($this->config)) {
$this->config = new Phergie_Config;
$this->config->read('Settings.php');
}
if ($index !== null) {
if (isset($this->config[$index])) {
return $this->config[$index];
} else {
return $default;
}
}
return $this->config;
}
/**
* Returns a plugin handler instance, creating it if it does not already
* exist and using a default class if none has been set.
*
* @return Phergie_Plugin_Handler
*/
public function getPluginHandler()
{
if (empty($this->plugins)) {
$this->plugins = new Phergie_Plugin_Handler(
$this->getConfig(),
$this->getEventHandler()
);
}
return $this->plugins;
}
/**
* Sets the plugin handler instance to use.
*
* @param Phergie_Plugin_Handler $handler Plugin handler instance
*
* @return Phergie_Bot Provides a fluent interface
*/
public function setPluginHandler(Phergie_Plugin_Handler $handler)
{
$this->plugins = $handler;
return $this;
}
/**
* Returns an event handler instance, creating it if it does not already
* exist and using a default class if none has been set.
*
* @return Phergie_Event_Handler
*/
public function getEventHandler()
{
if (empty($this->events)) {
$this->events = new Phergie_Event_Handler;
}
return $this->events;
}
/**
* Sets the event handler instance to use.
*
* @param Phergie_Event_Handler $handler Event handler instance
*
* @return Phergie_Bot Provides a fluent interface
*/
public function setEventHandler(Phergie_Event_Handler $handler)
{
$this->events = $handler;
return $this;
}
/**
* Returns a connection handler instance, creating it if it does not
* already exist and using a default class if none has been set.
*
* @return Phergie_Connection_Handler
*/
public function getConnectionHandler()
{
if (empty($this->connections)) {
$this->connections = new Phergie_Connection_Handler;
}
return $this->connections;
}
/**
* Sets the connection handler instance to use.
*
* @param Phergie_Connection_Handler $handler Connection handler instance
*
* @return Phergie_Bot Provides a fluent interface
*/
public function setConnectionHandler(Phergie_Connection_Handler $handler)
{
$this->connections = $handler;
return $this;
}
/**
* Returns an end-user interface instance, creating it if it does not
* already exist and using a default class if none has been set.
*
* @return Phergie_Ui_Abstract
*/
public function getUi()
{
if (empty($this->ui)) {
$this->ui = new Phergie_Ui_Console;
}
return $this->ui;
}
/**
* Sets the end-user interface instance to use.
*
* @param Phergie_Ui_Abstract $ui End-user interface instance
*
* @return Phergie_Bot Provides a fluent interface
*/
public function setUi(Phergie_Ui_Abstract $ui)
{
$this->ui = $ui;
return $this;
}
/**
* Returns a processer instance, creating one if none exists.
*
* @return Phergie_Process_Abstract
*/
public function getProcessor()
{
if (empty($this->processor)) {
$class = 'Phergie_Process_Standard';
$type = $this->getConfig('processor');
if (!empty($type)) {
$class = 'Phergie_Process_' . ucfirst($type);
}
$this->processor = new $class(
$this,
$this->getConfig('processor.options', array())
);
}
return $this->processor;
}
/**
* Sets the processer instance to use.
*
* @param Phergie_Process_Abstract $processor Processer instance
*
* @return Phergie_Bot Provides a fluent interface
*/
public function setProcessor(Phergie_Process_Abstract $processor)
{
$this->processor = $processor;
return $this;
}
/**
* Loads plugins into the plugin handler.
*
* @return void
*/
protected function loadPlugins()
{
$config = $this->getConfig();
$plugins = $this->getPluginHandler();
$ui = $this->getUi();
$plugins->setAutoload($config['plugins.autoload']);
foreach ($config['plugins'] as $name) {
try {
$plugin = $plugins->addPlugin($name);
$ui->onPluginLoad($name);
} catch (Phergie_Plugin_Exception $e) {
$ui->onPluginFailure($name, $e->getMessage());
if (!empty($plugin)) {
$plugins->removePlugin($plugin);
}
}
}
}
/**
* Configures and establishes connections to IRC servers.
*
* @return void
*/
protected function loadConnections()
{
$config = $this->getConfig();
$driver = $this->getDriver();
$connections = $this->getConnectionHandler();
$plugins = $this->getPluginHandler();
$ui = $this->getUi();
foreach ($config['connections'] as $data) {
$connection = new Phergie_Connection($data);
$connections->addConnection($connection);
$ui->onConnect($data['host']);
$driver->setConnection($connection)->doConnect();
$plugins->setConnection($connection);
$plugins->onConnect();
}
}
/**
* Establishes server connections and initiates an execution loop to
* continuously receive and process events.
*
* @return Phergie_Bot Provides a fluent interface
*/
public function run()
{
set_time_limit(0);
$timezone = $this->getConfig('timezone', 'UTC');
date_default_timezone_set($timezone);
$ui = $this->getUi();
$ui->setEnabled($this->getConfig('ui.enabled'));
$this->loadPlugins();
$this->loadConnections();
$processor = $this->getProcessor();
$connections = $this->getConnectionHandler();
while (count($connections)) {
$processor->handleEvents();
}
$ui->onShutdown();
return $this;
}
}

View File

@ -0,0 +1,186 @@
<?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
*/
/**
* Reads from and writes to PHP configuration files and provides access to
* the settings they contain.
*
* @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_Config implements ArrayAccess
{
/**
* Mapping of configuration file paths to an array of names of settings
* they contain
*
* @var array
*/
protected $files = array();
/**
* Mapping of setting names to their current corresponding values
*
* @var array
*/
protected $settings = array();
/**
* Includes a specified PHP configuration file and incorporates its
* return value (which should be an associative array) into the current
* configuration settings.
*
* @param string $file Path to the file to read
*
* @return Phergie_Config Provides a fluent interface
* @throws Phergie_Config_Exception
*/
public function read($file)
{
if (!(strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'
&& file_exists($file))
&& !is_executable($file)
) {
throw new Phergie_Config_Exception(
'Path "' . $file . '" does not reference an executable file',
Phergie_Config_Exception::ERR_FILE_NOT_EXECUTABLE
);
}
$settings = include $file;
if (!is_array($settings)) {
throw new Phergie_Config_Exception(
'File "' . $file . '" does not return an array',
Phergie_Config_Exception::ERR_ARRAY_NOT_RETURNED
);
}
$this->files[$file] = array_keys($settings);
$this->settings += $settings;
return $this;
}
/**
* Merges an associative array of configuration setting values into the
* current configuration settings.
*
* @param array $settings Associative array of configuration setting
* values keyed by setting name
*
* @return Phergie_Config Provides a fluent interface
*/
public function readArray(array $settings)
{
$this->settings += $settings;
return $this;
}
/**
* Writes the values of the current configuration settings back to their
* originating files.
*
* @return Phergie_Config Provides a fluent interface
*/
public function write()
{
foreach ($this->files as $file => &$settings) {
$values = array();
foreach ($settings as $setting) {
$values[$setting] = $this->settings[$setting];
}
$source = '<?php' . PHP_EOL . PHP_EOL .
'return ' . var_export($value, true) . ';';
file_put_contents($file, $source);
}
}
/**
* Checks to see if a configuration setting is assigned a value.
*
* @param string $offset Configuration setting name
*
* @return bool TRUE if the setting has a value, FALSE otherwise
* @see ArrayAccess::offsetExists()
*/
public function offsetExists($offset)
{
return isset($this->settings[$offset]);
}
/**
* Returns the value of a configuration setting.
*
* @param string $offset Configuration setting name
*
* @return mixed Configuration setting value or NULL if it is not
* assigned a value
* @see ArrayAccess::offsetGet()
*/
public function offsetGet($offset)
{
if (isset($this->settings[$offset])) {
$value = &$this->settings[$offset];
} else {
$value = null;
}
return $value;
}
/**
* Sets the value of a configuration setting.
*
* @param string $offset Configuration setting name
* @param mixed $value New setting value
*
* @return void
* @see ArrayAccess::offsetSet()
*/
public function offsetSet($offset, $value)
{
$this->settings[$offset] = $value;
}
/**
* Removes the value set for a configuration setting.
*
* @param string $offset Configuration setting name
*
* @return void
* @see ArrayAccess::offsetUnset()
*/
public function offsetUnset($offset)
{
unset($this->settings[$offset]);
foreach ($this->files as $file => $settings) {
$key = array_search($offset, $settings);
if ($key !== false) {
unset($this->files[$file][$key]);
}
}
}
}

View File

@ -0,0 +1,44 @@
<?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 configuration.
*
* @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_Config_Exception extends Phergie_Exception
{
/**
* Error indicating that an attempt was made to read a configuration
* file that could not be executed
*/
const ERR_FILE_NOT_EXECUTABLE = 1;
/**
* Error indicating that a read configuration file does not return an
* array
*/
const ERR_ARRAY_NOT_RETURNED = 2;
}

View File

@ -0,0 +1,401 @@
<?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
*/
/**
* Data structure for connection metadata.
*
* @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_Connection
{
/**
* Host to which the client will connect
*
* @var string
*/
protected $host;
/**
* Port on which the client will connect, defaults to the standard IRC
* port
*
* @var int
*/
protected $port;
/**
* Transport for the connection, defaults to tcp but can be set to ssl
* or variations thereof to connect over SSL
*
* @var string
*/
protected $transport;
/**
* Encoding method for the connection, defaults to ISO-8859-1 but can
* be set to UTF8 if necessary
*
* @var strng
*/
protected $encoding;
/**
* Nick that the client will use
*
* @var string
*/
protected $nick;
/**
* Username that the client will use
*
* @var string
*/
protected $username;
/**
* Realname that the client will use
*
* @var string
*/
protected $realname;
/**
* Password that the client will use
*
* @var string
*/
protected $password;
/**
* Hostmask for the connection
*
* @var Phergie_Hostmask
*/
protected $hostmask;
/**
* Constructor to initialize instance properties.
*
* @param array $options Optional associative array of property values
* to initialize
*
* @return void
*/
public function __construct(array $options = array())
{
$this->transport = 'tcp';
$this->encoding = 'ISO-8859-1';
// @note this may need changed to something different, for broader support.
// @note also may need to make use of http://us.php.net/manual/en/function.stream-encoding.php
$this->setOptions($options);
}
/**
* Emits an error related to a required connection setting does not have
* value set for it.
*
* @param string $setting Name of the setting
*
* @return void
*/
protected function checkSetting($setting)
{
if (empty($this->$setting)) {
throw new Phergie_Connection_Exception(
'Required connection setting "' . $setting . '" missing',
Phergie_Connection_Exception::ERR_REQUIRED_SETTING_MISSING
);
}
}
/**
* Returns a hostmask that uniquely identifies the connection.
*
* @return string
*/
public function getHostmask()
{
if (empty($this->hostmask)) {
$this->hostmask = new Phergie_Hostmask(
$this->getNick(),
$this->getUsername(),
$this->getHost()
);
}
return $this->hostmask;
}
/**
* Sets the host to which the client will connect.
*
* @param string $host Hostname
*
* @return Phergie_Connection Provides a fluent interface
*/
public function setHost($host)
{
if (empty($this->host)) {
$this->host = (string) $host;
}
return $this;
}
/**
* Returns the host to which the client will connect if it is set or
* emits an error if it is not set.
*
* @return string
*/
public function getHost()
{
$this->checkSetting('host');
return $this->host;
}
/**
* Sets the port on which the client will connect.
*
* @param int $port Port
*
* @return Phergie_Connection Provides a fluent interface
*/
public function setPort($port)
{
if (empty($this->port)) {
$this->port = (int) $port;
}
return $this;
}
/**
* Returns the port on which the client will connect.
*
* @return int
*/
public function getPort()
{
if (empty($this->port)) {
$this->port = 6667;
}
return $this->port;
}
/**
* Sets the transport for the connection to use.
*
* @param string $transport Transport (ex: tcp, ssl, etc.)
*
* @return Phergie_Connection Provides a fluent interface
*/
public function setTransport($transport)
{
$this->transport = (string) $transport;
if (!in_array($this->transport, stream_get_transports())) {
throw new Phergie_Connection_Exception(
'Transport ' . $this->transport . ' is not supported',
Phergie_Connection_Exception::ERR_TRANSPORT_NOT_SUPPORTED
);
}
return $this;
}
/**
* Returns the transport in use by the connection.
*
* @return string Transport (ex: tcp, ssl, etc.)
*/
public function getTransport()
{
return $this->transport;
}
/**
* Sets the encoding for the connection to use.
*
* @param string $encoding Encoding to use (ex: ASCII, ISO-8859-1, UTF8, etc.)
*
* @return Phergie_Connection Provides a fluent interface
*/
public function setEncoding($encoding)
{
$this->encoding = (string) $encoding;
if (!in_array($this->encoding, mb_list_encodings())) {
throw new Phergie_Connection_Exception(
'Encoding ' . $this->encoding . ' is not supported',
Phergie_Connection_Exception::ERR_ENCODING_NOT_SUPPORTED
);
}
return $this;
}
/**
* Returns the encoding in use by the connection.
*
* @return string Encoding (ex: ASCII, ISO-8859-1, UTF8, etc.)
*/
public function getEncoding()
{
return $this->encoding;
}
/**
* Sets the nick that the client will use.
*
* @param string $nick Nickname
*
* @return Phergie_Connection Provides a fluent interface
*/
public function setNick($nick)
{
if (empty($this->nick)) {
$this->nick = (string) $nick;
}
return $this;
}
/**
* Returns the nick that the client will use.
*
* @return string
*/
public function getNick()
{
$this->checkSetting('nick');
return $this->nick;
}
/**
* Sets the username that the client will use.
*
* @param string $username Username
*
* @return Phergie_Connection Provides a fluent interface
*/
public function setUsername($username)
{
if (empty($this->username)) {
$this->username = (string) $username;
}
return $this;
}
/**
* Returns the username that the client will use.
*
* @return string
*/
public function getUsername()
{
$this->checkSetting('username');
return $this->username;
}
/**
* Sets the realname that the client will use.
*
* @param string $realname Real name
*
* @return Phergie_Connection Provides a fluent interface
*/
public function setRealname($realname)
{
if (empty($this->realname)) {
$this->realname = (string) $realname;
}
return $this;
}
/**
* Returns the realname that the client will use.
*
* @return string
*/
public function getRealname()
{
$this->checkSetting('realname');
return $this->realname;
}
/**
* Sets the password that the client will use.
*
* @param string $password Password
*
* @return Phergie_Connection Provides a fluent interface
*/
public function setPassword($password)
{
if (empty($this->password)) {
$this->password = (string) $password;
}
return $this;
}
/**
* Returns the password that the client will use.
*
* @return string
*/
public function getPassword()
{
return $this->password;
}
/**
* Sets multiple connection settings using an array.
*
* @param array $options Associative array of setting names mapped to
* corresponding values
*
* @return Phergie_Connection Provides a fluent interface
*/
public function setOptions(array $options)
{
foreach ($options as $option => $value) {
$method = 'set' . ucfirst($option);
if (method_exists($this, $method)) {
$this->$method($value);
}
}
}
}

View File

@ -0,0 +1,50 @@
<?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 a connection to an IRC server.
*
* @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_Connection_Exception extends Phergie_Exception
{
/**
* Error indicating that an operation was attempted requiring a value
* for a specific configuration setting, but none was set
*/
const ERR_REQUIRED_SETTING_MISSING = 1;
/**
* Error indicating that a connection is configured to use a transport,
* but that transport is not supported by the current PHP installation
*/
const ERR_TRANSPORT_NOT_SUPPORTED = 2;
/**
* Error indicating that a connection is configured to use an encoding,
* but that encoding is not supported by the current PHP installation
*/
const ERR_ENCODING_NOT_SUPPORTED = 3;
}

View File

@ -0,0 +1,130 @@
<?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
*/
/**
* Handles connections initiated by the bot.
*
* @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_Connection_Handler implements Countable, IteratorAggregate
{
/**
* Map of connections indexed by hostmask
*
* @var array
*/
protected $connections;
/**
* Constructor to initialize storage for connections.
*
* @return void
*/
public function __construct()
{
$this->connections = array();
}
/**
* Adds a connection to the connection list.
*
* @param Phergie_Connection $connection Connection to add
*
* @return Phergie_Connection_Handler Provides a fluent interface
*/
public function addConnection(Phergie_Connection $connection)
{
$this->connections[(string) $connection->getHostmask()] = $connection;
return $this;
}
/**
* Removes a connection from the connection list.
*
* @param Phergie_Connection|string $connection Instance or hostmask for
* the connection to remove
*
* @return Phergie_Connection_Handler Provides a fluent interface
*/
public function removeConnection($connection)
{
if ($connection instanceof Phergie_Connection) {
$hostmask = (string) $connection->getHostmask();
} elseif (is_string($connection)
&& isset($this->connections[$connection])) {
$hostmask = $connection;
} else {
return $this;
}
unset($this->connections[$hostmask]);
return $this;
}
/**
* Returns the number of connections in the list.
*
* @return int Number of connections
*/
public function count()
{
return count($this->connections);
}
/**
* Returns an iterator for the connection list.
*
* @return ArrayIterator
*/
public function getIterator()
{
return new ArrayIterator($this->connections);
}
/**
* Returns a list of specified connection objects.
*
* @param array|string $keys One or more hostmasks identifying the
* connections to return
*
* @return array List of Phergie_Connection objects corresponding to the
* specified hostmask(s)
*/
public function getConnections($keys)
{
$connections = array();
if (!is_array($keys)) {
$keys = array($keys);
}
foreach ($keys as $key) {
if (isset($this->connections[$key])) {
$connections[] = $this->connections[$key];
}
}
return $connections;
}
}

View File

@ -0,0 +1,301 @@
<?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 drivers which handle issuing client commands to the IRC
* server and converting responses into usable data objects.
*
* @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_Driver_Abstract
{
/**
* Currently active connection
*
* @var Phergie_Connection
*/
protected $connection;
/**
* Sets the currently active connection.
*
* @param Phergie_Connection $connection Active connection
*
* @return Phergie_Driver_Abstract Provides a fluent interface
*/
public function setConnection(Phergie_Connection $connection)
{
$this->connection = $connection;
return $this;
}
/**
* Returns the currently active connection.
*
* @return Phergie_Connection
* @throws Phergie_Driver_Exception
*/
public function getConnection()
{
if (empty($this->connection)) {
throw new Phergie_Driver_Exception(
'Operation requires an active connection, but none is set',
Phergie_Driver_Exception::ERR_NO_ACTIVE_CONNECTION
);
}
return $this->connection;
}
/**
* Returns an event if one has been received from the server.
*
* @return Phergie_Event_Interface|null Event instance if an event has
* been received, NULL otherwise
*/
public abstract function getEvent();
/**
* Initiates a connection with the server.
*
* @return void
*/
public abstract function doConnect();
/**
* Terminates the connection with the server.
*
* @param string $reason Reason for connection termination (optional)
*
* @return void
* @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_1_6
*/
public abstract function doQuit($reason = null);
/**
* Joins a channel.
*
* @param string $channels Comma-delimited list of channels to join
* @param string $keys Optional comma-delimited list of channel keys
*
* @return void
* @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_1
*/
public abstract function doJoin($channels, $keys = null);
/**
* Leaves a channel.
*
* @param string $channels Comma-delimited list of channels to leave
*
* @return void
* @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_2
*/
public abstract function doPart($channels);
/**
* Invites a user to an invite-only channel.
*
* @param string $nick Nick of the user to invite
* @param string $channel Name of the channel
*
* @return void
* @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_7
*/
public abstract function doInvite($nick, $channel);
/**
* Obtains a list of nicks of users in specified channels.
*
* @param string $channels Comma-delimited list of one or more channels
*
* @return void
* @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_5
*/
public abstract function doNames($channels);
/**
* Obtains a list of channel names and topics.
*
* @param string $channels Comma-delimited list of one or more channels
* to which the response should be restricted
* (optional)
*
* @return void
* @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_6
*/
public abstract function doList($channels = null);
/**
* Retrieves or changes a channel topic.
*
* @param string $channel Name of the channel
* @param string $topic New topic to assign (optional)
*
* @return void
* @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_4
*/
public abstract function doTopic($channel, $topic = null);
/**
* Retrieves or changes a channel or user mode.
*
* @param string $target Channel name or user nick
* @param string $mode New mode to assign (optional)
*
* @return void
* @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_3
*/
public abstract function doMode($target, $mode = null);
/**
* Changes the client nick.
*
* @param string $nick New nick to assign
*
* @return void
* @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_1_2
*/
public abstract function doNick($nick);
/**
* Retrieves information about a nick.
*
* @param string $nick Nick
*
* @return void
* @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_5_2
*/
public abstract function doWhois($nick);
/**
* Sends a message to a nick or channel.
*
* @param string $target Channel name or user nick
* @param string $text Text of the message to send
*
* @return void
* @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_4_1
*/
public abstract function doPrivmsg($target, $text);
/**
* Sends a notice to a nick or channel.
*
* @param string $target Channel name or user nick
* @param string $text Text of the notice to send
*
* @return void
* @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_4_2
*/
public abstract function doNotice($target, $text);
/**
* Kicks a user from a channel.
*
* @param string $nick Nick of the user
* @param string $channel Channel name
* @param string $reason Reason for the kick (optional)
*
* @return void
* @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_8
*/
public abstract function doKick($nick, $channel, $reason = null);
/**
* Responds to a server test of client responsiveness.
*
* @param string $daemon Daemon from which the original request originates
*
* @return void
* @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_6_3
*/
public abstract function doPong($daemon);
/**
* Sends a CTCP ACTION (/me) command to a nick or channel.
*
* @param string $target Channel name or user nick
* @param string $text Text of the action to perform
*
* @return void
* @link http://www.invlogic.com/irc/ctcp.html#4.4
*/
public abstract function doAction($target, $text);
/**
* Sends a CTCP PING request to a user.
*
* @param string $nick User nick
* @param string $hash Hash to use in the handshake
*
* @return void
* @link http://www.invlogic.com/irc/ctcp.html#4.2
*/
public abstract function doPing($nick, $hash);
/**
* Sends a CTCP VERSION request or response to a user.
*
* @param string $nick User nick
* @param string $version Version string to send for a response
*
* @return void
* @link http://www.invlogic.com/irc/ctcp.html#4.1
*/
public abstract function doVersion($nick, $version = null);
/**
* Sends a CTCP TIME request to a user.
*
* @param string $nick User nick
* @param string $time Time string to send for a response
*
* @return void
* @link http://www.invlogic.com/irc/ctcp.html#4.6
*/
public abstract function doTime($nick, $time = null);
/**
* Sends a CTCP FINGER request to a user.
*
* @param string $nick User nick
* @param string $finger Finger string to send for a response
*
* @return void
* @link http://www.irchelp.org/irchelp/rfc/ctcpspec.html
*/
public abstract function doFinger($nick, $finger = null);
/**
* Sends a raw command to the server.
*
* @param string $command Command string to send
*
* @return void
*/
public abstract function doRaw($command);
}

View File

@ -0,0 +1,59 @@
<?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 driver 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_Driver_Exception extends Phergie_Exception
{
/**
* Error indicating that an operation was requested requiring an active
* connection before one had been set
*/
const ERR_NO_ACTIVE_CONNECTION = 1;
/**
* Error indicating that an operation was requested requiring an active
* connection where one had been set but not initiated
*/
const ERR_NO_INITIATED_CONNECTION = 2;
/**
* Error indicating that an attempt to initiate a connection failed
*/
const ERR_CONNECTION_ATTEMPT_FAILED = 3;
/**
* Error indicating that an attempt to send data via a connection failed
*/
const ERR_CONNECTION_WRITE_FAILED = 4;
/**
* Error indicating that an attempt to read data via a connection failed
*/
const ERR_CONNECTION_READ_FAILED = 5;
}

View File

@ -0,0 +1,66 @@
<?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 Streams driver (Phergie_Driver_Streams) to give external access
* to the socket resources and send method
*
* @category Phergie
* @package Phergie_Driver_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_Driver_Statusnet extends Phergie_Driver_Streams {
/**
* 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 parent::send($command, $args);
}
public function forceQuit() {
try {
// Send a QUIT command to the server
$this->send('QUIT', 'Reconnecting');
} catch (Phergie_Driver_Exception $e){}
// Terminate the socket connection
fclose($this->socket);
// Remove the socket from the internal socket list
unset($this->sockets[(string) $this->getConnection()->getHostmask()]);
}
/**
* Returns the array of sockets
*
* @return array Array of socket resources
*/
public function getSockets() {
return $this->sockets;
}
}

View File

@ -0,0 +1,729 @@
<?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
*/
/**
* Driver that uses the sockets wrapper of the streams extension for
* communicating with the server and handles formatting and parsing of
* events using PHP.
*
* @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_Driver_Streams extends Phergie_Driver_Abstract
{
/**
* Socket handlers
*
* @var array
*/
protected $sockets = array();
/**
* Reference to the currently active socket handler
*
* @var resource
*/
protected $socket;
/**
* Amount of time in seconds to wait to receive an event each time the
* socket is polled
*
* @var float
*/
protected $timeout = 0.1;
/**
* 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
*/
protected function send($command, $args = '')
{
$connection = $this->getConnection();
$encoding = $connection->getEncoding();
// Require an open socket connection to continue
if (empty($this->socket)) {
throw new Phergie_Driver_Exception(
'doConnect() must be called first',
Phergie_Driver_Exception::ERR_NO_INITIATED_CONNECTION
);
}
// Add the command
$buffer = strtoupper($command);
// Add arguments
if (!empty($args)) {
// Apply formatting if arguments are passed in as an array
if (is_array($args)) {
$end = count($args) - 1;
$args[$end] = ':' . $args[$end];
$args = implode(' ', $args);
} else {
$args = ':' . $args;
}
$buffer .= ' ' . $args;
}
// Transmit the command over the socket connection
$attempts = $written = 0;
$temp = $buffer . "\r\n";
$is_multibyte = !substr($encoding, 0, 8) === 'ISO-8859' && $encoding !== 'ASCII' && $encoding !== 'CP1252';
$length = ($is_multibyte) ? mb_strlen($buffer, '8bit') : strlen($buffer);
while (true) {
$written += (int) fwrite($this->socket, $temp);
if ($written < $length) {
$temp = substr($temp, $written);
$attempts++;
if ($attempts == 3) {
throw new Phergie_Driver_Exception(
'Unable to write to socket',
Phergie_Driver_Exception::ERR_CONNECTION_WRITE_FAILED
);
}
} else {
break;
}
}
// Return the command string that was transmitted
return $buffer;
}
/**
* Overrides the parent class to set the currently active socket handler
* when the active connection is changed.
*
* @param Phergie_Connection $connection Active connection
*
* @return Phergie_Driver_Streams Provides a fluent interface
*/
public function setConnection(Phergie_Connection $connection)
{
// Set the active socket handler
$hostmask = (string) $connection->getHostmask();
if (!empty($this->sockets[$hostmask])) {
$this->socket = $this->sockets[$hostmask];
}
// Set the active connection
return parent::setConnection($connection);
}
/**
* Returns a list of hostmasks corresponding to sockets with data to read.
*
* @param int $sec Length of time to wait for new data (seconds)
* @param int $usec Length of time to wait for new data (microseconds)
*
* @return array List of hostmasks or an empty array if none were found
* to have data to read
*/
public function getActiveReadSockets($sec = 0, $usec = 200000)
{
$read = $this->sockets;
$write = null;
$error = null;
$active = array();
if (count($this->sockets) > 0) {
$number = stream_select($read, $write, $error, $sec, $usec);
if ($number > 0) {
foreach ($read as $item) {
$active[] = array_search($item, $this->sockets);
}
}
}
return $active;
}
/**
* Sets the amount of time to wait for a new event each time the socket
* is polled.
*
* @param float $timeout Amount of time in seconds
*
* @return Phergie_Driver_Streams Provides a fluent interface
*/
public function setTimeout($timeout)
{
$timeout = (float) $timeout;
if ($timeout) {
$this->timeout = $timeout;
}
return $this;
}
/**
* Returns the amount of time to wait for a new event each time the
* socket is polled.
*
* @return float Amount of time in seconds
*/
public function getTimeout()
{
return $this->timeout;
}
/**
* Supporting method to parse event argument strings where the last
* argument may contain a colon.
*
* @param string $args Argument string to parse
* @param int $count Optional maximum number of arguments
*
* @return array Array of argument values
*/
protected function parseArguments($args, $count = -1)
{
return preg_split('/ :?/S', $args, $count);
}
/**
* Listens for an event on the current connection.
*
* @return Phergie_Event_Interface|null Event instance if an event was
* received, NULL otherwise
*/
public function getEvent()
{
// Check the socket is still active
if (feof($this->socket)) {
throw new Phergie_Driver_Exception(
'EOF detected on socket',
Phergie_Driver_Exception::ERR_CONNECTION_READ_FAILED
);
}
// Check for a new event on the current connection
$buffer = fgets($this->socket, 512);
// If no new event was found, return NULL
if (empty($buffer)) {
return null;
}
// Strip the trailing newline from the buffer
$buffer = rtrim($buffer);
// If the event is from the server...
if (substr($buffer, 0, 1) != ':') {
// Parse the command and arguments
list($cmd, $args) = array_pad(explode(' ', $buffer, 2), 2, null);
$hostmask = new Phergie_Hostmask(null, null, $this->connection->getHost());
} else {
// If the event could be from the server or a user...
// Parse the server hostname or user hostmask, command, and arguments
list($prefix, $cmd, $args)
= array_pad(explode(' ', ltrim($buffer, ':'), 3), 3, null);
if (strpos($prefix, '@') !== false) {
$hostmask = Phergie_Hostmask::fromString($prefix);
} else {
$hostmask = new Phergie_Hostmask(null, null, $prefix);
}
}
// Parse the event arguments depending on the event type
$cmd = strtolower($cmd);
switch ($cmd) {
case 'names':
case 'nick':
case 'quit':
case 'ping':
case 'join':
case 'error':
$args = array(ltrim($args, ':'));
break;
case 'privmsg':
case 'notice':
$args = $this->parseArguments($args, 2);
list($source, $ctcp) = $args;
if (substr($ctcp, 0, 1) === "\001" && substr($ctcp, -1) === "\001") {
$ctcp = substr($ctcp, 1, -1);
$reply = ($cmd == 'notice');
list($cmd, $args) = array_pad(explode(' ', $ctcp, 2), 2, null);
$cmd = strtolower($cmd);
switch ($cmd) {
case 'version':
case 'time':
case 'finger':
if ($reply) {
$args = $ctcp;
}
break;
case 'ping':
if ($reply) {
$cmd .= 'Response';
} else {
$cmd = 'ctcpPing';
}
break;
case 'action':
$args = array($source, $args);
break;
default:
$cmd = 'ctcp';
if ($reply) {
$cmd .= 'Response';
}
$args = array($source, $args);
break;
}
}
break;
case 'oper':
case 'topic':
case 'mode':
$args = $this->parseArguments($args);
break;
case 'part':
case 'kill':
case 'invite':
$args = $this->parseArguments($args, 2);
break;
case 'kick':
$args = $this->parseArguments($args, 3);
break;
// Remove the target from responses
default:
$args = substr($args, strpos($args, ' ') + 1);
break;
}
// Create, populate, and return an event object
if (ctype_digit($cmd)) {
$event = new Phergie_Event_Response;
$event
->setCode($cmd)
->setDescription($args);
} else {
$event = new Phergie_Event_Request;
$event
->setType($cmd)
->setArguments($args);
if (isset($hostmask)) {
$event->setHostmask($hostmask);
}
}
$event->setRawData($buffer);
return $event;
}
/**
* Initiates a connection with the server.
*
* @return void
*/
public function doConnect()
{
// Listen for input indefinitely
set_time_limit(0);
// Get connection information
$connection = $this->getConnection();
$hostname = $connection->getHost();
$port = $connection->getPort();
$password = $connection->getPassword();
$username = $connection->getUsername();
$nick = $connection->getNick();
$realname = $connection->getRealname();
$transport = $connection->getTransport();
// Establish and configure the socket connection
$remote = $transport . '://' . $hostname . ':' . $port;
$this->socket = @stream_socket_client($remote, $errno, $errstr);
if (!$this->socket) {
throw new Phergie_Driver_Exception(
'Unable to connect: socket error ' . $errno . ' ' . $errstr,
Phergie_Driver_Exception::ERR_CONNECTION_ATTEMPT_FAILED
);
}
$seconds = (int) $this->timeout;
$microseconds = ($this->timeout - $seconds) * 1000000;
stream_set_timeout($this->socket, $seconds, $microseconds);
// Send the password if one is specified
if (!empty($password)) {
$this->send('PASS', $password);
}
// Send user information
$this->send(
'USER',
array(
$username,
$hostname,
$hostname,
$realname
)
);
$this->send('NICK', $nick);
// Add the socket handler to the internal array for socket handlers
$this->sockets[(string) $connection->getHostmask()] = $this->socket;
}
/**
* Terminates the connection with the server.
*
* @param string $reason Reason for connection termination (optional)
*
* @return void
*/
public function doQuit($reason = null)
{
// Send a QUIT command to the server
$this->send('QUIT', $reason);
// Terminate the socket connection
fclose($this->socket);
// Remove the socket from the internal socket list
unset($this->sockets[(string) $this->getConnection()->getHostmask()]);
}
/**
* Joins a channel.
*
* @param string $channels Comma-delimited list of channels to join
* @param string $keys Optional comma-delimited list of channel keys
*
* @return void
*/
public function doJoin($channels, $keys = null)
{
$args = array($channels);
if (!empty($keys)) {
$args[] = $keys;
}
$this->send('JOIN', $args);
}
/**
* Leaves a channel.
*
* @param string $channels Comma-delimited list of channels to leave
*
* @return void
*/
public function doPart($channels)
{
$this->send('PART', $channels);
}
/**
* Invites a user to an invite-only channel.
*
* @param string $nick Nick of the user to invite
* @param string $channel Name of the channel
*
* @return void
*/
public function doInvite($nick, $channel)
{
$this->send('INVITE', array($nick, $channel));
}
/**
* Obtains a list of nicks of usrs in currently joined channels.
*
* @param string $channels Comma-delimited list of one or more channels
*
* @return void
*/
public function doNames($channels)
{
$this->send('NAMES', $channels);
}
/**
* Obtains a list of channel names and topics.
*
* @param string $channels Comma-delimited list of one or more channels
* to which the response should be restricted
* (optional)
*
* @return void
*/
public function doList($channels = null)
{
$this->send('LIST', $channels);
}
/**
* Retrieves or changes a channel topic.
*
* @param string $channel Name of the channel
* @param string $topic New topic to assign (optional)
*
* @return void
*/
public function doTopic($channel, $topic = null)
{
$args = array($channel);
if (!empty($topic)) {
$args[] = $topic;
}
$this->send('TOPIC', $args);
}
/**
* Retrieves or changes a channel or user mode.
*
* @param string $target Channel name or user nick
* @param string $mode New mode to assign (optional)
*
* @return void
*/
public function doMode($target, $mode = null)
{
$args = array($target);
if (!empty($mode)) {
$args[] = $mode;
}
$this->send('MODE', $args);
}
/**
* Changes the client nick.
*
* @param string $nick New nick to assign
*
* @return void
*/
public function doNick($nick)
{
$this->send('NICK', $nick);
}
/**
* Retrieves information about a nick.
*
* @param string $nick Nick
*
* @return void
*/
public function doWhois($nick)
{
$this->send('WHOIS', $nick);
}
/**
* Sends a message to a nick or channel.
*
* @param string $target Channel name or user nick
* @param string $text Text of the message to send
*
* @return void
*/
public function doPrivmsg($target, $text)
{
$this->send('PRIVMSG', array($target, $text));
}
/**
* Sends a notice to a nick or channel.
*
* @param string $target Channel name or user nick
* @param string $text Text of the notice to send
*
* @return void
*/
public function doNotice($target, $text)
{
$this->send('NOTICE', array($target, $text));
}
/**
* Kicks a user from a channel.
*
* @param string $nick Nick of the user
* @param string $channel Channel name
* @param string $reason Reason for the kick (optional)
*
* @return void
*/
public function doKick($nick, $channel, $reason = null)
{
$args = array($nick, $channel);
if (!empty($reason)) {
$args[] = $response;
}
$this->send('KICK', $args);
}
/**
* Responds to a server test of client responsiveness.
*
* @param string $daemon Daemon from which the original request originates
*
* @return void
*/
public function doPong($daemon)
{
$this->send('PONG', $daemon);
}
/**
* Sends a CTCP ACTION (/me) command to a nick or channel.
*
* @param string $target Channel name or user nick
* @param string $text Text of the action to perform
*
* @return void
*/
public function doAction($target, $text)
{
$buffer = rtrim('ACTION ' . $text);
$this->doPrivmsg($target, chr(1) . $buffer . chr(1));
}
/**
* Sends a CTCP response to a user.
*
* @param string $nick User nick
* @param string $command Command to send
* @param string|array $args String or array of sequential arguments
* (optional)
*
* @return void
*/
protected function doCtcp($nick, $command, $args = null)
{
if (is_array($args)) {
$args = implode(' ', $args);
}
$buffer = rtrim(strtoupper($command) . ' ' . $args);
$this->doNotice($nick, chr(1) . $buffer . chr(1));
}
/**
* Sends a CTCP PING request or response (they are identical) to a user.
*
* @param string $nick User nick
* @param string $hash Hash to use in the handshake
*
* @return void
*/
public function doPing($nick, $hash)
{
$this->doCtcp($nick, 'PING', $hash);
}
/**
* Sends a CTCP VERSION request or response to a user.
*
* @param string $nick User nick
* @param string $version Version string to send for a response
*
* @return void
*/
public function doVersion($nick, $version = null)
{
if ($version) {
$this->doCtcp($nick, 'VERSION', $version);
} else {
$this->doCtcp($nick, 'VERSION');
}
}
/**
* Sends a CTCP TIME request to a user.
*
* @param string $nick User nick
* @param string $time Time string to send for a response
*
* @return void
*/
public function doTime($nick, $time = null)
{
if ($time) {
$this->doCtcp($nick, 'TIME', $time);
} else {
$this->doCtcp($nick, 'TIME');
}
}
/**
* Sends a CTCP FINGER request to a user.
*
* @param string $nick User nick
* @param string $finger Finger string to send for a response
*
* @return void
*/
public function doFinger($nick, $finger = null)
{
if ($finger) {
$this->doCtcp($nick, 'FINGER', $finger);
} else {
$this->doCtcp($nick, 'FINGER');
}
}
/**
* Sends a raw command to the server.
*
* @param string $command Command string to send
*
* @return void
*/
public function doRaw($command)
{
$this->send('RAW', $command);
}
}

View File

@ -0,0 +1,62 @@
<?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 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_Event_Abstract
{
/**
* Event type, used for determining the callback to execute in response
*
* @var string
*/
protected $type;
/**
* Returns the event type.
*
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* Sets the event type.
*
* @param string $type Event type
*
* @return Phergie_Event_Abstract Implements a fluent interface
*/
public function setType($type)
{
$this->type = (string) $type;
return $this;
}
}

View File

@ -0,0 +1,62 @@
<?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
*/
/**
* Event originating from a plugin for the bot.
*
* @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_Event_Command extends Phergie_Event_Request
{
/**
* Reference to the plugin instance that created the event
*
* @var Phergie_Plugin_Abstract
*/
protected $plugin;
/**
* Stores a reference to the plugin instance that created the event.
*
* @param Phergie_Plugin_Abstract $plugin Plugin instance
*
* @return Phergie_Event_Command Provides a fluent interface
*/
public function setPlugin(Phergie_Plugin_Abstract $plugin)
{
$this->plugin = $plugin;
return $this;
}
/**
* Returns a reference to the plugin instance that created the event.
*
* @return Phergie_Plugin_Abstract
*/
public function getPlugin()
{
return $this->plugin;
}
}

View File

@ -0,0 +1,38 @@
<?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 outgoing 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
*/
class Phergie_Event_Exception extends Phergie_Exception
{
/**
* Error indicating that an attempt was made to create an event of an
* unknown type
*/
const ERR_UNKNOWN_EVENT_TYPE = 1;
}

View File

@ -0,0 +1,190 @@
<?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
*/
/**
* Handles events initiated by plugins.
*
* @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_Event_Handler implements IteratorAggregate, Countable
{
/**
* Current queue of events
*
* @var array
*/
protected $events;
/**
* Constructor to initialize the event queue.
*
* @return void
*/
public function __construct()
{
$this->events = array();
}
/**
* Adds an event to the queue.
*
* @param Phergie_Plugin_Abstract $plugin Plugin originating the event
* @param string $type Event type, corresponding to a
* Phergie_Event_Command::TYPE_* constant
* @param array $args Optional event arguments
*
* @return Phergie_Event_Handler Provides a fluent interface
*/
public function addEvent(Phergie_Plugin_Abstract $plugin, $type,
array $args = array()
) {
if (!defined('Phergie_Event_Command::TYPE_' . strtoupper($type))) {
throw new Phergie_Event_Exception(
'Unknown event type "' . $type . '"',
Phergie_Event_Exception::ERR_UNKNOWN_EVENT_TYPE
);
}
$event = new Phergie_Event_Command;
$event
->setPlugin($plugin)
->setType($type)
->setArguments($args);
$this->events[] = $event;
return $this;
}
/**
* Returns the current event queue.
*
* @return array Enumerated array of Phergie_Event_Command objects
*/
public function getEvents()
{
return $this->events;
}
/**
* Clears the event queue.
*
* @return Phergie_Event_Handler Provides a fluent interface
*/
public function clearEvents()
{
$this->events = array();
return $this;
}
/**
* Replaces the current event queue with a given queue of events.
*
* @param array $events Ordered list of objects of the class
* Phergie_Event_Command
*
* @return Phergie_Event_Handler Provides a fluent interface
*/
public function replaceEvents(array $events)
{
$this->events = $events;
return $this;
}
/**
* Returns whether an event of the given type exists in the queue.
*
* @param string $type Event type from Phergie_Event_Request::TYPE_*
* constants
*
* @return bool TRUE if an event of the specified type exists in the
* queue, FALSE otherwise
*/
public function hasEventOfType($type)
{
foreach ($this->events as $event) {
if ($event->getType() == $type) {
return true;
}
}
return false;
}
/**
* Returns a list of events of a specified type.
*
* @param string $type Event type from Phergie_Event_Request::TYPE_*
* constants
*
* @return array Array containing event instances of the specified type
* or an empty array if no such events were found
*/
public function getEventsOfType($type)
{
$events = array();
foreach ($this->events as $event) {
if ($event->getType() == $type) {
$events[] = $event;
}
}
return $events;
}
/**
* Removes a single event from the event queue.
*
* @param Phergie_Event_Command $event Event to remove
*
* @return Phergie_Event_Handler Provides a fluent interface
*/
public function removeEvent(Phergie_Event_Command $event)
{
$key = array_search($event, $this->events);
if ($key !== false) {
unset($this->events[$key]);
}
return $this;
}
/**
* Returns an iterator for the current event queue.
*
* @return ArrayIterator
*/
public function getIterator()
{
return new ArrayIterator($this->events);
}
/**
* Returns the number of events in the event queue
*
* @return int number of queued events
*/
public function count()
{
return count($this->events);
}
}

View File

@ -0,0 +1,468 @@
<?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
*/
/**
* Autonomous event originating from a user or the server.
*
* @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
* @link http://www.irchelp.org/irchelp/rfc/chapter4.html
*/
class Phergie_Event_Request
extends Phergie_Event_Abstract
implements ArrayAccess
{
/**
* Nick message event type
*/
const TYPE_NICK = 'nick';
/**
* Whois message event type
*/
const TYPE_WHOIS = 'whois';
/**
* Quit command event type
*/
const TYPE_QUIT = 'quit';
/**
* Join message event type
*/
const TYPE_JOIN = 'join';
/**
* Kick message event type
*/
const TYPE_KICK = 'kick';
/**
* Part message event type
*/
const TYPE_PART = 'part';
/**
* Invite message event type
*/
const TYPE_INVITE = 'invite';
/**
* Mode message event type
*/
const TYPE_MODE = 'mode';
/**
* Topic message event type
*/
const TYPE_TOPIC = 'topic';
/**
* Private message command event type
*/
const TYPE_PRIVMSG = 'privmsg';
/**
* Notice message event type
*/
const TYPE_NOTICE = 'notice';
/**
* Pong message event type
*/
const TYPE_PONG = 'pong';
/**
* CTCP ACTION command event type
*/
const TYPE_ACTION = 'action';
/**
* CTCP PING command event type
*/
const TYPE_PING = 'ping';
/**
* CTCP TIME command event type
*/
const TYPE_TIME = 'time';
/**
* CTCP VERSION command event type
*/
const TYPE_VERSION = 'version';
/**
* RAW message event type
*/
const TYPE_RAW = 'raw';
/**
* Mapping of event types to their named parameters
*
* @var array
*/
protected static $map = array(
self::TYPE_QUIT => array(
'message' => 0
),
self::TYPE_JOIN => array(
'channel' => 0
),
self::TYPE_KICK => array(
'channel' => 0,
'user' => 1,
'comment' => 2
),
self::TYPE_PART => array(
'channel' => 0,
'message' => 1
),
self::TYPE_INVITE => array(
'nickname' => 0,
'channel' => 1
),
self::TYPE_MODE => array(
'target' => 0,
'mode' => 1,
'limit' => 2,
'user' => 3,
'banmask' => 4
),
self::TYPE_TOPIC => array(
'channel' => 0,
'topic' => 1
),
self::TYPE_PRIVMSG => array(
'receiver' => 0,
'text' => 1
),
self::TYPE_NOTICE => array(
'nickname' => 0,
'text' => 1
),
self::TYPE_ACTION => array(
'target' => 0,
'action' => 1
),
self::TYPE_RAW => array(
'message' => 0
)
);
/**
* Hostmask representing the originating user, if applicable
*
* @var Phergie_Hostmask
*/
protected $hostmask;
/**
* Arguments included with the message
*
* @var array
*/
protected $arguments;
/**
* Raw data sent by the server
*
* @var string
*/
protected $rawData;
/**
* Sets the hostmask representing the originating user.
*
* @param Phergie_Hostmask $hostmask User hostmask
*
* @return Phergie_Event_Request Provides a fluent interface
*/
public function setHostmask(Phergie_Hostmask $hostmask)
{
$this->hostmask = $hostmask;
return $this;
}
/**
* Returns the hostmask representing the originating user.
*
* @return Phergie_Event_Request|null Hostmask or NULL if none was set
*/
public function getHostmask()
{
return $this->hostmask;
}
/**
* Sets the arguments for the request.
*
* @param array $arguments Request arguments
*
* @return Phergie_Event_Request Provides a fluent interface
*/
public function setArguments($arguments)
{
$this->arguments = $arguments;
return $this;
}
/**
* Sets the value of a single argument for the request.
*
* @param mixed $argument Integer position (starting from 0) or the
* equivalent string name of the argument from self::$map
* @param string $value Value to assign to the argument
*
* @return Phergie_Event_Request Provides a fluent interface
*/
public function setArgument($argument, $value)
{
$argument = $this->resolveArgument($argument);
if ($argument !== null) {
$this->arguments[$argument] = (string) $value;
}
return $this;
}
/**
* Returns the arguments for the request.
*
* @return array
*/
public function getArguments()
{
return $this->arguments;
}
/**
* Resolves an argument specification to an integer position.
*
* @param mixed $argument Integer position (starting from 0) or the
* equivalent string name of the argument from self::$map
*
* @return int|null Integer position of the argument or NULL if no
* corresponding argument was found
*/
protected function resolveArgument($argument)
{
if (isset($this->arguments[$argument])) {
return $argument;
} else {
$argument = strtolower($argument);
if (isset(self::$map[$this->type][$argument])
&& isset($this->arguments[self::$map[$this->type][$argument]])
) {
return self::$map[$this->type][$argument];
}
}
return null;
}
/**
* Returns a single specified argument for the request.
*
* @param mixed $argument Integer position (starting from 0) or the
* equivalent string name of the argument from self::$map
*
* @return string|null Argument value or NULL if none is set
*/
public function getArgument($argument)
{
$argument = $this->resolveArgument($argument);
if ($argument !== null) {
return $this->arguments[$argument];
}
return null;
}
/**
* Sets the raw buffer for the event.
*
* @param string $buffer Raw event buffer
*
* @return Phergie_Event_Request Provides a fluent interface
*/
public function setRawData($buffer)
{
$this->rawData = $buffer;
return $this;
}
/**
* Returns the raw buffer sent from the server for the event.
*
* @return string
*/
public function getRawData()
{
return $this->rawData;
}
/**
* Returns the nick of the user who originated the event.
*
* @return string
*/
public function getNick()
{
return $this->hostmask->getNick();
}
/**
* Returns the channel name if the event occurred in a channel or the
* user nick if the event was a private message directed at the bot by a
* user.
*
* @return string
*/
public function getSource()
{
if (substr($this->arguments[0], 0, 1) == '#') {
return $this->arguments[0];
}
return $this->hostmask->getNick();
}
/**
* Returns whether or not the event occurred within a channel.
*
* @return TRUE if the event is in a channel, FALSE otherwise
*/
public function isInChannel()
{
return (substr($this->getSource(), 0, 1) == '#');
}
/**
* Returns whether or not the event originated from a user.
*
* @return TRUE if the event is from a user, FALSE otherwise
*/
public function isFromUser()
{
if (empty($this->hostmask)) {
return false;
}
$username = $this->hostmask->getUsername();
return !empty($username);
}
/**
* Returns whether or not the event originated from the server.
*
* @return TRUE if the event is from the server, FALSE otherwise
*/
public function isFromServer()
{
$username = $this->hostmask->getUsername();
return empty($username);
}
/**
* Provides access to named parameters via virtual "getter" methods.
*
* @param string $name Name of the method called
* @param array $arguments Arguments passed to the method (should always
* be empty)
*
* @return mixed Method return value
*/
public function __call($name, array $arguments)
{
if (!count($arguments) && substr($name, 0, 3) == 'get') {
return $this->getArgument(substr($name, 3));
}
}
/**
* Checks to see if an event argument is assigned a value.
*
* @param string|int $offset Argument name or position beginning from 0
*
* @return bool TRUE if the argument has a value, FALSE otherwise
* @see ArrayAccess::offsetExists()
*/
public function offsetExists($offset)
{
return ($this->resolveArgument($offset) !== null);
}
/**
* Returns the value of an event argument.
*
* @param string|int $offset Argument name or position beginning from 0
*
* @return string|null Argument value or NULL if none is set
* @see ArrayAccess::offsetGet()
*/
public function offsetGet($offset)
{
return $this->getArgument($offset);
}
/**
* Sets the value of an event argument.
*
* @param string|int $offset Argument name or position beginning from 0
* @param string $value New argument value
*
* @return void
* @see ArrayAccess::offsetSet()
*/
public function offsetSet($offset, $value)
{
$offset = $this->resolveArgument($offset);
if ($offset !== null) {
$this->arguments[$offset] = $value;
}
}
/**
* Removes the value set for an event argument.
*
* @param string|int $offset Argument name or position beginning from 0
*
* @return void
* @see ArrayAccess::offsetUnset()
*/
public function offsetUnset($offset)
{
if ($offset = $this->resolveArgument($offset)) {
unset($this->arguments[$offset]);
}
}
}

View File

@ -0,0 +1,953 @@
<?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
*/
/**
* Event originating from the server in response to an event sent by the
* current client.
*
* @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
* @link http://www.irchelp.org/irchelp/rfc/chapter6.html
*/
class Phergie_Event_Response extends Phergie_Event_Abstract
{
/**
* <nickname> No such nick/channel
*
* Used to indicate the nickname parameter supplied to a command is currently
* unused.
*/
const ERR_NOSUCHNICK = '401';
/**
* <server name> No such server
*
* Used to indicate the server name given currently doesn't exist.
*/
const ERR_NOSUCHSERVER = '402';
/**
* <channel name> No such channel
*
* Used to indicate the given channel name is invalid.
*/
const ERR_NOSUCHCHANNEL = '403';
/**
* <channel name> Cannot send to channel
*
* Sent to a user who is either (a) not on a channel which is mode +n or (b) not
* a chanop (or mode +v) on a channel which has mode +m set and is trying to send
* a PRIVMSG message to that channel.
*/
const ERR_CANNOTSENDTOCHAN = '404';
/**
* <channel name> You have joined too many channels
*
* Sent to a user when they have joined the maximum number of allowed channels
* and they try to join another channel.
*/
const ERR_TOOMANYCHANNELS = '405';
/**
* <nickname> There was no such nickname
*
* Returned by WHOWAS to indicate there is no history information for that
* nickname.
*/
const ERR_WASNOSUCHNICK = '406';
/**
* <target> Duplicate recipients. No message delivered
*
* Returned to a client which is attempting to send PRIVMSG/NOTICE using the
* user@host destination format and for a user@host which has several
* occurrences.
*/
const ERR_TOOMANYTARGETS = '407';
/**
* No origin specified
*
* PING or PONG message missing the originator parameter which is required since
* these commands must work without valid prefixes.
*/
const ERR_NOORIGIN = '409';
/**
* No recipient given (<command>)
*/
const ERR_NORECIPIENT = '411';
/**
* No text to send
*/
const ERR_NOTEXTTOSEND = '412';
/**
* <mask> No toplevel domain specified
*/
const ERR_NOTOPLEVEL = '413';
/**
* <mask> Wildcard in toplevel domain
*
* 412 - 414 are returned by PRIVMSG to indicate that the message wasn't
* delivered for some reason. ERR_NOTOPLEVEL and ERR_WILDTOPLEVEL are errors that
* are returned when an invalid use of "PRIVMSG $<server>" or "PRIVMSG #<host>"
* is attempted.
*/
const ERR_WILDTOPLEVEL = '414';
/**
* <command> Unknown command
*
* Returned to a registered client to indicate that the command sent is unknown
* by the server.
*/
const ERR_UNKNOWNCOMMAND = '421';
/**
* MOTD File is missing
*
* Server's MOTD file could not be opened by the server.
*/
const ERR_NOMOTD = '422';
/**
* <server> No administrative info available
*
* Returned by a server in response to an ADMIN message when there is an error in
* finding the appropriate information.
*/
const ERR_NOADMININFO = '423';
/**
* File error doing <file op> on <file>
*
* Generic error message used to report a failed file operation during the
* processing of a message.
*/
const ERR_FILEERROR = '424';
/**
* No nickname given
*
* Returned when a nickname parameter expected for a command and isn't found.
*/
const ERR_NONICKNAMEGIVEN = '431';
/**
* <nick> Erroneus nickname
*
* Returned after receiving a NICK message which contains characters which do not
* fall in the defined set. See section x.x.x for details on valid nicknames.
*/
const ERR_ERRONEUSNICKNAME = '432';
/**
* <nick> Nickname is already in use
*
* Returned when a NICK message is processed that results in an attempt to change
* to a currently existing nickname.
*/
const ERR_NICKNAMEINUSE = '433';
/**
* <nick> Nickname collision KILL
*
* Returned by a server to a client when it detects a nickname collision
* (registered of a NICK that already exists by another server).
*/
const ERR_NICKCOLLISION = '436';
/**
* <nick> <channel> They aren't on that channel
*
* Returned by the server to indicate that the target user of the command is not
* on the given channel.
*/
const ERR_USERNOTINCHANNEL = '441';
/**
* <channel> You're not on that channel
*
* Returned by the server whenever a client tries to perform a channel effecting
* command for which the client isn't a member.
*/
const ERR_NOTONCHANNEL = '442';
/**
* <user> <channel> is already on channel
*
* Returned when a client tries to invite a user to a channel they are already
* on.
*/
const ERR_USERONCHANNEL = '443';
/**
* <user> User not logged in
*
* Returned by the summon after a SUMMON command for a user was unable to be
* performed since they were not logged in.
*/
const ERR_NOLOGIN = '444';
/**
* SUMMON has been disabled
*
* Returned as a response to the SUMMON command. Must be returned by any server
* which does not implement it.
*/
const ERR_SUMMONDISABLED = '445';
/**
* USERS has been disabled
*
* Returned as a response to the USERS command. Must be returned by any server
* which does not implement it.
*/
const ERR_USERSDISABLED = '446';
/**
* You have not registered
*
* Returned by the server to indicate that the client must be registered before
* the server will allow it to be parsed in detail.
*/
const ERR_NOTREGISTERED = '451';
/**
* <command> Not enough parameters
*
* Returned by the server by numerous commands to indicate to the client that it
* didn't supply enough parameters.
*/
const ERR_NEEDMOREPARAMS = '461';
/**
* You may not reregister
*
* Returned by the server to any link which tries to change part of the
* registered details (such as password or user details from second USER
* message).
*/
const ERR_ALREADYREGISTRED = '462';
/**
* Your host isn't among the privileged
*
* Returned to a client which attempts to register with a server which does not
* been setup to allow connections from the host the attempted connection is
* tried.
*/
const ERR_NOPERMFORHOST = '463';
/**
* Password incorrect
*
* Returned to indicate a failed attempt at registering a connection for which a
* password was required and was either not given or incorrect.
*/
const ERR_PASSWDMISMATCH = '464';
/**
* You are banned from this server
*
* Returned after an attempt to connect and register yourself with a server which
* has been setup to explicitly deny connections to you.
*/
const ERR_YOUREBANNEDCREEP = '465';
/**
* <channel> Channel key already set
*/
const ERR_KEYSET = '467';
/**
* <channel> Cannot join channel (+l)
*/
const ERR_CHANNELISFULL = '471';
/**
* <char> is unknown mode char to me
*/
const ERR_UNKNOWNMODE = '472';
/**
* <channel> Cannot join channel (+i)
*/
const ERR_INVITEONLYCHAN = '473';
/**
* <channel> Cannot join channel (+b)
*/
const ERR_BANNEDFROMCHAN = '474';
/**
* <channel> Cannot join channel (+k)
*/
const ERR_BADCHANNELKEY = '475';
/**
* Permission Denied- You're not an IRC operator
*
* Any command requiring operator privileges to operate must return this error to
* indicate the attempt was unsuccessful.
*/
const ERR_NOPRIVILEGES = '481';
/**
* <channel> You're not channel operator
*
* Any command requiring 'chanop' privileges (such as MODE messages) must return
* this error if the client making the attempt is not a chanop on the specified
* channel.
*/
const ERR_CHANOPRIVSNEEDED = '482';
/**
* You cant kill a server!
*
* Any attempts to use the KILL command on a server are to be refused and this
* error returned directly to the client.
*/
const ERR_CANTKILLSERVER = '483';
/**
* No O-lines for your host
*
* If a client sends an OPER message and the server has not been configured to
* allow connections from the client's host as an operator, this error must be
* returned.
*/
const ERR_NOOPERHOST = '491';
/**
* Unknown MODE flag
*
* Returned by the server to indicate that a MODE message was sent with a
* nickname parameter and that the a mode flag sent was not recognized.
*/
const ERR_UMODEUNKNOWNFLAG = '501';
/**
* Cant change mode for other users
*
* Error sent to any user trying to view or change the user mode for a user other
* than themselves.
*/
const ERR_USERSDONTMATCH = '502';
/**
* Dummy reply number. Not used.
*/
const RPL_NONE = '300';
/**
* [<reply>{<space><reply>}]
*
* Reply format used by USERHOST to list replies to the query list. The reply
* string is composed as follows <reply> = <nick>['*'] '=' <'+'|'-'><hostname>
* The '*' indicates whether the client has registered as an Operator. The '-' or
* '+' characters represent whether the client has set an AWAY message or not
* respectively.
*/
const RPL_USERHOST = '302';
/**
* [<nick> {<space><nick>}]
*
* Reply format used by ISON to list replies to the query list.
*/
const RPL_ISON = '303';
/**
* <nick> <away message>
*/
const RPL_AWAY = '301';
/**
* You are no longer marked as being away
*/
const RPL_UNAWAY = '305';
/**
* You have been marked as being away
*
* These replies are used with the AWAY command (if allowed). RPL_AWAY is sent to
* any client sending a PRIVMSG to a client which is away. RPL_AWAY is only sent
* by the server to which the client is connected. Replies RPL_UNAWAY and
* RPL_NOWAWAY are sent when the client removes and sets an AWAY message.
*/
const RPL_NOWAWAY = '306';
/**
* <nick> <user> <host> * <real name>
*/
const RPL_WHOISUSER = '311';
/**
* <nick> <server> <server info>
*/
const RPL_WHOISSERVER = '312';
/**
* <nick> is an IRC operator
*/
const RPL_WHOISOPERATOR = '313';
/**
* <nick> <integer> seconds idle
*/
const RPL_WHOISIDLE = '317';
/**
* <nick> End of /WHOIS list
*/
const RPL_ENDOFWHOIS = '318';
/**
* <nick> {[@|+]<channel><space>}
*
* Replies 311 - 313, 317 - 319 are all replies generated in response to a WHOIS
* message. Given that there are enough parameters present, the answering server
* must either formulate a reply out of the above numerics (if the query nick is
* found) or return an error reply. The '*' in RPL_WHOISUSER is there as the
* literal character and not as a wild card. For each reply set, only
* RPL_WHOISCHANNELS may appear more than once (for long lists of channel names).
* The '@' and '+' characters next to the channel name indicate whether a client
* is a channel operator or has been granted permission to speak on a moderated
* channel. The RPL_ENDOFWHOIS reply is used to mark the end of processing a
* WHOIS message.
*/
const RPL_WHOISCHANNELS = '319';
/**
* <nick> <user> <host> * <real name>
*/
const RPL_WHOWASUSER = '314';
/**
* <nick> End of WHOWAS
*
* When replying to a WHOWAS message, a server must use the replies
* RPL_WHOWASUSER, RPL_WHOISSERVER or ERR_WASNOSUCHNICK for each nickname in the
* presented list. At the end of all reply batches, there must be RPL_ENDOFWHOWAS
* (even if there was only one reply and it was an error).
*/
const RPL_ENDOFWHOWAS = '369';
/**
* Channel Users Name
*/
const RPL_LISTSTART = '321';
/**
* <channel> <# visible> <topic>
*/
const RPL_LIST = '322';
/**
* End of /LIST
*
* Replies RPL_LISTSTART, RPL_LIST, RPL_LISTEND mark the start, actual replies
* with data and end of the server's response to a LIST command. If there are no
* channels available to return, only the start and end reply must be sent.
*/
const RPL_LISTEND = '323';
/**
* <channel> <mode> <mode params>
*/
const RPL_CHANNELMODEIS = '324';
/**
* <channel> No topic is set
*/
const RPL_NOTOPIC = '331';
/**
* <channel> <topic>
*
* When sending a TOPIC message to determine the channel topic, one of two
* replies is sent. If the topic is set, RPL_TOPIC is sent back else RPL_NOTOPIC.
*/
const RPL_TOPIC = '332';
/**
* <channel> <nick>
*
* Returned by the server to indicate that the attempted INVITE message was
* successful and is being passed onto the end client.
*/
const RPL_INVITING = '341';
/**
* <user> Summoning user to IRC
*
* Returned by a server answering a SUMMON message to indicate that it is
* summoning that user.
*/
const RPL_SUMMONING = '342';
/**
* <version>.<debuglevel> <server> <comments>
*
* Reply by the server showing its version details. The <version> is the version
* of the software being used (including any patchlevel revisions) and the
* <debuglevel> is used to indicate if the server is running in "debug mode". The
* "comments" field may contain any comments about the version or further version
* details.
*/
const RPL_VERSION = '351';
/**
* <channel> <user> <host> <server> <nick> <H|G>[*][@|+] <hopcount> <real name>
*/
const RPL_WHOREPLY = '352';
/**
* <name> End of /WHO list
*
* The RPL_WHOREPLY and RPL_ENDOFWHO pair are used to answer a WHO message. The
* RPL_WHOREPLY is only sent if there is an appropriate match to the WHO query.
* If there is a list of parameters supplied with a WHO message, a RPL_ENDOFWHO
* must be sent after processing each list item with <name> being the item.
*/
const RPL_ENDOFWHO = '315';
/**
* <channel> [[@|+]<nick> [[@|+]<nick> [...]]]
*/
const RPL_NAMREPLY = '353';
/**
* <channel> End of /NAMES list
*
* To reply to a NAMES message, a reply pair consisting of RPL_NAMREPLY and
* RPL_ENDOFNAMES is sent by the server back to the client. If there is no
* channel found as in the query, then only RPL_ENDOFNAMES is returned. The
* exception to this is when a NAMES message is sent with no parameters and all
* visible channels and contents are sent back in a series of RPL_NAMEREPLY
* messages with a RPL_ENDOFNAMES to mark the end.
*/
const RPL_ENDOFNAMES = '366';
/**
* <mask> <server> <hopcount> <server info>
*/
const RPL_LINKS = '364';
/**
* <mask> End of /LINKS list
*
* In replying to the LINKS message, a server must send replies back using the
* RPL_LINKS numeric and mark the end of the list using an RPL_ENDOFLINKS reply.v
*/
const RPL_ENDOFLINKS = '365';
/**
* <channel> <banid>
*/
const RPL_BANLIST = '367';
/**
* <channel> End of channel ban list
*
* When listing the active 'bans' for a given channel, a server is required to
* send the list back using the RPL_BANLIST and RPL_ENDOFBANLIST messages. A
* separate RPL_BANLIST is sent for each active banid. After the banids have been
* listed (or if none present) a RPL_ENDOFBANLIST must be sent.
*/
const RPL_ENDOFBANLIST = '368';
/**
* <string>
*/
const RPL_INFO = '371';
/**
* End of /INFO list
*
* A server responding to an INFO message is required to send all its 'info' in a
* series of RPL_INFO messages with a RPL_ENDOFINFO reply to indicate the end of
* the replies.
*/
const RPL_ENDOFINFO = '374';
/**
* - <server> Message of the day -
*/
const RPL_MOTDSTART = '375';
/**
* - <text>
*/
const RPL_MOTD = '372';
/**
* End of /MOTD command
*
* When responding to the MOTD message and the MOTD file is found, the file is
* displayed line by line, with each line no longer than 80 characters, using
* RPL_MOTD format replies. These should be surrounded by a RPL_MOTDSTART (before
* the RPL_MOTDs) and an RPL_ENDOFMOTD (after).
*/
const RPL_ENDOFMOTD = '376';
/**
* You are now an IRC operator
*
* RPL_YOUREOPER is sent back to a client which has just successfully issued an
* OPER message and gained operator status.
*/
const RPL_YOUREOPER = '381';
/**
* <config file> Rehashing
*
* If the REHASH option is used and an operator sends a REHASH message, an
* RPL_REHASHING is sent back to the operator.
*/
const RPL_REHASHING = '382';
/**
* <server> <string showing server's local time>
*
* When replying to the TIME message, a server must send the reply using the
* RPL_TIME format above. The string showing the time need only contain the
* correct day and time there. There is no further requirement for the time
* string.
*/
const RPL_TIME = '391';
/**
* UserID Terminal Host
*/
const RPL_USERSSTART = '392';
/**
* %-8s %-9s %-8s
*/
const RPL_USERS = '393';
/**
* End of users
*/
const RPL_ENDOFUSERS = '394';
/**
* Nobody logged in
*
* If the USERS message is handled by a server, the replies RPL_USERSTART,
* RPL_USERS, RPL_ENDOFUSERS and RPL_NOUSERS are used. RPL_USERSSTART must be
* sent first, following by either a sequence of RPL_USERS or a single
* RPL_NOUSER. Following this is RPL_ENDOFUSERS.
*/
const RPL_NOUSERS = '395';
/**
* Link <version & debug level> <destination> <next server>
*/
const RPL_TRACELINK = '200';
/**
* Try. <class> <server>
*/
const RPL_TRACECONNECTING = '201';
/**
* H.S. <class> <server>
*/
const RPL_TRACEHANDSHAKE = '202';
/**
* ???? <class> [<client IP address in dot form>]
*/
const RPL_TRACEUNKNOWN = '203';
/**
* Oper <class> <nick>
*/
const RPL_TRACEOPERATOR = '204';
/**
* User <class> <nick>
*/
const RPL_TRACEUSER = '205';
/**
* Serv <class> <int>S <int>C <server> <nick!user|*!*>@<host|server>
*/
const RPL_TRACESERVER = '206';
/**
* <newtype> 0 <client name>
*/
const RPL_TRACENEWTYPE = '208';
/**
* File <logfile> <debug level>
*
* The RPL_TRACE* are all returned by the server in response to the TRACE
* message. How many are returned is dependent on the the TRACE message and
* whether it was sent by an operator or not. There is no predefined order for
* which occurs first. Replies RPL_TRACEUNKNOWN, RPL_TRACECONNECTING and
* RPL_TRACEHANDSHAKE are all used for connections which have not been fully
* established and are either unknown, still attempting to connect or in the
* process of completing the 'server handshake'. RPL_TRACELINK is sent by any
* server which handles a TRACE message and has to pass it on to another server.
* The list of RPL_TRACELINKs sent in response to a TRACE command traversing the
* IRC network should reflect the actual connectivity of the servers themselves
* along that path. RPL_TRACENEWTYPE is to be used for any connection which does
* not fit in the other categories but is being displayed anyway.
*/
const RPL_TRACELOG = '261';
/**
* <linkname> <sendq> <sent messages> <sent bytes> <received messages> <received
* bytes> <time open>
*/
const RPL_STATSLINKINFO = '211';
/**
* <command> <count>
*/
const RPL_STATSCOMMANDS = '212';
/**
* C <host> * <name> <port> <class>
*/
const RPL_STATSCLINE = '213';
/**
* N <host> * <name> <port> <class>
*/
const RPL_STATSNLINE = '214';
/**
* I <host> * <host> <port> <class>
*/
const RPL_STATSILINE = '215';
/**
* K <host> * <username> <port> <class>
*/
const RPL_STATSKLINE = '216';
/**
* Y <class> <ping frequency> <connect frequency> <max sendq>
*/
const RPL_STATSYLINE = '218';
/**
* <stats letter> End of /STATS report
*/
const RPL_ENDOFSTATS = '219';
/**
* L <hostmask> * <servername> <maxdepth>
*/
const RPL_STATSLLINE = '241';
/**
* Server Up %d days %d%02d%02d
*/
const RPL_STATSUPTIME = '242';
/**
* O <hostmask> * <name>
*/
const RPL_STATSOLINE = '243';
/**
* H <hostmask> * <servername>
*/
const RPL_STATSHLINE = '244';
/**
* <user mode string>
*
* To answer a query about a client's own mode, RPL_UMODEIS is sent back.
*/
const RPL_UMODEIS = '221';
/**
* There are <integer> users and <integer> invisible on <integer> servers
*/
const RPL_LUSERCLIENT = '251';
/**
* <integer> operator(s) online
*/
const RPL_LUSEROP = '252';
/**
* <integer> unknown connection(s)
*/
const RPL_LUSERUNKNOWN = '253';
/**
* <integer> channels formed
*/
const RPL_LUSERCHANNELS = '254';
/**
* I have <integer> clients and <integer> servers
*
* In processing an LUSERS message, the server sends a set of replies from
* RPL_LUSERCLIENT, RPL_LUSEROP, RPL_USERUNKNOWN, RPL_LUSERCHANNELS and
* RPL_LUSERME. When replying, a server must send back RPL_LUSERCLIENT and
* RPL_LUSERME. The other replies are only sent back if a non-zero count is found
* for them.
*/
const RPL_LUSERME = '255';
/**
* <server> Administrative info
*/
const RPL_ADMINME = '256';
/**
* <admin info>
*/
const RPL_ADMINLOC1 = '257';
/**
* <admin info>
*/
const RPL_ADMINLOC2 = '258';
/**
* <admin info>
*
* When replying to an ADMIN message, a server is expected to use replies
* RLP_ADMINME through to RPL_ADMINEMAIL and provide a text message with each.
* For RPL_ADMINLOC1 a description of what city, state and country the server is
* in is expected, followed by details of the university and department
* (RPL_ADMINLOC2) and finally the administrative contact for the server (an
* email address here is required) in RPL_ADMINEMAIL.
*/
const RPL_ADMINEMAIL = '259';
/**
* Reply code sent by the server, which can be compared to the ERR_* and
* RPL_* constants
*
* @var string
*/
protected $code;
/**
* Reply code description sent by the server.
*
* @var string
*/
protected $description;
/**
* Raw data sent by the server
*
* @var string
*/
protected $rawData;
/**
* Event type
*
* @var string
*/
protected $type = 'response';
/**
* Sets the reply code sent by the server.
*
* @param string $code Reply code
*
* @return Phergie_Event_Response Provides a fluent interface
*/
public function setCode($code)
{
$this->code = $code;
return $this;
}
/**
* Returns the reply code sent by the server.
*
* @return string
*/
public function getCode()
{
return $this->code;
}
/**
* Sets the reply code description sent by the server.
*
* @param string $description Reply code description
*
* @return Phergie_Event_Response Provides a fluent interface
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Returns the reply code description sent by the server.
*
* @return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Sets the raw buffer for the given event
*
* @param string $buffer Raw event buffer
*
* @return Phergie_Event_Response Provides a fluent interface
*/
public function setRawData($buffer)
{
$this->rawData = $buffer;
return $this;
}
/**
* Returns the raw buffer that was sent from the server for that event
*
* @return string
*/
public function getRawData()
{
return $this->rawData;
}
}

View File

@ -0,0 +1,33 @@
<?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 all Phergie-related exceptions.
*
* @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_Exception extends Exception
{
}

View File

@ -0,0 +1,217 @@
<?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
*/
/**
* Data structure for a hostmask.
*
* @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_Hostmask
{
/**
* Host
*
* @var string
*/
protected $host;
/**
* Nick
*
* @var string
*/
protected $nick;
/**
* Username
*
* @var string
*/
protected $username;
/**
* Regular expression used to parse a hostmask
*
* @var string
*/
protected static $regex = '/^([^!@]+)!(?:[ni]=)?([^@]+)@([^ ]+)/';
/**
* Constructor to initialize components of the hostmask.
*
* @param string $nick Nick component
* @param string $username Username component
* @param string $host Host component
*
* @return void
*/
public function __construct($nick, $username, $host)
{
$this->nick = $nick;
$this->username = $username;
$this->host = $host;
}
/**
* Returns whether a given string appears to be a valid hostmask.
*
* @param string $string Alleged hostmask string
*
* @return bool TRUE if the string appears to be a valid hostmask, FALSE
* otherwise
*/
public static function isValid($string)
{
return (preg_match(self::$regex, $string) > 0);
}
/**
* Parses a string containing the entire hostmask into a new instance of
* this class.
*
* @param string $hostmask Entire hostmask including the nick, username,
* and host components
*
* @return Phergie_Hostmask New instance populated with data parsed from
* the provided hostmask string
* @throws Phergie_Hostmask_Exception
*/
public static function fromString($hostmask)
{
if (preg_match(self::$regex, $hostmask, $match)) {
list(, $nick, $username, $host) = $match;
return new self($nick, $username, $host);
}
throw new Phergie_Hostmask_Exception(
'Invalid hostmask specified: "' . $hostmask . '"',
Phergie_Hostmask_Exception::ERR_INVALID_HOSTMASK
);
}
/**
* Sets the hostname.
*
* @param string $host Hostname
*
* @return Phergie_Hostmask Provides a fluent interface
*/
public function setHost($host)
{
$this->host = $host;
return $this;
}
/**
* Returns the hostname.
*
* @return string
*/
public function getHost()
{
return $this->host;
}
/**
* Sets the username of the user.
*
* @param string $username Username
*
* @return Phergie_Hostmask Provides a fluent interface
*/
public function setUsername($username)
{
$this->username = $username;
return $this;
}
/**
* Returns the username of the user.
*
* @return string
*/
public function getUsername()
{
return $this->username;
}
/**
* Sets the nick of the user.
*
* @param string $nick User nick
*
* @return Phergie_Hostmask Provides a fluent interface
*/
public function setNick($nick)
{
$this->nick = $nick;
return $this;
}
/**
* Returns the nick of the user.
*
* @return string
*/
public function getNick()
{
return $this->nick;
}
/**
* Returns the hostmask for the originating server or user.
*
* @return string
*/
public function __toString()
{
return $this->nick . '!' . $this->username . '@' . $this->host;
}
/**
* Returns whether a given hostmask matches a given pattern.
*
* @param string $pattern Pattern using conventions of a ban mask where
* represents a wildcard
* @param string $hostmask Optional hostmask to match against, if not
* the current hostmask instance
*
* @return bool TRUE if the hostmask matches the pattern, FALSE otherwise
* @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_2_3 Examples
*/
public function matches($pattern, $hostmask = null)
{
if (!$hostmask) {
$hostmask = (string) $this;
}
$pattern = str_replace('*', '.*', $pattern);
return (preg_match('#^' . $pattern . '$#', $hostmask) > 0);
}
}

View File

@ -0,0 +1,37 @@
<?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 hostmask handling.
*
* @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_Hostmask_Exception extends Phergie_Exception
{
/**
* Error indicating that an invalid hostmask string was specified
*/
const ERR_INVALID_HOSTMASK = 1;
}

View File

@ -0,0 +1,605 @@
<?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 plugins to provide event handler stubs and commonly needed
* functionality.
*
* @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_Plugin_Abstract
{
/**
* Current configuration handler
*
* @var Phergie_Config
*/
protected $config;
/**
* Plugin handler used to provide access to other plugins
*
* @var Phergie_Plugin_Handler
*/
protected $plugins;
/**
* Current event handler instance for outgoing events
*
* @var Phergie_Event_Handler
*/
protected $events;
/**
* Current connection instance
*
* @var Phergie_Connection
*/
protected $connection;
/**
* Current incoming event being handled
*
* @var Phergie_Event_Request|Phergie_Event_Response
*/
protected $event;
/**
* Plugin short name
*
* @var string
*/
protected $name;
/**
* Returns the short name for the plugin based on its class name.
*
* @return string
*/
public function getName()
{
if (empty($this->name)) {
$this->name = substr(strrchr(get_class($this), '_'), 1);
}
return $this->name;
}
/**
* Sets the short name for the plugin.
*
* @param string $name Plugin short name
*
* @return Phergie_Plugin_Abstract Provides a fluent interface
*/
public function setName($name)
{
$this->name = (string) $name;
return $this;
}
/**
* Indicates that the plugin failed to load due to an unsatisfied
* runtime requirement, such as a missing dependency.
*
* @param string $message Error message to provide more information
* about the reason for the failure
*
* @return Phergie_Plugin_Abstract Provides a fluent interface
* @throws Phergie_Plugin_Exception Always
*/
protected function fail($message)
{
throw new Phergie_Plugin_Exception(
$message,
Phergie_Plugin_Exception::ERR_REQUIREMENT_UNSATISFIED
);
}
/**
* Sets the current configuration handler.
*
* @param Phergie_Config $config Configuration handler
*
* @return Phergie_Plugin_Abstract Provides a fluent interface
*/
public function setConfig(Phergie_Config $config)
{
$this->config = $config;
return $this;
}
/**
* Returns the current configuration handler or the value of a single
* setting from it.
*
* @param string $name Optional name of a setting for which the value
* should be returned instead of the entire configuration handler
* @param mixed $default Optional default value to return if no value
* is set for the setting indicated by $name
*
* @return Phergie_Config|mixed Configuration handler or value of the
* setting specified by $name
* @throws Phergie_Plugin_Exception No configuration handler has been set
*/
public function getConfig($name = null, $default = null)
{
if (empty($this->config)) {
throw new Phergie_Plugin_Exception(
'Configuration handler cannot be accessed before one is set',
Phergie_Plugin_Exception::ERR_NO_CONFIG_HANDLER
);
}
if (!is_null($name)) {
if (!isset($this->config[$name])) {
return $default;
}
return $this->config[$name];
}
return $this->config;
}
/**
* Sets the current plugin handler.
*
* @param Phergie_Plugin_Handler $handler Plugin handler
*
* @return Phergie_Plugin_Abstract Provides a fluent interface
*/
public function setPluginHandler(Phergie_Plugin_Handler $handler)
{
$this->plugins = $handler;
return $this;
}
/**
* Returns the current plugin handler.
*
* @return Phergie_Plugin_Handler
* @throws Phergie_Plugin_Exception No plugin handler has been set
*/
public function getPluginHandler()
{
if (empty($this->plugins)) {
throw new Phergie_Plugin_Exception(
'Plugin handler cannot be accessed before one is set',
Phergie_Plugin_Exception::ERR_NO_PLUGIN_HANDLER
);
}
return $this->plugins;
}
/**
* Sets the current event handler.
*
* @param Phergie_Event_Handler $handler Event handler
*
* @return Phergie_Plugin_Abstract Provides a fluent interface
*/
public function setEventHandler(Phergie_Event_Handler $handler)
{
$this->events = $handler;
return $this;
}
/**
* Returns the current event handler.
*
* @return Phergie_Event_Handler
* @throws Phergie_Plugin_Exception No event handler has been set
*/
public function getEventHandler()
{
if (empty($this->events)) {
throw new Phergie_Plugin_Exception(
'Event handler cannot be accessed before one is set',
Phergie_Plugin_Exception::ERR_NO_EVENT_HANDLER
);
}
return $this->events;
}
/**
* Sets the current connection.
*
* @param Phergie_Connection $connection Connection
*
* @return Phergie_Plugin_Abstract Provides a fluent interface
*/
public function setConnection(Phergie_Connection $connection)
{
$this->connection = $connection;
return $this;
}
/**
* Returns the current event connection.
*
* @return Phergie_Connection
* @throws Phergie_Plugin_Exception No connection has been set
*/
public function getConnection()
{
if (empty($this->connection)) {
throw new Phergie_Plugin_Exception(
'Connection cannot be accessed before one is set',
Phergie_Plugin_Exception::ERR_NO_CONNECTION
);
}
return $this->connection;
}
/**
* Sets the current incoming event to be handled.
*
* @param Phergie_Event_Request|Phergie_Event_Response $event Event
*
* @return Phergie_Plugin_Abstract Provides a fluent interface
*/
public function setEvent($event)
{
$this->event = $event;
return $this;
}
/**
* Returns the current incoming event to be handled.
*
* @return Phergie_Event_Request|Phergie_Event_Response
*/
public function getEvent()
{
if (empty($this->event)) {
throw new Phergie_Plugin_Exception(
'Event cannot be accessed before one is set',
Phergie_Plugin_Exception::ERR_NO_EVENT
);
}
return $this->event;
}
/**
* Provides do* methods with signatures identical to those of
* Phergie_Driver_Abstract but that queue up events to be dispatched
* later.
*
* @param string $name Name of the method called
* @param array $args Arguments passed in the call
*
* @return mixed
*/
public function __call($name, array $args)
{
$subcmd = substr($name, 0, 2);
if ($subcmd == 'do') {
$type = strtolower(substr($name, 2));
$this->getEventHandler()->addEvent($this, $type, $args);
} else if ($subcmd != 'on') {
throw new Phergie_Plugin_Exception(
'Called invalid method ' . $name . ' in ' . get_class($this),
Phergie_Plugin_Exception::ERR_INVALID_CALL
);
}
}
/**
* Handler for when the plugin is initially loaded - useful for checking
* runtime dependencies or performing any setup necessary for the plugin
* to function properly such as initializing a database.
*
* @return void
*/
public function onLoad()
{
}
/**
* Handler for when the bot initially connects to a server.
*
* @return void
*/
public function onConnect()
{
}
/**
* Handler for each tick, a single iteration of the continuous loop
* executed by the bot to receive, handle, and send events - useful for
* repeated execution of tasks on a time interval.
*
* @return void
*/
public function onTick()
{
}
/**
* Handler for when any event is received but has not yet been dispatched
* to the plugin handler method specific to its event type.
*
* @return bool|null|void FALSE to short-circuit further event
* processing, TRUE or NULL otherwise
*/
public function preEvent()
{
}
/**
* Handler for after plugin processing of an event has concluded but
* before any events triggered in response by plugins are sent to the
* server - useful for modifying outgoing events before they are sent.
*
* @return void
*/
public function preDispatch()
{
}
/**
* Handler for after any events triggered by plugins in response to a
* received event are sent to the server.
*
* @return void
*/
public function postDispatch()
{
}
/**
* Handler for when the server prompts the client for a nick.
*
* @return void
* @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_1_2
*/
public function onNick()
{
}
/**
* Handler for when a user obtains operator privileges.
*
* @return void
* @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_1_5
*/
public function onOper()
{
}
/**
* Handler for when the client session is about to be terminated.
*
* @return void
* @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_1_6
*/
public function onQuit()
{
}
/**
* Handler for when a user joins a channel.
*
* @return void
* @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_2_1
*/
public function onJoin()
{
}
/**
* Handler for when a user leaves a channel.
*
* @return void
* @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_2_2
*/
public function onPart()
{
}
/**
* Handler for when a user or channel mode is changed.
*
* @return void
* @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_2_3
*/
public function onMode()
{
}
/**
* Handler for when a channel topic is viewed or changed.
*
* @return void
* @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_2_4
*/
public function onTopic()
{
}
/**
* Handler for when a message is received from a channel or user.
*
* @return void
* @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_4_1
*/
public function onPrivmsg()
{
}
/**
* Handler for when the bot receives a CTCP ACTION request.
*
* @return void
* @link http://www.invlogic.com/irc/ctcp.html#4.4
*/
public function onAction()
{
}
/**
* Handler for when a notice is received.
*
* @return void
* @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_4_2
*/
public function onNotice()
{
}
/**
* Handler for when a user is kicked from a channel.
*
* @return void
* @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_2_8
*/
public function onKick()
{
}
/**
* Handler for when the bot receives a ping event from a server, at
* which point it is expected to respond with a pong request within
* a short period else the server may terminate its connection.
*
* @return void
* @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_6_2
*/
public function onPing()
{
}
/**
* Handler for when the bot receives a CTCP TIME request.
*
* @return void
* @link http://www.invlogic.com/irc/ctcp.html#4.6
*/
public function onTime()
{
}
/**
* Handler for when the bot receives a CTCP VERSION request.
*
* @return void
* @link http://www.invlogic.com/irc/ctcp.html#4.1
*/
public function onVersion()
{
}
/**
* Handler for when the bot receives a CTCP PING request.
*
* @return void
* @link http://www.invlogic.com/irc/ctcp.html#4.2
*/
public function onCtcpPing()
{
}
/**
* Handler for when the bot receives a CTCP request of an unknown type.
*
* @return void
* @link http://www.invlogic.com/irc/ctcp.html
*/
public function onCtcp()
{
}
/**
* Handler for when a reply is received for a CTCP PING request sent by
* the bot.
*
* @return void
* @link http://www.invlogic.com/irc/ctcp.html#4.2
*/
public function onPingReply()
{
}
/**
* Handler for when a reply is received for a CTCP TIME request sent by
* the bot.
*
* @return void
* @link http://www.invlogic.com/irc/ctcp.html#4.6
*/
public function onTimeReply()
{
}
/**
* Handler for when a reply is received for a CTCP VERSION request sent
* by the bot.
*
* @return void
* @link http://www.invlogic.com/irc/ctcp.html#4.1
*/
public function onVersionReply()
{
}
/**
* Handler for when a reply received for a CTCP request of an unknown
* type.
*
* @return void
* @link http://www.invlogic.com/irc/ctcp.html
*/
public function onCtcpReply()
{
}
/**
* Handler for when the bot receives a kill request from a server.
*
* @return void
* @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_6_1
*/
public function onKill()
{
}
/**
* Handler for when the bot receives an invitation to join a channel.
*
* @return void
* @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_2_7
*/
public function onInvite()
{
}
/**
* Handler for when a server response is received to a command issued by
* the bot.
*
* @return void
* @link http://irchelp.org/irchelp/rfc/chapter6.html
*/
public function onResponse()
{
}
}

View File

@ -0,0 +1,186 @@
<?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_Acl
* @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_Acl
*/
/**
* Provides an access control system to limit reponses to events based on
* the users who originate them.
*
* Configuration settings:
* acl.whitelist - mapping of user hostmask patterns (optionally by host) to
* plugins and methods where those plugins and methods will
* only be accessible to those users (i.e. and inaccessible
* to other users)
* acl.blacklist - mapping of user hostmasks (optionally by host) to plugins
* and methods where where those plugins and methods will be
* inaccessible to those users but accessible to other users
* acl.ops - TRUE to automatically give access to whitelisted plugins
* and methods to users with ops for events they initiate in
* channels where they have ops
*
* The whitelist and blacklist settings are formatted like so:
* <code>
* 'acl.whitelist' => array(
* 'hostname1' => array(
* 'pattern1' => array(
* 'plugins' => array(
* 'ShortPluginName'
* ),
* 'methods' => array(
* 'methodName'
* )
* ),
* )
* ),
* </code>
*
* The hostname array dimension is optional; if not used, rules will be
* applied across all connections. The pattern is a user hostmask pattern
* where asterisks (*) are used for wildcards. Plugins and methods do not
* need to be set to empty arrays if they are not used; simply exclude them.
*
* @category Phergie
* @package Phergie_Plugin_Acl
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_Acl
* @uses Phergie_Plugin_UserInfo pear.phergie.org
*/
class Phergie_Plugin_Acl extends Phergie_Plugin_Abstract
{
/**
* Checks for permission settings and removes the plugin if none are set.
*
* @return void
*/
public function onLoad()
{
$this->plugins->getPlugin('UserInfo');
if (!$this->getConfig('acl.blacklist')
&& !$this->getConfig('acl.whitelist')
) {
$this->plugins->removePlugin($this);
}
}
/**
* Applies a set of rules to a plugin handler iterator.
*
* @param Phergie_Plugin_Iterator $iterator Iterator to receive rules
* @param array $rules Associate array containing
* either a 'plugins' key pointing to an array containing plugin
* short names to filter, a 'methods' key pointing to an array
* containing method names to filter, or both
*
* @return void
*/
protected function applyRules(Phergie_Plugin_Iterator $iterator, array $rules)
{
if (!empty($rules['plugins'])) {
$iterator->addPluginFilter($rules['plugins']);
}
if (!empty($rules['methods'])) {
$iterator->addMethodFilter($rules['methods']);
}
}
/**
* Checks permission settings and short-circuits event processing for
* blacklisted users.
*
* @return void
*/
public function preEvent()
{
// Ignore server responses
if ($this->event instanceof Phergie_Event_Response) {
return;
}
// Ignore server-initiated events
if (!$this->event->isFromUser()) {
return;
}
// Get the iterator used to filter plugins when processing events
$iterator = $this->plugins->getIterator();
// Get configuration setting values
$whitelist = $this->getConfig('acl.whitelist', array());
$blacklist = $this->getConfig('acl.blacklist', array());
$ops = $this->getConfig('acl.ops', false);
// Support host-specific lists
$host = $this->connection->getHost();
foreach (array('whitelist', 'blacklist') as $var) {
foreach ($$var as $pattern => $rules) {
$regex = '/^' . str_replace('*', '.*', $pattern) . '$/i';
if (preg_match($regex, $host)) {
${$var} = ${$var}[$pattern];
break;
}
}
}
// Get information on the user initiating the current event
$hostmask = $this->event->getHostmask();
$isOp = $ops
&& $this->event->isInChannel()
&& $this->plugins->userInfo->isOp(
$this->event->getNick(),
$this->event->getSource()
);
// Filter whitelisted commands if the user is not on the whitelist
if (!$isOp) {
$whitelisted = false;
foreach ($whitelist as $pattern => $rules) {
if ($hostmask->matches($pattern)) {
$whitelisted = true;
}
}
if (!$whitelisted) {
foreach ($whitelist as $pattern => $rules) {
$this->applyRules($iterator, $rules);
}
}
}
// Filter blacklisted commands if the user is on the blacklist
$blacklisted = false;
foreach ($blacklist as $pattern => $rules) {
if ($hostmask->matches($pattern)) {
$this->applyRules($iterator, $rules);
break;
}
}
}
/**
* Clears filters on the plugin handler iterator.
*
* @return void
*/
public function postDispatch()
{
$this->plugins->getIterator()->clearFilters();
}
}

View File

@ -0,0 +1,95 @@
<?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_AltNick
* @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_AltNick
*/
/**
* Handles switching to alternate nicks in cases where the primary nick is
* not available for use.
*
* @category Phergie
* @package Phergie_Plugin_AltNick
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_AltNick
* @uses extension spl
*/
class Phergie_Plugin_AltNick extends Phergie_Plugin_Abstract
{
/**
* Iterator for the alternate nick list
*
* @var ArrayIterator
*/
protected $iterator;
/**
* Initializes instance variables.
*
* @return void
*/
public function onConnect()
{
if ($this->config['altnick.nicks']) {
if (is_string($this->config['altnick.nicks'])) {
$this->config['altnick.nicks']
= array($this->config['altnick.nicks']);
}
$this->iterator = new ArrayIterator($this->config['altnick.nicks']);
}
}
/**
* Switches to alternate nicks as needed when nick collisions occur.
*
* @return void
*/
public function onResponse()
{
// If no alternate nick list was found, return
if (empty($this->iterator)) {
return;
}
// If the response event indicates that the nick set is in use...
$code = $this->getEvent()->getCode();
if ($code == Phergie_Event_Response::ERR_NICKNAMEINUSE) {
// Attempt to move to the next nick in the alternate nick list
$this->iterator->next();
// If another nick is available...
if ($this->iterator->valid()) {
// Switch to the new nick
$altNick = $this->iterator->current();
$this->doNick($altNick);
// Update the connection to reflect the nick change
$this->getConnection()->setNick($altNick);
} else {
// If no other nicks are available...
// Terminate the connection
$this->doQuit('All specified alternate nicks are in use');
}
}
}
}

View File

@ -0,0 +1,191 @@
<?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_AudioScrobbler
* @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_AudioScrobbler
*/
/**
* Provides commands to look up information on tracks played by specific
* users on the Last.fm and Libre.fm services.
*
* TODO: Make the "nick-binding" use an SQLite database instead of having them
* hard-coded in to the config file.
*
* Configuration settings:
* "audioscrobbler.lastfm_api_key": API given by last.fm (string).
* "audioscrobbler.librefm_api_key": API key given by libre.fm (string).
*
* @category Phergie
* @package Phergie_Plugin_AudioScrobbler
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_AudioScrobbler
* @uses Phergie_Plugin_Command pear.phergie.org
* @uses Phergie_Plugin_Http pear.phergie.org
* @uses extension simplexml
*/
class Phergie_Plugin_AudioScrobbler extends Phergie_Plugin_Abstract
{
/**
* Last.FM API entry point
*
* @var string
*/
protected $lastfmUrl = 'http://ws.audioscrobbler.com/2.0/';
/**
* Libre.FM API entry point
*
* @var string
*/
protected $librefmUrl = 'http://alpha.dev.libre.fm/2.0/';
/**
* Scrobbler query string for user.getRecentTracks
*
* @var string
*/
protected $query = '?method=user.getrecenttracks&user=%s&api_key=%s';
/**
* HTTP plugin
*
* @var Phergie_Plugin_Http
*/
protected $http;
/**
* Check for dependencies.
*
* @return void
*/
public function onLoad()
{
if (!extension_loaded('simplexml')) {
$this->fail('SimpleXML php extension is required');
}
$plugins = $this->getPluginHandler();
$plugins->getPlugin('Command');
$this->http = $plugins->getPlugin('Http');
}
/**
* Command function to get a user's status on last.fm.
*
* @param string $user User identifier
*
* @return void
*/
public function onCommandLastfm($user = null)
{
if ($key = $this->config['audioscrobbler.lastfm_api_key']) {
$scrobbled = $this->getScrobbled($user, $this->lastfmUrl, $key);
if ($scrobbled) {
$this->doPrivmsg($this->getEvent()->getSource(), $scrobbled);
}
}
}
/**
* Command function to get a user's status on libre.fm.
*
* @param string $user User identifier
*
* @return void
*/
public function onCommandLibrefm($user = null)
{
if ($key = $this->config['audioscrobbler.librefm_api_key']) {
$scrobbled = $this->getScrobbled($user, $this->librefmUrl, $key);
if ($scrobbled) {
$this->doPrivmsg($this->getEvent()->getSource(), $scrobbled);
}
}
}
/**
* Simple Scrobbler API function to get a formatted string of the most
* recent track played by a user.
*
* @param string $user Username to look up
* @param string $url Base URL of the scrobbler service
* @param string $key Scrobbler service API key
*
* @return string Formatted string of the most recent track played
*/
public function getScrobbled($user, $url, $key)
{
$event = $this->getEvent();
$user = $user ? $user : $event->getNick();
$url = sprintf($url . $this->query, urlencode($user), urlencode($key));
$response = $this->http->get($url);
if ($response->isError()) {
$this->doNotice(
$event->getNick(),
'Can\'t find status for ' . $user . ': HTTP ' .
$response->getCode() . ' ' . $response->getMessage()
);
return false;
}
$xml = $response->getContent();
if ($xml->error) {
$this->doNotice(
$event->getNick(),
'Can\'t find status for ' . $user . ': API ' . $xml->error
);
return false;
}
$recenttracks = $xml->recenttracks;
$track = $recenttracks->track[0];
// If the user exists but has not scrobbled anything, the result will
// be empty.
if (empty($track->name) && empty($track->artist)) {
$this->doNotice(
$event->getNick(),
'Can\'t find track information for ' . $recenttracks['user']
);
return false;
}
if (isset($track['nowplaying'])) {
$msg = sprintf(
'%s is listening to %s by %s',
$recenttracks['user'],
$track->name,
$track->artist
);
} else {
$msg = sprintf(
'%s, %s was listening to %s by %s',
date('j M Y, H:i', (int) $track->date['uts']),
$recenttracks['user'],
$track->name,
$track->artist
);
}
if ($track->streamable == 1) {
$msg .= ' - ' . $track->url;
}
return $msg;
}
}

View File

@ -0,0 +1,69 @@
<?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_AutoJoin
* @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_AutoJoin
*/
/**
* Automates the process of having the bot join one or more channels upon
* connection to the server.
*
* The configuration setting autojoin.channels is used to determine which
* channels to join. This setting can point to a comma-delimited string or
* enumerated array containing a single list of channels or an associative
* array keyed by hostname where each value is a comma-delimited string or
* enumerated array containing a list of channels to join on the server
* corresponding to that hostname.
*
* @category Phergie
* @package Phergie_Plugin_AutoJoin
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_AutoJoin
*/
class Phergie_Plugin_AutoJoin extends Phergie_Plugin_Abstract
{
/**
* Intercepts the end of the "message of the day" response and responds by
* joining the channels specified in the configuration file.
*
* @return void
*/
public function onResponse()
{
switch ($this->getEvent()->getCode()) {
case Phergie_Event_Response::RPL_ENDOFMOTD:
case Phergie_Event_Response::ERR_NOMOTD:
if ($channels = $this->config['autojoin.channels']) {
if (is_array($channels)) {
// Support autojoin.channels being in these formats:
// 'hostname' => array('#channel1', '#channel2', ... )
$host = $this->getConnection()->getHost();
if (isset($channels[$host])) {
$channels = $channels[$host];
}
if (is_array($channels)) {
$channels = implode(',', $channels);
}
}
$this->doJoin($channels);
}
$this->getPluginHandler()->removePlugin($this);
}
}
}

View File

@ -0,0 +1,82 @@
<?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_Beer
* @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_Beer
*/
/**
* Processes requests to serve users beer.
*
* @category Phergie
* @package Phergie_Plugin_Beer
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_Beer
* @uses Phergie_Plugin_Command pear.phergie.org
* @uses Phergie_Plugin_Serve pear.phergie.org
*/
class Phergie_Plugin_Beer 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 beer.
*
* @param string $request Request including the target and an optional
* suggestion of what beer to serve
*
* @return void
*/
public function onCommandBeer($request)
{
$format = $this->getConfig(
'beer.format',
'throws %target% %article% %item%.'
);
$this->plugins->getPlugin('Serve')->serve(
dirname(__FILE__) . '/Beer/beer.db',
'beer',
$format,
$request
);
}
/**
* Adds a "booze" alias for the "beer" command.
*
* @param string $request Request including the target and an optional
* suggestion of what beer to serve
*
* @return void
*/
public function onCommandBooze($request)
{
$this->onCommandBeer($request);
}
}

View File

@ -0,0 +1,81 @@
<?php
if (!defined('__DIR__')) {
define('__DIR__', dirname(__FILE__));
}
// Create database schema
echo 'Creating database', PHP_EOL;
$file = __DIR__ . '/beer.db';
if (file_exists($file)) {
unlink($file);
}
$db = new PDO('sqlite:' . $file);
$db->exec('CREATE TABLE beer (name VARCHAR(255), link VARCHAR(255))');
$db->exec('CREATE UNIQUE INDEX beer_name ON beer (name)');
$insert = $db->prepare('INSERT INTO beer (name, link) VALUES (:name, :link)');
// Get raw beerme.com data set
echo 'Downloading beerme.com data set', PHP_EOL;
$file = __DIR__ . '/beerlist.txt';
if (!file_exists($file)) {
copy('http://beerme.com/beerlist.php', $file);
}
$contents = file_get_contents($file);
// Extract data from data set
echo 'Processing beerme.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);
$beers = $xpath->query('//table[@class="beerlist"]/tr/td[1]');
$db->beginTransaction();
foreach ($beers as $beer) {
$name = iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $beer->textContent);
$name = preg_replace('/\h*\v+\h*/', '', $name);
$link = 'http://beerme.com' . $beer->childNodes->item(1)->getAttribute('href');
$insert->execute(array($name, $link));
}
$db->commit();
// Clean up
echo 'Cleaning up', PHP_EOL;
unlink($file);
// Get and decompress openbeerdb.com data set
$archive = __DIR__ . '/beers.zip';
if (!file_exists($archive)) {
echo 'Downloading openbeerdb.com data set', PHP_EOL;
copy('http://openbeerdb.googlecode.com/files/beers.zip', $archive);
}
echo 'Decompressing openbeerdb.com data set', PHP_EOL;
$zip = new ZipArchive;
$zip->open($archive);
$zip->extractTo(__DIR__, 'beers/beers.csv');
$zip->close();
$file = __DIR__ . '/beers/beers.csv';
// Extract data from data set
echo 'Processing openbeerdb.com data', PHP_EOL;
$fp = fopen($file, 'r');
$columns = fgetcsv($fp, 0, '|');
$db->beginTransaction();
while ($line = fgetcsv($fp, 0, '|')) {
$line = array_combine($columns, $line);
$name = iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $line['name']);
$name = preg_replace('/\h*\v+\h*/', '', $name);
$link = null;
$insert->execute(array($name, $link));
}
$db->commit();
fclose($fp);
// Clean up
echo 'Cleaning up', PHP_EOL;
unlink($file);
unlink($archive);
rmdir(__DIR__ . '/beers');

View File

@ -0,0 +1,156 @@
<?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_BeerScore
* @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_BeerScore
*/
/**
* Handles incoming requests for beer scores.
*
* @category Phergie
* @package Phergie_Plugin_BeerScore
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_BeerScore
* @uses Phergie_Plugin_Http pear.phergie.org
*/
class Phergie_Plugin_BeerScore extends Phergie_Plugin_Abstract
{
/**
* Score result type
*
* @const string
*/
const TYPE_SCORE = 'SCORE';
/**
* Search result type
*
* @const string
*/
const TYPE_SEARCH = 'SEARCH';
/**
* Refine result type
*
* @const type
*/
const TYPE_REFINE = 'REFINE';
/**
* Base API URL
*
* @const string
*/
const API_BASE_URL = 'http://caedmon.net/beerscore/';
/**
* HTTP plugin
*
* @var Phergie_Plugin_Http
*/
protected $http;
/**
* Checks for dependencies.
*
* @return void
*/
public function onLoad()
{
$this->http = $this->getPluginHandler()->getPlugin('Http');
}
/**
* Handles beerscore commands.
*
* @param string $searchstring String to use in seaching for beer scores
*
* @return void
*/
public function onCommandBeerscore($searchstring)
{
$event = $this->getEvent();
$target = $event->getNick();
$source = $event->getSource();
$apiurl = self::API_BASE_URL . rawurlencode($searchstring);
$response = $this->http->get($apiurl);
if ($response->isError()) {
$this->doNotice($target, 'Score not found (or failed to contact API)');
return;
}
$result = $response->getContent();
switch ($result->type) {
case self::TYPE_SCORE:
// small enough number to get scores
foreach ($result->beer as $beer) {
if ($beer->score === -1) {
$score = '(not rated)';
} else {
$score = $beer->score;
}
$str
= "{$target}: rating for {$beer->name}" .
" = {$score} ({$beer->url})";
$this->doPrivmsg($source, $str);
}
break;
case self::TYPE_SEARCH:
// only beer names, no scores
$str = '';
$found = 0;
foreach ($result->beer as $beer) {
if (isset($beer->score)) {
++$found;
if ($beer->score === -1) {
$score = '(not rated)';
} else {
$score = $beer->score;
}
$str
= "{$target}: rating for {$beer->name}" .
" = {$score} ({$beer->url})";
$this->doPrivmsg($source, $str);
} else {
$str .= "({$beer->name} -> {$beer->url}) ";
}
}
$foundnum = $result->num - $found;
$more = $found ? 'more ' : '';
$str = "{$target}: {$foundnum} {$more}results... {$str}";
$this->doPrivmsg($source, $str);
break;
case self::TYPE_REFINE:
// Too many results; only output search URL
if ($result->num < 100) {
$num = $result->num;
} else {
$num = 'at least 100';
}
$resultsword = (($result->num > 1) ? 'results' : 'result');
$str = "{$target}: {$num} {$resultsword}; {$result->searchurl}";
$this->doPrivmsg($source, $str);
break;
}
}
}

View File

@ -0,0 +1,106 @@
<?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_Cache
* @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_Cache
*/
/**
* Implements a generic cache to be used by other plugins.
*
* @category Phergie
* @package Phergie_Plugin_Cache
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_Cache
*/
class Phergie_Plugin_Cache extends Phergie_Plugin_Abstract
{
/**
* Key-value data storage for the cache
*
* @var array
*/
protected $cache = array();
/**
* Stores a value in the cache.
*
* @param string $key Key to associate with the value
* @param mixed $data Data to be stored
* @param int|null $ttl Time to live in seconds or NULL for forever
* @param bool $overwrite TRUE to overwrite any existing value
* associated with the specified key
*
* @return bool
*/
public function store($key, $data, $ttl = 3600, $overwrite = true)
{
if (!$overwrite && isset($this->cache[$key])) {
return false;
}
if ($ttl) {
$expires = time()+$ttl;
} else {
$expires = null;
}
$this->cache[$key] = array('data' => $data, 'expires' => $expires);
return true;
}
/**
* Fetches a previously stored value.
*
* @param string $key Key associated with the value
*
* @return mixed Stored value or FALSE if no value or an expired value
* is associated with the specified key
*/
public function fetch($key)
{
if (!isset($this->cache[$key])) {
return false;
}
$item = $this->cache[$key];
if (!is_null($item['expires']) && $item['expires'] < time()) {
$this->expire($key);
return false;
}
return $item['data'];
}
/**
* Expires a value that has exceeded its time to live.
*
* @param string $key Key associated with the value to expire
*
* @return bool
*/
protected function expire($key)
{
if (!isset($this->cache[$key])) {
return false;
}
unset($this->cache[$key]);
return true;
}
}

View File

@ -0,0 +1,69 @@
<?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_Caffeine
* @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_Caffeine
*/
/**
* Processes requests to serve users caffeinated beverages.
*
* @category Phergie
* @package Phergie_Plugin_Caffeine
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_Caffeine
* @uses Phergie_Plugin_Command pear.phergie.org
* @uses Phergie_Plugin_Serve pear.phergie.org
*/
class Phergie_Plugin_Caffeine extends Phergie_Plugin_Abstract
{
/**
* Checks for dependencies.
*
* @return void
*/
public function onLoad()
{
$plugins = $this->plugins;
$plugins->getPlugin('Command');
$plugins->getPlugin('Serve');
}
/**
* Processes requests to serve a user a caffeinated beverage.
*
* @param string $request Request including the target and an optional
* suggestion of what caffeinated beverage to serve
*
* @return void
*/
public function onCommandCaffeine($request)
{
$format = $this->getConfig(
'beer.format',
'throws %target% %article% %item%.'
);
$this->plugins->getPlugin('Serve')->serve(
dirname(__FILE__) . '/Caffeine/caffeine.db',
'caffeine',
$format,
$request
);
}
}

View File

@ -0,0 +1,51 @@
<?php
if (!defined('__DIR__')) {
define('__DIR__', dirname(__FILE__));
}
// Create database schema
echo 'Creating database', PHP_EOL;
$file = __DIR__ . '/caffeine.db';
if (file_exists($file)) {
unlink($file);
}
$db = new PDO('sqlite:' . $file);
$db->exec('CREATE TABLE caffeine (name VARCHAR(255), link VARCHAR(255))');
$db->exec('CREATE UNIQUE INDEX caffeine_name ON caffeine (name)');
$insert = $db->prepare('INSERT INTO caffeine (name, link) VALUES (:name, :link)');
// Get raw energyfiend.com data set
echo 'Downloading energyfiend.com data set', PHP_EOL;
$file = __DIR__ . '/the-caffeine-database.html';
if (!file_exists($file)) {
copy('http://www.energyfiend.com/the-caffeine-database', $file);
}
$contents = file_get_contents($file);
// Extract data from data set
echo 'Processing energyfiend.com data', PHP_EOL;
$contents = tidy_repair_string($contents);
libxml_use_internal_errors(true);
$doc = new DOMDocument;
$doc->loadHTML($contents);
libxml_clear_errors();
$xpath = new DOMXPath($doc);
$caffeine = $xpath->query('//table[@id="caffeinedb"]//tr/td[1]');
$db->beginTransaction();
foreach ($caffeine as $drink) {
$name = iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $drink->textContent);
$name = preg_replace('/\s*\v+\s*/', ' ', $name);
if ($drink->firstChild->nodeName == 'a') {
$link = 'http://energyfiend.com'
. $drink->firstChild->getAttribute('href');
} else {
$link = null;
}
$insert->execute(array($name, $link));
}
$db->commit();
// Clean up
echo 'Cleaning up', PHP_EOL;
unlink($file);

View File

@ -0,0 +1,120 @@
<?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_Censor
* @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_Censor
*/
/**
* Facilitates censoring of event content or discardment of events
* containing potentially offensive phrases depending on the value of the
* configuration setting censor.mode ('off', 'censor', 'discard'). Also
* provides access to a web service for detecting censored words so that
* other plugins may optionally integrate and adjust behavior accordingly to
* prevent discardment of events.
*
* @category Phergie
* @package Phergie_Plugin_Censor
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_Censor
* @uses extension soap
*/
class Phergie_Plugin_Censor extends Phergie_Plugin_Abstract
{
/**
* SOAP client to interact with the CDYNE Profanity Filter API
*
* @var SoapClient
*/
protected $soap;
/**
* Checks for dependencies.
*
* @return void
*/
public function onLoad()
{
if (!extension_loaded('soap')) {
$this->fail('The PHP soap extension is required');
}
if (!in_array($this->config['censor.mode'], array('censor', 'discard'))) {
$this->plugins->removePlugin($this);
}
}
/**
* Returns a "clean" version of a given string.
*
* @param string $string String to clean
*
* @return string Cleaned string
*/
public function cleanString($string)
{
if (empty($this->soap)) {
$this->soap = new SoapClient('http://ws.cdyne.com/ProfanityWS/Profanity.asmx?wsdl');
}
$params = array('Text' => $string);
$attempts = 0;
while ($attempts < 3) {
try {
$response = $this->soap->SimpleProfanityFilter($params);
break;
} catch (SoapFault $e) {
$attempts++;
sleep(1);
}
}
if ($attempts == 3) {
return $string;
}
return $response->SimpleProfanityFilterResult->CleanText;
}
/**
* Processes events before they are dispatched and either censors their
* content or discards them if they contain potentially offensive
* content.
*
* @return void
*/
public function preDispatch()
{
$events = $this->events->getEvents();
foreach ($events as $event) {
switch ($event->getType()) {
case Phergie_Event_Request::TYPE_PRIVMSG:
case Phergie_Event_Request::TYPE_ACTION:
case Phergie_Event_Request::TYPE_NOTICE:
$text = $event->getArgument(1);
$clean = $this->cleanString($text);
if ($text != $clean) {
if ($this->config['censor.mode'] == 'censor') {
$event->setArgument(1, $clean);
} else {
$this->events->removeEvent($event);
}
}
break;
}
}
}
}

View File

@ -0,0 +1,71 @@
<?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_Cocktail
* @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_Cocktail
*/
/**
* Processes requests to serve users cocktail.
*
* @category Phergie
* @package Phergie_Plugin_Cocktail
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_Cocktail
* @uses Phergie_Plugin_Command pear.phergie.org
* @uses Phergie_Plugin_Serve pear.phergie.org
*/
class Phergie_Plugin_Cocktail 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 cocktail.
*
* @param string $request Request including the target and an optional
* suggestion of what cocktail to serve
*
* @return void
*/
public function onCommandCocktail($request)
{
$format = $this->getConfig(
'cocktail.format',
'throws %target% %article% %item%.'
);
$this->plugins->getPlugin('Serve')->serve(
dirname(__FILE__) . '/Cocktail/cocktail.db',
'cocktail',
$format,
$request,
true
);
}
}

View File

@ -0,0 +1,74 @@
<?php
if (!defined('__DIR__')) {
define('__DIR__', dirname(__FILE__));
}
// Create database schema
echo 'Creating database', PHP_EOL;
$file = __DIR__ . '/cocktail.db';
if (file_exists($file)) {
unlink($file);
}
$db = new PDO('sqlite:' . $file);
$db->exec('CREATE TABLE cocktail (name VARCHAR(255), link VARCHAR(255))');
$db->exec('CREATE UNIQUE INDEX cocktail_name ON cocktail (name)');
$insert = $db->prepare('INSERT INTO cocktail (name, link) VALUES (:name, :link)');
// Get raw webtender.com data set
echo 'Downloading webtender.com data set', PHP_EOL;
$start = 1;
do {
$file = __DIR__ . '/' . $start . '.html';
if (file_exists($file)) {
continue;
}
copy(
'http://www.webtender.com/db/browse?level=2&dir=drinks&char=%2A&start=' . $start,
$file
);
if (!isset($limit)) {
$contents = file_get_contents($file);
preg_match('/([0-9]+) found/', $contents, $match);
$limit = $match[1] + (150 - ($match[1] % 150));
}
echo 'Got records ', $start, ' - ', min($start + 150, $limit), ' of ', $limit, PHP_EOL;
$start += 150;
} while ($start < $limit);
// Extract data from data set
$start = 1;
while ($start < $limit) {
echo 'Processing ', $start, ' - ', min($start + 150, $limit), ' of ', $limit, PHP_EOL;
$file = __DIR__ . '/' . $start . '.html';
$contents = file_get_contents($file);
$contents = tidy_repair_string($contents);
libxml_use_internal_errors(true);
$doc = new DOMDocument;
$doc->loadHTML($contents);
libxml_clear_errors();
$xpath = new DOMXPath($doc);
$cocktails = $xpath->query('//li/a');
$db->beginTransaction();
foreach ($cocktails as $cocktail) {
$name = $cocktail->nodeValue;
$name = preg_replace('/ The$|^The |\s*\([^)]+\)\s*| #[0-9]+$/', '', $name);
$name = html_entity_decode($name);
$link = 'http://www.webtender.com' . $cocktail->getAttribute('href');
$insert->execute(array($name, $link));
}
$db->commit();
$start += 150;
}
// Clean up
echo 'Cleaning up', PHP_EOL;
$start = 1;
while ($start < $limit) {
$file = __DIR__ . '/' . $start . '.html';
unlink($file);
$start += 150;
}

View File

@ -0,0 +1,146 @@
<?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_Command
* @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_Command
*/
/**
* Handles parsing and execution of commands sent by users via messages sent
* to channels in which the bot is present or directly to the bot.
*
* @category Phergie
* @package Phergie_Plugin_Command
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_Command
* @uses extension reflection
* @uses Phergie_Plugin_Message pear.phergie.org
*/
class Phergie_Plugin_Command extends Phergie_Plugin_Abstract
{
/**
* Prefix for command method names
*
* @var string
*/
const METHOD_PREFIX = 'onCommand';
/**
* Cache for command lookups used to confirm that methods exist and
* parameter counts match
*
* @var array
*/
protected $methods = array();
/**
* Load the Message plugin
*
* @return void
*/
public function onLoad()
{
$plugins = $this->getPluginHandler();
$plugins->getPlugin('Message');
}
/**
* Populates the methods cache.
*
* @return void
*/
public function populateMethodCache()
{
foreach ($this->getPluginHandler()->getPlugins() as $plugin) {
$reflector = new ReflectionClass($plugin);
foreach ($reflector->getMethods() as $method) {
$name = $method->getName();
if (strpos($name, self::METHOD_PREFIX) === 0
&& !isset($this->methods[$name])
) {
$this->methods[$name] = array(
'total' => $method->getNumberOfParameters(),
'required' => $method->getNumberOfRequiredParameters()
);
}
}
}
}
/**
* Parses a given message and, if its format corresponds to that of a
* defined command, calls the handler method for that command with any
* provided parameters.
*
* @return void
*/
public function onPrivmsg()
{
// Populate the methods cache if needed
if (empty($this->methods)) {
$this->populateMethodCache();
}
// Check for a prefixed message
$msg = $this->plugins->message->getMessage();
if ($msg === false) {
return;
}
// Separate the command and arguments
$parsed = preg_split('/\s+/', $msg, 2);
$command = strtolower(array_shift($parsed));
$args = count($parsed) ? array_shift($parsed) : '';
// Resolve aliases to their corresponding commands
$aliases = $this->getConfig('command.aliases', array());
$result = preg_grep('/^' . preg_quote($command, '/') . '$/i', array_keys($aliases));
if ($result) {
$command = $aliases[array_shift($result)];
}
// Check to ensure the command exists
$method = self::METHOD_PREFIX . ucfirst($command);
if (empty($this->methods[$method])) {
return;
}
// If no arguments are passed...
if (empty($args)) {
// If the method requires no arguments, call it
if (empty($this->methods[$method]['required'])) {
$this->getPluginHandler()->$method();
}
} else {
// If arguments are passed...
// Parse the arguments
$args = preg_split('/\s+/', $args, $this->methods[$method]['total']);
// If the minimum arguments are passed, call the method
if ($this->methods[$method]['required'] <= count($args)) {
call_user_func_array(
array($this->getPluginHandler(), $method),
$args
);
}
}
}
}

View File

@ -0,0 +1,69 @@
<?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_Cookie
* @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_Cookie
*/
/**
* Processes requests to serve users cookies.
*
* @category Phergie
* @package Phergie_Plugin_Cookie
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_Cookie
* @uses Phergie_Plugin_Command pear.phergie.org
* @uses Phergie_Plugin_Serve pear.phergie.org
*/
class Phergie_Plugin_Cookie 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 cookie.
*
* @param string $request Request including the target and an optional
* suggestion of what cookie to serve
*
* @return void
*/
public function onCommandCookie($request)
{
$format = $this->getConfig(
'cookie.format',
'throws %target% %article% %item%.'
);
$this->plugins->getPlugin('Serve')->serve(
dirname(__FILE__) . '/Cookie/cookie.db',
'cookies',
$format,
$request
);
}
}

View File

@ -0,0 +1,55 @@
<?php
if (!defined('__DIR__')) {
define('__DIR__', dirname(__FILE__));
}
// Create database schema
echo 'Creating database', PHP_EOL;
$file = __DIR__ . '/cookie.db';
if (file_exists($file)) {
unlink($file);
}
$db = new PDO('sqlite:' . $file);
$db->exec('CREATE TABLE cookies (name VARCHAR(255), link VARCHAR(255))');
$db->exec('CREATE UNIQUE INDEX cookie_name ON cookies (name)');
$insert = $db->prepare('INSERT INTO cookies (name, link) VALUES (:name, :link)');
// Get Cookies list from http://en.wikipedia.org/wiki/List_of_cookies
echo 'Downloading data from Wikipedia', PHP_EOL;
$file = __DIR__ . '/cookieslist.txt';
if (!file_exists($file)) {
copy('http://en.wikipedia.org/wiki/List_of_cookies', $file);
}
$contents = file_get_contents($file);
// Extract data from data set
echo 'Processing Wikipedia\'s cookies list', 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);
$cookies = $xpath->query('//table[@width="90%"]/tr/td[1]/a');
foreach ($cookies as $cookie) {
$name = $cookie->textContent;
$name = str_replace(
array('(',')',"\n", 'cookies'),
array('','', ' ', 'cookie'),
$name
);
$name = iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $name);
$name = trim($name);
$name = rtrim($name, 's');
$link = 'http://en.wikipedia.org' . $cookie->getAttribute('href');
$insert->execute(array($name, $link));
echo 'added [' . $name . '] -> '. $link . PHP_EOL;
}
// Clean up
echo 'Cleaning up', PHP_EOL;
unlink($file);

View File

@ -0,0 +1,147 @@
<?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_Cron
* @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_Cron
*/
/**
* Allows callbacks to be registered for asynchronous execution.
*
* @category Phergie
* @package Phergie_Plugin_Cron
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_Cron
*/
class Phergie_Plugin_Cron extends Phergie_Plugin_Abstract
{
/**
* Array of all registered callbacks with delays and arguments
*
* @var array
*/
protected $callbacks;
/**
* Returns a human-readable representation of a callback for debugging
* purposes.
*
* @param callback $callback Callback to analyze
*
* @return string|boolean String representation of the callback or FALSE
* if the specified value is not a valid callback
*/
protected function getCallbackString($callback)
{
if (!is_callable($callback)) {
return false;
}
if (is_array($callback)) {
$class = is_string($callback[0]) ?
$callback[0] : get_class($callback[0]);
$method = $class . '::' . $callback[1];
return $method;
}
return $callback;
}
/**
* Registers a callback for execution sometime after a given delay
* relative to now.
*
* @param callback $callback Callback to be registered
* @param int $delay Delay in seconds from now when the callback
* will be executed
* @param array $arguments Arguments to pass to the callback when
* it's executed
* @param bool $repeat TRUE to automatically re-register the
* callback for the same delay after it's executed, FALSE
* otherwise
*
* @return void
*/
public function registerCallback($callback, $delay,
array $arguments = array(), $repeat = false)
{
$callbackString = $this->getCallbackString($callback);
if ($callbackString === false) {
echo 'DEBUG(Cron): Invalid callback specified - ',
var_export($callback, true), PHP_EOL;
return;
}
$registered = time();
$scheduled = $registered + $delay;
$this->callbacks[] = array(
'callback' => $callback,
'delay' => $delay,
'arguments' => $arguments,
'registered' => $registered,
'scheduled' => $scheduled,
'repeat' => $repeat,
);
echo 'DEBUG(Cron): Callback ', $callbackString,
' scheduled for ', date('H:i:s', $scheduled), PHP_EOL;
}
/**
* Handles callback execution.
*
* @return void
*/
public function onTick()
{
$time = time();
foreach ($this->callbacks as $key => &$callback) {
$callbackString = $this->getCallbackString($callback);
$scheduled = $callback['scheduled'];
if ($time < $scheduled) {
continue;
}
if (empty($callback['arguments'])) {
call_user_func($callback['callback']);
} else {
call_user_func_array(
$callback['callback'],
$callback['arguments']
);
}
echo 'DEBUG(Cron): Callback ', $callbackString,
' scheduled for ', date('H:i:s', $scheduled), ',',
' executed at ', date('H:i:s', $now), PHP_EOL;
if ($callback['repeat']) {
$callback['scheduled'] = $time + $callback['delay'];
echo 'DEBUG(Cron): Callback ', $callbackString,
' scheduled for ', date('H:i:s', $callback['scheduled']),
PHP_EOL;
} else {
echo 'DEBUG(Cron): Callback ', $callbackString,
' removed from callback list', PHP_EOL;
unset($this->callbacks[$key]);
}
}
}
}

View File

@ -0,0 +1,91 @@
<?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_Ctcp
* @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_Ctcp
*/
/**
* Responds to various CTCP requests sent by the server and users.
*
* @category Phergie
* @package Phergie_Plugin_Ctcp
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_Ctcp
* @link http://www.irchelp.org/irchelp/rfc/ctcpspec.html
*/
class Phergie_Plugin_Ctcp extends Phergie_Plugin_Abstract
{
/**
* Responds to a CTCP TIME request from a user with the current local
* time.
*
* @return void
*/
public function onTime()
{
$source = $this->getEvent()->getSource();
$this->doTime($source, strftime('%c %z'));
}
/**
* Responds to a CTCP VERSION request from a user with the codebase
* version.
*
* @return void
*/
public function onVersion()
{
$source = $this->getEvent()->getSource();
$msg = 'Phergie ' . Phergie_Bot::VERSION . ' (http://phergie.org)';
$this->doVersion($source, $msg);
}
/**
* Responds to a CTCP PING request from a user.
*
* @return void
*/
public function onCtcpPing()
{
$event = $this->getEvent();
$source = $event->getSource();
$handshake = $event->getArgument(1);
$this->doPing($source, $handshake);
}
/**
* Responds to a CTCP FINGER request from a user.
*
* @return void
*/
public function onFinger()
{
$connection = $this->getConnection();
$name = $connection->getNick();
$realname = $connection->getRealname();
$username = $connection->getUsername();
$finger
= (empty($realname) ? $realname : $name) .
' (' . (!empty($username) ? $username : $name) . ')';
$this->doFinger($source, $finger);
}
}
?>

View File

@ -0,0 +1,56 @@
<?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_Daddy
* @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_Daddy
*/
/**
* Simply responds to messages addressed to the bot that contain the phrase
* "Who's your daddy?" and related variations.
*
* @category Phergie
* @package Phergie_Plugin_Daddy
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_Daddy
*/
class Phergie_Plugin_Daddy extends Phergie_Plugin_Abstract
{
/**
* Checks messages for the question to which it should respond and sends a
* response when appropriate
*
* @return void
*/
public function onPrivmsg()
{
$config = $this->getConfig();
$prefix = $config['command.prefix'];
$event = $this->getEvent();
$text = $event->getArgument(1);
$target = $event->getNick();
$source = $event->getSource();
$pattern
= '/' . preg_quote($prefix) .
'\s*?who\'?s y(?:our|a) ([^?]+)\??/iAD';
if (preg_match($pattern, $text, $m)) {
$msg = 'You\'re my ' . $m[1] . ', ' . $target . '!';
$this->doPrivmsg($source, $msg);
}
}
}

View File

@ -0,0 +1,182 @@
<?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_Encoding
* @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_Encoding
*/
/**
* Handles decoding markup entities and converting text between character
* encodings.
*
* @category Phergie
* @package Phergie_Plugin_Encoding
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_Encoding
*/
class Phergie_Plugin_Encoding extends Phergie_Plugin_Abstract
{
/**
* Lookup table for entity conversions not supported by
* html_entity_decode()
*
* @var array
* @link http://us.php.net/manual/en/function.get-html-translation-table.php#73409
* @link http://us.php.net/manual/en/function.get-html-translation-table.php#73410
*/
protected static $entities = array(
'&alpha;' => 913,
'&apos;' => 39,
'&beta;' => 914,
'&bull;' => 149,
'&chi;' => 935,
'&circ;' => 94,
'&delta;' => 916,
'&epsilon;' => 917,
'&eta;' => 919,
'&fnof;' => 402,
'&gamma;' => 915,
'&iota;' => 921,
'&kappa;' => 922,
'&lambda;' => 923,
'&ldquo;' => 147,
'&lsaquo;' => 139,
'&lsquo;' => 145,
'&mdash;' => 151,
'&minus;' => 45,
'&mu;' => 924,
'&ndash;' => 150,
'&nu;' => 925,
'&oelig;' => 140,
'&omega;' => 937,
'&omicron;' => 927,
'&phi;' => 934,
'&pi;' => 928,
'&piv;' => 982,
'&psi;' => 936,
'&rdquo;' => 148,
'&rho;' => 929,
'&rsaquo;' => 155,
'&rsquo;' => 146,
'&scaron;' => 138,
'&sigma;' => 931,
'&sigmaf;' => 962,
'&tau;' => 932,
'&theta;' => 920,
'&thetasym;' => 977,
'&tilde;' => 126,
'&trade;' => 153,
'&upsih;' => 978,
'&upsilon;' => 933,
'&xi;' => 926,
'&yuml;' => 159,
'&zeta;' => 918,
);
/**
* Decodes markup entities in a given string.
*
* @param string $string String containing markup entities
* @param string $charset Optional character set name to use in decoding
* entities, defaults to UTF-8
*
* @return string String with markup entities decoded
*/
public function decodeEntities($string, $charset = 'UTF-8')
{
$string = str_ireplace(
array_keys(self::$entities),
array_map('chr', self::$entities),
$string
);
$string = html_entity_decode($string, ENT_QUOTES, $charset);
$string = preg_replace(
array('/&#0*([0-9]+);/me', '/&#x0*([a-f0-9]+);/mei'),
array('$this->codeToUtf(\\1)', '$this->codeToUtf(hexdec(\\1))'),
$string
);
return $string;
}
/**
* Converts a given unicode to its UTF-8 equivalent.
*
* @param int $code Code to convert
* @return string Character corresponding to code
*/
public function codeToUtf8($code)
{
$code = (int) $code;
switch ($code) {
// 1 byte, 7 bits
case 0:
return chr(0);
case ($code & 0x7F):
return chr($code);
// 2 bytes, 11 bits
case ($code & 0x7FF):
return chr(0xC0 | (($code >> 6) & 0x1F)) .
chr(0x80 | ($code & 0x3F));
// 3 bytes, 16 bits
case ($code & 0xFFFF):
return chr(0xE0 | (($code >> 12) & 0x0F)) .
chr(0x80 | (($code >> 6) & 0x3F)) .
chr(0x80 | ($code & 0x3F));
// 4 bytes, 21 bits
case ($code & 0x1FFFFF):
return chr(0xF0 | ($code >> 18)) .
chr(0x80 | (($code >> 12) & 0x3F)) .
chr(0x80 | (($code >> 6) & 0x3F)) .
chr(0x80 | ($code & 0x3F));
}
}
/**
* Transliterates characters in a given string where possible.
*
* @param string $string String containing characters to
* transliterate
* @param string $charsetFrom Optional character set of the string,
* defaults to UTF-8
* @param string $charsetTo Optional character set to which the string
* should be converted, defaults to ISO-8859-1
*
* @return string String with characters transliterated or the original
* string if transliteration was not possible
*/
public function transliterate($string, $charsetFrom = 'UTF-8', $charsetTo = 'ISO-8859-1')
{
// @link http://pecl.php.net/package/translit
if (function_exists('transliterate')) {
$string = transliterate($string, array('han_transliterate', 'diacritical_remove'), $charsetFrom, $charsetTo);
} elseif (function_exists('iconv')) {
$string = iconv($charsetFrom, $charsetTo . '//TRANSLIT', $string);
} else {
// @link http://stackoverflow.com/questions/1284535/php-transliteration/1285491#1285491
$string = preg_replace(
'~&([a-z]{1,2})(acute|cedil|circ|grave|lig|orn|ring|slash|th|tilde|uml);~i',
'$1',
htmlentities($string, ENT_COMPAT, $charsetFrom)
);
}
return $string;
}
}

View File

@ -0,0 +1,120 @@
<?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 plugin handling.
*
* @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_Plugin_Exception extends Phergie_Exception
{
/**
* Error indicating that a path containing plugins was specified, but
* did not reference a readable directory
*/
const ERR_DIRECTORY_NOT_READABLE = 1;
/**
* Error indicating that an attempt was made to locate the class for a
* specified plugin, but the class could not be found
*/
const ERR_CLASS_NOT_FOUND = 2;
/**
* Error indicating that an attempt was made to locate the class for a
* specified plugin, but that the found class did not extend the base
* plugin class
*/
const ERR_INCORRECT_BASE_CLASS = 3;
/**
* Error indicating that an attempt was made to locate the class for a
* specified plugin, but that the found class cannot be instantiated
*/
const ERR_CLASS_NOT_INSTANTIABLE = 4;
/**
* Error indicating that an attempt was made to access a plugin that had
* not been loaded and autoloading was not enabled to load it
*/
const ERR_PLUGIN_NOT_LOADED = 5;
/**
* Error indicating that an attempt was made to access the configuration
* handler before one had been set
*/
const ERR_NO_CONFIG_HANDLER = 6;
/**
* Error indicating that an attempt was made to access the plugin
* handler before one had been set
*/
const ERR_NO_PLUGIN_HANDLER = 7;
/**
* Error indicating that an attempt was made to access the event
* handler before one had been set
*/
const ERR_NO_EVENT_HANDLER = 8;
/**
* Error indicating that an attempt was made to access the connection
* before one had been set
*/
const ERR_NO_CONNECTION = 9;
/**
* Error indicating that an attempt was made to access the current
* incoming event before one had been set
*/
const ERR_NO_EVENT = 10;
/**
* Error indicating that a dependency of the plugin was unavailable at
* the time that an attempt was made to load it
*/
const ERR_REQUIREMENT_UNSATISFIED = 11;
/**
* Error indicating that a call was made to a nonexistent plugin method
* and that its __call() implementation did not process that call as an
* attempt to trigger an event - this is intended to aid in debugging of
* such situations
*/
const ERR_INVALID_CALL = 12;
/**
* Error indicating that a fatal runtime issue was encountered within a
* plugin
*/
const ERR_FATAL_ERROR = 13;
/**
* Error indicating that a class specified to be used for iterating
* plugins cannot be found by the autoloader or does not extend
* FilterIterator
*/
const ERR_INVALID_ITERATOR_CLASS = 14;
}

View File

@ -0,0 +1,459 @@
<?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_Google
* @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_Google
*/
/**
* 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_Google
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_Google
* @uses Phergie_Plugin_Command pear.phergie.org
* @uses Phergie_Plugin_Http pear.phergie.org
* @uses Phergie_Plugin_Temperature pear.phergie.org
*/
class Phergie_Plugin_Google extends Phergie_Plugin_Abstract
{
/**
* Checks for dependencies.
*
* @return void
*/
public function onLoad()
{
$plugins = $this->getPluginHandler();
$plugins->getPlugin('Command');
$plugins->getPlugin('Http');
$plugins->getPlugin('Weather');
}
/**
* Returns the first result of a Google search.
*
* @param string $query Search term
*
* @return void
* @todo Implement use of URL shortening here
*/
public function onCommandG($query)
{
$url = 'http://ajax.googleapis.com/ajax/services/search/web';
$params = array(
'v' => '1.0',
'q' => $query
);
$response = $this->plugins->http->get($url, $params);
$json = $response->getContent()->responseData;
$event = $this->getEvent();
$source = $event->getSource();
$nick = $event->getNick();
if ($json->cursor->estimatedResultCount > 0) {
$msg
= $nick
. ': [ '
. $json->results[0]->titleNoFormatting
. ' ] - '
. $json->results[0]->url
. ' - More results: '
. $json->cursor->moreResultsUrl;
$this->doPrivmsg($source, $msg);
} else {
$msg = $nick . ': No results for this query.';
$this->doPrivmsg($source, $msg);
}
}
/**
* Performs a Google Count search for the given term.
*
* @param string $query Search term
*
* @return void
*/
public function onCommandGc($query)
{
$url = 'http://ajax.googleapis.com/ajax/services/search/web';
$params = array(
'v' => '1.0',
'q' => $query
);
$response = $this->plugins->http->get($url, $params);
$json = $response->getContent()->responseData->cursor;
$count = $json->estimatedResultCount;
$event = $this->getEvent();
$source = $event->getSource();
$nick = $event->getNick();
if ($count) {
$msg
= $nick . ': ' .
number_format($count, 0) .
' estimated results for ' . $query;
$this->doPrivmsg($source, $msg);
} else {
$this->doPrivmsg($source, $nick . ': No results for this query.');
}
}
/**
* Performs a Google Translate search for the given term.
*
* @param string $from Language of the search term
* @param string $to Language to which the search term should be
* translated
* @param string $query Term to translate
*
* @return void
*/
public function onCommandGt($from, $to, $query)
{
$url = 'http://ajax.googleapis.com/ajax/services/language/translate';
$params = array(
'v' => '1.0',
'q' => $query,
'langpair' => $from . '|' . $to
);
$response = $this->plugins->http->get($url, $params);
$json = $response->getContent();
$event = $this->getEvent();
$source = $event->getSource();
$nick = $event->getNick();
if (empty($json->responseData->translatedText)) {
$this->doPrivmsg($source, $nick . ': ' . $json->responseDetails);
} else {
$this->doPrivmsg(
$source,
$nick . ': ' . $json->responseData->translatedText
);
}
}
/**
* Performs a Google Weather search for the given term.
*
* @param string $location Location to search for
* @param int $offset Optional day offset from the current date
* between 0 and 3 to get the forecast
*
* @return void
*/
public function onCommandGw($location, $offset = null)
{
$url = 'http://www.google.com/ig/api';
$params = array(
'weather' => $location,
'hl' => $this->getConfig('google.lang', 'en'),
'oe' => 'UTF-8'
);
$response = $this->plugins->http->get($url, $params);
$xml = $response->getContent()->weather;
$event = $this->getEvent();
$source = $event->getSource();
$msg = '';
if ($event->isInChannel()) {
$msg .= $event->getNick() . ': ';
}
if (isset($xml->problem_cause)) {
$msg .= $xml->problem_cause->attributes()->data[0];
$this->doPrivmsg($source, $msg);
return;
}
$temperature = $this->plugins->getPlugin('Temperature');
$forecast = $xml->forecast_information;
$city = $forecast->city->attributes()->data[0];
$zip = $forecast->postal_code->attributes()->data[0];
if ($offset !== null) {
$offset = (int) $offset;
if ($offset < 0) {
$this->doNotice($source, 'Past weather data is not available');
return;
} elseif ($offset > 3) {
$this->doNotice($source, 'Future weather data is limited to 3 days from today');
return;
}
$linha = $xml->forecast_conditions[$offset];
$low = $linha->low->attributes()->data[0];
$high = $linha->high->attributes()->data[0];
$units = $forecast->unit_system->attributes()->data[0];
$condition = $linha->condition->attributes()->data[0];
$day = $linha->day_of_week->attributes()->data[0];
$date = ($offset == 0) ? time() : strtotime('next ' . $day);
$day = ucfirst($day) . ' ' . date('n/j/y', $date);
if ($units == 'US') {
$lowF = $low;
$lowC = $temperature->convertFahrenheitToCelsius($low);
$highF = $high;
$highC = $temperature->convertFahrenheitToCelsius($high);
} else {
$lowC = $low;
$lowF = $temperature->convertCelsiusToFahrenheit($lowC);
$highC = $high;
$highF = $temperature->convertCelsiusToFahrenheit($high);
}
$msg .= 'Forecast for ' . $city . ' (' . $zip . ')'
. ' on ' . $day . ' ::'
. ' Low: ' . $lowF . 'F/' . $lowC . 'C,'
. ' High: ' . $highF . 'F/' . $highC . 'C,'
. ' Conditions: ' . $condition;
} else {
$conditions = $xml->current_conditions;
$condition = $conditions->condition->attributes()->data[0];
$tempF = $conditions->temp_f->attributes()->data[0];
$tempC = $conditions->temp_c->attributes()->data[0];
$humidity = $conditions->humidity->attributes()->data[0];
$wind = $conditions->wind_condition->attributes()->data[0];
$time = $forecast->current_date_time->attributes()->data[0];
$time = date('n/j/y g:i A', strtotime($time)) . ' +0000';
$hiF = $temperature->getHeatIndex($tempF, $humidity);
$hiC = $temperature->convertFahrenheitToCelsius($hiF);
$msg .= 'Weather for ' . $city . ' (' . $zip . ') -'
. ' Temperature: ' . $tempF . 'F/' . $tempC . 'C,'
. ' ' . $humidity . ','
. ' Heat Index: ' . $hiF . 'F/' . $hiC . 'C,'
. ' Conditions: ' . $condition . ','
. ' Updated: ' . $time;
}
$this->doPrivmsg($source, $msg);
}
/**
* Performs a Google Maps search for the given term.
*
* @param string $location Location to search for
*
* @return void
*/
public function onCommandGmap($location)
{
$event = $this->getEvent();
$source = $event->getSource();
$nick = $event->getNick();
$location = utf8_encode($location);
$url = 'http://maps.google.com/maps/geo';
$params = array(
'q' => $location,
'output' => 'json',
'gl' => $this->getConfig('google.lang', 'en'),
'sensor' => 'false',
'oe' => 'utf8',
'mrt' => 'all',
'key' => $this->getConfig('google.key')
);
$response = $this->plugins->http->get($url, $params);
$json = $response->getContent();
if (!empty($json)) {
$qtd = count($json->Placemark);
if ($qtd > 1) {
if ($qtd <= 3) {
foreach ($json->Placemark as $places) {
$xy = $places->Point->coordinates;
$address = utf8_decode($places->address);
$url = 'http://maps.google.com/maps?sll=' . $xy[1] . ','
. $xy[0] . '&z=15';
$msg = $nick . ' -> ' . $address . ' - ' . $url;
$this->doPrivmsg($source, $msg);
}
} else {
$msg
= $nick .
', there are a lot of places with that query.' .
' Try to be more specific!';
$this->doPrivmsg($source, $msg);
}
} elseif ($qtd == 1) {
$xy = $json->Placemark[0]->Point->coordinates;
$address = utf8_decode($json->Placemark[0]->address);
$url = 'http://maps.google.com/maps?sll=' . $xy[1] . ',' . $xy[0]
. '&z=15';
$msg = $nick . ' -> ' . $address . ' - ' . $url;
$this->doPrivmsg($source, $msg);
} else {
$this->doPrivmsg($source, $nick . ', I found nothing.');
}
} else {
$this->doPrivmsg($source, $nick . ', we have a problem.');
}
}
/**
* Perform a Google Convert query to convert a value from one metric to
* another.
*
* @param string $value Value to convert
* @param string $from Source metric
* @param string $to Destination metric
*
* @return void
*/
public function onCommandGconvert($value, $from, $to)
{
$url = 'http://www.google.com/finance/converter';
$params = array(
'a' => $value,
'from' => $from,
'to' => $to
);
$response = $this->plugins->http->get($url, $params);
$contents = $response->getContent();
$event = $this->getEvent();
$source = $event->getSource();
$nick = $event->getNick();
if ($contents) {
libxml_use_internal_errors(true);
$doc = new DOMDocument;
$doc->loadHTML($contents);
libxml_clear_errors();
$xpath = new DOMXPath($doc);
$result = $xpath->query('//div[@id="currency_converter_result"]');
$div = $result->item(0);
$text = rtrim($div->textContent);
$this->doPrivmsg($source, $text);
}
}
/**
* Performs a Google search to convert a value from one unit to another.
*
* @param string $query Query of the form "[quantity] [unit] to [unit2]"
*
* @return void
*
* @pluginCmd [quantity] [unit] to [unit2] Convert a value from one
* metric to another
*/
public function onCommandConvert($query)
{
$url = 'http://www.google.com/search?q=' . urlencode($query);
$response = $this->plugins->http->get($url);
$contents = $response->getContent();
$event = $this->getEvent();
$source = $event->getSource();
$nick = $event->getNick();
if ($response->isError()) {
$code = $response->getCode();
$message = $response->getMessage();
$this->doNotice($nick, 'ERROR: ' . $code . ' ' . $message);
return;
}
$start = strpos($contents, '<h3 class=r>');
if ($start !== false) {
$end = strpos($contents, '</b>', $start);
$text = strip_tags(substr($contents, $start, $end - $start));
$text = str_replace(
array(chr(195), chr(151), chr(160)),
array('x', '', ' '),
$text
);
}
if (isset($text)) {
$this->doPrivmsg($source, $nick . ': ' . $text);
} else {
$this->doNotice($nick, 'Sorry I couldn\'t find an answer.');
}
}
/**
* Returns the first definition of a Google Dictionary search.
*
* @param string $query Word to get the definition
*
* @return void
* @todo Implement use of URL shortening here
*/
public function onCommandDefine($query)
{
$lang = $this->getConfig('google.lang', 'en');
$url = 'http://www.google.com/dictionary/json';
$params = array(
'callback' => 'result',
'q' => $query,
'sl' => $lang,
'tl' => $lang,
'restrict' => 'pr,de'
);
$response = $this->plugins->http->get($url, $params);
$json = $response->getContent();
// Remove some garbage from the JSON and decode it
$json = str_replace(array('result(', ',200,null)'), '', $json);
$json = str_replace('"', '¿?¿', $json);
$json = strip_tags(stripcslashes($json));
$json = str_replace('"', "'", $json);
$json = str_replace('¿?¿', '"', $json);
$json = json_decode($json);
$event = $this->getEvent();
$source = $event->getSource();
$nick = $event->getNick();
if (!empty($json->webDefinitions)) {
$results = 0;
foreach ($json->primaries[0]->entries as $entry) {
if ($entry->type == 'meaning') {
$results++;
if (empty($text)) {
foreach ($entry->terms as $term) {
if ($term->type == 'text') {
$text = trim($term->text);
}
}
}
}
}
$more = $results > 1 ? ($results - 1) . ' ' : '';
$lang_code = substr($lang, 0, 2);
$msg = $nick . ': ' . $text
. ' - You can find ' . $more . 'more results at '
. 'http://www.google.com/dictionary'
. '?aq=f'
. '&langpair=' . $lang_code . '%7C' . $lang_code
. '&q=' . $query
. '&hl=' . $lang_code;
$this->doPrivmsg($source, $msg);
} else {
if ($lang != 'en'){
$lang = 'en';
$this->onCommandDefine($query);
} else {
$msg = $nick . ': No results for this query.';
$this->doPrivmsg($source, $msg);
}
}
}
}

View File

@ -0,0 +1,501 @@
<?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
*/
/**
* Handles on-demand loading of, iteration over, and access to plugins.
*
* @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_Plugin_Handler implements IteratorAggregate, Countable
{
/**
* Current list of plugin instances
*
* @var array
*/
protected $plugins;
/**
* Paths in which to search for plugin class files
*
* @var array
*/
protected $paths;
/**
* Flag indicating whether plugin classes should be instantiated on
* demand if they are requested but no instance currently exists
*
* @var bool
*/
protected $autoload;
/**
* Phergie_Config instance that should be passed in to any plugin
* instantiated within the handler
*
* @var Phergie_Config
*/
protected $config;
/**
* Phergie_Event_Handler instance that should be passed in to any plugin
* instantiated within the handler
*
* @var Phergie_Event_Handler
*/
protected $events;
/**
* Name of the class to use for iterating over all currently loaded
* plugins
*
* @var string
*/
protected $iteratorClass = 'Phergie_Plugin_Iterator';
/**
* Constructor to initialize class properties and add the path for core
* plugins.
*
* @param Phergie_Config $config configuration to pass to any
* instantiated plugin
* @param Phergie_Event_Handler $events event handler to pass to any
* instantiated plugin
*
* @return void
*/
public function __construct(
Phergie_Config $config,
Phergie_Event_Handler $events
) {
$this->config = $config;
$this->events = $events;
$this->plugins = array();
$this->paths = array();
$this->autoload = false;
if (!empty($config['plugins.paths'])) {
foreach ($config['plugins.paths'] as $dir => $prefix) {
$this->addPath($dir, $prefix);
}
}
$this->addPath(dirname(__FILE__), 'Phergie_Plugin_');
}
/**
* Adds a path to search for plugin class files. Paths are searched in
* the reverse order in which they are added.
*
* @param string $path Filesystem directory path
* @param string $prefix Optional class name prefix corresponding to the
* path
*
* @return Phergie_Plugin_Handler Provides a fluent interface
* @throws Phergie_Plugin_Exception
*/
public function addPath($path, $prefix = '')
{
if (!is_readable($path)) {
throw new Phergie_Plugin_Exception(
'Path "' . $path . '" does not reference a readable directory',
Phergie_Plugin_Exception::ERR_DIRECTORY_NOT_READABLE
);
}
$this->paths[] = array(
'path' => rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR,
'prefix' => $prefix
);
return $this;
}
/**
* Returns metadata corresponding to a specified plugin.
*
* @param string $plugin Short name of the plugin class
*
* @throws Phergie_Plugin_Exception Class file can't be found
*
* @return array|boolean Associative array containing the path to the
* class file and its containing directory as well as the full
* class name
*/
public function getPluginInfo($plugin)
{
foreach (array_reverse($this->paths) as $path) {
$file = $path['path'] . $plugin . '.php';
if (file_exists($file)) {
$path = array(
'dir' => $path['path'],
'file' => $file,
'class' => $path['prefix'] . $plugin,
);
return $path;
}
}
// If the class can't be found, display an error
throw new Phergie_Plugin_Exception(
'Class file for plugin "' . $plugin . '" cannot be found',
Phergie_Plugin_Exception::ERR_CLASS_NOT_FOUND
);
}
/**
* Adds a plugin instance to the handler.
*
* @param string|Phergie_Plugin_Abstract $plugin Short name of the
* plugin class or a plugin object
* @param array $args Optional array of
* arguments to pass to the plugin constructor if a short name is
* passed for $plugin
*
* @return Phergie_Plugin_Abstract New plugin instance
*/
public function addPlugin($plugin, array $args = null)
{
// If a short plugin name is specified...
if (is_string($plugin)) {
$index = strtolower($plugin);
if (isset($this->plugins[$index])) {
return $this->plugins[$index];
}
// Attempt to locate and load the class
$info = $this->getPluginInfo($plugin);
$file = $info['file'];
$class = $info['class'];
include_once $file;
if (!class_exists($class, false)) {
throw new Phergie_Plugin_Exception(
'File "' . $file . '" does not contain class "' . $class . '"',
Phergie_Plugin_Exception::ERR_CLASS_NOT_FOUND
);
}
// Check to ensure the class is a plugin class
if (!is_subclass_of($class, 'Phergie_Plugin_Abstract')) {
$msg
= 'Class for plugin "' . $plugin .
'" does not extend Phergie_Plugin_Abstract';
throw new Phergie_Plugin_Exception(
$msg,
Phergie_Plugin_Exception::ERR_INCORRECT_BASE_CLASS
);
}
// Check to ensure the class can be instantiated
$reflection = new ReflectionClass($class);
if (!$reflection->isInstantiable()) {
throw new Phergie_Plugin_Exception(
'Class for plugin "' . $plugin . '" cannot be instantiated',
Phergie_Plugin_Exception::ERR_CLASS_NOT_INSTANTIABLE
);
}
// If the class is found, instantiate it
if (!empty($args)) {
$instance = $reflection->newInstanceArgs($args);
} else {
$instance = new $class;
}
// Store the instance
$this->plugins[$index] = $instance;
$plugin = $instance;
} elseif ($plugin instanceof Phergie_Plugin_Abstract) {
// If a plugin instance is specified...
// Add the plugin instance to the list of plugins
$this->plugins[strtolower($plugin->getName())] = $plugin;
}
// Configure and initialize the instance
$plugin->setPluginHandler($this);
$plugin->setConfig($this->config);
$plugin->setEventHandler($this->events);
$plugin->onLoad();
return $plugin;
}
/**
* Adds multiple plugin instances to the handler.
*
* @param array $plugins List of elements where each is of the form
* 'ShortPluginName' or array('ShortPluginName', array($arg1,
* ..., $argN))
*
* @return Phergie_Plugin_Handler Provides a fluent interface
*/
public function addPlugins(array $plugins)
{
foreach ($plugins as $plugin) {
if (is_array($plugin)) {
$this->addPlugin($plugin[0], $plugin[1]);
} else {
$this->addPlugin($plugin);
}
}
return $this;
}
/**
* Removes a plugin instance from the handler.
*
* @param string|Phergie_Plugin_Abstract $plugin Short name of the
* plugin class or a plugin object
*
* @return Phergie_Plugin_Handler Provides a fluent interface
*/
public function removePlugin($plugin)
{
if ($plugin instanceof Phergie_Plugin_Abstract) {
$plugin = $plugin->getName();
}
$plugin = strtolower($plugin);
unset($this->plugins[$plugin]);
return $this;
}
/**
* Returns the corresponding instance for a specified plugin, loading it
* if it is not already loaded and autoloading is enabled.
*
* @param string $name Short name of the plugin class
*
* @return Phergie_Plugin_Abstract Plugin instance
*/
public function getPlugin($name)
{
// If the plugin is loaded, return the instance
$lower = strtolower($name);
if (isset($this->plugins[$lower])) {
return $this->plugins[$lower];
}
// If autoloading is disabled, display an error
if (!$this->autoload) {
$msg
= 'Plugin "' . $name . '" has been requested, ' .
'is not loaded, and autoload is disabled';
throw new Phergie_Plugin_Exception(
$msg,
Phergie_Plugin_Exception::ERR_PLUGIN_NOT_LOADED
);
}
// If autoloading is enabled, attempt to load the plugin
$plugin = $this->addPlugin($name);
// Return the added plugin
return $plugin;
}
/**
* Returns the corresponding instances for multiple specified plugins,
* loading them if they are not already loaded and autoloading is
* enabled.
*
* @param array $names Optional list of short names of the plugin
* classes to which the returned plugin list will be limited,
* defaults to all presently loaded plugins
*
* @return array Associative array mapping lowercased plugin class short
* names to corresponding plugin instances
*/
public function getPlugins(array $names = array())
{
if (empty($names)) {
return $this->plugins;
}
$plugins = array();
foreach ($names as $name) {
$plugins[strtolower($name)] = $this->getPlugin($name);
}
return $plugins;
}
/**
* Returns whether or not at least one instance of a specified plugin
* class is loaded.
*
* @param string $name Short name of the plugin class
*
* @return bool TRUE if an instance exists, FALSE otherwise
*/
public function hasPlugin($name)
{
return isset($this->plugins[strtolower($name)]);
}
/**
* Sets a flag used to determine whether plugins should be loaded
* automatically if they have not been explicitly loaded.
*
* @param bool $flag TRUE to have plugins autoload (default), FALSE
* otherwise
*
* @return Phergie_Plugin_Handler Provides a fluent interface.
*/
public function setAutoload($flag = true)
{
$this->autoload = $flag;
return $this;
}
/**
* Returns the value of a flag used to determine whether plugins should
* be loaded automatically if they have not been explicitly loaded.
*
* @return bool TRUE if autoloading is enabled, FALSE otherwise
*/
public function getAutoload()
{
return $this->autoload;
}
/**
* Allows plugin instances to be accessed as properties of the handler.
*
* @param string $name Short name of the plugin
*
* @return Phergie_Plugin_Abstract Requested plugin instance
*/
public function __get($name)
{
return $this->getPlugin($name);
}
/**
* Allows plugin instances to be detected as properties of the handler.
*
* @param string $name Short name of the plugin
*
* @return bool TRUE if the plugin is loaded, FALSE otherwise
*/
public function __isset($name)
{
return $this->hasPlugin($name);
}
/**
* Allows plugin instances to be removed as properties of handler.
*
* @param string $name Short name of the plugin
*
* @return void
*/
public function __unset($name)
{
$this->removePlugin($name);
}
/**
* Returns an iterator for all currently loaded plugin instances.
*
* @return ArrayIterator
*/
public function getIterator()
{
return new $this->iteratorClass(
new ArrayIterator($this->plugins)
);
}
/**
* Sets the iterator class used for all currently loaded plugin
* instances.
*
* @param string $class Name of a class that extends FilterIterator
*
* @return Phergie_Plugin_Handler Provides a fluent API
* @throws Phergie_Plugin_Exception Class cannot be found or is not an
* FilterIterator-based class
*/
public function setIteratorClass($class)
{
$valid = true;
try {
$error_reporting = error_reporting(0); // ignore autoloader errors
$r = new ReflectionClass($class);
error_reporting($error_reporting);
if (!$r->isSubclassOf('FilterIterator')) {
$message = 'Class ' . $class . ' is not a subclass of FilterIterator';
$valid = false;
}
} catch (ReflectionException $e) {
$message = $e->getMessage();
$valid = false;
}
if (!$valid) {
throw new Phergie_Plugin_Exception(
$message,
Phergie_Plugin_Exception::ERR_INVALID_ITERATOR_CLASS
);
}
$this->iteratorClass = $class;
}
/**
* Proxies method calls to all plugins containing the called method.
*
* @param string $name Name of the method called
* @param array $args Arguments passed in the method call
*
* @return void
*/
public function __call($name, array $args)
{
foreach ($this->getIterator() as $plugin) {
call_user_func_array(array($plugin, $name), $args);
}
return true;
}
/**
* Returns the number of plugins contained within the handler.
*
* @return int Plugin count
*/
public function count()
{
return count($this->plugins);
}
}

View File

@ -0,0 +1,276 @@
<?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_Help
* @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_Help
*/
/**
* Provides access to descriptions of plugins and the commands they provide.
*
* @category Phergie
* @package Phergie_Plugin_Help
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_Help
* @uses Phergie_Plugin_Command pear.phergie.org
*/
class Phergie_Plugin_Help extends Phergie_Plugin_Abstract
{
/**
* Registry of help data indexed by plugin name
*
* @var array
*/
protected $registry;
/**
* Checks for dependencies.
*
* @return void
*/
public function onLoad()
{
$this->getPluginHandler()->getPlugin('Command');
}
/**
* Creates a registry of plugin metadata on connect.
*
* @return void
*/
public function onConnect()
{
$this->populateRegistry();
}
/**
* Creates a registry of plugin metadata.
*
* @return void
*/
public function populateRegistry()
{
$this->registry = array();
foreach ($this->plugins as $plugin) {
$class = new ReflectionClass($plugin);
$pluginName = strtolower($plugin->getName());
// Parse the plugin description
$docblock = $class->getDocComment();
$annotations = $this->getAnnotations($docblock);
if (isset($annotations['pluginDesc'])) {
$pluginDesc = implode(' ', $annotations['pluginDesc']);
} else {
$pluginDesc = $this->parseShortDescription($docblock);
}
$this->registry[$pluginName] = array(
'desc' => $pluginDesc,
'cmds' => array()
);
// Parse command method descriptions
$methodPrefix = Phergie_Plugin_Command::METHOD_PREFIX;
$methodPrefixLength = strlen($methodPrefix);
foreach ($class->getMethods() as $method) {
if (strpos($method->getName(), $methodPrefix) !== 0) {
continue;
}
$cmd = strtolower(substr($method->getName(), $methodPrefixLength));
$docblock = $method->getDocComment();
$annotations = $this->getAnnotations($docblock);
if (isset($annotations['pluginCmd'])) {
$cmdDesc = implode(' ', $annotations['pluginCmd']);
} else {
$cmdDesc = $this->parseShortDescription($docblock);
}
$cmdParams = array();
if (!empty($annotations['param'])) {
foreach ($annotations['param'] as $param) {
$match = null;
if (preg_match('/\h+\$([^\h]+)\h+/', $param, $match)) {
$cmdParams[] = $match[1];
}
}
}
$this->registry[$pluginName]['cmds'][$cmd] = array(
'desc' => $cmdDesc,
'params' => $cmdParams
);
}
if (empty($this->registry[$pluginName]['cmds'])) {
unset($this->registry[$pluginName]);
}
}
}
/**
* Displays a list of plugins with help information available or
* commands available for a specific plugin.
*
* @param string $query Optional short name of a plugin for which commands
* should be returned or a command; if unspecified, a list of
* plugins with help information available is returned
*
* @return void
*/
public function onCommandHelp($query = null)
{
if ($query == 'refresh') {
$this->populateRegistry();
}
$nick = $this->getEvent()->getNick();
$delay = $this->getConfig('help.delay', 2);
// Handle requests for a plugin list
if (!$query) {
$msg = 'These plugins have help information available: '
. implode(', ', array_keys($this->registry));
$this->doPrivmsg($nick, $msg);
return;
}
// Handle requests for plugin information
$query = strtolower($query);
if (isset($this->registry[$query])
&& empty($this->registry[$query]['cmds'][$query])) {
$msg = $query . ' - ' . $this->registry[$query]['desc'];
$this->doPrivmsg($nick, $msg);
$msg = 'Available commands - '
. implode(', ', array_keys($this->registry[$query]['cmds']));
$this->doPrivmsg($nick, $msg);
if ($this->getConfig('command.prefix')) {
$msg
= 'Note that these commands must be prefixed with "'
. $this->getConfig('command.prefix')
. '" (without quotes) when issued in a public channel.';
$this->doPrivmsg($nick, $msg);
}
return;
}
// Handle requests for command information
foreach ($this->registry as $plugin => $data) {
if (empty($data['cmds'])) {
continue;
}
$result = preg_grep('/^' . $query . '$/i', array_keys($data['cmds']));
if (!$result) {
continue;
}
$cmd = $data['cmds'][array_shift($result)];
$msg = $query;
if (!empty($cmd['params'])) {
$msg .= ' [' . implode('] [', $cmd['params']) . ']';
}
$msg .= ' - ' . $cmd['desc'];
$this->doPrivmsg($nick, $msg);
}
}
/**
* Parses and returns the short description from a docblock.
*
* @param string $docblock Docblock comment code
*
* @return string Short description (i.e. content from the start of the
* docblock up to the first double-newline)
*/
protected function parseShortDescription($docblock)
{
$desc = preg_replace(
array('#^\h*\*\h*#m', '#^/\*\*\h*\v+\h*#', '#(?:\r?\n){2,}.*#s', '#\s*\v+\s*#'),
array('', '', '', ' '),
$docblock
);
return $desc;
}
/**
* Taken from PHPUnit/Util/Test.php and modified to fix an issue with
* tag content spanning multiple lines.
*
* PHPUnit
*
* Copyright (c) 2002-2010, Sebastian Bergmann <sb@sebastian-bergmann.de>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Sebastian Bergmann nor the names of his
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @param string $docblock docblock to parse
*
* @return array
*/
protected function getAnnotations($docblock)
{
$annotations = array();
$regex = '/@(?P<name>[A-Za-z_-]+)(?:[ \t]+(?P<value>.*?))?(?:\*\/|\* @)/ms';
if (preg_match_all($regex, $docblock, $matches)) {
$numMatches = count($matches[0]);
for ($i = 0; $i < $numMatches; ++$i) {
$annotation = $matches['value'][$i];
$annotation = preg_replace('/\s*\v+\s*\*\s*/', ' ', $annotation);
$annotation = rtrim($annotation);
$annotations[$matches['name'][$i]][] = $annotation;
}
}
return $annotations;
}
}

View File

@ -0,0 +1,284 @@
<?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_Http
* @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_Http
*/
/**
* Provides an HTTP client for plugins to use in contacting web services or
* retrieving feeds or web pages.
*
* @category Phergie
* @package Phergie_Plugin_Http
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_Http
* @uses extension simplexml optional
* @uses extension json optional
*/
class Phergie_Plugin_Http extends Phergie_Plugin_Abstract
{
/**
* Response to the last executed HTTP request
*
* @var Phergie_Plugin_Http_Response
*/
protected $response;
/**
* Mapping of content types to handlers for them
*
* @var array
*/
protected $handlers;
/**
* Initializes the handler lookup table.
*
* @return void
*/
public function onLoad()
{
$this->handlers = array(
'(?:application|text)/xml(?:;.*)?'
=> 'simplexml_load_string',
'(?:(?:application|text)/(?:x-)?json)|text/javascript.*'
=> 'json_decode',
);
if (is_array($this->config['http.handlers'])) {
$this->handlers = array_merge(
$this->handlers,
$this->config['http.handlers']
);
}
}
/**
* Sets a handler callback for a content type, which is called when a
* response of that content type is received to perform any needed
* transformations on the response body content before storing it in the
* response object. Note that the calling plugin is responsible for
* indicating any dependencies related to specified handler callbacks.
*
* @param string $type PCRE regular expression (without delimiters) that
* matches one or more MIME types
* @param callback $callback Callback to execute when a response of a content
* type matched by $type is encountered
*
* @return Phergie_Plugin_Http Provides a fluent interface
*/
public function setHandler($type, $callback)
{
if (!is_callable($callback)) {
throw new Phergie_Plugin_Exception(
'Invalid callback specified',
Phergie_Plugin_Exception::ERR_FATAL_ERROR
);
}
$this->handlers[$type] = $callback;
return $this;
}
/**
* Supporting method that parses the status line of an HTTP response
* message.
*
* @param string $status Status line
*
* @return array Associative array containing the HTTP version, response
* code, and response description
*/
protected function parseStatusLine($status)
{
$parts = explode(' ', $status, 3);
$parsed = array(
'version' => str_replace('HTTP/', '', $parts[0]),
'code' => $parts[1],
'message' => rtrim($parts[2])
);
return $parsed;
}
/**
* Supporting method that acts as an error handler to intercept HTTP
* responses resulting in PHP-level errors.
*
* @param int $errno Level of the error raised
* @param string $errstr Error message
* @param string $errfile Name of the file in which the error was raised
* @param string $errline Line number on which the error was raised
*
* @return bool Always returns TRUE to allow normal execution to
* continue once this method terminates
*/
public function handleError($errno, $errstr, $errfile, $errline)
{
if ($httperr = strstr($errstr, 'HTTP/')) {
$parts = $this->parseStatusLine($httperr);
$this->response
->setCode($parts['code'])
->setMessage($parts['message']);
}
return true;
}
/**
* Supporting method that executes a request and handles the response.
*
* @param string $url URL to request
* @param array $context Associative array of stream context parameters
*
* @return Phergie_Plugin_Http_Response Object representing the response
* resulting from the request
*/
public function request($url, array $context)
{
$this->response = new Phergie_Plugin_Http_Response;
$url = (string) $url;
$context = stream_context_create(array('http' => $context));
set_error_handler(array($this, 'handleError'), E_WARNING);
$stream = fopen($url, 'r', false, $context);
if ($stream) {
$meta = stream_get_meta_data($stream);
$status = $this->parseStatusLine($meta['wrapper_data'][0]);
$code = $status['code'];
$message = $status['message'];
$headers = array();
foreach (array_slice($meta['wrapper_data'], 1) as $header) {
if (!strpos($header, ':')) {
continue;
}
list($name, $value) = explode(': ', $header, 2);
$headers[$name] = $value;
}
unset($meta['wrapper_data']);
$this->response
->setCode($code)
->setMessage($message)
->setHeaders($headers)
->setMeta($meta);
$body = stream_get_contents($stream);
$type = $this->response->getHeaders('content-type');
foreach ($this->handlers as $expr => $handler) {
if (preg_match('#^' . $expr . '$#i', $type)) {
$handled = call_user_func($handler, $body);
if (!empty($handled)) {
$body = $handled;
}
}
}
$this->response->setContent($body);
}
restore_error_handler();
return $this->response;
}
/**
* Performs a GET request.
*
* @param string $url URL for the request
* @param array $query Optional associative array of parameters
* constituting the URL query string if $url has none
* @param array $context Optional associative array of additional stream
* context parameters
*
* @return Phergie_Plugin_Http_Response Received response data
*/
public function get($url, array $query = array(), array $context = array())
{
if (!empty($query)) {
$url .= '?' . http_build_query($query);
}
$context['method'] = 'GET';
return $this->request($url, $context);
}
/**
* Performs a HEAD request.
*
* @param string $url URL for the request
* @param array $query Optional associative array of parameters
* constituting the URL query string if $url has none
* @param array $context Optional associative array of additional stream
* context parameters
*
* @return Phergie_Plugin_Http_Response Received response data
*/
public function head($url, array $query = array(), array $context = array())
{
if (!empty($query)) {
$url .= '?' . http_build_query($query);
}
$context['method'] = 'HEAD';
return $this->request($url, $context);
}
/**
* Performs a POST request.
*
* @param string $url URL for the request
* @param array $query Optional associative array of parameters
* constituting the URL query string if $url has none
* @param array $post Optional associative array of parameters
* constituting the POST request body if it is using the
* traditional URL-encoded format
* @param array $context Optional associative array of additional stream
* context parameters
*
* @return Phergie_Plugin_Http_Response Received response data
*/
public function post($url, array $query = array(),
array $post = array(), array $context = array()
) {
if (!empty($query)) {
$url .= '?' . http_build_query($query);
}
$context['method'] = 'POST';
if (!empty($post)
&& (!empty($context['header'])
xor stripos($context['header'], 'Content-Type'))
) {
if (!empty($context['header'])) {
$context['header'] .= "\r\n";
} else {
$context['header'] = '';
}
$context['header'] .=
'Content-Type: application/x-www-form-urlencoded';
$context['content'] = http_build_query($post);
}
return $this->request($url, $context);
}
}

View File

@ -0,0 +1,281 @@
<?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_Http
* @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_Http
*/
/**
* Data structure for HTTP response information.
*
* @category Phergie
* @package Phergie_Plugin_Http
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_Http
*/
class Phergie_Plugin_Http_Response
{
/**
* HTTP response code or 0 if no HTTP response was received
*
* @var string
*/
protected $code;
/**
* HTTP response strings
*
* @var array
*/
protected static $codeStrings = array(
0 => 'No Response',
100 => 'Continue',
200 => 'OK',
201 => 'Created',
204 => 'No Content',
206 => 'Partial Content',
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
307 => 'Temporary Redirect',
400 => 'Bad Request',
401 => 'Unauthorized',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
408 => 'Request Timeout',
410 => 'Gone',
413 => 'Request Entity Too Large',
414 => 'Request URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Requested Range Not Satisfiable',
417 => 'Expectation Failed',
500 => 'Internal Server Error',
501 => 'Method Not Implemented',
503 => 'Service Unavailable',
506 => 'Variant Also Negotiates'
);
/**
* Description of the HTTP response code or the error message if no HTTP
* response was received
*
* @var string
*/
protected $message;
/**
* Content of the response body, decoded for supported content types
*
* @var mixed
*/
protected $content;
/**
* Associative array mapping response header names to their values
*
* @var array
*/
protected $headers;
/**
* Associative array containing other metadata about the response
*
* @var array
*/
protected $meta;
/**
* Sets the HTTP response code.
*
* @param string $code Response code
*
* @return Phergie_Plugin_Http_Response Provides a fluent interface
*/
public function setCode($code)
{
$this->code = $code;
return $this;
}
/**
* Returns the HTTP response code.
*
* @return string Response code
*/
public function getCode()
{
return $this->code;
}
/**
* Returns the HTTP response code text.
*
* @return string Response code text
*/
public function getCodeAsString()
{
$code = $this->code;
if (!isset(self::$codeStrings[$code])) {
return 'Unkown HTTP Status';
}
return self::$codeStrings[$code];
}
/**
* Returns whether the response indicates a client- or server-side error.
*
* @return bool TRUE if the response indicates an error, FALSE otherwise
*/
public function isError()
{
switch (substr($this->code, 0, 1)) {
case '0':
case '4':
case '5':
return true;
default:
return false;
}
}
/**
* Sets the HTTP response description.
*
* @param string $message Response description
*
* @return Phergie_Plugin_Http_Response Provides a fluent interface
*/
public function setMessage($message)
{
$this->message = $message;
return $this;
}
/**
* Returns the HTTP response description.
*
* @return string
*/
public function getMessage()
{
return $this->message;
}
/**
* Sets the content of the response body.
*
* @param mixed $content Response body content
*
* @return Phergie_Plugin_Http_Response Provides a fluent interface
*/
public function setContent($content)
{
$this->content = $content;
return $this;
}
/**
* Returns the content of the response body.
*
* @return mixed Response body content, decoded for supported content
* types
*/
public function getContent()
{
return $this->content;
}
/**
* Sets the response headers.
*
* @param array $headers Associative array of response headers indexed
* by header name
*
* @return Phergie_Plugin_Http_Response Provides a fluent interface
*/
public function setHeaders(array $headers)
{
$names = array_map('strtolower', array_keys($headers));
$values = array_values($headers);
$this->headers = array_combine($names, $values);
return $this;
}
/**
* Returns all response headers or the value of a single specified
* response header.
*
* @param string $name Optional name of a single header for which the
* associated value should be returned
*
* @return array|string Associative array of all header values, a string
* containing the value of the header indicated by $name if one
* is set, or null if one is not
*/
public function getHeaders($name = null)
{
if ($name) {
$name = strtolower($name);
if (empty($this->headers[$name])) {
return null;
}
return $this->headers[$name];
}
return $this->headers;
}
/**
* Sets the response metadata.
*
* @param array $meta Associative array of response metadata
*
* @return Phergie_Plugin_Http_Response Provides a fluent interface
*/
public function setMeta(array $meta)
{
$this->meta = $meta;
return $this;
}
/**
* Returns all metadata or the value of a single specified metadatum.
*
* @param string $name Optional name of a single metadatum for which the
* associated value should be returned
*
* @return array|string|null Associative array of all metadata values, a
* string containing the value of the metadatum indicated by
* $name if one is set, or null if one is not
*/
public function getMeta($name = null)
{
if ($name) {
if (empty($this->meta[$name])) {
return null;
}
return $this->meta[$name];
}
return $this->meta;
}
}

View File

@ -0,0 +1,180 @@
<?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_Ideone
* @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_Ideone
*/
/**
* Interfaces with ideone.com to execute code and return the result.
*
* @category Phergie
* @package Phergie_Plugin_Ideone
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_Ideone
* @uses Phergie_Plugin_Command pear.phergie.org
*/
class Phergie_Plugin_Ideone extends Phergie_Plugin_Abstract
{
/**
* Checks for dependencies.
*
* @return void
*/
public function onLoad()
{
$this->plugins->getPlugin('Command');
}
/**
* Checks a service response for an error, sends a notice to the event
* source if an error has occurred, and returns whether an error was found.
*
* @param array $result Associative array representing the service response
*
* @return boolean TRUE if an error is found, FALSE otherwise
*/
protected function isError($result)
{
if ($result['error'] != 'OK') {
$this->doNotice($this->event->getNick(), 'ideone error: ' . $result['error']);
return true;
}
return false;
}
/**
* Executes a source code sequence in a specified language and returns
* the result.
*
* @param string $language Programming language the source code is in
* @param string $code Source code to execute
*
* @return void
*/
public function onCommandIdeone($language, $code)
{
$source = $this->event->getSource();
$nick = $this->event->getNick();
// Get authentication credentials
$user = $this->getConfig('ideone.user', 'test');
$pass = $this->getConfig('ideone.pass', 'test');
// Normalize the command parameters
$language = strtolower($language);
// Massage PHP code to allow for convenient shorthand
if ($language == 'php') {
if (!preg_match('/^<\?(?:php)?/', $code)) {
$code = '<?php ' . $code;
}
switch (substr($code, -1)) {
case '}':
case ';':
break;
default:
$code .= ';';
break;
}
}
// Identify the language to use
$client = new SoapClient('http://ideone.com/api/1/service.wsdl');
$response = $client->getLanguages($user, $pass);
if ($this->isError($response)) {
return;
}
$languageLength = strlen($language);
foreach ($response['languages'] as $languageId => $languageName) {
if (strncasecmp($language, $languageName, $languageLength) == 0) {
break;
}
}
// Send the paste data
$response = $client->createSubmission(
$user,
$pass,
$code,
$languageId,
null, // string input - data from stdin
true, // boolean run - TRUE to execute the code
false // boolean private - FALSE to make the paste public
);
if ($this->isError($response)) {
return;
}
$link = $response['link'];
// Wait until the paste data is processed or the service fails
$attempts = $this->getConfig('ideone.attempts', 10);
foreach (range(1, $attempts) as $attempt) {
$response = $client->getSubmissionStatus($user, $pass, $link);
if ($this->isError($response)) {
return;
}
if ($response['status'] == 0) {
$result = $response['result'];
break;
} else {
$result = null;
sleep(1);
}
}
if ($result == null) {
$this->doNotice($nick, 'ideone error: Timed out');
return;
}
if ($result != 15) {
$this->doNotice($nick, 'ideone error: Status code ' . $result);
return;
}
// Get details for the created paste
$response = $client->getSubmissionDetails(
$user,
$pass,
$link,
false, // boolean withSource - FALSE to not return the source code
false, // boolean withInput - FALSE to not return stdin data
true, // boolean withOutput - TRUE to include output
true, // boolean withStderr - TRUE to return stderr data
false // boolean withCmpinfo - TRUE to return compilation info
);
if ($this->isError($response)) {
return;
}
// Replace the output if it exceeds a specified maximum length
$outputLimit = $this->getConfig('ideone.output_limit', 100);
var_dump($response);
if ($outputLimit && strlen($response['output']) > $outputLimit) {
$response['output'] = 'Output is too long to post';
}
// Format the message
$msg = $this->getConfig('ideone.format', '%nick%: [ %link% ] %output%');
$response['nick'] = $nick;
$response['link'] = 'http://ideone.com/' . $link;
foreach ($response as $key => $value) {
$msg = str_replace('%' . $key . '%', $value, $msg);
}
$this->doPrivmsg($source, $msg);
}
}

View File

@ -0,0 +1,44 @@
<?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_Invisible
* @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_Invisible
*/
/**
* Marks the bot as invisible when it connects to the server.
*
* @category Phergie
* @package Phergie_Plugin_Invisible
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_Invisible
* @link http://irchelp.org/irchelp/rfc/chapter4.html#c4_2_3_2
*/
class Phergie_Plugin_Invisible extends Phergie_Plugin_Abstract
{
/**
* Marks the bot as invisible when it connects to the server.
*
* @return void
*/
public function onConnect()
{
$this->doMode($this->getConnection()->getNick(), '+i');
$this->getPluginHandler()->removePlugin($this);
}
}

View File

@ -0,0 +1,142 @@
<?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
*/
/**
* Implements a filtering iterator for limiting executing of methods across
* a group of plugins.
*
* @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_Plugin_Iterator extends FilterIterator
{
/**
* List of short names of plugins to exclude when iterating
*
* @var array
*/
protected $plugins = array();
/**
* List of method names where plugins with these methods will be
* excluded when iterating
*
* @var array
*/
protected $methods = array();
/**
* Overrides the parent constructor to reset the internal iterator's
* pointer to the current item, which the parent class errantly does not
* do.
*
* @param Iterator $iterator Iterator to filter
*
* @return void
* @link http://bugs.php.net/bug.php?id=52560
*/
public function __construct(Iterator $iterator)
{
parent::__construct($iterator);
$this->rewind();
}
/**
* Adds to a list of plugins to exclude when iterating.
*
* @param mixed $plugins String containing the short name of a single
* plugin to exclude or an array of short names of multiple
* plugins to exclude
*
* @return Phergie_Plugin_Iterator Provides a fluent interface
*/
public function addPluginFilter($plugins)
{
if (is_array($plugins)) {
$this->plugins = array_unique(
array_merge($this->plugins, $plugins)
);
} else {
$this->plugins[] = $plugins;
}
return $this;
}
/**
* Adds to a list of method names where plugins defining these methods
* will be excluded when iterating.
*
* @param mixed $methods String containing the name of a single method
* or an array containing the name of multiple methods
*
* @return Phergie_Plugin_Iterator Provides a fluent interface
*/
public function addMethodFilter($methods)
{
if (is_array($methods)) {
$this->methods = array_merge($this->methods, $methods);
} else {
$this->methods[]= $methods;
}
return $this;
}
/**
* Clears any existing plugin and methods filters.
*
* @return Phergie_Plugin_Iterator Provides a fluent interface
*/
public function clearFilters()
{
$this->plugins = array();
$this->methods = array();
}
/**
* Implements FilterIterator::accept().
*
* @return boolean TRUE to include the current item in those by returned
* during iteration, FALSE otherwise
*/
public function accept()
{
if (!$this->plugins && !$this->methods) {
return true;
}
$current = $this->current();
if (in_array($current->getName(), $this->plugins)) {
return false;
}
foreach ($this->methods as $method) {
if (method_exists($current, $method)) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,56 @@
<?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_Join
* @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_Join
*/
/**
* Joins a specified channel on command from a user.
*
* @category Phergie
* @package Phergie_Plugin_Join
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_Join
* @uses Phergie_Plugin_Command pear.phergie.org
*/
class Phergie_Plugin_Join extends Phergie_Plugin_Abstract
{
/**
* Checks for dependencies.
*
* @return void
*/
public function onLoad()
{
$this->getPluginHandler()->getPlugin('Command');
}
/**
* Joins a channel.
*
* @param string $channels Comma-delimited list of channels to join
* @param string $keys Optional comma-delimited list of channel keys
*
* @return void
*/
public function onCommandJoin($channels, $keys = null)
{
$this->doJoin($channels, $keys);
}
}

View File

@ -0,0 +1,451 @@
<?php
/**
* Phergie
*
* PHP version 5
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.
* It is also available through the world-wide-web at this URL:
* http://phergie.org/license
*
* @category Phergie
* @package Phergie_Plugin_Karma
* @author Phergie Development Team <team@phergie.org>
* @copyright 2008-2010 Phergie Development Team (http://phergie.org)
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_Karma
*/
/**
* Handles requests for incrementation or decrementation of a maintained list
* of counters for specified terms.
*
* @category Phergie
* @package Phergie_Plugin_Karma
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_Karma
* @uses extension PDO
* @uses extension pdo_sqlite
* @uses Phergie_Plugin_Command pear.phergie.org
*/
class Phergie_Plugin_Karma extends Phergie_Plugin_Abstract
{
/**
* SQLite object
*
* @var resource
*/
protected $db = null;
/**
* Prepared statement to add a new karma record
*
* @var PDOStatement
*/
protected $insertKarma;
/**
* Prepared statement to update an existing karma record
*
* @var PDOStatement
*/
protected $updateKarma;
/**
* Retrieves an existing karma record
*
* @var PDOStatement
*/
protected $fetchKarma;
/**
* Retrieves an existing fixed karma record
*
* @var PDOStatement
*/
protected $fetchFixedKarma;
/**
* Retrieves a positive answer for a karma comparison
*
* @var PDOStatement
*/
protected $fetchPositiveAnswer;
/**
* Retrieves a negative answer for a karma comparison
*
* @var PDOStatement
*/
protected $fetchNegativeAnswer;
/**
* Check for dependencies and initializes a database connection and
* prepared statements.
*
* @return void
*/
public function onLoad()
{
$plugins = $this->getPluginHandler();
$plugins->getPlugin('Command');
$this->getDb();
}
/**
* Initializes prepared statements used by the plugin.
*
* @return void
*/
protected function initializePreparedStatements()
{
$this->fetchKarma = $this->db->prepare('
SELECT karma
FROM karmas
WHERE term = :term
LIMIT 1
');
$this->insertKarma = $this->db->prepare('
INSERT INTO karmas (term, karma)
VALUES (:term, :karma)
');
$this->updateKarma = $this->db->prepare('
UPDATE karmas
SET karma = :karma
WHERE term = :term
');
$this->fetchFixedKarma = $this->db->prepare('
SELECT karma
FROM fixed_karmas
WHERE term = :term
LIMIT 1
');
$this->fetchPositiveAnswer = $this->db->prepare('
SELECT answer
FROM positive_answers
ORDER BY RANDOM()
LIMIT 1
');
$this->fetchNegativeAnswer = $this->db->prepare('
SELECT answer
FROM negative_answers
ORDER BY RANDOM()
LIMIT 1
');
}
/**
* Returns a connection to the plugin database, initializing one if none
* is explicitly set.
*
* @return PDO Database connection
*/
public function getDb()
{
if (empty($this->db)) {
$this->db = new PDO('sqlite:' . dirname(__FILE__) . '/Karma/karma.db');
$this->initializePreparedStatements();
}
return $this->db;
}
/**
* Sets the connection to the plugin database, mainly intended for unit
* testing.
*
* @param PDO $db Database connection
*
* @return Phergie_Plugin_Karma Provides a fluent interface
*/
public function setDb(PDO $db)
{
$this->db = $db;
$this->initializePreparedStatements();
return $this;
}
/**
* Get the canonical form of a given term.
*
* In the canonical form all sequences of whitespace
* are replaced by a single space and all characters
* are lowercased.
*
* @param string $term Term for which a canonical form is required
*
* @return string Canonical term
*/
protected function getCanonicalTerm($term)
{
$canonicalTerm = strtolower(preg_replace('|\s+|', ' ', trim($term, '()')));
switch ($canonicalTerm) {
case 'me':
$canonicalTerm = strtolower($this->event->getNick());
break;
case 'all':
case '*':
case 'everything':
$canonicalTerm = 'everything';
break;
}
return $canonicalTerm;
}
/**
* Intercepts a message and processes any contained recognized commands.
*
* @return void
*/
public function onPrivmsg()
{
$message = $this->getEvent()->getText();
$termPattern = '\S+?|\([^<>]+?\)+';
$actionPattern = '(?P<action>\+\+|--)';
$modifyPattern = <<<REGEX
{^
(?J) # allow overwriting capture names
\s* # ignore leading whitespace
(?: # start with ++ or -- before the term
$actionPattern
(?P<term>$termPattern)
| # follow the term with ++ or --
(?P<term>$termPattern)
$actionPattern # allow no whitespace between the term and the action
)
$}ix
REGEX;
$versusPattern = <<<REGEX
{^
(?P<term0>$termPattern)
\s+(?P<method><|>)\s+
(?P<term1>$termPattern)$#
$}ix
REGEX;
$match = null;
if (preg_match($modifyPattern, $message, $match)) {
$action = $match['action'];
$term = $this->getCanonicalTerm($match['term']);
$this->modifyKarma($term, $action);
} elseif (preg_match($versusPattern, $message, $match)) {
$term0 = trim($match['term0']);
$term1 = trim($match['term1']);
$method = $match['method'];
$this->compareKarma($term0, $term1, $method);
}
}
/**
* Get the karma rating for a given term.
*
* @param string $term Term for which the karma rating needs to be
* retrieved
*
* @return void
*/
public function onCommandKarma($term)
{
$source = $this->getEvent()->getSource();
$nick = $this->getEvent()->getNick();
$canonicalTerm = $this->getCanonicalTerm($term);
$fixedKarma = $this->fetchFixedKarma($canonicalTerm);
if ($fixedKarma) {
$message = $nick . ': ' . $term . ' ' . $fixedKarma;
$this->doPrivmsg($source, $message);
return;
}
$karma = $this->fetchKarma($canonicalTerm);
$message = $nick . ': ';
if ($term == 'me') {
$message .= 'You have';
} else {
$message .= $term . ' has';
}
$message .= ' ';
if ($karma) {
$message .= 'karma of ' . $karma;
} else {
$message .= 'neutral karma';
}
$message .= '.';
$this->doPrivmsg($source, $message);
}
/**
* Resets the karma for a term to 0.
*
* @param string $term Term for which to reset the karma rating
*
* @return void
*/
public function onCommandReincarnate($term)
{
$data = array(
':term' => $term,
':karma' => 0
);
$this->updateKarma->execute($data);
}
/**
* Compares the karma between two terms. Optionally increases/decreases
* the karma of either term.
*
* @param string $term0 First term
* @param string $term1 Second term
* @param string $method Comparison method (< or >)
*
* @return void
*/
protected function compareKarma($term0, $term1, $method)
{
$event = $this->getEvent();
$nick = $event->getNick();
$source = $event->getSource();
$canonicalTerm0 = $this->getCanonicalTerm($term0);
$canonicalTerm1 = $this->getCanonicalTerm($term1);
$fixedKarma0 = $this->fetchFixedKarma($canonicalTerm0);
$fixedKarma1 = $this->fetchFixedKarma($canonicalTerm1);
if ($fixedKarma0 || $fixedKarma1) {
return;
}
if ($canonicalTerm0 == 'everything') {
$change = $method == '<' ? '++' : '--';
$karma0 = 0;
$karma1 = $this->modifyKarma($canonicalTerm1, $change);
} elseif ($canonicalTerm1 == 'everything') {
$change = $method == '<' ? '--' : '++';
$karma0 = $this->modifyKarma($canonicalTerm0, $change);
$karma1 = 0;
} else {
$karma0 = $this->fetchKarma($canonicalTerm0);
$karma1 = $this->fetchKarma($canonicalTerm1);
}
// Combining the first and second branches here causes an odd
// single-line lapse in code coverage, but the lapse disappears if
// they're separated
if ($method == '<' && $karma0 < $karma1) {
$replies = $this->fetchPositiveAnswer;
} elseif ($method == '>' && $karma0 > $karma1) {
$replies = $this->fetchPositiveAnswer;
} else {
$replies = $this->fetchNegativeAnswer;
}
$replies->execute();
$reply = $replies->fetchColumn();
if (max($karma0, $karma1) == $karma1) {
list($canonicalTerm0, $canonicalTerm1) =
array($canonicalTerm1, $canonicalTerm0);
}
$message = str_replace(
array('%owner%','%owned%'),
array($canonicalTerm0, $canonicalTerm1),
$reply
);
$this->doPrivmsg($source, $message);
}
/**
* Modifes a term's karma.
*
* @param string $term Term to modify
* @param string $action Karma action (either ++ or --)
*
* @return int Modified karma rating
*/
protected function modifyKarma($term, $action)
{
$karma = $this->fetchKarma($term);
if ($karma !== false) {
$statement = $this->updateKarma;
} else {
$statement = $this->insertKarma;
}
$karma += ($action == '++') ? 1 : -1;
$args = array(
':term' => $term,
':karma' => $karma
);
$statement->execute($args);
return $karma;
}
/**
* Returns the karma rating for a specified term for which the karma
* rating can be modified.
*
* @param string $term Term for which to fetch the corresponding karma
* rating
*
* @return integer|boolean Integer value denoting the term's karma or
* FALSE if there is the specified term has no associated karma
* rating
*/
protected function fetchKarma($term)
{
$this->fetchKarma->execute(array(':term' => $term));
$result = $this->fetchKarma->fetch(PDO::FETCH_ASSOC);
if ($result === false) {
return false;
}
return (int) $result['karma'];
}
/**
* Returns a phrase describing the karma rating for a specified term for
* which the karma rating is fixed.
*
* @param string $term Term for which to fetch the corresponding karma
* rating
*
* @return string Phrase describing the karma rating, which may be append
* to the term to form a complete response
*/
protected function fetchFixedKarma($term)
{
$this->fetchFixedKarma->execute(array(':term' => $term));
$result = $this->fetchFixedKarma->fetch(PDO::FETCH_ASSOC);
if ($result === false) {
return false;
}
return $result['karma'];
}
}

View File

@ -0,0 +1,303 @@
<?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_Lart
* @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_Lart
*/
/**
* Accepts terms and corresponding definitions for storage to a local data
* source and performs and returns the result of lookups for term definitions
* as they are requested.
*
* @category Phergie
* @package Phergie_Plugin_Lart
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_Lart
* @uses Phergie_Plugin_Command pear.phergie.org
* @uses extension PDO
* @uses extension pdo_sqlite
*/
class Phergie_Plugin_Lart extends Phergie_Plugin_Abstract
{
/**
* PDO instance for the database
*
* @var PDO
*/
protected $db;
/**
* Prepared statement for inserting a new definition
*
* @var PDOStatement
*/
protected $save;
/**
* Prepared statement for deleting the definition for a given term
*
* @var PDOStatement
*/
protected $delete;
/**
* Prepared statement for searching for a definition for which the term
* matches as a regular expression against a given search string
*
* @var PDOStatement
*/
protected $process;
/**
* Prepared statement for searching for a definition by its exact term
*
* @var PDOStatement
*/
protected $select;
/**
* Checks for dependencies and initializes the database.
*
* @return void
*/
public function onLoad()
{
if (!extension_loaded('PDO') || !extension_loaded('pdo_sqlite')) {
$this->fail('PDO and pdo_sqlite extensions must be installed');
}
$this->plugins->getPlugin('Command');
$dir = dirname(__FILE__) . '/' . $this->getName();
$path = $dir . '/lart.db';
$exists = file_exists($path);
if (!$exists) {
mkdir($dir);
}
try {
$this->db = new PDO('sqlite:' . $path);
} catch (PDO_Exception $e) {
throw new Phergie_Plugin_Exception($e->getMessage());
}
$this->db->sqliteCreateFunction('preg_match', 'preg_match');
if (!$exists) {
$this->db->exec('
CREATE TABLE lart (
name VARCHAR(255),
definition TEXT,
hostmask VARCHAR(50),
tstamp VARCHAR(19)
)
');
$this->db->exec('
CREATE UNIQUE INDEX lart_name ON lart (name)
');
}
$this->save = $this->db->prepare('
REPLACE INTO lart (name, definition, hostmask, tstamp)
VALUES (:name, :definition, :hostmask, :tstamp)
');
$this->process = $this->db->prepare('
SELECT *
FROM lart
WHERE preg_match(name, :name)
');
$this->select = $this->db->prepare('
SELECT *
FROM lart
WHERE name = :name
');
$this->delete = $this->db->prepare('
DELETE FROM lart
WHERE name = :name
');
}
/**
* Retrieves the definition for a given term if it exists.
*
* @param string $term Term to search for
*
* @return mixed String containing the definition or FALSE if no definition
* exists
*/
protected function getLart($term)
{
$this->process->execute(array(':name' => $term));
$row = $this->process->fetchObject();
if ($row === false) {
return false;
}
preg_match($row->name, $term, $match);
$definition = preg_replace(
"/(?:\\\\|\\$)([0-9]+)/e",
'$match[\1]',
$row->definition
);
$event = $this->getEvent();
$definition = str_replace(
array('$source', '$nick'),
array($event->getSource(), $event->getNick()),
$definition
);
return $definition;
}
/**
* Deletes a given definition.
*
* @param string $term Term for which the definition should be deleted
*
* @return boolean TRUE if the definition was found and deleted, FALSE
* otherwise
*/
protected function deleteLart($term)
{
$this->delete->execute(array(':name' => $term));
return ($this->delete->rowCount() > 0);
}
/**
* Saves a given definition.
*
* @param string $term Term to trigger a response containing the
* corresponding definition, may be a regular expression
* @param string $definition Definition corresponding to the term
*
* @return boolean TRUE if the definition was saved successfully, FALSE
* otherwise
*/
protected function saveLart($term, $definition)
{
$data = array(
':name' => $term,
':definition' => $definition,
':hostmask' => (string) $this->getEvent()->getHostmask(),
':tstamp' => time()
);
$this->save->execute($data);
return ($this->save->rowCount() > 0);
}
/**
* Returns information about a definition.
*
* @param string $term Term about which to return information
*
* @return void
*/
public function onCommandLartinfo($term)
{
$this->select->execute(array(':name' => $term));
$row = $this->select->fetchObject();
$msg = $this->getEvent()->getNick() . ': ';
if (!$row) {
$msg .= 'Lart not found';
} else {
$msg .= 'Term: ' . $row->name
. ', Definition: ' . $row->definition
. ', User: ' . $row->hostmask
. ', Added: ' . date('n/j/y g:i A', $row->tstamp);
}
$this->doNotice($this->getEvent()->getSource(), $msg);
}
/**
* Creates a new definition.
*
* @param string $term Term to add
* @param string $definition Definition to add
*
* @return void
*/
public function onCommandAddlart($term, $definition)
{
$result = $this->saveLart($term, $definition);
if ($result) {
$msg = 'Lart saved successfully';
} else {
$msg = 'Lart could not be saved';
}
$this->doNotice($this->getEvent()->getSource(), $msg);
}
/**
* Removes an existing definition.
*
* @param string $term Term for which the definition should be removed
*
* @return void
*/
public function onCommandDeletelart($term)
{
$source = $this->getEvent()->getSource();
if ($this->deleteLart($term)) {
$msg = 'Lart deleted successfully';
} else {
$msg = 'Lart not found';
}
$this->doNotice($source, $msg);
}
/**
* Processes definition triggers in the text of the current event.
*
* @return void
*/
protected function processLart()
{
$lart = $this->getLart($this->getEvent()->getText());
if ($lart) {
if (strpos($lart, '/me') === 0) {
$lart = substr($lart, 4);
$method = 'doAction';
} else {
$method = 'doPrivmsg';
}
$this->$method($this->getEvent()->getSource(), $lart);
}
}
/**
* Processes definition triggers in messages.
*
* @return void
*/
public function onPrivmsg()
{
$this->processLart();
}
/**
* Processes definition triggers in CTCP actions.
*
* @return void
*/
public function onAction()
{
$this->processLart();
}
}

View File

@ -0,0 +1,112 @@
<?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_Message
* @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_Message
*/
/**
* Generalized plugin providing utility methods for
* prefix and bot named based message extraction.
*
* @category Phergie
* @package Phergie_Plugin_Message
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_Message
*/
class Phergie_Plugin_Message extends Phergie_Plugin_Abstract
{
/**
* Check whether a message is specifically targeted at the bot.
* This is the case when the message starts with the bot's name
* followed by [,:>] or when it is a private message.
*
* @return boolean true when the message is specifically targeted at the bot,
* false otherwise.
*/
public function isTargetedMessage()
{
$event = $this->getEvent();
$self = preg_quote($this->connection->getNick());
$targetPattern = <<<REGEX
{^
\s*{$self}\s*[:>,].* # expect the bots name, followed by a [:>,]
$}ix
REGEX;
return !$event->isInChannel()
|| preg_match($targetPattern, $event->getText()) > 0;
}
/**
* Allow for prefix and bot name aware extraction of a message
*
* @return string|bool $message The message, which is possibly targeted at the
* bot or false if a prefix requirement failed
*/
public function getMessage()
{
$event = $this->getEvent();
$prefix = preg_quote($this->getConfig('command.prefix'));
$self = preg_quote($this->connection->getNick());
$message = $event->getText();
// $prefixPattern matches : Phergie, do command <parameters>
// where $prefix = 'do' : do command <parameters>
// : Phergie, command <parameters>
$prefixPattern = <<<REGEX
{^
(?:
\s*{$self}\s*[:>,]\s* # start with bot name
(?:{$prefix})? # which is optionally followed by the prefix
|
\s*{$prefix} # or start with the prefix
)
\s*(.*) # always end with the message
$}ix
REGEX;
// $noPrefixPattern matches : Phergie, command <parameters>
// : command <parameters>
$noPrefixPattern = <<<REGEX
{^
\s*(?:{$self}\s*[:>,]\s*)? # optionally start with the bot name
(.*?) # always end with the message
$}ix
REGEX;
$pattern = $noPrefixPattern;
// If a prefix is set, force it as a requirement
if ($prefix && $event->isInChannel()) {
$pattern = $prefixPattern;
}
$match = null;
if (!preg_match($pattern, $message, $match)) {
return false;
}
return $match[1];
}
}

View File

@ -0,0 +1,178 @@
<?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_NickServ
* @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_NickServ
*/
/**
* Intercepts and responds to messages from the NickServ agent requesting that
* the bot authenticate its identify.
*
* The password configuration setting should contain the password registered
* with NickServ for the nick used by the bot.
*
* @category Phergie
* @package Phergie_Plugin_NickServ
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_NickServ
* @uses Phergie_Plugin_Command pear.phergie.org
*/
class Phergie_Plugin_NickServ extends Phergie_Plugin_Abstract
{
/**
* Nick of the NickServ bot
*
* @var string
*/
protected $botNick;
/**
* Identify message
*/
protected $identifyMessage;
/**
* Checks for dependencies and required configuration settings.
*
* @return void
*/
public function onLoad()
{
$this->getPluginHandler()->getPlugin('Command');
// Get the name of the NickServ bot, defaults to NickServ
$this->botNick = $this->getConfig('nickserv.botnick', 'NickServ');
// Get the identify message
$this->identifyMessage = $this->getConfig(
'nickserv.identify_message',
'/This nickname is registered./'
);
}
/**
* Checks for a notice from NickServ and responds accordingly if it is an
* authentication request or a notice that a ghost connection has been
* killed.
*
* @return void
*/
public function onNotice()
{
$event = $this->event;
if (strtolower($event->getNick()) == strtolower($this->botNick)) {
$message = $event->getArgument(1);
$nick = $this->connection->getNick();
if (preg_match($this->identifyMessage, $message)) {
$password = $this->config['nickserv.password'];
if (!empty($password)) {
$this->doPrivmsg($this->botNick, 'IDENTIFY ' . $password);
}
unset($password);
} elseif (preg_match('/^.*' . $nick . '.* has been killed/', $message)) {
$this->doNick($nick);
}
}
}
/**
* Checks to see if the original nick has quit; if so, take the name back.
*
* @return void
*/
public function onQuit()
{
$eventNick = $this->event->getNick();
$nick = $this->connection->getNick();
if ($eventNick == $nick) {
$this->doNick($nick);
}
}
/**
* Changes the in-memory configuration setting for the bot nick if it is
* successfully changed.
*
* @return void
*/
public function onNick()
{
$event = $this->event;
$connection = $this->connection;
if ($event->getNick() == $connection->getNick()) {
$connection->setNick($event->getArgument(0));
}
}
/**
* Provides a command to terminate ghost connections.
*
* @return void
*/
public function onCommandGhostbust()
{
$event = $this->event;
$user = $event->getNick();
$conn = $this->connection;
$nick = $conn->getNick();
if ($nick != $this->config['connections'][$conn->getHost()]['nick']) {
$password = $this->config['nickserv.password'];
if (!empty($password)) {
$this->doPrivmsg(
$this->event->getSource(),
$user . ': Attempting to ghost ' . $nick .'.'
);
$this->doPrivmsg(
$this->botNick,
'GHOST ' . $nick . ' ' . $password,
true
);
}
}
}
/**
* Automatically send the GHOST command if the bot's nick is in use.
*
* @return void
*/
public function onResponse()
{
if ($this->event->getCode() == Phergie_Event_Response::ERR_NICKNAMEINUSE) {
$password = $this->config['nickserv.password'];
if (!empty($password)) {
$this->doPrivmsg(
$this->botNick,
'GHOST ' . $this->connection->getNick() . ' ' . $password
);
}
}
}
/**
* Handle the server sending a KILL request.
*
* @return void
*/
public function onKill()
{
$this->doQuit($this->event->getArgument(1));
}
}

View File

@ -0,0 +1,55 @@
<?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_Part
* @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_Part
*/
/**
* Parts a specified channel on command from a user.
*
* @category Phergie
* @package Phergie_Plugin_Part
* @author Phergie Development Team <team@phergie.org>
* @license http://phergie.org/license New BSD License
* @link http://pear.phergie.org/package/Phergie_Plugin_Part
* @uses Phergie_Plugin_Command pear.phergie.org
*/
class Phergie_Plugin_Part extends Phergie_Plugin_Abstract
{
/**
* Checks for dependencies.
*
* @return void
*/
public function onLoad()
{
$this->getPluginHandler()->getPlugin('Command');
}
/**
* Parts a channel.
*
* @param string $channels Comma-delimited list of channels to leave
*
* @return void
*/
public function onCommandPart($channels)
{
$this->doPart($channels);
}
}

View File

@ -0,0 +1,78 @@
<?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);
}
}
}

View File

@ -0,0 +1,46 @@
<?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);
}

View File

@ -0,0 +1,208 @@
<?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);
}
}
}

View File

@ -0,0 +1,162 @@
<?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;
}
}

View File

@ -0,0 +1,44 @@
<?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));
}
}

View File

@ -0,0 +1,99 @@
<?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);
}
}

View File

@ -0,0 +1,89 @@
<?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.
*
* <code>say #chan message</code>
*
* @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.
*
* <code>act #chan action</code>
*
* @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
*
* <code>raw message</code>
*
* @param string $message Message to send
*
* @return void
*/
public function onCommandRaw($message)
{
$this->doRaw($message);
}
}

View File

@ -0,0 +1,54 @@
<?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());
}
}

View File

@ -0,0 +1,122 @@
<?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;
}
}

View File

@ -0,0 +1,378 @@
<?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'];
}
}
}

View File

@ -0,0 +1,160 @@
<?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);
}
}

View File

@ -0,0 +1,118 @@
<?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);
}
}

View File

@ -0,0 +1,143 @@
<?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);
}
}
}
}

View File

@ -0,0 +1,69 @@
<?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
);
}
}

View File

@ -0,0 +1,49 @@
<?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);

View File

@ -0,0 +1,81 @@
<?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)
);
}
}

View File

@ -0,0 +1,94 @@
<?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);
}
}
}
}

View File

@ -0,0 +1,144 @@
<?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(
'#<div><span class="small">(.*?)<\/span><\/div>#im',
$content, $matches
);
$location = $matches[1][0];
if (!empty($location)) {
preg_match_all(
'#<div class="large" >(.*?)<br \/>#im',
$content, $matches
);
$temp_numb = (int) $matches[1][0];
$temp_numb .= ' F / ' . round(($temp_numb - 32) / 1.8, 0) . ' C?!';
preg_match_all(
'#<br \/>(.*?)<\/div><div id="remark"><br \/>#im',
$content, $matches
);
$temp_desc = $matches[1][0];
preg_match_all(
'#<div id="remark"><br \/>\n<span>(.*?)<\/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.';
}
}
}

View File

@ -0,0 +1,72 @@
<?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);
}
}

View File

@ -0,0 +1,148 @@
<?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;
}
}

View File

@ -0,0 +1,68 @@
<?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);
}

View File

@ -0,0 +1,206 @@
<?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;
}
}

View File

@ -0,0 +1,41 @@
<?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);
}
}

View File

@ -0,0 +1,287 @@
<?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';
}
}

View File

@ -0,0 +1,638 @@
<?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;
}
}

View File

@ -0,0 +1,105 @@
<?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();
}
}

View File

@ -0,0 +1,64 @@
<?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();
}
}

View File

@ -0,0 +1,413 @@
<?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;
}
}

View File

@ -0,0 +1,149 @@
<?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);
}
}

View File

@ -0,0 +1,69 @@
<?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
);
}
}

View File

@ -0,0 +1,53 @@
<?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);

View File

@ -0,0 +1,168 @@
<?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;
}
}

View File

@ -0,0 +1,130 @@
<?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();
}

View File

@ -0,0 +1,157 @@
<?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();
}
}

View File

@ -0,0 +1,33 @@
<?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
{
}

View File

@ -0,0 +1,57 @@
<?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);
}
}
}

Some files were not shown because too many files have changed in this diff Show More