Added auto reconnect support.
authorDan
Sun, 07 Dec 2008 01:55:03 -0500
changeset 30 2cfcd2801e5a
parent 29 300f673fbbdc
child 31 d75124700259
Added auto reconnect support.
enanobot.php
libirc.php
--- a/enanobot.php	Thu Dec 04 19:40:27 2008 -0500
+++ b/enanobot.php	Sun Dec 07 01:55:03 2008 -0500
@@ -98,6 +98,7 @@
 $irc = new Request_IRC($server);
 $irc->connect($nick, $user, $name, $pass);
 $irc->set_privmsg_handler('enanobot_privmsg_event');
+$irc->set_timeout_handlers(false, 'enanobot_timeout_event');
 
 foreach ( $channels as $channel )
 {
@@ -191,6 +192,65 @@
   }
 }
 
+function enanobot_timeout_event($irc)
+{
+  // uh-oh.
+  $irc->close();
+  if ( defined('LIBIRC_DEBUG') )
+  {
+    $now = date('r');
+    echo "!!! [$now] Connection timed out; waiting 10 seconds and reconnecting\n";
+  }
+  
+  // re-init
+  global $server, $nick, $user, $name, $pass, $channels, $libirc_channels;
+  
+  // wait until we can get into the server
+  while ( true )
+  {
+    sleep(10);
+    if ( defined('LIBIRC_DEBUG') )
+    {
+      $now = date('r');
+      echo "... [$now] Attempting reconnect\n";
+    }
+    $conn = @fsockopen($server, 6667, $errno, $errstr, 5);
+    if ( $conn )
+    {
+      if ( defined('LIBIRC_DEBUG') )
+      {
+        $now = date('r');
+        echo "!!! [$now] Reconnection succesful, ghosting old login\n";
+      }
+      fclose($conn);
+      break;
+    }
+    else
+    {
+      if ( defined('LIBIRC_DEBUG') )
+      {
+        $now = date('r');
+        echo "!!! [$now] Still waiting for connection to come back up\n";
+      }
+    }
+  }
+  
+  $libirc_channels = array();
+  
+  // we were able to get back in; ask NickServ to GHOST the old nick
+  $irc->connect("$nick`gh", $user, $name, false);
+  $irc->privmsg('NickServ', "GHOST $nick $pass");
+  $irc->change_nick($nick, $pass);
+  
+  foreach ( $channels as $channel )
+  {
+    $libirc_channels[$channel] = $irc->join($channel, 'enanobot_channel_event');
+    $channel_clean = preg_replace('/^[#&]/', '', $channel);
+    $libirc_channels[$channel_clean] =& $libirc_channels[$channel];
+    $irc->privmsg('ChanServ', "OP $channel $nick");
+  }
+}
+
 if ( $_shutdown )
 {
   exit(2);
--- a/libirc.php	Thu Dec 04 19:40:27 2008 -0500
+++ b/libirc.php	Sun Dec 07 01:55:03 2008 -0500
@@ -55,6 +55,20 @@
   private $privmsg_handler = false;
   
   /**
+   * The function called when a timeout is suspected.
+   * @var string
+   */
+  
+  private $timeout_warning_handler = false;
+  
+  /**
+   * The function called when a timeout is confirmed.
+   * @var string
+   */
+  
+  private $timeout_error_handler = false;
+  
+  /**
    * Switch to track if quitted or not. Helps avoid quitting the connection twice thus causing write errors.
    * @var bool
    * @access private
@@ -105,7 +119,7 @@
    * @param int Flags, defaults to 0.
    */
   
-  public function connect($nick, $username, $realname, $pass, $flags = 0)
+  public function connect($nick, $username, $realname, $pass = false, $flags = 0)
   {
     // Init connection
     $this->sock = fsockopen($this->host, $this->port);
@@ -135,10 +149,12 @@
     }
     
     // identify to nickserv
-    $this->privmsg('NickServ', "IDENTIFY $pass");
+    if ( $pass )
+      $this->privmsg('NickServ', "IDENTIFY $pass");
     
     $this->nick = $nick;
     $this->user = $username;
+    $this->quitted = false;
   }
   
   /**
@@ -164,7 +180,7 @@
    * @return string
    */
   
-  public function get()
+  public function get($timeout = 1)
   {
     if ( !$this->sock )
     {
@@ -172,11 +188,18 @@
         echo "<<< READ FAILED\n";
       return false;
     }
-    $out = fgets($this->sock, 4096);
-    if ( defined('LIBIRC_DEBUG') )
-      if ( !empty($out) )
-        echo "<<< $out";
-    return $out;
+    if ( ($c = stream_select($r = array($this->sock), $w = null, $e = null, $timeout)) !== false )
+    {
+      if ( $c > 0 )
+      {
+        $out = fgets($this->sock, 4096);
+        if ( defined('LIBIRC_DEBUG') )
+          if ( !empty($out) )
+            echo "<<< $out";
+        return $out;
+      }
+    }
+    return false;
   }
   
   /**
@@ -201,16 +224,52 @@
   
   public function event_loop()
   {
-    stream_set_timeout($this->sock, 0xFFFFFFFE);
-    while ( $data = $this->get() )
+    $timeout = 180;
+    $timeout_warn = false;
+    while ( true )
     {
-      $data_trim = trim($data);
+      $data = $this->get($timeout);
+      $data_trim = $data ? trim($data) : false;
+      if ( empty($data_trim) )
+      {
+        if ( $timeout_warn )
+        {
+          // timeout confirmed :-/
+          if ( $this->timeout_error_handler )
+          {
+            $result = @call_user_func($this->timeout_error_handler, $this);
+            if ( $result == 'BREAK' )
+              break;
+          }
+          $timeout = 180;
+          $timeout_warn = false;
+          continue;
+        }
+        // timeout suspected
+        if ( $this->timeout_warning_handler )
+        {
+          $result = @call_user_func($this->timeout_warning_handler, $this);
+          if ( $result == 'BREAK' )
+            break;
+        }
+        // ping the server
+        $this->put("PING :{$this->nick}\r\n");
+        // set timeout lower to make reconnecting work as fast as possible
+        $timeout = 10;
+        $timeout_warn = true;
+        continue;
+      }
       $match = self::parse_message($data_trim);
       if ( preg_match('/^PING :(.+?)$/', $data_trim, $pmatch) )
       {
         $this->put("PONG :{$pmatch[1]}\r\n");
         eval(eb_fetch_hook('event_ping'));
       }
+      else if ( preg_match('/^:((?:[a-z0-9-]+\.)*[a-z0-9-]+) PONG \\1 :' . preg_quote($this->nick) .'/', $data_trim) )
+      {
+        $timeout = 180;
+        $timeout_warn = false;
+      }
       else if ( $match )
       {
         // Received PRIVMSG or other mainstream action
@@ -269,6 +328,27 @@
   }
   
   /**
+   * Changes the functions called when IRC connection timeouts occur.
+   * @param string Function to call when a warning (no traffic within 3 minutes) occurs. If false, nothing will be called.
+   * @param string Function to call if a ping timeout occurs. If false, nothing will be called.
+   */
+  
+  function set_timeout_handlers($warn_func, $error_func)
+  {
+    $this->timeout_warning_handler = false;
+    $this->timeout_error_handler = false;
+    if ( function_exists($warn_func) )
+    {
+      $this->timeout_warning_handler = $warn_func;
+    }
+    if ( function_exists($error_func) )
+    {
+      $this->timeout_error_handler = $error_func;
+    }
+    return true;
+  }
+  
+  /**
    * Parses a message line.
    * @param string Message text
    * @return array Associative with keys: nick, user, host, action, target, message
@@ -309,6 +389,29 @@
   }
   
   /**
+   * Changes the current nick.
+   * @param string New nick.
+   * @param string Password to authenticate to NickServ if needed.
+   */
+  
+  public function change_nick($nick, $pass = false)
+  {
+    $this->put("NICK $nick\r\n");
+    $this->nick = $nick;
+    if ( $pass )
+    {
+      while ( $data = $this->get(3) )
+      {
+        if ( strstr($data, 'NickServ') )
+        {
+          $this->privmsg('NickServ', "IDENTIFY $pass");
+          break;
+        }
+      }
+    }
+  }
+  
+  /**
    * Closes the connection and quits.
    * @param string Optional part message
    */
@@ -330,7 +433,7 @@
     
     $this->put("QUIT\r\n");
     
-    while ( $msg = $this->get() )
+    while ( $msg = $this->get(1) )
     {
       // Do nothing.
     }