Jump to content

Recommended Posts

I've posted below the entire bot's source code, with edited values for passwords and such.

 

My bot for IRC works great, doesn't disconnect for the most part... but after 1-3 days, the bot hangs without any data being received or sent. If for example, I reset/disable my NIC card locally, the bot detects it and keeps trying to re-connect(detects fine...).

 

I went as far as adding all raw data received to a TXT file, but even then... nothing seems abnormal.

I suspect that the server is closing the connection, for whatever reason after a certain amount of time, and sends no message to my bot, so my bot stays connected and is waiting for data on the following line (line 337)

$flag = @socket_recv($this->socket, $buffer, 4096, 0);

I check for things like the buffer being empty or the flag being empty, I've been struggling with this error for a while... any help would be most grateful.

I also tried using MSG_DONTWAIT as follows:

$flag = @socket_recv($this->socket, $buffer, 4096, MSG_DONTWAIT);

But with this method, I was unable to obtain any data correctly... just prints nothing for flags or the buffer... maybe i just don't know what this does or understand how it works?

If I could get MSG_DONTWAIT working correctly, I could check the time between the last data and reconnect the bot this way...

Thanks in advance

/*
 * JTV Chat bot by Gawdsed
 *
 * @author      Mathieu "Gawdsed" Graham (mathieugraham@gmail.com) and unofficially Ferdinand E. Silva (six519@phpugph.com)
 * @version     Version 0.25 Alpha 
 * 
 * Special thanks to Ferdinand E. Silva for his starter code to connect to the IRC server, his code was great help as a starting point.
 * I've edited this starter code heavily though, including parsing text with correct triggers and editing the login process.
 * More is in stock and I will keep this program updated.
 */
<?php
    class PHPIRC {


        /*************************************
        /////////////EDIT BELOW///////////////
        *************************************/


        //server and channel connection info
        private $IrcServer = "avilo.jtvirc.com";
        private $IrcPort = 6667;
        private $IrcNick = "avilomodbot";
        private $IrcPass = "fake123";
        private $IrcRoom = "avilo";


        //database connection if you have one, set usedatabase to false if you dont
        private $useDatabase = false;
        private $DB_HOST = "123.123.123.123";
        private $DB_USER = "asdasdasda";
        private $DB_NAME = "asd";
        private $DB_PASS = "fake123";


        //timezone since PHP can't grab the local time for whatever reason... Just change it to your timezone number... central -6 for example
        private $timezone = "-7";
        //by minutes... interval between announcement messages... recommended(20-30)
        private $announcementInterval = 30;
        //Decide if the bot should announce itself when logging in.
        private $announceSelf = false;
        //Show ping/pong?
        private $showPingPong = false;


        /*************************************
        ////////////STOP EDITING//////////////
        *************************************/


        //standard variables and objects
        private $socket;
        private $isConnected = false;
        private $isAuthenticated = false;
        private $nextmessage = "";


        //stores ops in array from information sent from server
        private $ops = [];




        //used for triggers for messages sent
        private $fileCanned = [];
        private $fileReply = [];
        private $fileNews = [];


        //constructor
        public function __construct()
        {
            $this->main();
        }


        //main loop
        private function main() 
        {
            date_default_timezone_set('Etc/GMT' . $this->timezone);
            //infinit loop
            while(1==1)
            {
                if(!$this->getDbInfo())
                {
                    echo $this->timestamp() .  "Database not loaded, using local files\n";
                }
                $this->loadFiles();
                $this->connect(); //connect to irc server
                sleep(5);
                echo $this->timestamp() .  "Attempting to reconnect...\n";
            }


        }   


        //send out message if time from now and set is greater
        private function checkminutes() 
        {
            $date1 = new DateTime($this->timestampTostring());
            if($this->nextmessage == "")
            {
                $this->nextmessage = new DateTime($this->timestampTostring());
                $this->nextmessage->add(new DateInterval("PT" . $this->announcementInterval . "M"));
                $date2 = $this->nextmessage;
            }
            else
            {
                $date2 = $this->nextmessage;
            }
            $interval = $date1->diff($date2, false);
            //echo $this->timestampTostring();
            $minutes = $interval->i;
            $hours = $interval->h;
            $days = $interval->d;
            $months = $interval->m;


            $minutescount = $minutes + ($hours*60) + ($days*24*60) + ($months*24*60*31);


            if (($minutescount <= 0 && count($this->fileNews) != 0) || (($date1 > $date2) && count($this->fileNews)))
            {
                for($i = 0;$i<=count($this->fileNews)-2;$i++)
                {
                    $this->sendMessage("PRIVMSG #" . $this->IrcRoom . " :" . $this->fileNews[$i] . "\r\n");
                    sleep(1);
                    //adding the amount requested by user till next broadcast
                    $this->nextmessage = new DateTime($this->timestampTostring());
                    $this->nextmessage->add(new DateInterval("PT" . $this->announcementInterval . "M"));
                    //echo to console
                    echo $this->timestamp() . $this->IrcNick . ": " . $this->fileNews[$i] . "\n";
                }


                //get mods as well, why not?            
                $this->sendMessage("PRIVMSG " . $this->IrcRoom . " :/mods\r\n");
            }   


        }


        //returns the time since there is no tostring for dates...
        private function timestampTostring()
        {
            return date('Y-m-d H:i:s');
        }


        //return string of a timestamp
        private function timestamp()
        {
            $date = date_create();
            return "(" . $date->format( 'h:i:s' ) . ")";
        }


        //loads files to arrays for easier access
        private function loadFiles()
        {
            $this->fileCanned = explode("\r\n", $this->readFile("1"));
            $this->fileReply = explode("\r\n", $this->readFile("2"));
            $this->fileNews = explode("\r\n", $this->readFile("3"));
        }


        //connects to db, overwrites files if needed
        private function getDbInfo()
        {
            if(!$this->useDatabase)
            {
                $this->createFile("1");
                $this->createFile("2");
                $this->createFile("3"); 
                return false;
            }
            $db = new mysqli($this->DB_HOST, $this->DB_USER, $this->DB_PASS, $this->DB_NAME);


            if (mysqli_connect_errno())
            {
                echo $this->timestamp() . "Connect failed: ", mysqli_connect_error() . "\n";
                return false;
            }


            //get canned trigger and reply from table (name) and put into files
            $query  = "SELECT * FROM ". $this->IrcRoom . " WHERE isnews = 0";
            $result = $db->query($query);
            if($result === true)
            {
                $rowCount = $result->num_rows;
            }
            else
            {
                $rowCount = 0;
            }


            if($rowCount != 0)
            {
                $this->recreateFile("1");
                $this->recreateFile("2");   
                while($row = mysqli_fetch_row($result))
                {
                    $this->appendFile($row[1],"1");
                    $this->appendFile($row[2],"2");             
                }
            }
            else
            {
                $this->createFile("1");
                $this->createFile("2");     
            }


            //get news and put into file (name)news
            $query  = "SELECT * FROM ". $this->IrcRoom . " WHERE isnews = 1";
            $result = $db->query($query);
            if($result === true)
            {
                $rowCount = $result->num_rows;
            }
            else
            {
                $rowCount = 0;
            }


            if($rowCount != 0)
            {
                $this->recreateFile("3");   
                while($row = mysqli_fetch_row($result))
                {
                    $this->appendFile($row[2],"3");             
                }
            }
            else
            {
                $this->createFile("3"); 
            }
            return true;
        }


        //reads a file
        private function readFile($number)
        {
            $data = "";
            $fileName = $this->IrcRoom . $number;
            if(file_exists($fileName) && (filesize($fileName) != 0))
            {   
                $handle = fopen($fileName, 'r');
                $data = fread($handle,filesize($fileName));
                fclose($handle);


            }
            return $data;
        } 


        //adds to a file
        private function appendFile($data,$number)
        {
            $fileName = $this->IrcRoom . $number;
            $fh = fopen($fileName, "a");
            fwrite($fh, "" . $data . "\r\n");
            fclose($fh);
        }


        //recreates files if they exist
        private function recreateFile($number)
        {
            $fileName = $this->IrcRoom . $number;
            if(file_exists($fileName))
            {
                unlink($fileName);
            }
            $handle = fopen($fileName, 'w') or die('Cannot open file: ' . $fileName);
            fclose($handle);
        }


        //creates a file
        private function createFile($number)
        {
            $fileName = $this->IrcRoom . $number;
            if(file_exists($fileName))
            {


            }
            else
            {
                $handle = fopen($fileName, 'w') or die('Cannot open file: ' . $fileName);
                fclose($handle);
            }
        }


        //gets user input
        private function getUserInput($msg)
        {
            $endInput = false;


            while(!$endInput) {
                echo "\n" . $msg . ": ";
                $handle = fopen ("php://stdin","r");
                $line = fgets($handle);


                if(trim($line) != "") {
                    $endInput = true;
                    return trim($line);
                }
                fclose($handle);
            }


        }


        //connects to IRC server
        private function connect()
        {


            $this->socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);


            if(@socket_connect($this->socket, $this->IrcServer,$this->IrcPort)) {
                $this->isConnected = true;    
                $this->receiveMessages();


            }else
            { 
                echo $this->timestamp() .  "Cannot Connect To Server. \n";
                echo $this->timestamp() .  socket_strerror(socket_last_error($this->socket)) . "\n";
            }
        }


        //main parsing loop for all incoming server messages
        private function receiveMessages()
        {
            if($this->isConnected)
            {




                $flag = 1;
                $buffer = "";
                $this->sendMessage("PASS " . $this->IrcPass . "\r\n" . "USER " . $this->IrcNick . " 0 * :" . $this->IrcNick . "\r\n" . "NICK " . $this->IrcNick . "\r\n");
                echo $this->timestamp() .  "Connecting to " . $this->IrcServer . " on port " . $this->IrcPort . " as user " . $this->IrcNick . "\n";


                $this->sendMessage("JOIN #" . $this->IrcRoom . "\r\n");
                echo $this->timestamp() .  "Joining channel " . $this->IrcRoom . "\n";


                $this->isAuthenticated=true;
                $this->sendMessage("JTVCLIENT \r\n");
                $this->sendMessage("PRIVMSG " . $this->IrcRoom . " :/mods\r\n");


                if($this->announceSelf)
                {
                    $this->sendMessage("PRIVMSG #" . $this->IrcRoom . " :Bot initializing... please wait 10 seconds before sending commands. \r\n");
                    echo $this->timestamp() .  $this->IrcNick . ": " . "Bot initializing... please wait up to 10 seconds before sending commands.\n";
                }


                echo $this->timestamp() .  "Connected successfully to server, waiting for text...\n";
                while($this->isConnected)
                {
                    $this->checkminutes();
                    $flag = @socket_recv($this->socket, $buffer, 4096, 0);


                    if($buffer == "" || $flag === 0)
                    {
                        $this->isConnected = false;
                        $this->isAuthenticated = false;
                        echo $this->timestamp() . "\n********Client Disconnected********\n";
                    }


                    //$this->appendFile("error: " . socket_strerror(socket_last_error($this->socket)),"debug");
                    $pieces = explode("\r\n", $buffer);
                    //$this->appendFile($this->timestamp() . "\n****new***** " . $buffer . " *****end*****","debug");
                    for($i = 0; $i < count($pieces);$i++)
                    {
                        //message handle
                        //echo "\n***BUFFER***:\n" . $pieces[$i] . "\n***ENDBUFFER***\n";
                        if(preg_match("/Checking Ident/",$pieces[$i]) && !$this->isAuthenticated) {
                            $this->sendMessage("PASS " . $this->IrcPass . "\r\n" . "USER " . $this->IrcNick . " 0 * :" . $this->IrcNick . "\r\n" . "NICK " . $this->IrcNick . "\r\n");
                        }
                        if(preg_match("/Nickname is already in use/",$pieces[$i]) && !$this->isAuthenticated) {
                            $this->IrcNick=$this->asdgetUserInput("Please Enter New Irc Nick");
                            $this->sendMessage("PASS " . $this->IrcPass . "\r\n" . "USER " . $this->IrcNick . " 0 * :" . $this->IrcNick . "\r\n" . "NICK " . $this->IrcNick . "\r\n");
                        }
                        if(preg_match("/Erroneous Nickname/",$pieces[$i]) && !$this->isAuthenticated) {
                            $this->IrcNick=$this->getUserInput("Please Enter New Irc Nick");
                            $this->sendMessage("PASS " . $this->IrcPass . "\r\n" . "USER " . $this->IrcNick . " 0 * :" . $this->IrcNick . "\r\n" . "NICK " . $this->IrcNick . "\r\n");
                        }
                        if(preg_match("/This nickname is registered/",$pieces[$i]) && !$this->isAuthenticated) {
                            $this->IrcNick=$this->getUserInput("Please Enter New Irc Nick");
                            $this->sendMessage("PASS " . $this->IrcPass . "\r\n" . "USER " . $this->IrcNick . " 0 * :" . $this->IrcNick . "\r\n" . "NICK " . $this->IrcNick . "\r\n");
                        }
                        if(preg_match("/End of \/MOTD command/",$pieces[$i]) && !$this->isAuthenticated) {
                            $this->isAuthenticated=true;
                            $this->sendMessage("JOIN #" . $this->IrcRoom . "\r\n");
                        }
                        if(preg_match("/PING/",$pieces[$i])) {
                            $this->sendMessage("PONG tmi.twitch.tv :TIMEOUTCHECK\r\n");
                            //$this->appendFile( $this->timestamp() . "PONG tmi.twitch.tv :TIMEOUTCHECK\r\n","debug");
                            if($this->showPingPong)
                            {
                                echo $this->timestamp() .  "ping pong\n";
                            }
                        }
                        if(preg_match("/PART/", $pieces[$i])) {
                            $tmpStr = preg_split("/:/", $pieces[$i]);
                            $tmpStr = preg_split("/!/", $tmpStr[1]);
                            $name = $tmpStr[0]; //nick of the sender
                            $message = explode(" ", $pieces[$i]);
                            echo $this->timestamp() .  $message[1] . ": " . $name . "\n";
                        }
                        if(preg_match("/JOIN/", $pieces[$i])) {
                            $tmpStr = preg_split("/:/", $pieces[$i]);
                            if(isset($tmpStr[1]))
                            {
                                $tmpStr = preg_split("/!/", $tmpStr[1]);
                                $name = $tmpStr[0]; //nick of the sender
                                $message = explode(" ", $pieces[$i]);
                                echo $this->timestamp() .  $message[1] . ": " . $name . "\n";
                            }
                            else
                            {
                                echo $this->timestamp() . "Exception caught: " . $pieces[$i] . "\n";
                            }
                        }
                        if(preg_match("/PRIVMSG/", $pieces[$i])) {
                            //echo $pieces[$i] . "(prvimsg)\n";
                            //parse and display channel messages
                            $messagetogether = "";
                            $k = 2;
                            $tmpStr = preg_split("/:/", $pieces[$i]);
                            $tmpStr = preg_split("/!/", $tmpStr[1]);
                            $name = $tmpStr[0]; //nick of the sender
                            if(strpos($name," ") !== false)
                            {
                                $nametemp = explode(" ", $name);
                                $name = $nametemp[0];
                            }
                            $message = explode(":", $pieces[$i]); //message received


                            //sometimes formatting changes a bit... lets make sure we only get the first item, aka the name.
                            if($name == "jtv")
                            {
                                if(strpos($pieces[$i],":The moderators of this room are:") !== false)
                                {
                                    $this->ops = [];
                                    $message[3] = str_replace(' ', '', $message[3]);
                                    $opsinfo = explode(",", $message[3]);
                                    for($t = 0;$t<count($opsinfo);$t++)
                                    {
                                        array_push($this->ops, $opsinfo[$t]);
                                    }
                                    echo $this->timestamp() . "Obtained " . count($opsinfo) . " mods from JTV" ."\n";
                                }
                            }else if(isset($message[2]))
                            {


                                while(isset($message[$k]))
                                {
                                    if($k == 2)
                                    {
                                        $messagetogether .= $message[$k];
                                        $k+=1;
                                    }
                                    else
                                    {
                                        $messagetogether .= ":" . $message[$k];
                                        $k+=1;                                      
                                    }
                                }


                                //check to see if user is a mod or not
                                $ismod = false;
                                for($x = 0;$x<count($this->ops)-1;$x++)
                                {
                                    if($name == $this->ops[$x])
                                    {
                                        $ismod = true;
                                    }
                                }


                                if($ismod)
                                {
                                    echo $this->timestamp() . "@" . $name . ": " . $messagetogether . "\n";
                                }
                                else
                                {
                                    echo $this->timestamp() . $name . ": " . $messagetogether . "\n";
                                }
                                $this->trigger($name,$messagetogether);
                            }
                            else
                            {
                                echo $this->timestamp() .  "Received message but parsed wrong... \n";
                                echo $pieces[$i] . "\n";
                            }
                        }
                        if(preg_match("/MODE/",$pieces[$i]))
                        {
                            //add mods to an array list, using /mods instead
                            /*
                            $tmpStr = preg_split("/ /", $pieces[$i]);
                            if($tmpStr[3] == "+o")
                            {
                                array_push($this->ops, $tmpStr[4]);
                                $this->ops = array_unique($this->ops);
                                //echo "adding " . $tmpStr[4] . " as ops\n";
                            }
                            elseif($tmpStr[3] == "-o")
                            {
                                $index = array_search($tmpStr[4],$this->ops);
                                if($index !== false){
                                    unset($this->ops[$index]);
                                    $this->ops = array_values($this->ops);
                                }                               
                            }*/
                        }


                    }


                }
                echo $this->timestamp() .  "Not connected anymore\n";
            }
        }


        //triggers for messages that people send, check for mod, then ! and the keyword, reply if found, exit once found.
        private function trigger($nick, $msg)
        {
            for($i = 0;$i<count($this->ops)-1;$i++)
            {
                if($nick == $this->ops[$i])
                {
                    if(isset($this->fileCanned[0]))
                    {
                        for($e=0;$e<=count($this->fileCanned)-1;$e++)
                        {
                            $temp = explode(" ", $this->fileCanned[$e]);
                            for($k=0;$k<=count($temp)-1;$k++)
                            {
                                if($msg == "!" . $temp[$k])
                                {
                                    echo $this->timestamp() . $this->IrcNick . ": " . $this->fileReply[$e] . "\n";
                                    $this->sendMessage("PRIVMSG #" . $this->IrcRoom . " :" . $this->fileReply[$e] . "\r\n");
                                    usleep(1500000);
                                    return;
                                }
                            }
                        }
                    }
                    if($msg == "!?")
                    {
                        $messagestring = "Possible commands:";
                        for($e=0;$e<=count($this->fileCanned)-1;$e++)
                        {
                            $temp = explode(" ", $this->fileCanned[$e]);
                            for($k=0;$k<=count($temp)-1;$k++)
                            {
                                $messagestring .= " " . $temp[$k];
                            }
                        }
                        $this->sendMessage("PRIVMSG #" . $this->IrcRoom . " :" . $messagestring . " sync " . "reload" . "\r\n");
                        echo $this->timestamp() . $this->IrcNick . ": " .  $messagestring . "sync" . " reload" . "\n";


                        return;
                    }
                    if($msg == "!sync" || $msg == "!reload")
                    {
                        if($this->useDatabase)
                        {
                            $this->sendMessage("PRIVMSG #" . $this->IrcRoom . " :" . "sync comand received, syncing from database!" . "\r\n");
                            echo $this->timestamp() . $this->IrcNick . ": " . "sync comand received, syncing from database!" . "\n";
                        }
                        else
                        {
                            $this->sendMessage("PRIVMSG #" . $this->IrcRoom . " :" . "Reload comand received, rebooting and re-loading files! (database sync not enabled)" . "\r\n");
                            echo $this->timestamp() . $this->IrcNick . ": " . "Reload comand received, rebooting and re-loading files! (database sync not enabled)" . "\n";


                        }
                        $this->isConnected = false;
                        return;
                    }
                }
            }


        }


        //sends a message to the server
        private function sendMessage($msg)
        {
            socket_write($this->socket,$msg,strlen($msg));
        }
    }




    //run PHPIRC
    $run = new PHPIRC();


    ?>

 

Read up on non-blocking sockets and socket_select. Try and implement that solution and if you still have issues then post back with some updated code. You could also try enabling the SO_KEEPALIVE option using socket_set_option.

I will attempt to implement this and reply if I have any more questions. I knew I had to use a non-blocking socket(which is why i tried using MSG_DONTWAIT ), but I didn't really know how or if there was a better method... first time socket coder.

 

Thanks

Edited by gawdsed
This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.