School Assignment? Project Due Tomorrow? Chat LIVE With A Programming Expert!

Welcome to Dream.In.Code
Become an Expert!

Join 307,003 Programmers for FREE! Get instant access to thousands of experts, tutorials, code snippets, and more! There are 1,970 people online right now. Registration is fast and FREE... Join Now!




Creating an IRC Bot in PHP

 
Reply to this topicStart new topic

> Creating an IRC Bot in PHP, how to make sockets useful

Auzzie
Group Icon



post 22 Jan, 2009 - 01:56 PM
Post #1


By the end of this tutorial you will end up with a functioning IRC bot that can connect to a server, join channels, give and take operator, half operator, voice and protected statuses from a user. In the next tutorial I will discuss the different ways to “log” certain parts of the information processed by the bot, how to build a statistics table of various statistics like who has been on the longest, speaks the most, etc.

Right a few assumptions are being made: you have a text editor, at least a basic knowledge of object orientated PHP, access to an IRC client, a web server with PHP 5.3.0 preferably, if not a work around is needed later in the tutorial.

So now we have all of that out of the way lets get down to business. First thing we need to do is set a couple of PHP settings dynamically with these two lines of code:

CODE

<?php
set_time_limit(0);
ini_set('display_errors', 'on');


set_time_limit is a PHP directive that says how long PHP should try to complete the script before it times out (in seconds), by setting it to 0 we are basically telling PHP never to time out on the script.

The other line is just telling PHP to let us know about any warnings or errors that the script encounters, helps for when we are testing new features.

Next we need to set up a couple of config values so that the bot can connect to the right IRC server properly...

CODE

$config = array(
        'server' => 'example.com',
        'port' => 6667,
        'nick' => 'nicknick',
        'name' => 'IhaveNoName',
        'pass' => 'meh',
    );


I set it up in an array to make it easier to access, within that example you need to change the values. The server value should be set to the IRCd's address like irc.cyberarmy.net for example. The default IRC port is 6667 so you won't change that unless you need to. The nick value is your nickname (screen name) as seen by the other users. The name value stores the user's “real name” and the pass is for registering with NickServ.

Now the enviroment has been set up and we can start working on the bulk of the code, the main class. I decided to create its own class for the simple reason, I like OO programming and it is a lot neater.

First thing we do is declare to PHP that we are using a class and then we will define the two objects that we will use throughout the rest of the script.

CODE

class IRCBot {
    //This is going to hold our TCP/IP connection
    var $socket;

    //This is going to hold all of the messages both server and client
    var $ex = array();


In OO PHP coding, to declare and object of a class you use the keyword var followed by the variables same (yes still with the $).

Next piece of code is going to be the construct, in PHP5 onwards the construct is a function that automatically gets called whenever a class is called, and its opposite is the destruct which isn't covered in this tutorial.

CODE

/*

     Construct item, opens the server connection, logs the bot in



     @param array

*/
function __construct($config)
{
    $this->socket = fsockopen($config['server'], $config['port']);
    $this->login($config);
    $this->main();
    $this->send_data('JOIN', '#chat');
}


Lets break this down and step through the code.

CODE

function __construct($config)


Due to the face that construct is a key feature in PHP it is prefixed with __ (two underscores), you don't have to pass any parameters to this function but if you remember the config we did near the top of the file, well that is what we will be passing to the construct.

CODE

$this->socket = fsockopen($config['server'], $config['port']);


To access an object from within a class you need to use the keyword $this-> (if you want to learn more check the php.net documentation). What this line does is opens a socket (connection to the server) using the server and port parameters that we set in $config. There are a lot more parameters that you can pass to fsockopen but we don't need them so I won't go into them.

If you used the php script so far (making sure you close the class off) and called the class, you would have an active connection to the server and nothing else.

CODE

$this->login($config);


The next thing we need to do is tell the bot it needs to “login” to the IRCd, but this function will be discussed a little later.

CODE

$this->main();


This will execute the main function in our script which handles all of the server messages etc.

CODE

$this->send_data('JOIN', '#chat');


This calls our send_data function which will be discussed in a minute but what we are doing is telling the bot to issue the JOIN (join channel) command and join the #chat channel.

Right that is our construct function finished so the next bit that we are going to look at is the login function.

CODE

/*

     Logs the bot in on the server



     @param array

*/

function login($config)

{

    $this->send_data('USER', $config['nick'].' acidavengers.co.uk '.$config['nick'].' :'.$config['name']);

    $this->send_data('NICK', $config['nick']);

}


The sole purpose of this function is to just get the bot to login (register) with the IRC server. To do this it needs to do two things. First off we need to tell the server who we are, so we use the send_data function to send the USER command, the information we need to send looks like this (nick host nick:realname). Once you have told the server that the bot is a user you need to send the bot's nickname so it ban be interacted with.

The next thing we need to do is create the function main(). As the name suggests this function is going to be the most used function. To make it work the way we need to we will need to code in an infinite loop. Yes, I know that coding 101 tells you never to create an infinite loop, but we will be doing so under controlled circumstances.

CODE

/*

     This is the workhorse function, grabs the data from the server and displays on the browser

    */

    function main()

    {

        $data = fgets($this->socket, 128);

        echo nl2br($data);

        flush();

        $this->ex = explode(' ', $data);


Lets look at this extract, first thing we do is we store the first 128 characters (or until it finds a newline character \n) from the socket and then store it in $data. The next thing the bot will do is output that data to the browser but converting all of the newline characters to HTML newlines (<br>). Then we flush the output buffer and explode (split) the $data variable's contents into the $ex object. The reason why we do this is so we can access it later on.

To be able to keep an active connection on IRC the server, the server periodically sends a command (PING) plus a code which IRC clients reply to with the PONG command and the exact same code. Considering our bot isn't using an IRC client we need to wait for the PING command and reply to it ourselves.

All commands sent by the server will always be stored in $this->ex[0] because it is the first word to be sent. So what we do is a simple if statement to see if $this->ex[0] matches PING, if it does, we then need to use our send_data function to reply to that with PONG and the code that the server sent (stored in $this->ex[1]). The following code does just that.

CODE

if($this->ex[0] == 'PING')

        {

            $this->send_data('PONG', $this->ex[1]); //Plays ping-pong with the server to stay connected.

        }


Next thing our bot needs to do is to is to keep an eye out for any commands that has been sent by a user and then process it.
CODE


$command = str_replace(array(chr(10), chr(13)), '', $this->ex[3]);



switch($command) //List of commands the bot responds to from a user.

{

    case ':!join':

        $this->join_channel($this->ex[4]);

        break;



    case ':!quit':

        $this->send_data('QUIT', 'acidavengers.co.uk made Bot');

        break;



    case ':!op':

        $this->op_user();

        break;



    case ':!deop':

        $this->op_user('','', false);

        break;


    case ':!voice':
        $this->voice_user();
        break;


    case ':!devoice':
        $this->voice_user('','',false);
        break;

    case ':!protect':

        $this->protect_user();

        break;

}


From this snippit you can see that we have used a switch statement to work out that to do with the command that is sent to the bot, you might wonder why they begin with : (colon) this is because of the way in which information is sent to the socket. All that is left is to loop back on itself by calling the main function again.

CODE

    $this->main();

}


That is all that should be needed to talk about for this tutorial so here is the full code listing.

CODE

<?php

//So the bot doesnt stop.

set_time_limit(0);

ini_set('display_errors', 'on');


    //Example connection stuff.

    $config = array(
        'server' => 'example.com',
        'port' => 6667,
        'nick' => 'nicknick',
        'name' => 'IhaveNoName',
        'pass' => 'meh',
    );
      

class IRCBot {

    //This is going to hold our TCP/IP connection

    var $socket;



    //This is going to hold all of the messages both server and client

    var $ex = array();



    /*

     Construct item, opens the server connection, logs the bot in



     @param array

    */

    function __construct($config)

    {

        $this->socket = fsockopen($config['server'], $config['port']);

        $this->login($config);

        $this->main();

        $this->send_data('JOIN', '#chat');

    }



    /*

     Logs the bot in on the server



     @param array

    */

    function login($config)

    {

        $this->send_data('USER', $config['nick'].' acidavengers.co.uk '.$config['nick'].' :'.$config['name']);

        $this->send_data('NICK', $config['nick']);

    }



    /*

     This is the workhorse function, grabs the data from the server and displays on the browser

    */

    function main()

    {

        $data = fgets($this->socket, 128);

        echo nl2br($data);

        flush();

        $this->ex = explode(' ', $data);



        if($this->ex[0] == 'PING')

        {

            $this->send_data('PONG', $this->ex[1]); //Plays ping-pong with the server to stay connected.

        }



        $command = str_replace(array(chr(10), chr(13)), '', $this->ex[3]);



        switch($command) //List of commands the bot responds to from a user.

        {

            case ':!join':

                $this->join_channel($this->ex[4]);

                break;



            case ':!quit':

                $this->send_data('QUIT', 'acidavengers.co.uk made Bot');

                break;



            case ':!op':

                $this->op_user();

                break;



            case ':!deop':

                $this->op_user('','', false);

                break;



            case ':!protect':

                $this->protect_user();

                break;

        }



        $this->main();

    }



    function send_data($cmd, $msg = null) //displays stuff to the broswer and sends data to the server.

    {

        if($msg == null)

        {

            fputs($this->socket, $cmd."\r\n");

            echo '<strong>'.$cmd.'</strong><br />';

        } else {

            fputs($this->socket, $cmd.' '.$msg."\r\n");

            echo '<strong>'.$cmd.' '.$msg.'</strong><br />';

        }

    }



    function join_channel($channel) //Joins a channel, used in the join function.

    {

        if(is_array($channel))

        {

            foreach($channel as $chan)

            {

                $this->send_data('JOIN', $chan);

            }

        } else {

            $this->send_data('JOIN', $channel);

        }

    }



    function protect_user($user = '')

    {

        if($user == '')

        {

if(php_version() >= '5.3.0')
            {
                $user = strstr($this->ex[0], '!', true);
            } else {
                $length = strstr($this->ex[0], '!');
                $user   = substr($this->ex[0], 0, $length);
            }
            }

        }



        $this->send_data('MODE', $this->ex[2] . ' +a ' . $user);

    }

            

    function op_user($channel = '', $user = '', $op = true)

    {

        if($channel == '' || $user == '')

        {

            if($channel == '')

            {

                $channel = $this->ex[2];

            }



            if($user == '')

            {

if(php_version() >= '5.3.0')
            {
                $user = strstr($this->ex[0], '!', true);
            } else {
                $length = strstr($this->ex[0], '!');
                $user   = substr($this->ex[0], 0, $length);
            }
            }

        }



        if($op)

        {

            $this->send_data('MODE', $channel . ' +o ' . $user);

        } else {

            $this->send_data('MODE', $channel . ' -o ' . $user);

        }

    }

}


    $bot = new IRCBot($config);

?>


Note: Make sure if you put this on a live server, you might want to watch out as it is a bit of a bandwidth also you need to keep the browser window open because it is a browser script, i havent gotten around to using CLI based PHP yet.

ToDo: Some implimentation for logging and auto join a channel.
Go to the top of the page
+Quote Post


Register to Make This Ad Go Away!

ellisgl
Group Icon



post 27 Jan, 2009 - 11:59 AM
Post #2
I was working on one myself:
CODE
<?php
/**
* IRC Client Class
* 10/23/2008
*
* TODO:
*  Improve plugin system - System and User types.
*  Channel handling
*   - User list
*    - OP link to user
*    - Moderator link to user
*    - Voice link to user
*
*  Callbacks from plugins?
*/
class IRCClient
{
  /**
   * Init the property and assign default values
   */
  private $Host        = '127.0.0.1';    // (STRING)   Server address
  private $Port        = 6667;           // (INT)      Server port
  private $Nick        = 'PHPBot';       // (STRING)   Nickname
  private $Alt         = 'PHPBot2';      // (STRING)   Alternate Nickname
  private $CurrNick    = "";             // (STRING)   The current nick name in use.
  private $Socket      = 0;              // (RESOURCE) Socket resource
  public  $Buffer      = "";             // (STRING)   Buffer
  private $Timeout     = 5;              // (INT)      Timeout in N seconds
  private $Reconnect   = 0;              // (BOOL)     Reconnect on disconnect?
  private $MaxRetries  = 0;              // (INT)      Max reconnect retries
  private $RetryWait   = 0;              // (INT)      Wait time in microseconds (1000000 = 1 second)
  private $PlugInsPath = 'plugins/irc/'; // (STRING)   Path where IRC plugins are located.
  private $PlugIns     = "";             // (ARRAY)    Plugins to load
  private $PluggedIn   = array();        // (ARRAY)    New objects of the plug-ins
  private $Channels    = array();        // (ARRAY)    Open Channels - with users lists.
  private $Debug       = 0;              // (BOOL)     Output debug messages
  private $Error       = "";             // (STRING)   Holder for error messages

   /**
   * Class constructor
   *
   * @param ARRAY $config
   */
  public function __construct($Config)
   {
    // Configure the private properties from an array
    // Use foreach loop to do so
    foreach($Config as $Key=>$Val)
     {
      // Set property - variable property name!
      $this->{$Key} = $Val;
     }

    // Load the plugins
    $this->loadPlugIns();
   }

  /**
   * Connect to IRC Server
   *
   * @param INT $init - intial connect?
   * @return BOOL
   */
  public function Connect($Init = 0)
   {
    // Connect to server using
    $this->Socket = @fsockopen($this->Host, $this->Port, $errno, $errstr, $this->Timeout);

    if(!$this->CheckConnection($Init))
     {
      die($this->Error."\r\n");
     }

    // Register with the server.
    $this->Register();

    // Return 1 (GOOD!) - AKA TRUE
    return 1;
   }

  /**
   * Return the error mssage
   *
   * @return STRING
   */
  public function GetError()
   {
    return $this->Error;
   }

  /**
   * Read from the server
   */
  public function Read($Bytes = 128)
   {
    // Are we stil connected?
    if(!$this->CheckConnection())
     {
      die($this->Error."\r\n");
     }

    // Put data into our buffer and test it.
    $this->Buffer = fgets($this->Socket, $Bytes);

    // Debug console messages
    if(!empty($this->Buffer))
     {
      $this->DebugMsg('[RCVD] '.$this->Buffer);

      // Respond to pings from server
      $this->Pong();

      // Return buffer
      return $this->Buffer;
     }
   }

  /**
   * Write to the server - auto new line
   *
   * @param STRING $data
   */
  public function Write($Data)
   {
    // Are we stil connected?
    if(!$this->CheckConnection())
     {
      die($this->Error."\r\n");
     }

    fputs($this->Socket, $Data."\n");
    $this->DebugMsg('[SENT] '.$Data);
   }

  /**
   * Join a channel - auto #
   *
   * @param STRING $chan
   */
  public function Join($Channel)
   {
    // Are we stil connected?
    if(!$this->CheckConnection())
     {
      die($this->Error."\r\n");
     }

    $this->Channels[$Channel] = 1;
    $this->Write('JOIN #'.$Channel);
   }

  /**
   * Leave a channel
   *
   * @param STRING $chan
   */
  public function Part($Channel)
   {
    unset($this->Channels[$Channel]);
    $this->Write('PART #'.$Channel);
   }

  /**
   * Register with the server
   */
  public function Register()
   {
    // Are we stil connected?
    if(!$this->CheckConnection())
     {
      die($this->Error."\r\n");
     }

    $this->Write('PASS NOPASS');                        //Sends the password not needed for most servers
    $this->Write('NICK '.$this->Nick);                  //sends the nickname -- Need to check to see nick is taken
    $this->Write('USER '.$this->Nick.' USING PHP IRC'); //sends the user must have 4 paramters    `

    $this->CurrNick = $this->Nick;

    while(1)
     {
      $this->Buffer = $this->Read(); // Get data from host

      // Nick name in use.. Use alternate
      // Error will show before MOTD.
      if(strpos($this->Buffer, ' 433 * '.$this->Nick))
       {
        $this->Write('NICK '.$this->Alt);
        $this->CurrNick = $this->Alt;
       }

      // Wait for the MOTD
      if(strpos($this->Buffer, ' 001 '.$this->Nick))
       {
        $this->DebugMsg('Ready to go!');
        break;
       }
     }
   }

  /**
   * Quit IRC
   *
   * @param STRING $msg
   */
  public function Quit($Msg = 'Buh bye!')
   {
    die('Terminiated.'."\r\n");
   }

  /**
   * Output debug messages
   * @param STRING $msg
   */
  public function DebugMsg($Msg)
   {
    if($this->Debug && !empty($Msg))
     {
      echo '['.date('h:i:s Ymd').']'.$Msg,"\r\n";
     }
   }

  /**
   * Respond to pings from the server
   */
  public function Pong()
   {
    // If the server has sent the ping command
    if(substr($this->Buffer, 0, 6) == 'PING :')
     {
      $this->Write('PONG :'.substr($this->Buffer, 6)."\n\r");
     }
   }

  /**
   * Returns the buffer
   * @return STRING
   */
  public function GetBuffer()
   {
    return $this->Buffer;
   }

  /**
   * Load selected plug-ins
   */
  public function LoadPlugIns()
   {
    if($this->PlugIns)
     {
      foreach($this->PlugIns as $Val)
       {
        include($this->PlugInsPath.$Val.'.class.php');
        $this->PluggedIn[$Val] = new $Val($this);
       }
     }
   }

  /**
   * Run the plugins
   */
  public function RunPlugIns($Buff = 0)
   {
    if($this->PlugIns)
     {
      // Plugins where defined
      if(!$Buff)
       {
        // Use standard buffer from class
        $Buff = $this->Buffer;
       }

      // Loop all the plugins
      foreach($this->PlugIns as $Val)
       {
        // Run the plugins
        $this->PluggedIn[$Val]->run($buff);
       }
     }
   }

  /**
   * Return the file pointer for the socket
   */
  public function GetSocket()
   {
    return($this->Socket);
   }

  /**
   * Return the nick that is currently being used.
   */
  public function GetCurrNick()
   {
    return $this->CurrNick;
   }

  public function SetBlocking($Var)
   {
    socket_set_blocking($this->Socket, $Var);
   }

  private function CheckConnection($Init = 0)
   {
    if(feof($this->Socket))
     {
      $this->DebugMsg('[Connection]: Disconnected');

      // Connection failed
      if($this->Reconnect)
       {
        // Try to reconnect
        $Tries        = Intval(0);
        $this->Socket = 0;

        do
         {
          if($Tries >= $this->MaxRetries)
           {
            // Hit the max # of retries
            if($Init === 1)
             {
              // Initial connect
              $this->Error = 'Could not connect to: '.$this->Host;
             }
            else
             {
              // Disconnected after inital connection
              $this->Error = 'Could not reconnect to: '.$this->Host;
             }

            // Return 0 (ERROR!) - AKA FALSE
            return 0;
           }

          // Try to reconnect
          $this->Socket = @fsockopen($this->Host, $this->Port, $errno, $errstr, $this->Timeout);

          // Pre increment (faster) the number of attempts.
          ++$Tries;

          if(!$this->Socket)
           {
            // Not connect, wait before retrying.
            usleep($this->RetryWait);
           }
         }while(!$this->Socket);

        // Re Register
        $this->Register();

        // Rejoin channels
        foreach($this->Channels as $Channel=>$val)
         {
          $this->Join($Channel);
         }

        $this->DebugMsg('[Connection]: Reconnected.');

        // Return 1 (GOOD!) - AKA TRUE
        return 1;
       }
      else
       {
        $this->Error = 'Could not connect to: '.$this->Host;
        return 0;
       }
     }
    else
     {
      // Return 1 (GOOD!) - AKA TRUE
      return 1;
     }
   }
}


Needs a bit more work - I know I have figured out a lot better ways of doing the reconnect stuff.

This post has been edited by ellisgl: 27 Jan, 2009 - 12:03 PM
Go to the top of the page
+Quote Post

bairey
*



post 26 Feb, 2009 - 06:52 PM
Post #3
Thanks so much for this.. I c/pd the full code and edited the few vars that need it, but am getting this error:

Parse error: syntax error, unexpected T_VARIABLE, expecting T_FUNCTION in /home/baireyc1/public_html/noop-dogg-irc/bot.php on line 135

After I took out newlines that comes out to:

CODE
$this->send_data('MODE', $this->ex[2] . ' +a ' . $user);


Turns out you have an extra closing curly brace just above that line. Just FYI, works awesome!
Go to the top of the page
+Quote Post

Auzzie
Group Icon



post 27 Feb, 2009 - 03:43 PM
Post #4
QUOTE(bairey @ 27 Feb, 2009 - 02:52 AM) *

Thanks so much for this.. I c/pd the full code and edited the few vars that need it, but am getting this error:

Parse error: syntax error, unexpected T_VARIABLE, expecting T_FUNCTION in /home/baireyc1/public_html/noop-dogg-irc/bot.php on line 135

After I took out newlines that comes out to:

CODE
$this->send_data('MODE', $this->ex[2] . ' +a ' . $user);


Turns out you have an extra closing curly brace just above that line. Just FYI, works awesome!

Glad you like it, post up a link so people can see it working if you want wink2.gif
Go to the top of the page
+Quote Post


Reply to this topicStart new topic
2 User(s) are reading this topic (2 Guests and 0 Anonymous Users)
0 Members:

 


Lo-Fi Version Time is now: 11/21/09 06:33AM

Live Help!

Be Social

Dream.In.Code RSS Feed Dream.In.Code LinkedIn Group Follow Us On Twitter Fan Us On Facebook

Tutorials

Programming

Web Development

Reference Sheets

Code Snippets

DIC Chatroom

Bye Bye Ads

Monthly Drawing

Thumb Drive

Top Contributors

Top 10 Kudos This Month