Changeset 60

Show
Ignore:
Timestamp:
02/17/08 04:25:26 (5 years ago)
Author:
tobias382
Message:

Fixes #1, #8, #14, #10, #13
* Fixed some parameter parsing issues in the streams driver and Command plugin
* Fixed an issue in the streams driver where quitting would not cause the

connection to be terminated on the client side

* Added automated directory creation to the event handler class
* Added isInChannel() and TYPE_KICK to request event class
* Modified the bootstrap file to use a full path when referencing the Plugin

directory and to include plugin loading debugging messages

* Fixed a bug in the bootstrap file where it would not produce an error when

required configuration settings existed, but were empty

* Cleared default values and unused settings out of the config file
* Made modifications to the fromAdmin method in the AdminCommand? plugin to

optimize performance

* Fixed a bug in the parsing logic in the Acronym plugin and added a check for

instances where the per-IP bandwidth limit has been executed

* Added a check for cases where a server has no MOTD to the Autojoin plugin
* Modified the Drink plugin to filter coffee drinks when scraping data for the

coke table and to remove the profanity filter array from memory once the
init method is done using it

* Added an avogadro alias for the mole fixed karma and added support for

configurable fixed karma for the bot

* Modified the Logging plugin to extend the Command plugin and to use PDO in

place of the standalone SQLite driver

* Modified the Php plugin to use full URLs to function pages rather than the

shorthand version, which does not always resolve to the expected URL

* Renamed the Say plugin to Puppet and added support emulation of CTCP ACTION

commands

* Modified the Url plugin to use the MIME type of the resource in place of

the title in cases where it is not HTML-based

* Fixed an issue with the parsing logic in the onMode handler of the Users

plugin

* Uncommented the onPrivmsg handler and modified it to use the new debug

configuration setting

* Added alternate nick support to the Nickserv plugin
* Removed the CONTRIBUTE file, as its fairly out of date

Location:
trunk/Phergie
Files:
1 removed
23 modified
1 moved

Legend:

Unmodified
Added
Removed
  • trunk/Phergie/Bot.php

    r59 r60  
    3030 
    3131/** 
     32* Path to the directory containing the Phergie directory 
     33* 
     34* @const string 
     35*/ 
     36define('PHERGIE_DIR', dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR); 
     37 
     38/** 
    3239* Add the Phergie directory to the include path 
    3340*/ 
     
    3542    get_include_path() 
    3643    . PATH_SEPARATOR . 
    37     dirname(dirname(__FILE__)) 
     44    PHERGIE_DIR 
    3845); 
    3946 
     
    6269*/ 
    6370$required = array('server', 'username', 'nick'); 
    64 $config = @parse_ini_file($ini); 
     71$config = @parse_ini_file(PHERGIE_DIR . 'Phergie' . DIRECTORY_SEPARATOR . $ini); 
    6572 
    6673if (count($config) == 0) { 
     
    7077 
    7178foreach ($required as $setting) { 
    72     if (!isset($config[$setting])) { 
     79    if (!isset($config[$setting]) || empty($config[$setting])) { 
    7380        echo 'Required configuration setting missing: ' . $setting . "\n"; 
    7481        return; 
     
    113120* Set up event handlers 
    114121*/ 
    115 $iterator = new DirectoryIterator('Plugin'); 
     122$iterator = new DirectoryIterator(PHERGIE_DIR . '/Phergie/Plugin'); 
    116123foreach ($iterator as $entry) { 
    117124    if ($iterator->isFile() 
     
    120127        require_once 'Phergie/Plugin/' . $entry; 
    121128        $class = 'Phergie_Plugin_' . str_replace('.php', '', $entry); 
    122         $client->addEventHandler(new $class($client)); 
     129        $instance = new $class($client); 
     130        $client->addEventHandler($instance); 
     131        $client->debug('Loaded ' . $instance->getName()); 
    123132    } 
    124133} 
  • trunk/Phergie/Driver/Abstract.php

    r59 r60  
    7676    { 
    7777        if ($this->getIni('debug')) { 
    78             echo date('H:i:s') . ' driver ' . $message . "\n"; 
     78            echo date('H:i:s') . ' ' . $message . "\n"; 
    7979        } 
    8080    } 
  • trunk/Phergie/Driver/Streams.php

    r59 r60  
    152152 
    153153            switch ($cmd) { 
     154                case 'names': 
     155                case 'nick': 
     156                case 'quit': 
     157                case 'ping': 
     158                case 'join': 
     159                    $args = array(ltrim($args, ':')); 
     160                    break; 
     161                case 'notice': 
     162                    $args = explode(' :', $args); 
     163                    break; 
    154164                case 'oper': 
    155165                case 'topic': 
    156166                case 'mode': 
    157                     $args = explode(' ', $args); 
    158                     break; 
    159                 case 'join': 
    160                     $args = explode(' ', $args); 
    161                     $args[0] = explode(',', $args[0]); 
    162                     if (isset ($args[1])) { 
    163                         $args[1] = explode(',', $args[1]); 
    164                     } 
     167                    $args = preg_split('/ :?/', $args); 
    165168                    break; 
    166169                case 'part': 
    167                 case 'names': 
    168                     $args = array(explode(',', $args)); 
    169                     break; 
    170                 case 'nick': 
    171                 case 'quit': 
    172                 case 'ping': 
    173                     $args = array(ltrim($args, ':')); 
    174                     break; 
    175                 case 'notice': 
    176                     $args = explode(' :', $args); 
     170                    $args = preg_split('/ :?/', $args, 2); 
     171                    break; 
     172                case 'kick': 
     173                    $args = preg_split('/ :?/', $args, 3); 
    177174                    break; 
    178175                case 'privmsg': 
     
    188185                    } else { 
    189186                        $args = explode(' :', $args); 
    190                         $args[0] = explode(',', $args[0]); 
    191187                    } 
    192188                    break; 
     
    234230                } 
    235231                $this->doQuit($reason); 
     232                break; 
    236233            } 
    237234            unset($this->queue, $event, $command, $arguments); 
  • trunk/Phergie/Event/Handler.php

    r59 r60  
    2323{ 
    2424    /** 
     25    * Flag indicating whether or not the plugin requires its own directory 
     26    * for local storage 
     27    * 
     28    * @see $dir 
     29    * @var bool 
     30    */ 
     31    protected $needsDir = false; 
     32 
     33    /** 
     34    * Path to the directory for the plugin if needsDir is enabled 
     35    * 
     36    * @see $needsDir 
     37    */ 
     38    protected $dir; 
     39 
     40    /** 
    2541    * Reference back to the client used to initiate commands 
    2642    * 
     
    5571        $name = get_class($this); 
    5672        $this->name = substr($name, strrpos($name, '_') + 1); 
     73 
     74        if ($this->needsDir) { 
     75            $class = new ReflectionClass($name); 
     76            $dir = 
     77                dirname($class->getFilename()) . 
     78                DIRECTORY_SEPARATOR . 
     79                $this->name . 
     80                DIRECTORY_SEPARATOR; 
     81            if (!file_exists($dir)) { 
     82                mkdir($dir); 
     83            } 
     84            $this->dir = $dir; 
     85        } 
    5786    } 
    5887 
     
    123152    public function debug($message) 
    124153    { 
    125         $this->client->debug(date('H:i:s') . ' ' . $this->name . ' -> ' . $message); 
     154        $this->client->debug(strtolower($this->name) . ' -> ' . $message); 
    126155    } 
    127156 
  • trunk/Phergie/Event/Request.php

    r59 r60  
    1010    /** 
    1111    * Password message 
     12    * 
     13    * @const string 
    1214    */ 
    1315    const TYPE_PASS = 'pass'; 
     
    1517    /** 
    1618    * Nick message 
     19    * 
     20    * @const string 
    1721    */ 
    1822    const TYPE_NICK = 'nick'; 
     
    2024    /** 
    2125    * User message 
     26    * 
     27    * @const string 
    2228    */ 
    2329    const TYPE_USER = 'user'; 
     
    2531    /** 
    2632    * Operator command 
     33    * 
     34    * @const string 
    2735    */ 
    2836    const TYPE_OPER = 'oper'; 
     
    3038    /** 
    3139    * Quit command 
     40    * 
     41    * @const string 
    3242    */ 
    3343    const TYPE_QUIT = 'quit'; 
     
    3545    /** 
    3646    * Join message 
     47    * 
     48    * @const string 
    3749    */ 
    3850    const TYPE_JOIN = 'join'; 
    3951 
    4052    /** 
     53    * Kick message 
     54    * 
     55    * @const string 
     56    */ 
     57    const TYPE_KICK = 'kick'; 
     58 
     59    /** 
    4160    * Part message 
     61    * 
     62    * @const string 
    4263    */ 
    4364    const TYPE_PART = 'part'; 
     
    4566    /** 
    4667    * Mode message 
     68    * 
     69    * @const string 
    4770    */ 
    4871    const TYPE_MODE = 'mode'; 
     
    5073    /** 
    5174    * Topic message 
     75    * 
     76    * @const string 
    5277    */ 
    5378    const TYPE_TOPIC = 'topic'; 
     
    5580    /** 
    5681    * Private message command 
     82    * 
     83    * @const string 
    5784    */ 
    5885    const TYPE_PRIVMSG = 'privmsg'; 
     
    6087    /** 
    6188    * Notice message 
     89    * 
     90    * @const string 
    6291    */ 
    6392    const TYPE_NOTICE = 'notice'; 
     
    6594    /** 
    6695    * Pong message 
     96    * 
     97    * @const string 
    6798    */ 
    6899    const TYPE_PONG = 'pong'; 
     
    70101    /** 
    71102    * CTCP ACTION command 
     103    * 
     104    * @const string 
    72105    */ 
    73106    const TYPE_ACTION = 'action'; 
     
    249282    public function getSource() 
    250283    { 
    251         if ($this->type == Phergie_Event_Request::TYPE_PRIVMSG 
    252             && $this->arguments[0][0] == '#') { 
     284        if ($this->arguments[0][0] == '#') { 
    253285            return $this->arguments[0]; 
    254286        } 
     
    257289 
    258290    /** 
     291    * Returns whether or not the event occurred within a channel. 
     292    * 
     293    * @return TRUE if the event is in a channel, FALSE otherwise 
     294    */ 
     295    public function isInChannel() 
     296    { 
     297        return (substr($this->getSource(), 0, 1) == '#'); 
     298    } 
     299 
     300    /** 
    259301    * Returns whether or not the event originated from a user. 
    260302    * 
  • trunk/Phergie/Plugin/Abstract/AdminCommand.php

    r59 r60  
    8282            $ini = $this->getIni('admins', 'admincommand'); 
    8383        } 
    84         $admins = preg_split('#[\s\r\n,]+#', $ini); 
    85         foreach ($admins as $admin) { 
    86             // Find out which chars are present in the config mask and exclude them from the regex match 
    87             $excluded = ''; 
    88             if (strpos($admin, '!')!==false) { 
    89                 $excluded .= '!'; 
     84        if (!empty ($ini)) { 
     85            $admins = preg_split('#[\s\r\n,]+#', $ini); 
     86            foreach ($admins as $admin) { 
     87                // Find out which chars are present in the config mask and exclude them from the regex match 
     88                $excluded = ''; 
     89                if (strpos($admin, '!')!==false) { 
     90                    $excluded .= '!'; 
     91                } 
     92                if (strpos($admin, '@')!==false) { 
     93                    $excluded .= '@'; 
     94                } 
     95 
     96                // Escape regex meta characters 
     97                $admin = str_replace( 
     98                    array('\\', '^', '$', '.', '[', ']', '|', '(', ')', '?', '+', '{', '}'), 
     99                    array('\\\\', '\\^', '\\$', '\\.', '\\[', '\\]', '\\|', '\\(', '\\)', '\\?', '\\+', '\\{', '\\}'), 
     100                    $admin 
     101                ); 
     102                // Replace * so that they match correctly in a regex 
     103                $this->admins[$class][] = str_replace('*', ($excluded === '' ? '.*' : '[^'.$excluded.']*'), $admin); 
    90104            } 
    91             if (strpos($admin, '@')!==false) { 
    92                 $excluded .= '@'; 
    93             } 
    94  
    95             // Escape regex meta characters 
    96             $admin = str_replace( 
    97                 array('\\', '^', '$', '.', '[', ']', '|', '(', ')', '?', '+', '{', '}'), 
    98                 array('\\\\', '\\^', '\\$', '\\.', '\\[', '\\]', '\\|', '\\(', '\\)', '\\?', '\\+', '\\{', '\\}'), 
    99                 $admin 
    100             ); 
    101             // Replace * so that they match correctly in a regex 
    102             $this->admins[$class][] = str_replace('*', ($excluded === '' ? '.*' : '[^'.$excluded.']*'), $admin); 
     105            $this->admins[$class] = '/^' . implode('|', $this->admins[$class]) . '$/'; 
    103106        } 
    104107    } 
     
    114117    { 
    115118        $class = $this->getName(); 
    116         $nick = $this->event->getSource(); 
    117         $mask = $this->event->getHostmask(); 
     119        // Try to match mask against admin masks 
     120        if (isset ($this->admins[$class]) 
     121            && preg_match($this->admins[$class], $this->event->getHostmask())) { 
     122            return true; 
     123        } 
    118124        // Check if is op and ops are admins 
    119         if ($this->ops[$class]) { 
    120             foreach (Phergie_Plugin_Users::getChannels($nick) as $chan) { 
    121                 if (Phergie_Plugin_Users::isOp($nick, $chan)) { 
    122                     return true; 
    123                 } 
    124             } 
    125         } 
    126         // Try to match mask against admin masks 
    127         if (isset ($this->admins[$class])) { 
    128             return in_array($mask, $this->admins[$class]); 
     125        $nick = $this->event->getNick(); 
     126        $source = $this->event->getSource(); 
     127        if ($this->ops[$class] && Phergie_Plugin_Users::isOp($nick, $source)) { 
     128            return true; 
    129129        } 
    130130        return false; 
     
    141141        if ($this->fromAdmin()) { 
    142142            $target = $this->event->getArgument(0); 
    143             $exp = $target[0][0] == '#' ? 
     143            $exp = $target[0] == '#' ? 
    144144                '/^' . $this->getIni('nick') . '\s*:?\s+(.*)$/' : 
    145145                '/^(?:' . $this->getIni('nick') . '\s*:?\s+)?(.*)$/'; 
  • trunk/Phergie/Plugin/Abstract/Command.php

    r59 r60  
    4545                $name = $method->getName(); 
    4646                if (strpos($name, 'onDo') === 0) { 
    47                     $this->methods[substr($name, 4)] = $method->getNumberOfRequiredParameters(); 
     47                    $this->methods[strtolower(substr($name, 4))] = 
     48                        $method->getNumberOfRequiredParameters(); 
    4849                } 
    4950            } 
     
    6061                    $this->$method(); 
    6162                } 
    62             } elseif ($this->methods[$command] <= count($params)) { 
     63            } else { 
    6364                $params = preg_split('/\s+/', $params, $this->methods[$command]); 
    64                 call_user_func_array(array($this, $method), $params); 
     65                if ($this->methods[$command] <= count($params)) { 
     66                    call_user_func_array(array($this, $method), $params); 
     67                } 
    6568            } 
    6669        } 
  • trunk/Phergie/Plugin/Acronym.php

    r59 r60  
    4646    { 
    4747        $limit = $this->getIni('limit'); 
    48         if ($limit < 0) { 
     48        if ($limit < 0 || $limit === null) { 
    4949            $this->limit = 5; 
    5050        } else { 
     
    6060    * @return void 
    6161    */ 
    62     protected function _randomAction($target) 
     62    protected function randomAction($target) 
    6363    { 
    6464        do { 
     
    8787        $message = $this->event->getArgument(1); 
    8888 
    89         if (!preg_match('/^(?:[^\s:]+\s*:?\s*)?((?:[A-Z]\.?){2,})\?$/', $message, $acronym)) { 
     89        if (!preg_match('/((?:[A-Z]\.?){2,})\?/', $message, $acronym)) { 
    9090            return; 
    9191        } 
     
    110110        $contents = @file_get_contents($url, false, $context); 
    111111 
    112         if (empty($contents)) { 
     112        if (empty ($contents) 
     113            || strpos($contents, 'no abbreviation matches') !== false 
     114            || strpos($contents, 'has exceeded the daily query limit') !== false) { 
    113115            $this->randomAction($target); 
    114116        } else { 
     
    130132            } while (($this->limit == 0 || count($matches) < $this->limit) && $count == 1); 
    131133 
    132             if (count($matches) == 0) { 
    133                 $this->randomAction($target); 
    134             } else { 
    135                 $text = 'Possible matches for ' . $acronym . ': ' . implode(', ', $matches); 
    136                 $this->doPrivmsg($target, $text); 
    137             } 
     134            $text = 'Possible matches for ' . $acronym . ': ' . implode(', ', $matches); 
     135            $this->doPrivmsg($target, $text); 
    138136        } 
    139137    } 
  • trunk/Phergie/Plugin/Autojoin.php

    r59 r60  
    2323    public function onResponse() 
    2424    { 
    25         if ($this->event->getCode() == Phergie_Event_Response::RPL_ENDOFMOTD) { 
    26             $channels = $this->getIni('channels'); 
    27             if (!empty ($channels)) { 
    28                 $this->doJoin(implode(' ', preg_split('#[, ]+#', $channels))); 
    29             } 
     25        switch ($this->event->getCode()) { 
     26            case Phergie_Event_Response::RPL_ENDOFMOTD: 
     27            case Phergie_Event_Response::ERR_NOMOTD: 
     28                $channels = $this->getIni('channels'); 
     29                if (!empty ($channels)) { 
     30                    $this->doJoin(implode(' ', preg_split('#[, ]+#', $channels))); 
     31                } 
     32                break; 
    3033        } 
    3134    } 
  • trunk/Phergie/Plugin/Daddy.php

    r59 r60  
    2222        $bot = $this->getIni('nick'); 
    2323        $text = $this->event->getArgument(1); 
    24         if (preg_match('/' . $bot . '\s*:?\s+?who\'?s your daddy\\??/iAD', $text)) { 
    25             $this->doPrivmsg($this->getSource(), 'You\'re my daddy, ' . $this->event->getNick() . '!'); 
     24        if (preg_match('/' . $bot . '\s*:?\s+?who\'?s your daddy\??/iAD', $text)) { 
     25            $this->doPrivmsg($this->event->getSource(), 'You\'re my daddy, ' . $this->event->getNick() . '!'); 
    2626        } 
    2727    } 
  • trunk/Phergie/Plugin/Debug.php

    r59 r60  
    2121    { 
    2222        $text = 'current : '.number_format(memory_get_usage() / 1024).'KB / peak : '.number_format(memory_get_peak_usage() / 1024).'KB'; 
    23         $this->doPrivmsg($this->getSource(), $text); 
     23        $this->doPrivmsg($this->event->getSource(), $text); 
    2424    } 
    2525} 
  • trunk/Phergie/Plugin/Drink.php

    r59 r60  
    1818class Phergie_Plugin_Drink extends Phergie_Plugin_Abstract_Command 
    1919{ 
     20    /** 
     21    * Indicates that a local directory is required for this plugin 
     22    * 
     23    * @var bool 
     24    */ 
     25    protected $needsDir = true; 
     26 
    2027    /** 
    2128    * PDO resource for a SQLite database containing the drinks 
     
    7683 
    7784    /** 
    78     * Determines if a table does not exist or is empty. 
    79     * 
    80     * @param string $name Table name 
    81     * @return bool TRUE if the table does not exist or is empty, FALSE 
    82     *              otherwise 
    83     */ 
    84     private function needTable($name) 
    85     { 
    86         $table = $this->db 
    87             ->query('SELECT COUNT(*) FROM sqlite_master WHERE name = ' . $this->db->quote($name)) 
    88             ->fetchColumn(); 
    89  
    90         if (!$table) { 
    91             return true; 
    92         } 
    93  
    94         return !$this->db 
    95             ->query('SELECT COUNT(*) FROM ' . $name) 
    96             ->fetchColumn(); 
    97     } 
    98  
    99     /** 
    100     * Populates a source database table with a given set of data. 
    101     * 
    102     * @param string $table Name of the table 
    103     * @param array $names List of drink names 
    104     * @return void 
    105     */ 
    106     private function populateTable($table, $names) 
    107     { 
    108         $this->db->exec(' 
    109             CREATE TABLE ' . $name . ' ( name VARCHAR(255) ); 
    110             CREATE UNIQUE INDEX ' . $name . ' ON ' . $name . ' (name); 
    111         '); 
    112  
    113         $stmt = $this->db->prepare('INSERT INTO ' . $table . ' (name) VALUES (:name)'); 
    114         $this->db->beginTransaction(); 
    115         foreach (array_unique($names) as $name) { 
    116             foreach ($this->filter as $filter) { 
    117                 if (preg_match('/(^|[^a-z])' . $filter . '([^a-z]|$)/i', $name)) { 
    118                     $this->debug('Filtered out ' . $name . ' because it contains ' . $filter); 
    119                     continue 2; 
    120                 } 
    121             } 
    122             $this->debug('Inserted ' . $name); 
    123             $stmt->execute(array('name' => $name)); 
    124         } 
    125         $this->db->commit(); 
    126     } 
    127  
    128     /** 
    129     * Returns a random record value from a given table 
    130     * 
    131     * @string $table Name of the table 
    132     * @return string Value of the name column for the selected record 
    133     */ 
    134     private function getRandomRecord($table) 
    135     { 
    136         return $this->db 
    137             ->query('SELECT name FROM ' . $table . ' ORDER BY Random() LIMIT 1') 
    138             ->fetchColumn(); 
    139     } 
    140  
    141     /** 
    142     * Returns whether or not a given value has characters that may not be 
    143     * displayed correctly 
    144     * 
    145     * @param string $name Value to check 
    146     */ 
    147     private function hasBadChars($name) 
    148     { 
    149         return (max(array_map('ord', str_split($name))) > 126); 
    150     } 
    151  
    152     /** 
    15385    * Connects to the database and populates tables where needed. 
    15486    * 
     
    16294        } 
    16395 
    164         // Create the plugin directory if needed 
    165         $dir = dirname(__FILE__) . '/' . $this->getName(); 
    166         if (!file_exists($dir)) { 
    167             mkdir($dir); 
    168         } 
    169  
    17096        // Initialize the database connection 
    171         $this->db = new PDO('sqlite:' . $dir . '/drink.db'); 
     97        $this->db = new PDO('sqlite:' . $this->dir . 'drink.db'); 
    17298 
    17399        // Populate the database if necessary 
     
    224150            $contents = @file_get_contents('http://www.energyfiend.com/huge-caffeine-database/'); 
    225151            if ($contents) { 
     152                // List of drinks to filter out 
     153                $filter = array( 
     154                    'tea', 
     155                    'coffee', 
     156                    'starbucks' 
     157                ); 
    226158                $start = stripos($contents, 'id="caffeinedb"'); 
    227159                $end = stripos($contents, '</table>', $start); 
     
    231163                foreach ($matches[2] as $name) { 
    232164                    $name = html_entity_decode(trim(preg_replace('/ \\([^)]+\\)| - .*$/', '', $name))); 
    233                     if (!preg_match('/(?:^|\s+)(?:tea|coffee)(?:\s+|$)/', $name)) { 
     165                    if (!preg_match('/(?:^|\s+)(?:' . implode('|', $filter) . ')(?:\s+|$)/i', $name)) { 
    234166                        $names[] = $name; 
    235167                    } 
     
    250182            } 
    251183        } 
     184 
     185        unset($this->filter); 
     186    } 
     187 
     188    /** 
     189    * Determines if a table does not exist or is empty. 
     190    * 
     191    * @param string $name Table name 
     192    * @return bool TRUE if the table does not exist or is empty, FALSE 
     193    *              otherwise 
     194    */ 
     195    private function needTable($name) 
     196    { 
     197        $table = $this->db 
     198            ->query('SELECT COUNT(*) FROM sqlite_master WHERE name = ' . $this->db->quote($name)) 
     199            ->fetchColumn(); 
     200 
     201        if (!$table) { 
     202            return true; 
     203        } 
     204 
     205        return !$this->db 
     206            ->query('SELECT COUNT(*) FROM ' . $name) 
     207            ->fetchColumn(); 
     208    } 
     209 
     210    /** 
     211    * Populates a source database table with a given set of data. 
     212    * 
     213    * @param string $table Name of the table 
     214    * @param array $names List of drink names 
     215    * @return void 
     216    */ 
     217    private function populateTable($table, $names) 
     218    { 
     219        $this->db->exec('CREATE TABLE ' . $table . ' (name VARCHAR(255))'); 
     220        $this->db->exec('CREATE UNIQUE INDEX ' . $table . '_name ON ' . $table . ' (name)'); 
     221 
     222        $stmt = $this->db->prepare('INSERT INTO ' . $table . ' (name) VALUES (:name)'); 
     223        $this->db->beginTransaction(); 
     224        foreach (array_unique($names) as $name) { 
     225            if (preg_match('/(?:^|[^a-z])(' . implode('|', $this->filter) . ')(?:[^a-z]|$)/i', $name, $match)) { 
     226                $this->debug('Filtered out ' . $name . ' because it contains ' . $match[1]); 
     227                continue; 
     228            } 
     229            $this->debug('Inserted ' . $name); 
     230            $stmt->execute(array('name' => $name)); 
     231        } 
     232        $this->db->commit(); 
     233    } 
     234 
     235    /** 
     236    * Returns a random record value from a given table 
     237    * 
     238    * @string $table Name of the table 
     239    * @return string Value of the name column for the selected record 
     240    */ 
     241    private function getRandomRecord($table) 
     242    { 
     243        return $this->db 
     244            ->query('SELECT name FROM ' . $table . ' ORDER BY Random() LIMIT 1') 
     245            ->fetchColumn(); 
     246    } 
     247 
     248    /** 
     249    * Returns whether or not a given value has characters that may not be 
     250    * displayed correctly 
     251    * 
     252    * @param string $name Value to check 
     253    */ 
     254    private function hasBadChars($name) 
     255    { 
     256        return (max(array_map('ord', str_split($name))) > 126); 
    252257    } 
    253258 
     
    290295                $text = 'pours '.$target.' a cup of '.$drink.' tea'; 
    291296            } 
    292             $this->doAction($this->event->getSource(), $text); 
     297            $this->doAction($this->event->getSource(), $text . '.'); 
    293298        } 
    294299    } 
  • trunk/Phergie/Plugin/Karma.php

    r59 r60  
    1313class Phergie_Plugin_Karma extends Phergie_Event_Handler 
    1414{ 
     15    /** 
     16    * Indicates that a local directory is required for this plugin 
     17    * 
     18    * @var bool 
     19    */ 
     20    protected $needsDir = true; 
     21 
    1522    /** 
    1623    * Stores the SQLite object 
     
    5764        ( 
    5865            'pi' => 'pi has karma of ' . M_PI, 
    59             'chucknorris' => 'pi has karma of Warning: Integer out of range', 
     66            'chucknorris' => 'chucknorris has karma of Warning: Integer out of range', 
    6067            'c' => 'c has karma of 299 792 458 m/s', 
    6168            'e' => 'e has karma of ' . M_E, 
    6269            'euler' => 'euler has karma of ' . M_EULER, 
    6370            'mole' => 'mole has karma of 6.02214e23 molecules', 
     71            'avagadro' => 'avagadro has karma of 6.02214e23 molecules', 
    6472            'spoon' => 'spoon has no karma. There is no spoon.', 
    65             strtolower($this->getIni('nick')) => $this->getIni('static'), 
    6673            'mc^2' => 'mc^2 has karma of e', 
    6774            'mc2' => 'mc2 has karma of e', 
    6875            'mc²' => 'mc² has karma of e', 
    6976        ); 
     77 
     78        $static = $this->getIni('static'); 
     79        if ($static) { 
     80            $this->fixedKarma[strtolower($this->getIni('nick'))] = $static; 
     81        } 
    7082 
    7183        // Check to ensure that the SQLite extension is loaded 
     
    7486        } 
    7587 
    76         // Create a directory to contain the database if needed 
    77         if (!file_exists(dirname(__FILE__).'/Karma')) 
    78             mkdir(dirname(__FILE__).'/Karma'); 
    79  
    8088        // Load or initialize the database 
    81         $db = dirname(__FILE__).'/Karma/karma.db'; 
     89        $db = $this->dir . 'karma.db'; 
    8290        if (!file_exists($db)) { 
    8391            $this->db = new SQLiteDatabase($db); 
     
    108116            return; 
    109117        } 
    110         $target = $this->getSource(); 
     118        $source = $this->event->getSource(); 
    111119        $message = $this->event->getArgument(1); 
    112120            // Karma status request 
    113121        if (preg_match('#^karma (\S+?)$#i', $message, $m)) { 
     122            $term = strtolower($m[1]); 
    114123            // Return fixed value if set 
    115             if(isset($this->fixedKarma[strtolower($m[1])])) { 
    116                 $this->doPrivmsg($source, $m[1] . ' ' . $this->fixedKarma[strtolower($m[1])]); 
     124            if (isset ($this->fixedKarma[$term])) { 
     125                $this->doPrivmsg($source, $this->fixedKarma[$term]); 
    117126                return; 
    118127            } 
    119128            // Return current karma or neutral if not set yet 
    120             $res = $this->db->query('SELECT karma FROM karmas WHERE word = \''.sqlite_escape_string(strtolower($m[1])).'\' LIMIT 1', SQLITE_NUM); 
     129            $res = $this->db->query('SELECT karma FROM karmas WHERE word = \''.sqlite_escape_string($term).'\' LIMIT 1', SQLITE_NUM); 
    121130            if ($res->numRows() && $res->column(0) != 0) { 
    122131                    $this->doPrivmsg($source, $m[1].' has karma of '.$res->column(0)); 
  • trunk/Phergie/Plugin/Lart.php

    r59 r60  
    1414{ 
    1515    /** 
     16    * Indicates that a local directory is required for this plugin 
     17    * 
     18    * @var bool 
     19    */ 
     20    protected $needsDir = true; 
     21 
     22    /** 
    1623    * Date string indicating the last time the cache was emptied 
    1724    * 
     
    2633    */ 
    2734    protected $cache; 
     35 
     36    /** 
     37    * PDO instance for the database 
     38    * 
     39    * @var PDO 
     40    */ 
     41    protected $db; 
    2842 
    2943    /** 
     
    6276        } 
    6377 
    64         // Create the plugin directory if needed 
    65         $dir = dirname(__FILE__) . '/' . $this->getName(); 
    66         if (!file_exists($dir)) { 
    67             mkdir($dir); 
    68         } 
    69  
    7078        // Initialize the database connection 
    71         $db = $dir . '/lart.db'; 
    72         $create= !file_exists($db); 
     79        $db = $this->dir . 'lart.db'; 
     80        $create = !file_exists($db); 
    7381        $this->db = new PDO('sqlite:' . $db); 
    7482 
    7583        // Create database tables if necessary 
    7684        if ($create) { 
    77             $this->db->exec('CREATE TABLE lart ( name VARCHAR(255), definition TEXT )'); 
    78             $this->db->exec('CREATE UNIQUE INDEX lart_name ON lart (name)'); 
     85            $this->db->exec(' 
     86                CREATE TABLE lart ( name VARCHAR(255), definition TEXT ); 
     87                CREATE UNIQUE INDEX lart_name ON lart (name) 
     88            '); 
    7989        } 
    8090 
     
    141151                        } 
    142152                        $this->doAction( 
    143                             $this->event->getSource(), 
     153                            $this->event->getArgument(0), 
    144154                            'puts her hands over her ears and cries, "Stop confusing me!"' 
    145155                        ); 
     156                        return; 
    146157                    } 
    147158                    $definition = $redirect; 
     
    182193        } 
    183194 
    184         if (preg_match('/^(' . $nick . ': )?(.*) is (.*)$/U', $message, $match)) { 
     195        if (preg_match('/^(' . $nick . ':?\s*)?(.*) is (.*)$/i', $message, $match)) { 
    185196            list (, $address, $name, $definition) = $match; 
    186             if (!empty($nick) || $source[0] != '#') { 
     197            if (empty ($address) || $source[0] == '#') { 
    187198                $name = strtolower($name); 
    188199                $this->replace->execute(array(':name' => $name, ':definition' => $definition)); 
    189200                $this->cache[$name] = $definition; 
    190201            } 
    191         } else if (preg_match('/^(' . $nick . ': )?forget (.*)$/U', $message, $match)) { 
    192             if (!empty($nick) || $source[0] != '#') { 
    193                 $name = strtolower($match[2]); 
     202        } else if (preg_match('/^(' . $nick . ':?\s*)?forget (.*)$/i', $message, $match)) { 
     203            list (, $address, $name) = $match; 
     204            if (empty ($address) || $source[0] == '#') { 
     205                $name = strtolower($name); 
    194206                $this->delete->execute(array(':name' => $name)); 
    195207                unset($this->cache[$name]); 
  • trunk/Phergie/Plugin/Logging.php

    r59 r60  
    22 
    33/** 
    4 * @see Phergie_Event_Handler 
     4* @see Phergie_Plugin_Abstract_Command 
    55*/ 
    6 require_once 'Phergie/Event/Handler.php'; 
     6require_once 'Phergie/Plugin/Abstract/Command.php'; 
    77 
    88/** 
     
    1111* containing a given search phrase. 
    1212*/ 
    13 class Phergie_Plugin_Logging extends Phergie_Event_Handler 
     13class Phergie_Plugin_Logging extends Phergie_Plugin_Abstract_Command 
    1414{ 
     15    /** 
     16    * Indicates that a local directory is required for this plugin 
     17    * 
     18    * @var bool 
     19    */ 
     20    protected $needsDir = true; 
     21 
     22    /** 
     23    * Indicates a JOIN event in the type column of the logs table 
     24    * 
     25    * @const int 
     26    */ 
    1527    const JOIN = 1; 
     28 
     29    /** 
     30    * Indicates a PART event in the type column of the logs table 
     31    * 
     32    * @const int 
     33    */ 
    1634    const PART = 2; 
     35 
     36    /** 
     37    * Indicates a QUIT event in the type column of the logs table 
     38    * 
     39    * @const int 
     40    */ 
    1741    const QUIT = 3; 
    18     const TALK = 4; 
     42 
     43    /** 
     44    * Indicates a PRIVMSG event in the type column of the logs table 
     45    * 
     46    * @const int 
     47    */ 
     48    const PRIVMSG = 4; 
     49 
     50    /** 
     51    * Indicates a CTCP ACTION event in the type column of the logs table 
     52    * 
     53    * @const int 
     54    */ 
    1955    const ACTION = 5; 
     56 
     57    /** 
     58    * Indicates a NICK event in the type column of the logs table 
     59    * 
     60    * @const int 
     61    */ 
    2062    const NICK = 6; 
    21     const KICKED = 7; 
    22  
    23     /** 
    24     * SQLite object 
    25     * 
    26     * @var resource 
     63 
     64    /** 
     65    * Indicates a KICK event in the type column of the logs table 
     66    * 
     67    * @const int 
     68    */ 
     69    const KICK = 7; 
     70 
     71    /** 
     72    * Indicates a MODE event in the type column of the logs table 
     73    * 
     74    * @const int 
     75    */ 
     76    const MODE = 8; 
     77 
     78    /** 
     79    * Indicates a TOPIC event in the type column of the logs table 
     80    * 
     81    * @const int 
     82    */ 
     83    const TOPIC = 9; 
     84 
     85    /** 
     86    * PDO instance for the database 
     87    * 
     88    * @var PDO 
    2789    */ 
    2890    protected $db = null; 
    2991 
    3092    /** 
     93    * Prepared statement for searching for the last logged action in which a 
     94    * particular user was mentioned 
     95    * 
     96    * @var PDOStatement 
     97    */ 
     98    protected $search; 
     99 
     100    /** 
     101    * Prepared statement for searching for the last logged action originating 
     102    * from a particular user 
     103    * 
     104    * @var PDOStatement 
     105    */ 
     106    protected $seen; 
     107 
     108    /** 
     109    * Prepared statement for searching for the last logged PRIVMSG action or 
     110    * CTCP ACTION command originating from a particular user 
     111    * 
     112    * @var PDOStatement 
     113    */ 
     114    protected $heard; 
     115 
     116    /** 
     117    * Prepared statement for inserting new log entries 
     118    * 
     119    * @var PDOStatement 
     120    */ 
     121    protected $insert; 
     122 
     123    /** 
    31124    * Answers for seen when the user is present 
    32125    * 
    33126    * @var array 
    34127    */ 
    35     protected $msgPresent = array 
     128    protected $present = array 
    36129    ( 
    37130        'Open your eyes already!', 
     
    42135 
    43136    /** 
    44     * Answers for heard when the user can't be found 
     137    * Answers for when record of a user cannot be found 
    45138    * 
    46139    * @var array 
    47140    */ 
    48     protected $msgNeverHeard = array 
     141    protected $missing = array 
    49142    ( 
    50143        'must be mute, or one of your imaginary friends?', 
     
    55148 
    56149    /** 
    57     * Action descriptions corresponding to event constants. 
     150    * Action descriptions corresponding to event constants 
    58151    * 
    59152    * @var array 
     
    64157        self::PART => 'leaving this channel because', 
    65158        self::QUIT => 'quitting', 
    66         self::TALK => 'saying', 
     159        self::PRIVMSG => 'saying', 
    67160        self::ACTION => 'saying', 
    68161        self::NICK => 'changing nick to', 
    69         self::KICKED => 'being kicked off because', 
     162        self::KICK => 'being kicked off because', 
    70163    ); 
    71164 
     
    77170    public function init() 
    78171    { 
    79         // Check to make sure SQLite is present 
    80         if (!extension_loaded('sqlite')) { 
     172        // Check for the necessary extensions 
     173        if (!extension_loaded('PDO') || !extension_loaded('pdo_sqlite')) { 
    81174            return; 
    82175        } 
    83176 
    84         // Create plugin dir if not there yet 
    85         $dir = dirname(__FILE__) . '/Logging'; 
    86         if (!file_exists($dir)) { 
    87             mkdir($dir); 
    88         } 
    89  
    90         // Load or initialize the logging database 
    91         $db = $dir . '/logging.db'; 
    92         if (!file_exists($db)) { 
    93             $this->db = new SQLiteDatabase($db); 
    94             $this->db->queryExec(' 
    95                 CREATE TABLE logs ( tstamp VARCHAR(14), type SHORTINT, chan VARCHAR(45), nick VARCHAR(25), message VARCHAR(255) ) ; 
    96                 CREATE INDEX channicktype ON logs ( tstamp, type, chan, nick ) ; 
    97                 CREATE INDEX channick ON logs ( tstamp, chan, nick ) ; 
     177        // Initialize the database connection 
     178        $db = $this->dir . 'logging.db'; 
     179        $create = !file_exists($db); 
     180        $this->db = new PDO('sqlite:' . $db); 
     181 
     182        // Create database tables if necessary 
     183        if ($create) { 
     184            $result = $this->db->exec(' 
     185                CREATE TABLE logs ( 
     186                    tstamp VARCHAR(14), 
     187                    type SHORTINT, 
     188                    chan VARCHAR(45), 
     189                    nick VARCHAR(25), 
     190                    message VARCHAR(255) 
     191                ); 
     192                CREATE INDEX channicktype ON logs (tstamp, type, chan, nick); 
     193                CREATE INDEX channick ON logs (tstamp, chan, nick); 
    98194            '); 
     195        } 
     196 
     197        // Initialize prepared statements for common operations 
     198        $this->search = $this->db->prepare(' 
     199            SELECT tstamp, type, chan, nick, message 
     200            FROM logs 
     201            WHERE nick LIKE :phrase 
     202            OR message LIKE :phrase 
     203            ORDER BY tstamp DESC 
     204            LIMIT :limit 
     205        '); 
     206 
     207        $this->seen = $this->db->prepare(' 
     208            SELECT tstamp, type, message 
     209            FROM logs 
     210            WHERE nick = :name 
     211            AND chan = :chan 
     212            ORDER BY tstamp DESC 
     213            LIMIT 1 
     214        '); 
     215 
     216        $this->heard = $this->db->prepare(' 
     217            SELECT tstamp, type, message 
     218            FROM logs 
     219            WHERE type IN (' . self::PRIVMSG . ', ' . self::ACTION . ') 
     220            AND nick = :nick 
     221            AND chan = :chan 
     222            ORDER BY tstamp DESC 
     223            LIMIT 1 
     224        '); 
     225 
     226        $this->insert = $this->db->prepare(' 
     227            INSERT INTO logs ( 
     228                tstamp, 
     229                type, 
     230                chan, 
     231                nick, 
     232                message 
     233            ) 
     234            VALUES ( 
     235                :tstamp, 
     236                :type, 
     237                :chan, 
     238                :nick, 
     239                :message 
     240            ) 
     241        '); 
     242    } 
     243 
     244    /** 
     245    * Formats a timestamp for display purposes. 
     246    * 
     247    * @param string $timestamp Timestamp to format 
     248    * @return string Formatted timestamp 
     249    */ 
     250    protected function formatTimestamp($timestamp) 
     251    { 
     252        return preg_replace( 
     253            '#^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$#', 
     254            '$1-$2-$3 @ $4:$5:$6', 
     255            $timestamp 
     256        ); 
     257    } 
     258 
     259    /** 
     260    * Returns a random message used when a user is currently present. 
     261    * 
     262    * @return string 
     263    */ 
     264    protected function randomPresent() 
     265    { 
     266        return $this->present[array_rand($this->present)]; 
     267    } 
     268 
     269    /** 
     270    * Returns a random message used when no logged record of a user exists. 
     271    * 
     272    * @return string 
     273    */ 
     274    protected function randomMissing() 
     275    { 
     276        return $this->missing[array_rand($this->missing)]; 
     277    } 
     278 
     279    /** 
     280    * Inserts a new entry in the log database. 
     281    * 
     282    * @param int $type Class constant representing the event type 
     283    * @param string $chan Name of the channel in which the event occurs 
     284    * @param string $nick Nick of the user from which the event originates 
     285    * @param string $message Message associated with the event if applicable 
     286    *                        (optional) 
     287    * @return void 
     288    */ 
     289    protected function insertEvent($type, $chan, $nick, $message = null) 
     290    { 
     291        $params = array( 
     292            ':tstamp' => date('YmdHis'), 
     293            ':type' => $type, 
     294            ':chan' => $chan, 
     295            ':nick' => $nick, 
     296            ':message' => trim($message) 
     297        ); 
     298 
     299        $result = $this->insert->execute($params); 
     300    } 
     301 
     302    /** 
     303    * Logs incoming messages. 
     304    * 
     305    * @return void 
     306    */ 
     307    public function onPrivmsg() 
     308    { 
     309        // Allow the Command plugin to process command calls 
     310        parent::onPrivmsg(); 
     311 
     312        if ($this->event->isInChannel()) { 
     313            $this->insertEvent( 
     314                self::PRIVMSG, 
     315                $this->event->getSource(), 
     316                $this->event->getNick(), 
     317                $this->event->getArgument(1) 
     318            ); 
     319        } 
     320    } 
     321 
     322    /** 
     323    * Logics incoming actions. 
     324    * 
     325    * @return void 
     326    */ 
     327    public function onAction() 
     328    { 
     329        if ($this->event->isInChannel()) { 
     330            $this->insertEvent( 
     331                self::ACTION, 
     332                $this->event->getSource(), 
     333                $this->event->getNick(), 
     334                $this->event->getArgument(1) 
     335            ); 
     336        } 
     337    } 
     338 
     339    /** 
     340    * Tracks users joining. 
     341    * 
     342    * @return void 
     343    */ 
     344    public function onJoin() 
     345    { 
     346        $this->insertEvent( 
     347            self::JOIN, 
     348            $this->event->getSource(), 
     349            $this->event->getNick() 
     350        ); 
     351    } 
     352 
     353    /** 
     354    * Tracks users parting. 
     355    * 
     356    * @return void 
     357    */ 
     358    public function onPart() 
     359    { 
     360        $this->insertEvent( 
     361            self::PART, 
     362            $this->event->getSource(), 
     363            $this->event->getNick(), 
     364            $this->event->getArgument(1) 
     365        ); 
     366    } 
     367 
     368    /** 
     369    * Tracks users being kicked. 
     370    * 
     371    * @return void 
     372    */ 
     373    public function onKick() 
     374    { 
     375        $this->insertEvent( 
     376            self::KICK, 
     377            $this->event->getSource(), 
     378            $this->event->getNick(), 
     379            $this->event->getArgument(1) 
     380        ); 
     381    } 
     382 
     383    /** 
     384    * Tracks users changing modes. 
     385    * 
     386    * @return void 
     387    */ 
     388    public function onMode() 
     389    { 
     390        $this->insertEvent( 
     391            self::MODE, 
     392            $this->event->getSource(), 
     393            $this->event->getNick(), 
     394            implode(' ', array_slice($this->event->getArguments(), 1)) 
     395        ); 
     396    } 
     397 
     398    /** 
     399    * Tracks channel topic changes. 
     400    * 
     401    * @return void 
     402    */ 
     403    public function onTopic() 
     404    { 
     405        $this->insertEvent( 
     406            self::TOPIC, 
     407            $this->event->getSource(), 
     408            $this->event->getNick(), 
     409            $this->event->getArgument(1) 
     410        ); 
     411    } 
     412 
     413    /** 
     414    * Tracks users quitting. 
     415    * 
     416    * @return void 
     417    */ 
     418    public function onQuit() 
     419    { 
     420        $nick = $this->event->getNick(); 
     421 
     422        foreach (Phergie_Plugin_Users::getChannels($nick) as $chan) { 
     423            $this->insertEvent( 
     424                self::QUIT, 
     425                $chan, 
     426                $this->event->getNick(), 
     427                $this->event->getArgument(0) 
     428            ); 
     429        } 
     430    } 
     431 
     432    /** 
     433    * Tracks users changing nicks. 
     434    * 
     435    * @return void 
     436    */ 
     437    public function onNick() 
     438    { 
     439        $nick = $this->event->getNick(); 
     440 
     441        foreach (Phergie_Plugin_Users::getChannels($nick) as $chan) { 
     442            $this->insertEvent( 
     443                self::NICK, 
     444                $chan, 
     445                $this->event->getNick(), 
     446                $this->event->getArgument(0) 
     447            ); 
     448        } 
     449    } 
     450 
     451    /** 
     452    * Responds to requests for logged messages containing a particular search 
     453    * phrase. 
     454    * 
     455    * @param string $phrase Phrase to search for 
     456    * @return void 
     457    */ 
     458    public function onDoSearch($phrase) 
     459    { 
     460        $source = $this->event->getSource(); 
     461 
     462        $params = array( 
     463            ':phrase' => '%' . $phrase . '%', 
     464            ':limit' => ($source[0] == '#' ? 1 : 6) 
     465        ); 
     466 
     467        $this->search->execute($params); 
     468 
     469        foreach($this->search as $row) { 
     470            $this->doPrivmsg( 
     471                $source, 
     472                sprintf( 
     473                    '%s was seen %s: %s on %s (on %s)', 
     474                    $row['nick'], 
     475                    $this->actions[$row['type']], 
     476                    $row['message'], 
     477                    $row['chan'], 
     478                    $this->formatTimestamp($row['tstamp']) 
     479                ) 
     480            ); 
     481        } 
     482    } 
     483 
     484    /** 
     485    * Responds to requests for the last logged action originating from a 
     486    * particular user. 
     487    * 
     488    * @param string $user Nick of the user to search for 
     489    * @return void 
     490    */ 
     491    public function onDoSeen($user) 
     492    { 
     493        $source = $this->event->getSource(); 
     494        $target = $this->event->getNick(); 
     495 
     496        // Person is online, send a random prank answer 
     497        if (Phergie_Plugin_Users::isIn($user, $source)) { 
     498            $this->doPrivmsg( 
     499                $source, 
     500                sprintf( 
     501                    '%s: %s', 
     502                    $target, 
     503                    $this->randomPresent() 
     504                ) 
     505            ); 
     506 
     507        // Person is offline 
    99508        } else { 
    100             $this->db = new SQLiteDatabase($db); 
    101         } 
    102     } 
    103  
    104     /** 
    105     * Logs incoming messages and responds to requests for the last action 
    106     * taken or message sent by a particular user or messages containing a 
    107     * particular search phrase. 
    108     * 
    109     * @todo Encapsulate log insertions into its own routine and call it here 
    110     * @return void 
    111     */ 
    112     public function onPrivmsg() 
    113     { 
    114         $source = $this->getSource(); 
    115         $target = $this->event->getNick(); 
    116         $message = $this->event->getArgument(1); 
    117         if (preg_match('#^search (\S+)$#', $message, $m)) { 
    118             // Search DB 
    119             $res = $this->db->query('SELECT tstamp, type, chan, nick, message FROM logs 
    120                 WHERE nick LIKE \'%'.sqlite_escape_string($m[1]).'%\' OR message LIKE \'%'.sqlite_escape_string($m[1]).'%\' 
    121                 ORDER BY tstamp DESC LIMIT '.($source[0] === '#' ? 1 : 6), SQLITE_ASSOC); 
    122             // Output 
    123             foreach($res as $r) { 
     509            $params = array( 
     510                ':name' => $user, 
     511                ':chan' => $source 
     512            ); 
     513 
     514            $this->seen->execute($params); 
     515            $row = $this->seen->fetch(PDO::FETCH_ASSOC); 
     516 
     517            // Send the last action if available 
     518            if ($row) { 
    124519                $this->doPrivmsg( 
    125520                    $source, 
    126                     // @todo Is there a SQLite function that could be used in the query itself rather than this preg_replace? 
    127                     $r['nick'].' was seen '.$this->actions[$r['type']] .'; '.$r['message'].' on '.$r['chan'].' (on '.preg_replace('#^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$#', '$1-$2-$3 @ $4:$5:$6', $r['tstamp']).')' 
     521                    sprintf( 
     522                        '%s: %s was last seen %s: %s (on %s)', 
     523                        $source, 
     524                        $user, 
     525                        $this->actions[$row['type']], 
     526                        $row['message'], 
     527                        $this->formatTimestamp($row['tstamp']) 
     528                    ) 
     529                ); 
     530 
     531            // Send a prank answer if no last action is found 
     532            } else { 
     533                $this->doPrivmsg( 
     534                    $source, 
     535                    sprintf( 
     536                        '%s: %s %s', 
     537                        $target, 
     538                        $user, 
     539                        $this->randomMissing() 
     540                    ) 
    128541                ); 
    129542            } 
    130             unset($res, $r, $m); 
    131         } 
    132         // @todo Why are logs restricted to in-channel messages only? 
    133         if ($source[0] === '#') { 
    134             // Last Seen Handler 
    135             if (preg_match('#^seen (\S+)$#', $message, $m)) { 
    136                 // Person is online, send a random prank answer 
    137                 if (Phergie_Plugin_Users::isIn($m[1], $source)) { 
    138                     $this->doPrivmsg($source, $target.': '.$this->msgPresent[array_rand($this->msgPresent,1)]); 
    139                 } else { // Person is offline, show last action 
    140                     $res = $this->db->query('SELECT tstamp, type, message FROM logs 
    141                     WHERE nick=\''.sqlite_escape_string($m[1]).'\' AND chan=\''.sqlite_escape_string($source).'\' 
    142                     ORDER BY tstamp DESC LIMIT 1', SQLITE_ASSOC); 
    143                     foreach($res as $r) { 
    144                         // @todo Is there a SQLite function that could be used in the query itself rather than this preg_replace? 
    145                         $this->doPrivmsg($source, $source.': '.$m[1].' was last seen '.$this->actions[$r['type']].'; '.$r['message'].' (on '.preg_replace('#^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$#', '$1-$2-$3 @ $4:$5:$6', $r['tstamp']).')'); 
    146                     } 
    147                     unset($res, $r); 
    148                 } 
    149                 unset($m); 
    150             // Last Heard Handler 
    151             } elseif (preg_match('#^heard (\S+)$#', $message, $m)) { 
    152                 $res = $this->db->query('SELECT tstamp, type, message FROM logs 
    153                     WHERE (type='.self::TALK.' OR type='.self::ACTION.') AND 
    154                     nick=\''.sqlite_escape_string($m[1]).'\' AND chan=\''.sqlite_escape_string($source).'\' 
    155                     ORDER BY tstamp DESC LIMIT 1', SQLITE_ASSOC); 
    156                 // Not found, random prank answer 
    157                 if($res->numRows() === 0) { 
    158                     $this->doPrivmsg($source, $target.': '.$m[1].' '. $this->msgNeverHeard[array_rand($this->msgNeverHeard,1)]); 
    159                 // Send last words 
    160                 } else { 
    161                     $r = $res->fetch(); 
    162                     $this->doPrivmsg($source, $target.': '.$m[1].'\'s last words were '.$r['message'].' (on '.preg_replace('#^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$#', '$1-$2-$3 @ $4:$5:$6', $r['tstamp']).')'); 
    163                 } 
    164                 unset($res, $r, $m); 
    165             } 
    166             $this->db->queryExec('INSERT INTO logs (tstamp, type, chan, nick, message) VALUES (\''.date('YmdHis').'\', '.self::TALK.', \''.sqlite_escape_string($source).'\', \''.sqlite_escape_string($target).'\', \''.sqlite_escape_string($message).'\')'); 
    167         } 
    168     } 
    169  
    170     /** 
    171     * Tracks users joining. 
    172     * 
    173     * @todo Encapsulate log insertions into its own routine and call it here 
    174     * @return void 
    175     */ 
    176     public function onJoin() 
    177     { 
    178         $this->db->queryExec('INSERT INTO logs (tstamp, type, chan, nick) VALUES (\''.date('YmdHis').'\', '.self::JOIN.', \''.sqlite_escape_string($this->getSource()).'\', \''.sqlite_escape_string($this->event->getNick()).'\')'); 
    179     } 
    180  
    181     /** 
    182     * Tracks users parting. 
    183     * 
    184     * @todo Encapsulate log insertions into its own routine and call it here 
    185     * @return void 
    186     */ 
    187     public function onPart() 
    188     { 
    189         $this->db->queryExec('INSERT INTO logs (tstamp, type, chan, nick, message) VALUES (\''.date('YmdHis').'\', '.self::PART.', \''.sqlite_escape_string($this->getSource()).'\', \''.sqlite_escape_string($this->event->getNick()).'\', \''.sqlite_escape_string(trim($this->event->getArgument(1), '"')).'\')'); 
    190     } 
    191  
    192     /** 
    193     * Tracks users quitting. 
    194     * 
    195     * @todo Encapsulate log insertions into its own routine and call it here 
    196     * @return void 
    197     */ 
    198     public function onQuit() 
    199     { 
    200         $nick = $this->event->getNick(); 
    201         $message = trim($this->event->getArgument(1), '"'); 
    202         foreach (Phergie_Plugin_Users::getChannels($nick) as $chan) { 
    203             $this->db->queryExec('INSERT INTO logs (tstamp, type, chan, nick, message) VALUES (\''.date('YmdHis').'\', '.self::QUIT.', \''.sqlite_escape_string($chan).'\', \''.sqlite_escape_string($nick).'\', \''.sqlite_escape_string($message).'\')'); 
    204         } 
    205     } 
    206  
    207     /** 
    208     * Tracks users changing nicks. 
    209     * 
    210     * @todo Encapsulate log insertions into its own routine and call it here 
    211     * @return void 
    212     */ 
    213     public function onNick() 
    214     { 
    215         $nick = $this->event->getNick(); 
    216         $message = $this->event->getArgument(0); 
    217         foreach (Phergie_Plugin_Users::getChannels($nick) as $chan) { 
    218             $this->db->queryExec('INSERT INTO logs (tstamp, type, chan, nick, message) VALUES (\''.date('YmdHis').'\', '.self::NICK.', \''.sqlite_escape_string($chan).'\', \''.sqlite_escape_string($nick).'\', \''.sqlite_escape_string($message).'\')'); 
     543        } 
     544    } 
     545 
     546    /** 
     547    * Responds to requests for the last logged PRIVMSG action or CTCP ACTION 
     548    * command originating from a particular user. 
     549    * 
     550    * @param string $user Nick of the user to search for 
     551    * @return void 
     552    */ 
     553    public function onDoHeard($user) 
     554    { 
     555        $source = $this->event->getSource(); 
     556        $target = $this->event->getNick(); 
     557 
     558        $params = array( 
     559            ':nick' => $user, 
     560            ':chan' => $source 
     561        ); 
     562 
     563        $this->heard->execute($params); 
     564        $row = $this->heard->fetch(PDO::FETCH_ASSOC); 
     565 
     566        // Send the last action if available 
     567        if($row) { 
     568            $this->doPrivmsg( 
     569                $source, 
     570                sprintf( 
     571                    '%s: %s\'s last words were: %s (on %s)', 
     572                    $target, 
     573                    $user, 
     574                    $row['message'], 
     575                    $this->formatTimestamp($row['tstamp']) 
     576                ) 
     577            ); 
     578 
     579        // Send a prank answer if no last action is found 
     580        } else { 
     581            $this->doPrivmsg( 
     582                $source, 
     583                sprintf( 
     584                    '%s: %s %s', 
     585                    $target, 
     586                    $user, 
     587                    $this->randomMissing() 
     588                ) 
     589            ); 
    219590        } 
    220591    } 
  • trunk/Phergie/Plugin/Math.php

    r59 r60  
    4747    public function onPrivmsg() 
    4848    { 
    49         $source = $this->getSource(); 
     49        $source = $this->event->getSource(); 
    5050        $message = $this->event->getArgument(1); 
    5151        if (preg_match('/^(?:math|calc) /', $message)) { 
     
    108108                } 
    109109            } 
    110             $res = eval('return ' . $out . ';'); 
     110            $res = @eval('return ' . $out . ';'); 
    111111            if($res === false) { 
    112112                $this->doPrivmsg($source, 'Computation error, division by zero?'); 
  • trunk/Phergie/Plugin/Nickserv.php

    r59 r60  
    6464            if ($altnick) { 
    6565                $this->doNick($altnick); 
     66                $this->setIni('nick', $altnick); 
    6667            } 
    6768        } 
  • trunk/Phergie/Plugin/Php.php

    r59 r60  
    4444    { 
    4545        if (!isset ($this->cache[$function])) { 
    46             $contents = file_get_contents('http://php.net/manual/en/function.' . $function . '.php'); 
     46            $name = str_replace('_', '-', $function); 
     47            $contents = file_get_contents('http://php.net/manual/en/function.' . $name . '.php'); 
    4748            $start = strpos($contents, '<div class="methodsynopsis">'); 
    4849            $end = strpos($contents, '</div>', $start); 
     
    5253            $this->cache[$function] = $contents; 
    5354        } 
    54         $this->doPrivmsg($this->getSource(), $this->cache[$function]); 
     55        $this->doPrivmsg($this->event->getSource(), $this->cache[$function]); 
    5556    } 
    5657} 
  • trunk/Phergie/Plugin/Puppet.php

    r59 r60  
    77 
    88/** 
    9 * Allows administrators to effectively speak as the bot. 
     9* Allows administrators to effectively speak and act as the bot. 
    1010*/ 
    11 class Phergie_Plugin_Say extends Phergie_Plugin_Abstract_AdminCommand 
     11class Phergie_Plugin_Puppet extends Phergie_Plugin_Abstract_AdminCommand 
    1212{ 
    1313    /** 
     
    1515    * channel. 
    1616    * 
    17     * <code>Botname: say [#chan] message</code> 
     17    * <code>say #chan message</code> 
    1818    * 
     19    * @param string $chan Name of the channel 
     20    * @param string $message Message to repeat 
    1921    * @return void 
    2022    */ 
    21     public function onDoSay($what) 
     23    public function onDoSay($chan, $message) 
    2224    { 
    23         $source = $this->getSource(); 
    24         preg_match('{^(#\S+\s)?(.+)}', $what, $match); 
    25         if (!empty ($match[1])) { 
    26             $this->doPrivmsg(trim($match[1]), $match[2]); 
    27         } elseif ($source[0] === '#') { 
    28             $this->doPrivmsg($source, $what); 
    29         } 
     25        $this->doPrivmsg($chan, $message); 
     26    } 
     27 
     28    /** 
     29    * Handles a request for the bot to repeat a given action in a specified 
     30    * channel. 
     31    * 
     32    * <code>act #chan action</code> 
     33    * 
     34    * @param string $chan Name of the channel 
     35    * @param string $action Action to perform 
     36    * @return void 
     37    */ 
     38    public function onDoAct($chan, $action) 
     39    { 
     40        $this->doAction($chan, $action); 
    3041    } 
    3142} 
  • trunk/Phergie/Plugin/UrbanDictionary.php

    r59 r60  
    4747            * length should be 510 characters according to the IRC RFC. 
    4848            */ 
    49             $max = 445 - strlen($channel) - strlen($url); 
     49            $target = $this->event->getSource(); 
     50            $max = 445 - strlen($target) - strlen($url); 
    5051            if (strlen($contents) > $max) { 
    5152                $contents = substr($contents, 0, $max); 
     
    5455                    $end = $max; 
    5556                } 
    56                 $contents = substr($contents, 0, $end) . '...' . $url; 
     57                $contents = substr($contents, 0, $end) . '...'; 
    5758            } 
     59            $contents .=  $url; 
    5860 
    59             $this->doPrivmsg($this->getSource(), $contents); 
     61            $this->doPrivmsg($target, $contents); 
    6062        } 
    6163    } 
  • trunk/Phergie/Plugin/Url.php

    r59 r60  
    4545    { 
    4646        // URL Match 
    47         if (preg_match('#(https?://(?:[a-z0-9_-]+\.)+[a-z]{2,6}[^\s]*)#is', $event->getArgument(1), $m)) { 
     47        if (preg_match('#(https?://(?:[a-z0-9_-]+\.)+[a-z]{2,6}[^\s]*)#is', $this->event->getArgument(1), $m)) { 
    4848            $url = rtrim($m[1], '), ]'); 
    4949            // @todo if image or something, output content type 
    5050            $tinyUrl = $this->tinyUrl($url); 
    51             $title = ''; 
    5251            $titleLength = $this->getIni('title_length'); 
    53             if ($page = @fopen($url, 'r')) { 
    54                 $content = ''; 
    55                 while ($chunk = fread($page, 512)) { 
    56                     $content .= $chunk; 
    57                     if (preg_match('#<title[^>]*>([^<]*)#is', $content, $m)) { 
    58                         $title = $this->decode($m[1], $titleLength); 
    59                         break; 
     52 
     53            $opts = array('http' => 
     54                array( 
     55                    'method' => 'GET', 
     56                    'header' => 'Content-type: application/x-www-form-urlencoded', 
     57                    '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' 
     58                ) 
     59            ); 
     60            $context = stream_context_create($opts); 
     61 
     62            if ($page = @fopen($url, 'r', false, $context)) { 
     63                $data = stream_get_meta_data($page); 
     64                foreach ($data['wrapper_data'] as $header) { 
     65                    if (preg_match('/^Content-Type: ([^;]+)/', $header, $match) 
     66                        && ! preg_match('/^text\\/x?html$/', $match[1])) { 
     67                        $title = $match[1]; 
    6068                    } 
    61                     if (preg_match('#</head>|<body#i', $content)) { 
    62                         break; 
     69                } 
     70                if (!isset ($title)) { 
     71                    $content = ''; 
     72                    while ($chunk = fread($page, 512)) { 
     73                        $content .= $chunk; 
     74                        if (preg_match('#<title[^>]*>([^<]*)#is', $content, $m)) { 
     75                            $title = $this->decode($m[1], $titleLength); 
     76                            break; 
     77                        } 
     78                        if (preg_match('#</head>|<body#i', $content)) { 
     79                            break; 
     80                        } 
    6381                    } 
    6482                } 
     
    6785 
    6886            $this->doPrivmsg( 
    69                 $this->getSource(), 
     87                $this->event->getSource(), 
    7088                str_replace( 
    7189                    array('%title%', '%link%'), 
  • trunk/Phergie/Plugin/Users.php

    r59 r60  
    2222    public function onMode() 
    2323    { 
    24         if (preg_match('{(\S+)\s((?:\+|-)[hov+-]+)\s((?:\s*\S+)+)$}i', $this->event->getArgument(0), $m)) { 
    25             $chan = $m[1]; 
    26             $modes = str_split($m[2], 1); 
    27             $nicks = explode(' ', $m[3]); 
     24        $args = $this->event->getArguments(); 
     25        if (count($args) != 3) { 
     26            return; 
     27        } 
     28        list ($chan, $modes, $nicks) = $args; 
     29        if (preg_match('/(?:\+|-)[hov+-]+/i', $modes)) { 
     30            $modes = str_split($modes, 1); 
     31            $nicks = explode(' ', $nicks); 
    2832            while ($char = array_shift($modes)) { 
    2933                switch($char) { 
     
    6367    } 
    6468 
    65     // Function used for debugging purposes 
    66     /* 
     69    /** 
     70    * Debugging function 
     71    * 
     72    * @return void 
     73    */ 
    6774    public function onPrivmsg() 
    6875    { 
    69         list ($target, $nick) = $this->event->getArguments(); 
    70         if(preg_match('#^ishere (\S+)$#', $nick, $m)) { 
    71             $this->doPrivmsg($target, self::isIn($m[1], $target) ? 'true' : 'false'); 
    72         } 
    73         if(preg_match('#^isop (\S+)$#', $nick, $m)) { 
    74             $this->doPrivmsg($target, self::isOp($m[1], $target) ? 'true' : 'false'); 
    75         } 
    76         if(preg_match('#^isvoice (\S+)$#', $nick, $m)) { 
    77             $this->doPrivmsg($target, self::isVoice($m[1], $target) ? 'true' : 'false'); 
    78         } 
    79     } 
    80     */ 
     76        if ($this->getIni('debug')) { 
     77            list ($target, $msg) = $this->event->getArguments(); 
     78            if (preg_match('#^ishere (\S+)$#', $msg, $m)) { 
     79                $this->doPrivmsg($target, self::isIn($m[1], $target) ? 'true' : 'false'); 
     80            } elseif (preg_match('#^isop (\S+)$#', $msg, $m)) { 
     81                $this->doPrivmsg($target, self::isOp($m[1], $target) ? 'true' : 'false'); 
     82            } elseif (preg_match('#^isvoice (\S+)$#', $msg, $m)) { 
     83                $this->doPrivmsg($target, self::isVoice($m[1], $target) ? 'true' : 'false'); 
     84            } 
     85        } 
     86    } 
    8187 
    8288    /** 
     
    128134        $nick = $this->event->getNick(); 
    129135        $newNick = $this->event->getArgument(0); 
    130         $this->debug($nick . ' => ' . $newNick); 
    131136        foreach (self::$list as $channame=>$chan) { 
    132137            if (isset($chan[$nick])) { 
  • trunk/Phergie/README

    r57 r60  
    22-- WHO IS PHERGIE? 
    33----------------------------------------------------------------------------- 
    4 Phergie is an IRC bot written in pure PHP 5 with an OO API. She is named  
    5 after the delicious and delectable Stacy Ann Ferguson, better known by her  
     4Phergie is an IRC bot written in pure PHP 5 with an OO API. She is named 
     5after the delicious and delectable Stacy Ann Ferguson, better known by her 
    66stage name Fergie. 
    77 
    88----------------------------------------------------------------------------- 
    9 -- LICENSE  
     9-- LICENSE 
    1010----------------------------------------------------------------------------- 
    1111Phergie is released under the GNU Lesser General Public License version 3.0. 
    12 See the LICENSE file included in the source tarball or go to the URL below  
     12See the LICENSE file included in the source tarball or go to the URL below 
    1313to obtain a copy. 
    1414 
     
    1616 
    1717----------------------------------------------------------------------------- 
    18 -- RUNNING PHERGIE  
     18-- RUNNING PHERGIE 
    1919----------------------------------------------------------------------------- 
    2020Download and decompress the source tarball, available at the URL below. 
     
    2222http://bluelyte.sytes.net/phergie.tar.gz 
    2323 
    24 Make sure that register_argc_argv is enabled if you want to use a  
    25 configuration file that is not in the Phergie directory or is not named  
    26 phergie.ini. register_argc_argv is often disabled in CGI environments since  
    27 it's generally only needed for CLI purposes. If you have not run PHP from the  
    28 command line before, it may be a good idea to read the related manual section  
     24Make sure that register_argc_argv is enabled if you want to use a 
     25configuration file that is not in the Phergie directory or is not named 
     26phergie.ini. register_argc_argv is often disabled in CGI environments since 
     27it's generally only needed for CLI purposes. If you have not run PHP from the 
     28command line before, it may be a good idea to read the related manual section 
    2929at the URL below. 
    3030 
    3131http://us.php.net/manual/en/features.commandline.php 
    3232 
    33 Once you've made any necessary PHP configuration changes, navigate to the  
    34 Phergie directory. From there, open your Phergie configuration file and  
    35 update configuration settings there as needed. Once finished, open up a  
    36 command prompt/terminal/shell. Execute the PHP interpreter in CLI mode and  
     33Once you've made any necessary PHP configuration changes, navigate to the 
     34Phergie directory. From there, open your Phergie configuration file and 
     35update configuration settings there as needed. Once finished, open up a 
     36command prompt/terminal/shell. Execute the PHP interpreter in CLI mode and 
    3737pass it the filename Bot.php, as shown below. 
    3838 
     
    4242 
    4343----------------------------------------------------------------------------- 
    44 -- USING PLUGINS  
     44-- USING PLUGINS 
    4545----------------------------------------------------------------------------- 
    46 Obviously, I hope that usage of Phergie gets to be widespread enough that  
    47 people post their own plugins and you happen upon one that you find to be  
    48 useful.  
     46Obviously, I hope that usage of Phergie gets to be widespread enough that 
     47people post their own plugins and you happen upon one that you find to be 
     48useful. 
    4949 
    50 Plugins are stored in the Phergie/Event/Handler folder. Each plugin should  
    51 have its own file in that folder and, if needed, a directory by the same   
    52 name (minus the extension, obviously) to include any other files it   
     50Plugins are stored in the Phergie/Event/Handler folder. Each plugin should 
     51have its own file in that folder and, if needed, a directory by the same 
     52name (minus the extension, obviously) to include any other files it 
    5353requires. 
    5454 
    55 Some plugins might also require certain configuration settings to be set up  
    56 in the phergie.ini configuration file, so be sure to look for any  
     55Some plugins might also require certain configuration settings to be set up 
     56in the phergie.ini configuration file, so be sure to look for any 
    5757instructions to this effect. 
    5858 
    5959----------------------------------------------------------------------------- 
    60 -- EXTENDING PHERGIE  
     60-- HISTORY 
    6161----------------------------------------------------------------------------- 
    62 For instructions on how to write plugins for Phergie, see the CONTRIBUTE  
    63 file included in the source tarball. 
    64  
    65 ----------------------------------------------------------------------------- 
    66 -- HISTORY  
    67 ----------------------------------------------------------------------------- 
    68 The maintainer of the Ai bot for the #phpc channel on the Freenode IRC  
    69 network could never find time to package its source code for release and  
    70 only had the odd moment to handle feature requests. Additionally, the bot was  
    71 written in PHP 4, for which support will end on 8/8/08. With other existing  
    72 libraries having odd APIs, missing features, or just overall not seeming cut  
    73 out for the job, it was decided that a new bot should be built from scratch.  
     62The maintainer of the Ai bot for the #phpc channel on the Freenode IRC 
     63network could never find time to package its source code for release and 
     64only had the odd moment to handle feature requests. Additionally, the bot was 
     65written in PHP 4, for which support will end on 8/8/08. With other existing 
     66libraries having odd APIs, missing features, or just overall not seeming cut 
     67out for the job, it was decided that a new bot should be built from scratch. 
    7468Hence, we have Phergie. 
  • trunk/Phergie/phergie.ini

    r59 r60  
    55; server : 
    66;    host name of the server to which the bot should connect 
    7 server = irc.freenode.net 
     7server = 
    88 
    99; username : 
    1010;    username to use for the bot 
    11 username = Phergie 
     11username = 
    1212 
    1313; nick : 
    1414;    nick to use for the bot 
    15 nick = Phergie 
     15nick = 
    1616 
    1717; altnick0 : 
     
    1919;    is unavailable; this setting may be repeated with a different number 
    2020;    (i. e. altnick1, altnick2, etc.) 
    21 altnick0 = Phergie_ 
    22 altnick1 = Phergie__ 
    23 altnick2 = Phergie___ 
     21altnick0 = 
    2422 
    2523; realname : 
    2624;    real name to use for the bot 
    27 realname = Phergie 
     25realname = 
    2826 
    2927; gender : 
    3028;   M or F to indicate the gender of the bot for instances when the bot must 
    3129;   refer to itself in the third person for actions 
    32 gender = F 
     30gender = 
    3331 
    3432; plugins : 
     
    7977;    a comma-delimited list of channels which the bot should automatically 
    8078;    join upon successfully connecting 
    81 autojoin.channels =  
    82  
    83 ;----------------------------------------------------------------------------- 
    84 ; Karma 
    85 ;----------------------------------------------------------------------------- 
    86  
    87 ; karma.limit : 
    88 ;   Maximum number of karma rating changes allowed per user, per term, per 
    89 ;   day, or 0 for unlimited changes 
    90 karma.limit = 5 
    91  
    92 ; karma.static : 
    93 ;   If non-empty, the static karma response to use for the bot's nick 
    94 karma.static =  
    95  
    96 ;----------------------------------------------------------------------------- 
    97 ; NickServ 
    98 ;----------------------------------------------------------------------------- 
    99  
    100 ; nickserv.password : 
    101 ;    password to use when identifying the bot to NickServ where 
    102 ;    authentication will only take place if a non-empty password is specified 
    103 nickserv.password =  
     79autojoin.channels = 
    10480 
    10581;----------------------------------------------------------------------------- 
     
    131107 
    132108;----------------------------------------------------------------------------- 
    133 ; Urban Dictionary 
     109; Karma 
    134110;----------------------------------------------------------------------------- 
    135111 
    136 ; urbandictionary.key : 
    137 ;    API key for Urban Dictionary 
    138 ;    @see http://www.urbandictionary.com/api.php 
    139 urbandictionary.key =  
     112; karma.limit : 
     113;   Maximum number of karma rating changes allowed per user, per term, per 
     114;   day, or 0 for unlimited changes 
     115karma.limit = 5 
     116 
     117; karma.static : 
     118;   If non-empty, the static karma response to use for the bot's nick 
     119karma.static = 
     120 
     121;----------------------------------------------------------------------------- 
     122; NickServ 
     123;----------------------------------------------------------------------------- 
     124 
     125; nickserv.password : 
     126;    password to use when identifying the bot to NickServ where 
     127;    authentication will only take place if a non-empty password is specified 
     128nickserv.password = 
    140129 
    141130;-----------------------------------------------------------------------------