MicroMacro 2 - IRC bot

For only tutorials. If you would like to post a tutorial here, first post it elsewhere (scripts, general, etc.) and private message me (Administrator) a link to the thread. I'll move it for you.
Post Reply
User avatar
Site Admin
Posts: 4849
Joined: Sat Jan 05, 2008 4:21 pm

MicroMacro 2 - IRC bot

#1 Post by Administrator » Sat Feb 21, 2015 2:30 pm

MicroMacro 2 allows you to use raw sockets to send and receive TCP packets. In this example, we'll use that to connect to an IRC server, properly authenticate ourselves, join a channel, then listen in on the chat to respond to triggers.

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);
	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);
		printf("Failed to connect to %s\n", host);
As you can see, it really is as simple as opening a socket, then setting our status to "wait" mode so we can wait for the MOTD to complete sending.

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");

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");

		if( self.ready ) then
			self.status = "ready";
Once the connection is opened and MOTD completes, we must send our USER, PASS, and NICK commands before we can join any channels or send any PRIVMSG. These commands are simply just a string in their respective formats and terminated with a "\r\n" (ie. "USER myusername mode unused :my real name\r\n") that is sent over the opened socket. Note: Strings that may contain spaces should begin with ":" to designate such.

Code: Select all

function Bot:user(username, realname)
	if( not bot.ready ) then
		error("Not ready.", 2);

	local cmd = string.format("USER %s %s %s :%s", username, '0', '*', realname)
The connection is now fully open and ready to go. What you do from here is entirely up to you.

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);
And while we're at it, we should also capture error messages from the server and display those:

Code: Select all

		-- Respond to errors
		local errmsg = string.match(line, "^ERROR :(.*)");
		if( errmsg ) then
			self.socket = nil;

			error(sprintf("We had a horrible error :(\nError: %s", errmsg), 0);

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 (.*) :(.*)");
We parse out that information and display it in our console. For fun, we should also respond to triggers. For example, if someone says "!test" in one of our channels, we should respond back with a message saying "Test successful.":

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.");
				printf("Msg did not match. DEBUG: \'%s\'\n", msg);

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:
Running '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 #

[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.
Now, if on your actual client you enter: !test
you should see the bot auto-respond to you.
(2.49 KiB) Downloaded 65 times

Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest