All pastes #750214 Raw Edit

Mine

public text v1 · immutable
#750214 ·published 2007-10-26 10:23 UTC
rendered paste body
<?php

/* $Id$

   ------------------------------------------------------------------

 | ircBot.class.php                                                   |

 | ------------------------------------------------------------------ |

 | Copyright (C) 2003 Donovan Schönknecht <ds@undesigned.org.za>      |

 | ------------------------------------------------------------------ |

 | Subtility - Core framework for IRC connections and event handling  |

 | ------------------------------------------------------------------ |

 | This program is free software; you can redistribute it and/or      |

 | modify it under the terms of the GNU General Public License as     |

 | published by the Free Software Foundation; either version 2 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 General Public License for more details.                       |

 |                                                                    |

 | You should have received a copy of the GNU General Public License  |

 | along with this program; if not, write to the Free Software        |

 | Foundation, Inc., 59 Temple Place, Suite 330, Boston,              |

 | MA 02111-1307  USA                                                 |

   ------------------------------------------------------------------ */



/* ------------------------------------------------------------------

 | TODO                                                               |

 | ------------------------------------------------------------------ |

 |                                                                    |

 | ------------------------------------------------------------------ |

 | CHANGELOG                                                          |

 | ------------------------------------------------------------------ |

 | 19th/Nov/2003                                                      |

 |  - New onNickChange event                                          |

 |  - Now supports multiple channels                                  |

 | 20th/Sep/2003                                                      |

 |  - Few changes, ircBotPath is now set in bot file, used for paths  |

 |  - New onCTCP and onChanCTCP events                                |

 |   - On any CTCP event the onCTCP/onChanCTCP events will be called  |

 |  - Added onInvite event                                            |

 | 10th/Sep/2003                                                      |

 |  - Now setting stream blocking and write buffer                    |

 |  - Now uses serverPort configuration variable (sorry)              |

 | 1st/Sep/2003                                                       |

 |  - Fixed stayAlive and ircConnect::_rotateServers();               |

 |  - Added mode handling for:                                        |

 |    onOp/onDeop, onVoice/onDevoice, onBan/onUnBan                   |

 | 31st/Aug/2003                                                      |

 |  - Initial public development release                              |

   ------------------------------------------------------------------ */



define("ircBotVersion", "subtility-20031119/php-".phpVersion());



/** Callback events */

define("onPing",       1000);



define("onChanMsg",    1100);

define("onPrivMsg",    1101);

define("onNotice",     1102);



define("onJoin",       1200);

define("onPart",       1201);

define("onKick",       1202);

define("onKill",       1203);

define("onNotInvited", 1204);

define("onBanned", 1204);



define("onTopic",      1300);

define("onQuit",       1301);

define("onVersion",    1302);



define("onMode",       1400);

define("onOp",         1401);

define("onDeOp",       1402);

define("onVoice",      1403);

define("onDeVoice",    1404);

define("onBan",        1405);

define("onUnBan",      1406);



define("onCTCP",       1500);

define("onChanCTCP",   1501);

define("onInvite",     1502);

define("onNickChange", 1504);



include(ircBotPath."/lib/ircConf.class.php");



/** Parent bot methods, handles socket and log streams */

class ircConnect {

  var $log;

  var $conn;

  var $servers;

  var $lastServer;

  var $currentServer;

  var $retryConnections;

  var $retryCount = 0;

  



  function _connect() {

    $this->_rotateServers();

    $port = ircConf::get("serverPort");

    $this->log("Connecting to $this->currentServer:$port (#".($this->retryCount + 1).")");

    if (!isset($this->conn) || !@is_resource($this->conn)) {

      if ($this->conn = @fSockOpen($this->currentServer,

      $port, $error, $errMsg, 120)) {

        /* For fGets() - wait for data to become available to stream */

        stream_set_blocking($this->conn, true);

        stream_set_timeout($this->conn, 120);

        stream_set_write_buffer($this->conn, 0);

        $this->log("Connected to $this->currentServer:$port");

        return true;

      }

    }

    $this->retryCount++;

    $this->log("Can't connect to $this->currentServer:$port ($error): $errMsg");

    sleep(2); /* Retry every 2 seconds (so we don't throttle) */

    return false;

  }

  

  function _rotateServers() {

    if (isset($this->servers[($this->lastServer + 1)])) {

      $this->currentServer = $this->servers[++$this->lastServer];

    } else {

      $this->currentServer = $this->servers[0];

      $this->lastServer = 0;

    }

  }



  function write($string) {

    fPuts($this->conn, $string . "\n");

  }



  function read($length = 2048) {

    if (($data = @fGets($this->conn, $length)) !== false) {

      if (strlen($data) <= 0) return false; /** ignore empty data */

      $data = trim($data);

      return $data;

    }

    return false;

  }



  function close($quitMsg = "*wave*") {

    $this->log("Shutting down...");

    $this->write("QUIT :$quitMsg");

    fClose($this->conn);

    fClose($this->log);

  }



  function log($str) {

    if (ircDebug || ircDoLog)

    fWrite($this->log, sprintf("[%s] %s\n", date("D jS M H:i:s"), $str));

  }



}



/** Extends ircConnect, main bot functionality */

class ircBot extends ircConnect {



  var $callbacks = array();



  function ircBot() {

    /** Retrieve servers */

    $this->servers = ircConf::get("servers");

    $this->currentServer = $this->servers[0];

    $this->lastServer = 0;



    /** Open the log file */

    $logDir = ircBotPath."/log";

    if (!file_exists(ircBotPath."/log")) @mkDir(ircBotPath."/log");

    $this->log = fOpen(ircBotPath."/log/subtility.log", "wb");

    

    $this->log(ircBotVersion);

  }



  function addCallback($event, $method) {

    if (!isset($this->callbacks[$event])) $this->callbacks[$event] = array();

    $this->callbacks[$event][] = $method;

  }



  function callback($code, &$args) {

    if (isset($this->callbacks[$code]) && is_array($this->callbacks[$code])) {

      $args[0] =& $this; /* Return the bot object to callback */

      foreach ($this->callbacks[$code] as $callback) {

        call_user_func_array($callback, $args);

      }

    }

  }



  function init() {

    set_time_limit(0);

    @ob_end_flush();

    ob_implicit_flush(true);

    $this->retryConnections = false;

    do {

      if ($this->_connect()) {

        $this->write("NICK ".ircConf::get("botNick"));

        $this->write("USER ".ircConf::get("botIdent")." 2 3 :".ircConf::get("botName"));

		$this->privMsg("NickServ", "identify ".ircConf::get("botNickServPass"));

        foreach ($GLOBALS["ircConf"]["chans"] as $channel => $data) {

          $this->write("JOIN $channel");

          $this->write("MODE $channel ".$data["modes"]);

        } unset($channel, $data);

        



        do {

          while (($buffer = $this->read()) !== false) {

              if (ircDebug) $this->log($buffer);



              /** Handle events in order of their estimated usage desc */



              /** Privmsgs */

              if (ereg("^:(.+)!(.+)@(.+) PRIVMSG (.+) :(.*)", $buffer, $data)) {



                /** onChanMsg */

                if ($data[4][0] == "#") {

                  /** Channel CTCP's */

                  if (substr($data[5], 0, 1) == chr(1) && substr($data[5], -1) == chr(1)) {

                    $this->callback(onChanCTCP, $data); break;

                  } else {

                    $this->callback(onChanMsg, $data); break;

                  }

                } elseif ($data[4] == ircConf::get("botNick")) {



                  /** Handle CTCP Messages */

                  if (substr($data[5], 0, 1) == chr(1) && substr($data[5], -1) == chr(1)) {

                    /** CTCP versions */

                    if ($data[5] == chr(1)."VERSION".chr(1)) {

                      $this->ctcpNotice($data[1], "VERSION", ircConf::get("versionReply"));



                    /** CTCP Times */

                    } elseif ($data[5] == chr(1)."TIME".chr(1)) {

                      $this->ctcpNotice($data[1], "TIME", date("D M j H:i:s Y"));



                    /** CTCP Fingers, reply random data..

                        (could also add a timer to ircConnect::write() */

                    } elseif ($data[5] == chr(1)."FINGER".chr(1)) {

                      $this->ctcpNotice($data[1], "FINGER",

                      ircConf::get("botName")." (".ircConf::get("botIdent").") ".

                      "Idle " . mt_rand(1, 5) . " seconds");



                    /** CTCP Pings */

                    } elseif (substr($data[5], 0, 5) == chr(1)."PING") {

                      $this->ctcpNotice($data[1], "PING", substr($data[5], 6, -1));

                    }

                    

                    $this->callback(onCTCP, $data);

                    break;

                    

                  /** onPrivMsg - handle all messages directed at us */

                  } else {

                    $this->callback(onPrivMsg, $data);

                    break;

                  }

                }

                

              /** PING? PONG! :) */

              } elseif (ereg("^PING :(.+)", $buffer, $data)) {

                $this->write("PONG " . $data[1]);

                break;



              /** Modes */

              } elseif (ereg("^:(.+)!(.+)@(.+) MODE ([a-zA-Z0-9#_\\\^\{\}\|_-]+) (.*)", $buffer, $data)) {

                $modes = array();

                $modeData = explode(" ", $data[5]);

                $modePos = 1;

                $size = strlen($modeData[0]);

                /** Shift through the raw mode data, collect our neccessary

                    data required for simple and complex modes */

                for ($i = 0; $i < $size; $i++) {

                  if ($modeData[0][$i] == "+" || $modeData[0][$i] == "-") {

                    $lastSwitch = $modeData[0][$i];

                  } else {

                    if (in_array($modeData[0][$i], array("n", "t", "s", "R", "p", "m", "i"))) {

                      $modes[] = array("type" => "simple", "switch" => $lastSwitch,

                      "mode" => $modeData[0][$i], "data" => $lastSwitch . $modeData[0][$i]);

                      /** Obviously some more mode handling needs to be done for

                          the l/k modes */

                    } elseif (in_array($modeData[0][$i], array("o", "v", "b", "l", "k"))) {

                      $modes[] = array("type" => "complex", "switch" => $lastSwitch,

                      "mode" => $modeData[0][$i], "data" => $modeData[$modePos++]);

                      /** Mode data position needs to be shifted */

                    }

                  }

                }

                $this->_handleModes($modes, $data);

                unset($modes, $modeData, $size, $lastSwitch, $modePos);

                break;



              /** Topics */

              } elseif (ereg("^:(.+)!(.+)@(.+) TOPIC (.+) :(.*)", $buffer, $data)) {

                $this->callback(onTopic, $data); break;



              /** Notices */

              } elseif (ereg("^:(.+)!(.+)@(.+) NOTICE (.+) :(.*)", $buffer, $data)) {

                $this->callback(onNotice, $data);

                break;



              /** Channel parts */

              } elseif (ereg("^:(.+)!(.+)@(.+) PART (.+)", $buffer, $data)) {

                $this->callback(onPart, $data);

                break;



              /** Channel joins */

              } elseif (ereg("^:(.+)!(.+)@(.+) JOIN :(.+)", $buffer, $data)) {

                $this->callback(onJoin, $data);

                break;



              /** Channel kicks */

              } elseif (ereg("^:(.+)!(.+)@(.+) KICK (.+) (.+) :(.*)", $buffer, $data)) {

                if ($data[5] == ircConf::get("botNick")) { /* we've been kicked, rejoin */

                  $this->write("JOIN ".$data[4]);

                }

                $this->callback(onKick, $data);

                break;



              /** Quit messages */

              } elseif (ereg("^:(.+)!(.+)@(.+) QUIT :(.*)", $buffer, $data)) {

                $this->callback(onQuit, $data);

                break;

                

              /** Kill messages */

              } elseif (ereg("^:(.+)!(.+)@(.+) KILL (.+) :(.*)", $buffer, $data)) {

                $this->callback(onKill, $data);

                

              /** Nickname taken */

              } elseif (ereg("^:(.+) 433", $buffer, $data)) {

                $botNick = ircConf::get("botNick");

                $this->write("NICK ".$botNick."_");

                if (($nickPass = ircConf::get("botNickServPass")) !== false) {

                  $this->privMsg("NickServ", "GHOST ".$botNick." ". $nickPass);

                  sleep(1); /** Wait then retry our nick and join channels */

                  $this->write("NICK ".$botNick);

                  foreach ($GLOBALS["ircConf"]["chans"] as $channel => $data) {

                    $this->write("JOIN $channel");

                    $this->write("MODE $channel ".$data["modes"]);

                  } unset($botNick, $channel, $data, $nickPass);

                } else {

                  $this->log("Nickname is taken, can't GHOST - botNickServPass not set");

                }

                unset($botNick);

                break;

                

              /** Nick changes */

              } elseif (ereg("^:(.+)!(.+)@(.+) NICK :(.+)", $buffer, $data)) {

                 $this->callback(onNickChange, $data);

                 break;



              /** Invites */

              } elseif (ereg("^:(.+)!(.+)@(.+) INVITE (.+) :(.+)", $buffer, $data)) {

                 $this->callback(onInvite, $data);

                 break;

                

              /** Error messages */

              } elseif (ereg("^ERROR :(.+)", $buffer, $data)) {

                $this->log("ERROR: ".implode("] [", $data));

                $this->log("Recieved ERROR, reconnecting");

                break ;

              } elseif ($data[1] == 473) {

              	$this->callback(onNotInvited, $data);

		break;

              	}



              /** END Hanlders */

            unset($buffer, $data);

          }

        /** Check socket */

        } while ((!feOf($this->conn) && is_resource($this->conn)));

        $this->log("Connection failed, cleaning up socket");

        fClose($this->conn);

        $this->retryConnections = true;

      } else {

        $this->retryConnections = true;

      }

      unset($this->conn);

      if ($this->retryCount >= 5) {

        if (!ircConf::get("stayAlive")) {

          $this->retryConnections = false;

        } else {

          $this->retryCount = 0;

          $this->log("Stay-Alive: trying again in 30 seconds\n");

          sleep(30);

        }

        

      }

    } while ($this->retryConnections);

    $this->log("Finished, shutting down");

    fClose($this->log);

    return false;

  }

  

  function _handleModes($modes, $data) {

    foreach ($modes as $mode) {

      $data[5] = $mode["data"];

      switch ($mode["mode"]) {

        case 'o': ($mode["switch"] == "+") ?

          $this->callback(onOp, $data) : $this->callback(onDeOp, $data);

        break;

        case 'v': ($mode["switch"] == "+") ?

          $this->callback(onVoice, $data) : $this->callback(onDeVoice, $data);

        break;

        case 'b': ($mode["switch"] == "+") ?

          $this->callback(onBan, $data) : $this->callback(onUnBan, $data);

        break;

        default:

          $this->callback(onMode, $data);

        break;

      }

    }

  }

  

  function notice($to, $message) {

    $this->write("NOTICE ".$to." :".$message);

  }



  function privMsg($to, $message) {

    $this->write("PRIVMSG ".$to." :".$message);

  }



  function ctcpNotice($to, $type, $string) {

    $this->write("NOTICE $to :".chr(1).strToUpper($type)." ".$string.chr(1));

  }

  

  function mode($target, $mode) {

    $this->write("MODE $target $mode");

  }





	function umode ($chan = NULL, $nick, $mode = NULL){

		if (!$mode) continue;

		else{

			if(!$chan) $this->write('MODE '.$nick.' '. $mode .'');

			else $this->write('MODE ' . $chan . ' ' . $mode . ' ' . $nick . '');

		}

	} 

	

	function cmode ($target, $newmode = NULL){

	if (!$newmode) continue;

	else $this->write('MODE ' . $target . ' ' . $newmode . '');

	}



	function kick ($chan, $nick, $reason = NULL){

		$this->write('KICK ' . $chan . ' ' . $nick. ' :'. $reason);

	} 







}



?>