Location: PHPKode > projects > MpiBot > system/MPIBot.class.php
<?php
	/* (C) Copyright 2004 Armond Carroll.  All rights reserved.
	   Distributed under the BSD License.  Please see license.txt for license,
	   or http://www.opensource.org/licenses/bsd-license.php.

	   http://mpibot.sourceforge.net

	   Purpose of this file:
	   	This is the actual bot.

	   File Version: 1.5
	   Last Modified: 4/30/2004 12:53AM
	*/

	class MPIBot {
		private $sock, $servers, $modules = array();
		public $nickname, $cfg, $db;

		/* Special/Core Functions */

		// Init:
		//	This used to be the constructor, but was moved to init because of scope
		//	problems within modules. (Modules were unable to access $bot->...)
		public function init() {
			global $cfg;
			$this->cfg = $cfg;

			// Load Modules
			foreach($cfg['irc']['modules'] as $m) {
				eval('if(!isset($this->modules["$m"])) $this->modules["$m"] = new $m(); else error("There are two or more modules name \'$m\' loaded.  Modules must have unique names.");');
			}

			// Make sure the modules' ancestors are type 'BaseModule'
			foreach($this->modules as $m) {
				if(get_parent_class($m) != 'BaseModule') {
					error('Module \'' . get_class($m) . '\' is not of ancestry to BaseModule');
				}
			}

			// Since the mySQLi object is somewhat "special", load it here
			if(@$cfg['db']['use'] == true) {
				$this->db = @new mysqli($cfg['db']['hostname'], $cfg['db']['username'], $cfg['db']['password'], $cfg['db']['database']);
				// The manual said to use !$this->db, but that doesn't seem to work well...
				if(mysqli_connect_error()) {
					error('Could not connect to mySQL: ' . mysqli_connect_error() . '; If you do not want mySQL support, turn db:use to false', OL_WARNING);
					$this->db = NULL;
				}
			} else {
				$this->db = NULL;
			}
		}
		// __destruct()
		//	Call quit(), and close the mySQL connection if needed.
		public function __destruct() {
			$this->quit();
			if($this->db != NULL) $this->db->close();
			echo "MPIBot Shutting down... ", EOL, EOL;
		}

		/* Low level socket functions */

		// Send:
		//	Send formatted data to the socket, and (NYI) optionally log/print it.
		public function send($w) {
			out($w, OL_PROTOSEND);
			return fputs($this->sock, "$w\r\n");
		}

		// Recv:
		//	Receive raw messages from the client.
		//	Note: Written in such an odd way to discard
		//	NULL messages some servers send an abundance of
		public function recv() {
			while(empty($d)) {
				$d = trim(fgets($this->sock));
			}
			out($d, OL_PROTORECV);

			foreach($this->modules as $m) {
				$m->on_recv($d);
			}

			return $d;
		}

		/* Connect, Keep, Disconnect Routines */

		// Connect:
		//	Open the socket, and send the NICK+USER login
		//	sequence, handle in-use/bad/no nicknames, store
		//	some initial data, and finally once 001 is received,
		//	pass off to main()
		public function connect() {
			if(is_resource($this->sock)) { error('Attempting to connect, while already connected to a server', warning); return 0; }

			// Keep trying to connect until we get a good socket
			do {
				// Get Server
				$server = current($this->cfg['irc']['servers']);
				next($this->cfg['irc']['servers']);

				if(current($this->cfg['irc']['servers']) == false)
					reset($this->cfg['irc']['servers']);

				if(strpos($server, ':') === false) $server = array($server, 6667);
				else {
					$server = explode(':', $server);
				}

				// Actually Connect
				out("Connecting to $server[0] on $server[1]... ", OL_INFO, true, false);
				$this->sock = @fsockopen($server[0], $server[1], $errno, $errstr, $this->cfg['irc']['timeout']);
				if($this->sock) out('Connected.', OL_INFO, false, true);
				else out('Failed.', OL_INFO, false, true);
			} while(!$this->sock);

			// Were now connected to the server.  Send NICK+USER
			$this->nickname = $this->cfg['irc']['nickname'];
			$this->send('NICK ' . $this->cfg['irc']['nickname']);
			$this->send('USER ' . $this->cfg['irc']['username'] . ' 0 0 :' . $this->cfg['irc']['realname']);

			// Loop and stay until we reach 001
			while(!feof($this->sock)) {
				$info = $this->parse($this->recv());
				switch($info['command']) {
					case '001':
						foreach($this->modules as $m) {
							$m->on_connect();
						}
						break 2;
					case '431':	// ERR_NONICKNAMEGIVEN
					case '432':	// ERR_ERRONEUSNICKNAME
					case '433':	// ERR_NICKNAMEINUSE
					case '436':	// ERR_NICKCOLLISION
						$ltr = mt_rand(65, 90);
						$nick = strtolower($ltr . substr($this->nickname, 0, 8));
						$this->send('NICK ' . $nick);
						$this->nickname = $nick;
						break;
					case 'PING':
						$this->send('PONG ' . $info['parameter']);
						break;
					case 'ERROR': // Old bug.. Break 2 so we can try to reconnect
						out('Disconnected from server.');
						break 2;
				}
			}
		}

		// Main:
		//	Keeps bot alive, and provides handling for
		//	additional module support
		public function main() {
			if(!is_resource($this->sock)) return 0;

			while(!feof($this->sock)) {
				$info = $this->parse($this->recv());
				switch($info['command']) {
					case 'PING':
						$this->send('PONG ' . $info['parameter']);
					break;
					case 'PRIVMSG':
						if(is_array($info['privmsg'])) {
							foreach($this->modules as $m) {
								$m->on_msg($info);
							}
							if(is_array($info['trigger'])) {
								foreach($this->modules as $m) {
									$m->on_trigger($info);
								}
							}
						}
					break;
				}
			}
		}

		// Quit:
		//	Sends QUIT to server and closes the socket
		public function quit($msg = NULL) {
			foreach($this->modules as $m) {
				$m->on_quit();
			}

			if(is_resource($this->sock)) {
				if($msg == NULL) $msg = $this->cfg['irc']['quitmsg'];
				@$this->send("QUIT :$msg");
				@fclose($this->sock);
			}
		}

		/* Parsing Routines */
		// Parse:
		//	Input a raw message from the server, returns a pretty formatted
		//	version.  Its rather tedious for a very used function.  If anyone
		//	knows a faster way, please tell me.
		public function parse($data) {
			// First parse: who, command, parameter
			if($data[0] != ':') {
				list($command, $parameter) = explode(' ', $data, 2);
			} else {
				list($who, $command, $parameter) = explode(' ', $data, 3);
				$who = substr($who, 1);
			}
			// Trim : from parameter, if present
			if($parameter[0] == ':') $parameter = substr($parameter, 1);

			// Now figure out $who
			if(!isset($who) || strpos($who, '@') === false) {
				$who = from_server;
			} else {
				list($nick, $rest) = explode('!', $who);
				list($user, $host) = explode('@', $rest);
				if($user[0] == '~') { $ident = false; $user = substr($user, 1); } else { $ident = true; }
				$who = array('nickname' => $nick, 'username' => $user, 'hostname' => $host, 'ident' => $ident);
			}

			if($command == 'PRIVMSG') {
				list($to, $msg) = explode(' ', $parameter, 2);
				if($to[0] == '#' || $to[0] == '&') { $reply_to = $to; $type = 'public'; } else { $reply_to = $who['nickname']; $type = 'private'; }
				if($msg[0] == ':') $msg = substr($msg, 1);
				$privmsg = array('to' => $to, 'reply-to' => $reply_to, 'type' => $type, 'msg' => $msg);
			}

			// Easy access to stuff for on_trigger
			if($who != from_server && $command == 'PRIVMSG' && $privmsg['msg'][0] == $this->cfg['irc']['triggerchar']) {
				@list($trigger, $trigger_parameter) = explode(' ', $privmsg['msg'], 2);
				$trigger = substr($trigger, 1); // Remove .
				$trigger = array('command' => $trigger, 'parameter' => $trigger_parameter);
			}
			return array('string' => $data, 'who' => @$who, 'command' => $command, 'parameter' => $parameter, 'privmsg' => @$privmsg, 'trigger' => @$trigger);
		}

		/* Protocol Methods */

		// Join:
		// 	Sends JOIN to the server and adds the channel
		//	to the channel list array.  Error handling
		//	(such as +b) should be done in main()
		public function join($chan, $key = NULL) {
			out("Attempting to join $chan...", OL_INFO);
			$this->send("JOIN $chan $key");
			foreach($this->modules as $m) {
				$m->on_joinchan($chan, $key);
			}
		}

		// Part:
		//	Parts a channel.  Error handling (if any)
		//	should be done in main()
		public function part($chan, $msg = NULL) {
			out("Parting $chan...", OL_INFO);
			if($msg == NULL) $msg = $this->cfg['irc']['partmsg'];
			$this->send("PART $chan");
			foreach($this->modules as $m) {
				$m->on_partchan($chan);
			}
		}

		// Say:
		//	Say something to a channel or person
		public function say($to, $what) {
			$this->send("PRIVMSG $to :$what");
		}

		// Nick:
		//	Change the bot's nickname
		public function nick($nick) {
			$this->nickname = $nick;
			$this->send("NICK $nick");
		}
	}

	// :)
?>
Return current item: MpiBot