New tutorials View all
New forum replies
New frontpage replies
New articles
New news
Creating an IRC bot in PHP (2627 views)
Combined Minds presents you the biggest and best PHP -> IRC bot tutorial on the web! This tutorial learns you how to create a very good working IRC bot from scratch!
You may think, "what does an IRC bot has to do with webdevelopment?". Well, nothing actually. I will just use a interesting subject for talking about the sockets. On this way the tutorial will be much more fun to write, and read.
When making a PHP powered bot to connect to some external source, you need to know 2 major things. How to connect and talk to the server, and what you need to talk.
The first one is the PHP part, while the second part is the API or Protocol of the sort of server you are connecting to.
We will connect to an IRC server in this tutorial, i will explain the basics of the IRC protocol. But i will mainly talk about how to connect and read/write to the server in PHP. For that is the one thing you will be using on every protocol.
How to connect
First select the server you want to connect to. The server i will use is irc.rizon.net, port 6667. We will use fsockopen(); for opening the socket You could also use the socket_create() function, but this does not work well often for the windows powered servers.
This function works by first specifying the server address (can be a URL, can be an IP address). The second is the port. You may use a third and fourth one for error handling, and a fifth one for a time-out number. For more information, please see PHP.net.
The following example is how to use it. I did not use the third, fourth and fifth parameters. Because i want to keep it simple for this tutorial.
| php | |
|
1 2 3 4 5 6 |
<?php // Opening the socket to the Rizon network $socket = fsockopen("irc.rizon.net", 6667); ?> |
As you can see, save the socket data in a variable/object.
After connecting to the server, you must give it some details:
USER A-trivial-nickname a-domain-like-combined-minds.net some-trivial-stuff :your-real-name
NICK CM-bot
The first line looks rather weird. But just fill in something the same as i got there. If you do not know what to edit exactly, just copy the line from me.
The second line is simple, just the NICK command, followed by the nickname desired.
To send text to the server, you need to use the fputs(); function. This functions works by first giving it the socket variable, where the data needs to go. The second is the text needs to be send. When you send text to the server, always close it with a new line! (\n) Or else the server will think the text you will send is all one line, and wont process it correctly.
This is what i send, using the fputs(); function.
| php | |
|
1 2 3 4 5 6 7 8 9 10 |
|
This will open the connection, and after that it will tell the server what/who it is.
Two important things, tell the server which channel it should join. This is done by the JOIN command. Followed by the channel you want to join. For this tutorial i registered the channel #cm-bot-test on the rizon network.
The second very important thing, is to change the nickname of the bot for testing!. If you will test it at the Rizon network that is. (i suggest you do it at the rizon network, for i will be there sometimes to help you guys)
| php | |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Next thing to do is read what the server has to say back. But there is one little problem you might think. Most people who think of "How can you keep an idle connection in PHP? It will stop after the script has run right?". Well of course the script stops when its ended. Thats why we'll make sure the script wont end
.We will need 2 things. An endless while, and something to stop PHP from canceling the script after the normal 30sec time limit. The last one is easy to solve, PHP has the function set_time_limit(). You use this function the following way:
| php | |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?php // Prevent PHP from stopping the script after 30 sec set_time_limit(0); // Opening the socket to the Rizon network $socket = fsockopen("irc.rizon.net", 6667); // Send auth info fputs($socket,"USER CMbot combined-minds.net CM :CM bot\n"); fputs($socket,"NICK CM-bot\n"); // Join channel fputs($socket,"JOIN #cm-bot-test\n"); ?> |
Simple give PHP the number of second you want the script to run maximally. When it's set to zero it will cancel the whole max run time thing. And you are able to run the script as long as you want.
Next is actually preventing from PHP to stop exiting the script while the bot is running. We will use while() for this job. We will make a while loop, that wont stop until we say it can. This is called an endless while, a while loop without an end. For this to work we need to keep the so called "expression" to be TRUE. (the expression is the check between the ( and the )) When the expression is TRUE, it will continue with the job thats done between the { and the } (accolades). We can just use "1" as the expression. For one is always TRUE!
| php | |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<?php // Prevent PHP from stopping the script after 30 sec set_time_limit(0); // Opening the socket to the Rizon network $socket = fsockopen("irc.rizon.net", 6667); // Send auth info fputs($socket,"USER CMbot combined-minds.net CM :CM bot\n"); fputs($socket,"NICK CM-bot\n"); // Join channel fputs($socket,"JOIN #cm-bot-test\n"); // Force an endless while while(1) { // Continue the rest of the script here } ?> |
Now the script won't stop.
Next is to read what the server is sending us. For this we will use the fgets() function. The function will return the text the server is sending us. So we will store the text what the function returns into a variable. The two things the function needs are the socket variable and the number of bytes it may read each time when its called.
| php | |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<?php // Prevent PHP from stopping the script after 30 sec set_time_limit(0); // Opening the socket to the Rizon network $socket = fsockopen("irc.rizon.net", 6667); // Send auth info fputs($socket,"USER CMbot combined-minds.net CM :CM bot\n"); fputs($socket,"NICK CM-bot\n"); // Join channel fputs($socket,"JOIN #cm-bot-test\n"); // Force an endless while while(1) { // Continue the rest of the script here while($data = fgets($socket, 128)) { echo $data; } } ?> |
Why i use 128 bytes to send is trivial information. I Think this is pretty clear, so save your file. And run it in your browser! If it all went right, you should be able to find your bot in the user list of channel #cm-bot-test!
When you are running the script in your browser. You may want to use the nl2br(), and flush() functions. nl2br() to make sure your $data output is all one a new line, that is quite handy for reading. ;-) You want to use the flush() function for flushing the data to the browser. Normally PHP send it's output to the browser after the outcome is calculated, so on the very and. But when flush() is called, all that's calculated so far will be already send to the browser.
| php | |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
<?php // Prevent PHP from stopping the script after 30 sec set_time_limit(0); // Opening the socket to the Rizon network $socket = fsockopen("irc.rizon.net", 6667); // Send auth info fputs($socket,"USER CMbot combined-minds.net CM :CM bot\n"); fputs($socket,"NICK CM-bot\n"); // Join channel fputs($socket,"JOIN #cm-bot-test\n"); // Force an endless while while(1) { // Continue the rest of the script here while($data = fgets($socket, 128)) { echo nl2br($data); flush(); } } ?> |
How to use IRC
Now a few other things are important. First, to stay connected. And second, how to make functions for the bot.
When I am talking about the first subject, I am talking about PING and PONG. This is an technique from the IRC protocol to check of you are still there. This is needed, because sometimes you cannot send the QUIT command when you are stopping your bot. This can happen by just stopping your bot in the browser, or your ISP can crash down. ;-)
Before we use the PING / PONG technique, we need to read what the server is saying right? Well, for this i often use the explode() function. This function can separate a string, or integer (whatever you want) and will store all separated things into an array. We will separate the string we receive with a simple " " (space). And store the data into an array called "$ex".
| php | |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<?php // Prevent PHP from stopping the script after 30 sec set_time_limit(0); // Opening the socket to the Rizon network $socket = fsockopen("irc.rizon.net", 6667); // Send auth info fputs($socket,"USER CMbot combined-minds.net CM :CM bot\n"); fputs($socket,"NICK CM-bot\n"); // Join channel fputs($socket,"JOIN #cm-bot-test\n"); // Force an endless while while(1) { // Continue the rest of the script here while($data = fgets($socket, 128)) { echo nl2br($data); flush(); // Separate all data $ex = explode(' ', $data); } } ?> |
Now, we got a an array with all words in it. This is easy for checking the commands. The fourth word (in channel) is most of the time the first word said in the channel itself. It is the fourth word because you still have 3 big and important words before the text. So knowing you can just check $ex[3] is rather nice, isn't it? (notice $ex[3] is the fourth, because the array starts at zero, not one)
Lets check for the PING command shall we? The server will send you something like this:
PING :Unique-Code
The server expects you will send:
PONG :Unique-Code
Back to the server. But be warned, every server has another unique code. So we will extract the unique code from the server, and then send it back in a variable!
| php | |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
<?php // Prevent PHP from stopping the script after 30 sec set_time_limit(0); // Opening the socket to the Rizon network $socket = fsockopen("irc.rizon.net", 6667); // Send auth info fputs($socket,"USER CMbot combined-minds.net CM :CM bot\n"); fputs($socket,"NICK CM-bot\n"); // Join channel fputs($socket,"JOIN #cm-bot-test\n"); // Force an endless while while(1) { // Continue the rest of the script here while($data = fgets($socket, 128)) { echo nl2br($data); flush(); // Separate all data $ex = explode(' ', $data); // Send PONG back to the server if($ex[0] == "PING"){ fputs($socket, "PONG ".$ex[1]."\n"); } } } ?> |
This should not be difficult right? Well then, lets continue to making your own commands! Lets make a command to let the bot simply say something in the same room the command is given in.
There are some problems with this though. But i will help you along the way, for i have done this a trillion times a few years ago. The problem is simple, when a command is given by the server it adds a newline, and carriage return with it. (not always the carriage return) So when you are checking a command, and it is the last word from this line, it has the newline after it.
This gives the following problem. When you want to check the word for a command, and you will simply use the if() function it won't work. This is because !command is not the same as !command\n for example. So we first have to strip the word we want to check from the newline and carriage return.
This is very simple though:
| php | |
|
1 2 3 4 5 |
<?php $command = str_replace(array(chr(10), chr(13)), '', $ex[3]); ?> |
What this does, is replacing an array of both newline, and carriage return with nothing. An saves that word in the $command variable. The chr() function returns an ASCII value by giving it's number. Check this website for all ASCII numbers.
Now you can easily check the $command for if it contains the command you want! Well then, lets continue with the command. First we need to check if the command variable contains the correct command. And then, we need an action for that command. To keep it simple, we will let the bot say something in the channel the command is given OK?
First we need to know more about the IRC protocol. When someone says something, the protocol will send something like this:
:Nickname!Host@name PRIVMSG #cm-bot-test :The stuff thats said
This little line is very resourceful. It contains the nickname of the person who said it. It contains the hostname (good for a admin module). It contains the channel, and it contains the string the person said in the channel.
So lets take a close look at that line, if we want to know the channel it's said in. We need the third word. So $ex[2] contains the channel!
Now lets use our freshly learned knowledge, and use it on the bot!
| php | |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
<?php // Prevent PHP from stopping the script after 30 sec set_time_limit(0); // Opening the socket to the Rizon network $socket = fsockopen("irc.rizon.net", 6667); // Send auth info fputs($socket,"USER CMbot combined-minds.net CM :CM bot\n"); fputs($socket,"NICK CM-bot\n"); // Join channel fputs($socket,"JOIN #cm-bot-test\n"); // Force an endless while while(1) { // Continue the rest of the script here while($data = fgets($socket, 128)) { echo nl2br($data); flush(); // Separate all data $ex = explode(' ', $data); // Send PONG back to the server if($ex[0] == "PING"){ fputs($socket, "PONG ".$ex[1]."\n"); } // Say something in the channel $command = str_replace(array(chr(10), chr(13)), '', $ex[3]); if ($command == ":!sayit") { fputs($socket, "PRIVMSG ".$ex[2]." :Combined-Minds.net irc bot tutorial!\n"); } } } ?> |
Notice the ":" in front of the !sayit command. This is because the word contains the : when extracting the words. This is for the IRC protocol.
Well then, I tested it here, and it works fine! Please also show your bot in the IRC channel, let me see what you guys made of it. :-)
Replies on Creating an IRC bot in PHP:
Joost, first of all welcome to Combined Minds! 
Why let the bot stop in 3000 seconds? This is 50 minutes, so a bit too short. When set_time_limit is set to zero, it would stop using a timelimit. (unless PHP's Safe mode is enabled)
About the server, PHP will stop executing the script when it cannot connect to the host. But indeed it would be better if you would build a little error check script. For some error reporting, please see php.net
Thank you for writing this! Just what I needed for my bot!
By the way, to stop the script remotely using IRC, you can specify a !stop command, which let the script die();
if($command == ":!stop"
{
fputs($socket,"PRIVMSG $ex[2] Bot stopped!n"
;
die();
}
Also, I would recommend placing a
sleep(0.1);
at the end of the whole loop, just to prevent CPU peaks, as I experienced myself without sleep();. 0.1 second is fast enough for both PING and user commands.
Oh sorry you are very right! Stupid of me to forget that! I will edit the tutorial asap.
About the stop. You could also use the QUIT :Reason IRC Protocol Command. This might be a little more clean. 
And erm, Welcome to Combined Minds!
Thanks Jim 
By the way, I made a little mistake with my sleep(); function, as it doesn't work as exspected. It does not have the ability to work with units smaller than a whole second (so, it will only work on whole digits, like 1, 2, 3, 2007 etc., and not with decimals like 0.1, 0.2, 0.3, 0.2007).
You might like to give the function usleep(); a try
http://nl3.php.net/manual/nl/function.usleep.php
What it does is exactly the same as sleep();, but now it listen to microseconds. This is a millionth of a second, so if you want to let your script sleep for 2 sec., you should use usleep(2000000);
For a bot I'd recommend
usleep(100000);
which sleeps for 0.1 sec.
Also, as for a bot, it would be handy to add a function called ignore_user_abort();
What this does, is ignoring the fact that the user closed his browser. If this function is not set, the script will die after the browser has been closed.
To activate this function, put this in the top of your script:
ignore_user_abort(TRUE);
Have fun!
Thanks for the great reply Bitage!
I've also been using the usleep() function. But 100ms (usleep(100)
seems to do the trick here. I even used it with 50ms and still low cpu usage.
About the ignore_user_abort() function.. Thanks allot! I had no idea such function existed. I might need to write a sequel to explain such intressting stuff..
But for now i hope they'll read the comment.
awesome guide! I love it
now I'm starting to understand php bots
I find the bot won't autojoin on quakenet.org maybe i need to add a small wait time? (Which I have no idea how to do)
So for the time being i've just added a command !join that i type in a query with the bot.
Welcome to CM Chis.
This problem is quite known, some servers don't allow you to join a channel that fast. You can try a usleep(100); before joining the channel. This "sleeps" the script for 100 miliseconds. But most people just use the !join command actually.
If you got problems, please see the contact page and connect to our IRC server.
Am I write in thinking that you could feasibly use this method for connecting to any IM service for example, Jabber (obviously the commands you send and receive will be different, but the methodology would be the same)?
He's what I've come up with.
<?php
// No execution Time limit
set_time_limit(0);
// Opening the socket to the Rizon network
$socket = fsockopen("irc.rizon.net", 6667) or die('Could not connect to the server');
// Send auth info
$nick = 'ellis-bot2';
fputs($socket,"USER CMbot combined-minds.net CM :CM botn"
;
fputs($socket,"NICK $nickn"
;
// Join a channel
fputs($socket,"JOIN #cm-bot-testn"
;
// Endless loop until exit.
while(1)
{
// Continue the rest of the script here
while($data = fgets($socket, 128))
{
echo nl2br($data);
flush();
// Separate all data
$ex = explode(' ', $data);
// Respond to PING W/ PONG
if($ex[0] == "PING"
{
fputs($socket, "PONG ".$ex[1]."n"
;
}
// Say something in the channel
$command = str_replace(array(chr(10), chr(13)), '', $ex[3]);
switch($command)
{
case ':!sayit':
if($ex[2] != $nick)
{
$to = $ex[2];
}
else
{
$arr = explode('!', $ex[0]);
$to = ltrim($arr[0],':');
}
fputs($socket, "PRIVMSG $to :Combined-Minds.net irc bot tutorial!n"
;
break;
case ':!dienow':
fputs($socket,"PRIVMSG ".$ex[2]." Goodbye cruel world!n"
;
die('Session ended.');
break;
default:
}
echo '<pre>';
print_r($ex);
echo '</pre>';
}
usleep(100000);
}
?>
@ piers, yes thats very posible. Although for more complicate protocol's i would use another language (eg C# or C++).
But it would work though. I'm actually busy with creating a MSN bot through PHP for fun. The only problem with such stuff is finding good protocol documentations.
Good luck!
@ ellisgl
Nice, looks good! 
Some other nice thing that can take your bot to a new level, is adding an addon system to it. In a way you can add a function to it, without restarting the bot. You can do this very easily by storing the code in a MySQL database, and using eval to parse the code. (Thanks Graham, for this technique!)
And of course the both of you, Welcome to Combined Minds.
You should check out our other tutorials too!
I've change some of the exit code so it used the QUIT command, still trying to figure out the QUIT message out. Thank god for RFC's.
As for the addon system. That should be to hard. Just look for the ! and split the command you want out and query MySQL. Of course there would be overhead for each time !command was sent.
The next thing I would want to add on would be a login system / system that does auto op, voice based on a host. etc...
The quit command with the message can be quite annoying. I recommend just get a TCP sniffer and read the protocol from the sniffer. That way you can't be wrong. 
About the rights system, this shouldn't be a problem at all. Just a little hour of work. You can get the hostname by the following way:
preg_match('/^
.*?)!(.*?)$/i', $ex[0], $matches);
$hostname = "!".$matches[2];
$username = $matches[1];
Or something like this. Setting a mode is also very simple. Just like this:
fputs($socket, "MODE ".$ex[2]." +v ".$ex[4]); // Asumming you use my tutorial
And about the login system. I registrate my hostname once with ~admin register [pass]. And then the bot checks my hostname, and if its similar to one in the database. This way you don't have to login everytime you get online.
Hey Jim, I was wondering if you could help me get a $nick feature working so I can do if ($nick == "Me"
{
Hi Mike, sure no problem.
preg_match('/^
.*?)!(.*?)$/i', $ex[0], $matches);
$hostname = "!".$matches[2];
$username = $matches[1];
This stores the Hostname and Username so you can use if($username == "me"
if you want.
If you are making a admin script, i recommend you letting it also check your hostname.
Good look. 
(Psst, you should have read the post above you
)
Ah sorry, good tutorial, I actually used Regex in between my post and your answer. But thanks all the same.
Great tutorial, thanks.
Quick question. How would you reply to a ctcp version request using this bot?
Hi and welcome to CM,
I think this link has the awnser for you:
http://www.user-com.undernet.org/documents/ctcpdcc.txt
Let me know if you can't get it working.
If you don't know how to do certain things try to look at the irc protocol:
http://www.irchelp.org/irchelp/text/rfc1459.txt
I was wondering if you could let the script work with on JOIN and PART... if so please tell me how 
Btw is it possible to check someone's channel status (op,hop,voice) before executing a command?
Hey would someone be able to help me out.
I'm trying to make a command when I would type >MSG #Channel texthere (it would be $2- in mrc). Any ideas?
Do you mean like a function for the bot, so when you type >MSG #chan text in the channel, the bot would type "text" in the channel "#chan"?
Yeah I got the basic but it only copies the first word. I know I could put like $ex[4] $ex[5] $ex[6] ect.. but you get the point
You are not logged in. Please login or register an account, it just takes 30 seconds.

Make sure to let the application die when you can't connect:
$socket = fsockopen("irc.swiftirc.net", 6667) or die();
If you're testing your bot you might not want it to stop running, use:
set_time_limit(3000);
instead of:
set_time_limit(0)