The flowchart:
We'll setup our connection in macro.init() [main.lua:9], and forward any messages we receive on our socket in macro.event() [main.lua:24]. As such, all messages from the server are forwarded to Bot:recv() [bot.lua:141] so that we can parse and respond to them as needed. After we open our connection to the server and are ready to begin sending data, we identify ourselves and join a channel in Bot:init() [bot.lua:76].
How it works:
IRC is a very simple protocol. In order to join into a channel, we just need to follow the specification. First, we open a connection to the server with a default port of 6667. Once connected, the server should begin sending the MOTD ("message of the day") which we must (by specification) wait for it to finish before we are allowed to send anything to it. Failure to do this will result in an improper connection and a failure of communication.
To connect, we setup the socket like so:
Code: Select all
function Bot:connect(host, port)
if( not host ) then
error("Host was not specified", 2);
end
port = port or 6667; -- Default port
self.socket = network.socket("tcp");
local success = self.socket:connect(host, port);
if( success ) then
self.status = "wait_til_ready";
self.lastMsg = time.getNow();
printf("Connected to %s. Must now wait until end of MOTD.\n", host);
else
printf("Failed to connect to %s\n", host);
end
end
As every server may signal the end of the MOTD differently, we'll just look for the message "End of /MOTD command" and use a time-out as a fallback:
Code: Select all
-- From bot.lua:152
if( not self.ready ) then
local _,match = string.match(line, "^:([a-zA-Z0-9.-]+) %d+ (.*) :End of /MOTD command.");
if( match ) then
bot.ready = true;
printf("-----------------------------------\n\n[DEBUG]\t\tMOTD signaled END\n");
end
end
Code: Select all
-- from bot.lua:59
if( self.status == "wait_til_ready" ) then
if( time.diff(self.lastMsg) > 20 ) then
self.ready = true;
printf("NOTICE: Timed out; Did not reach end of MOTD. Assuming ready.\n");
end
if( self.ready ) then
print("NOTICE:\t\tReady.");
self.status = "ready";
self:init();
else
return;
end
end
Code: Select all
function Bot:user(username, realname)
if( not bot.ready ) then
error("Not ready.", 2);
end
local cmd = string.format("USER %s %s %s :%s", username, '0', '*', realname)
self:command(cmd);
end
Ok, so, we're connected. Now what?
Now, we should make sure we're responding to pings and handling errors. In the IRC protocol, the client will occasionally receive a "ping" message which is used to test if the connection is still live. If we do not respond to these messages in a timely fashion, the server will assume there is a problem communicating and terminate our connection. To respond, we simply take the token string (which is typically just going to be the server name, ie. "PING irc.freenode.net", but could contain anything) and respond using the same token as a "PONG" message. If we do not use the exact same token, well, you guessed it: we'll probably be disconnected.
Code: Select all
-- from bot.lua:165
-- Respond to PING requests with a PONG
local daemon = string.match(line, "^PING (.*)");
if( daemon ) then
printf("Received ping request; respond with pong.\n");
local cmd = string.format("PONG %s", daemon);
self:command(cmd);
end
Code: Select all
-- Respond to errors
local errmsg = string.match(line, "^ERROR :(.*)");
if( errmsg ) then
self.socket:close();
self.socket = nil;
error(sprintf("We had a horrible error :(\nError: %s", errmsg), 0);
end
Joining a channel and the fun stuff
You should finally be ready to join in on an actual chat. All you need to do is send the JOIN command ("JOIN #TheChannelName") to begin receiving chat messages from the other people in that channel. These are sent as a PRIVMSG that states which channel, who said it, and what they are saying:
Code: Select all
-- from bot.lua:182
local from,channel,msg = string.match(line, "^:([a-zA-Z0-9.-]+)!.*@.* PRIVMSG (.*) :(.*)");
Code: Select all
-- from bot.lua:183
if( from and channel and msg ) then
printf("PRIVMSG\t[%s]\t%s: \'%s\'\n", channel, from, msg);
if( msg == "!test" ) then
self:say(channel, "Test successful.");
else
printf("Msg did not match. DEBUG: \'%s\'\n", msg);
end
end
Testing it out:
First, you may want to change some stuff from config.lua to connect to whichever server & channel you prefer, but this isn't necessary for a test. You should begin by connecting your own client. If you do not have a proper IRC client, you can use Kiwi IRC. Connect to #bottest on irc.freenode.net port 6667.
Now, open up MicroMacro 2, and run irc/main.lua. After some time, you'll connect to the server, receive the MOTD, and join the target channel:
Now, if on your actual client you enter: !testRunning 'irc/main'
Connected to irc.freenode.net. Must now wait until end of MOTD.
[DEBUG EVENT] socketreceived
824 :sinisalo.freenode.net NOTICE * :*** Looking up your hostname...
[NOTICE] *** Looking up your hostname...
[DEBUG EVENT] socketreceived
824 :sinisalo.freenode.net NOTICE * :*** Checking Ident
[NOTICE] *** Checking Ident
[DEBUG EVENT] socketreceived
824 :sinisalo.freenode.net NOTICE * :*** Found your hostname
[NOTICE] *** Found your hostname
<-- snip here -->
:sinisalo.freenode.net 376 LuaBotTest_cXyx :End of /MOTD command.
:LuaBotTest_cXyx MODE LuaBotTest_cXyx :+i
:NickServ!NickServ@services. NOTICE LuaBotTest_cXyx :☻LuaBotTest_cXyx☻ is not a
registered nickname.
[DEBUG EVENT] socketreceived
824 :LuaBotTest_cXyx!~LuaBotTes@IP.GOES.HERE.hostname JOIN #
bottest
[DEBUG EVENT] socketreceived
824 :sinisalo.freenode.net 353 LuaBotTest_cXyx = #bottest :LuaBotTes
t_cXyx dikfuk TieSoul jfokkan globbot
:sinisalo.freenode.net 366 LuaBotTest_cXyx #bottest :End of /NAMES list.
you should see the bot auto-respond to you.