Aronsson Datateknik

Botrick - IRC Robot Kit

by Lars Aronsson, May 1991

This code was written by me, but is no longer supported.
You may copy it freely, without any restrictions what so ever.

So you say... Be warned: The following code is known to contain bugs.
Finding them is left as an exercise for the reader. Copy as you may!

Here is your IRC Robot Kit. There are three files. They all compile
and run on a Sun 3/50 with SunOS 4.1.1 with GNU's "gcc -ansi" and our
home-made ANSI-C include files (not included here), that's all I know.
All instructions you need are the comments in the first one. Plus my
e-mail address is Aronsson@Lysator.LiU.SE. Good luck with your IRC
robot kit! (Batteries not included)

---- cut here --------------------------------------------------------

/* File ircrobot.h
 * Aronsson was here
 * 6 MAY 1991
 *
 *			   A do-it-yourself
 *			      IRC Robot
 *
 *
 * This is ircrobot.h, the include file. The C file ircrobot.c contains
 * a main() for a general purpose IRC robot. To make your own IRC robot
 * you need this include file, the supplied C file, and a second C file
 * with your own customized robot functions. You should compile the two
 * C files and then link them together to form your robot.
 *
 * In order to run your robot, you will also need a tiny program called
 * ircserv. It comes with the IRC-II IRC client. Ircserv is not the IRC
 * server process, it is a program to connect to the server. It runs in
 * a separate process and takes care of some low-level business so your
 * robot can concentrate on more important things.
 *
 * You should #define below your full path name of the ircserv program.
 * Also #define the address and port number of your closest IRC server.
 */

#define IRCSERV "/usr/local/bin/ircserv"
#define SERVER	"nanny.lysator.liu.se"
#define PORT	"6667"

/* When you run your robot, execution will be in the main() function in
 * the file ircrobot.c. When something important happens, like somebody
 * says something on a channel, main() will make function calls to your
 * robot and it has the chance to respond. After that your robot has to
 * return control to main(). You need to know two kinds of functions to
 * write your own robot. These are events and responses. Events are the
 * functions that you should write and that will get called from main()
 * while responses are functions that your IRC robot could call when it
 * responds to events. Your robot has to return control to main() after
 * every event. For your robot to have memories last longer than single
 * events, you can use static variables in its C file. The simplest way
 * to write your own IRC robot is to modify the file botrick.c.
 */

/* In both ircrobot.c and in botrick.c there are several #include lines
 * which you may have to modify. I have not had the time to correct all
 * bugs in our ANSI-C include directories, so this is the messiest part
 * of the IRC robot code.
 */

/*********************************************************************
 * Event functions -- your robot will get called
 */

int birth (int argc, char *argv[]);

/* After connecting to an IRC server, main() will call this event
 * function with the same arguments that were sent to main(). The
 * function birth() is supposed to call nick() and join() and to return
 * zero if all went well.
 */

extern void informs	(char *what);

/* When your robot is informed about something, this event function is
 * called. The something is in the argument.
 */

extern void invites	(char *nick, char *chan);

/* When another person invites your robot to another channel, this
 * event function is called. The nickname of the invitor and the name
 * of her channel are passed as argument. The robot can choose to join
 * the channel or to stay, perhaps telling the invitor why.
 */

extern void joins	(char *who);

/* This event function gets called whenever a person joins the channel
 * where the robot is. An excellent opportunity for greetings. The
 * nickname passed as argument can be used in calls to tell().
 */

extern void says	(char *who, char *what);

/* When this event function gets called, it means that somebody (who)
 * has written something (what) to the channel.
 */

extern void tells	(char *who, char *what);

/* This event function call means that person who sent a private
 * message what (with /MSG) to your robot. Private reply messages
 * are sent with tell() using who as the argument.
 */

extern void left	(char *who);

/* The event function is called when a person (who) left the channel,
 * but still has not left IRC. Private messages could still be sent
 * to the person using tell().
 */
   
extern void quits	(char *who);

/* The event function is called when a person (who) has quit IRC.
 */

extern void topic	(char *who, char *what);

/* Somebody (who) changed the topic (to what). If who is a NULL
 * pointer, that means main() doesn't know who changed the topic.
 * This will be the case immediately after your robot has joined
 * a new channel.
 */

/*********************************************************************
 * Response functions -- your robot can call these
 */

extern void invite	(char *nick, char *chan);

/* Your robot should call this function if it wants to invite a person
 * (having the specified nickname) to his channel (chan).
 */

extern int join 	(char *chan);

/* If your robot wishes to join a new channel, this is just the
 * function to call. He would probably want that when the birth()
 * event function is called from main(). If join() returns non-zero
 * it means something went wrong, perhaps it was a private channel.
 */

extern int nick 	(char *nick);

/* If your robot wishes to change his nickname, this is just the
 * function to call. He would probably want that when the birth ()
 * event function is called from main(). If nick() returns non-zero
 * it means something went wrong, perhaps the nickname was used by
 * somebody else.
 */

extern void say 	(char *what);

/* Of course, your robot will wish to write text to the channel he
 * is on. Just call this response function.
 */

extern void tell	(char *who, char *what);

/* Sometimes, private messages (like /MSG) are more appropriate than
 * writing to the channel. This response function allows your robot
 * to specify a receiver (who) together with the message (what).
 */

/* Of course, there could have been a lot more event and response
 * functions than these, but these will have to do for now. When
 * you have used all of them and need more, you can probably read
 * the ircrobot.c file and write them yourself. If not, write an
 * electronic mail to me at Aronsson@Lysator.LiU.SE, I'll see what
 * I can do.
 */

---- cut here --------------------------------------------------------

/* File ircrobot.c
 * Aronsson was here
 * 23 FEB 1991
 *
 *
 * Things yet to me done
 *	sleep()		-- what does this mean? (old note)
 *	argv to a birth process.
 *
 * A do-it-yourself IRC robot. See instructions in ircrobot.h.
 */


#include <stdlib.h>
#include <stdio.h>
#include <strings.h>
#include <ctype.h>
/* #include "stupid-gcc.h" */
/* #include <sys/file.h> */

/* Perhaps you'll need to define open(2) aswell */
int close (int); 
extern int pipe (int *);
extern int fork (void);
extern int dup2 (int, int);
extern int execlp (char *, ...);

#include "ircrobot.h"

/*********************************************************************
 * Data types and global data
 */

static char *mynick, *mychan;
static FILE *RxD, *TxD;

typedef enum				/* Are there any other? */
{
  c_301, c_311, c_312, c_313, c_314, c_315, c_316, c_317, c_321,
  c_322, c_323, c_324, c_332, c_341, c_351, c_365, c_366, c_381,
  c_401, c_403, c_421, c_431, c_432, c_433, c_451, c_462, c_464,
  c_channel, c_eof, c_error, c_invite, c_join, c_kick, c_kill,
  c_linreply, c_mode, c_msg, c_namreply, c_nick, c_notice, c_part,
  c_ping, c_pong, c_privmsg, c_quit, c_topic, c_wall, c_wallops,
  c_whoreply, c_zilch
} commands;

struct cmdict
{
  char 		*spell;
  commands	 code;
};

static struct cmdict ircmd[] =		/* A dictionary */
{
  {"301", c_301}, {"311", c_311}, {"312", c_312}, {"313", c_313},
  {"314", c_314}, {"315", c_315}, {"316", c_316}, {"317", c_317},
  {"321", c_321}, {"322", c_322}, {"323", c_323}, {"324", c_324},
  {"332", c_332}, {"341", c_341}, {"351", c_351}, {"365", c_365},
  {"366", c_366}, {"381", c_381}, {"401", c_401}, {"403", c_403},
  {"421", c_421}, {"431", c_431}, {"432", c_432}, {"433", c_433},
  {"451", c_451}, {"462", c_462}, {"464", c_464},
  {"CHANNEL", c_channel},	{"ERROR", c_error},
  {"INVITE", c_invite}, 	{"JOIN", c_join},
  {"KICK", c_kick}, 		{"KILL", c_kill},
  {"LINREPLY", c_linreply}, 	{"MODE", c_mode},
  {"MSG", c_msg}, 		{"NAMREPLY", c_namreply},
  {"NICK", c_nick}, 		{"NOTICE", c_notice},
  {"PART", c_part}, 		{"PING", c_ping},
  {"PONG", c_pong}, 		{"PRIVMSG", c_privmsg},
  {"QUIT", c_quit}, 		{"TOPIC", c_topic},
  {"WALL", c_wall}, 		{"WALLOPS", c_wallops},
  {"WHOREPLY", c_whoreply},
  {(char *) 0, c_zilch}
};


/*********************************************************************
 * Igets() and action() -- called mostly from main().
 */

#ifdef DEBUG
static int igetc (FILE *f)
{
  int c;
  c = getc (f);
  putc (c, stderr);
  return c;
}
#else	/* DEBUG */
#define igetc(x) getc(x)
#endif

/* igets()
 * Use buffer as a string buffer for buflen chars.
 * Return the code for the command.
 * Return c_eof on end of file (or error).
 * Return c_zilch on unknown commands.
 * argv[0] points to the first optional colon argument.
 * argv[1] points to the command.
 * argv[2] points to the first argument after the command.
 *	Starting here, the args are a double-null terminated sequence
 *	of nullterminated strings.
 * argv[3] points to the rest-of-line argument.
 * argc tells the number of arguments retrievable under argv[2].
 */

static commands igets (char *buffer, int buflen,
		       int *argc, char *argv[])
{
  int c, i, rest;
  char *cp, *last;
  struct cmdict *cmd;

  /* Assume we are at start of a new line.
   * Assume exactly one blank separator.
   * Assume that is a SPACE.
   */

  if (! buffer || buflen < 1)
    return c_eof;

  cp = buffer;
  i = 0;
  argv[0] = buffer;
  argv[1] = argv[2] = argv[3] = (char *) 0;
  c = igetc (RxD);
  if (c == EOF)
    return c_eof;
  else if (c == ':')			/* first colon? */
      c = igetc (RxD);
  else
    {
      *cp++ = '\0';
      i ++;
      argv[1] = last = cp;
    }

  rest = 0;
  for (; c != '\n' && c != EOF; c = igetc (RxD))
    {
      if (cp >= buffer + buflen - 2)
	continue;
      if (!rest && c == ' ')
	{
	  i++;
	  *cp++ = '\0';
	  if (i < 3)
	    argv[i] = cp;
	  last = cp;
	}
      else
	{
	  if (c == ':' && !rest && *(cp - 1) == '\0')
	    {
	      rest = 1;
	      *cp++ = '\0';
	      argv[3] = cp;
	    }
	  else
	    *cp++ = c;
	}
    }
  if (cp < buffer + buflen - 2 && !rest)
    {
      i++;
      *cp++ = '\0';
      if (i < 3)
	argv[i] = cp;
      last = cp;
    }

  cp = (cp > buffer + buflen - 1) ? buffer + buflen - 1 : cp;
  *cp = '\0';

  *argc = i - 2;
  *last = '\0';
  for (cmd = ircmd; cmd -> spell; cmd++) /* command is text */
    if (! strcmp (cmd -> spell, argv[1]))
      break;
  return cmd -> code;
}

/* Take action on a command.
 * In most cases, this means call an event function
 * in the robot's C file.
 */

static void action (char *who, commands cmd,
		    int argc, char *args, char *rest)
{
  char *argv[argc];
  int i;

  argv[0] = args;			/* Retrieve arguments */
  for (i = 1; i < argc; i++)
    argv[i] = argv[i - 1] + strlen (argv[i - 1]) + 1;

  switch (cmd)
    {
    case c_332:
      topic ((char *) 0, rest);
      break;
    case c_invite:
      invites (who, argv[1]);
      break;
    case c_join:
      if (strcmp (who, mynick))
	joins (who);
      break;
    case c_msg:
      says (who, rest);
      break;
    case c_notice:
      informs (rest);
      break;
    case c_part:
      if (strcmp (who, mynick))
	left (who);
      break;
    case c_privmsg:
      if (!strcmp (mynick, argv[0]))
	tells (who, rest);
      else
	says (who, rest);
      break;
    case c_quit:
      quits (who);
      break;
    case c_topic:
      topic (who, rest);
      break;
    default:
      break;
    }
  return;
}


/*********************************************************************
 * Open and close -- session management
 * These are static functions, not available to the robot.
 */

/* Ircopen() uses pipe-fork-exec and ircserv.
 * This is the easy way.
 */

static int ircopen (char *arg_name, char *arg_user,
		    char *arg_server, char *arg_port)
{
  int p1[2], p2[2];
  int status;
  int c;

  
  status = pipe (p1) + pipe (p2);

  if (status)
    return 1;
  
  status = fork ();

  if (status == -1)
    {
      close (p1[0]);
      close (p1[1]);
      close (p2[0]);
      close (p2[1]);
      return 1;
    }
  
  if (status == 0)			/* Child process */
    {
      status = (-1 == close (p1[1]))
	+ (-1 == close (p2[0]))
	  + (-1 == dup2 (p1[0], 0))	/* stdin */
	    + (-1 == dup2 (p2[1], 1));	/* stdout */
      if (p1[0] > 2)
	status += (-1 == close (p1[0]));
      if (p2[1] > 2)
	status += (-1 == close (p2[1]));

      if (status)
	return 1;

      (void) execlp (IRCSERV, "ircserv",
		     arg_server, arg_port, (char *) 0);
      /* Parent will notice */
      exit (1);
      /* NOTREACHED */
    }


  close (p1[0]);			/* Parent process */
  close (p2[1]);

  mynick = (char *) 0;
  mychan = (char *) 0;
  
  RxD = fdopen (p2[0], "r");
  TxD = fdopen (p1[1], "w");

  if (!RxD || !TxD)
    {
      fclose (TxD);
      fclose (RxD);
      return 1;
    }
  
  fscanf (RxD, "%d", &c);		/* time-out? */
  if (c != 0)
    {
      fclose (TxD);
      fclose (RxD);
      return 1;
    }      

  /* Should look up host name, rather than to use "foo" */
  fprintf (TxD, "USER %s %s %s :%s\n", arg_user, "foo", arg_server, arg_name);
  fflush (TxD);
  return 0;
}

static int irclose (void)
{
  int s;
  
  fputs ("QUIT", TxD);
  fflush (TxD);
  s =  fclose (TxD);
  s += fclose (RxD);
  if (s)
    return EOF;
  else
    return 0;
}

/*********************************************************************
 * Respons functions. Used by the robot to respond to events.
 * These are all described in ircrobots.h.
 * Events are created when action() calls the robot.
 */

static void type2 (char *cp1, char *cp2)
{
  fputs (cp1, TxD);
  fputs (cp2, TxD);
  putc ('\n', TxD);
  fflush (TxD);
}

#define type(cp) type2(cp, "")

int join (char *arg_chan)
{
  commands cmd;
  char buf[54];
  char *cp[4];
  int i;
  
  type2 ("JOIN ", arg_chan);
  do
    {
      cmd = igets (buf, 54, &i, cp);
      action (cp[0], cmd, i, cp[2], cp[3]);
    }
  while (! ((cmd == c_join && !strcmp(cp[0], mynick)
	     && !strcmp (cp[2], arg_chan))
	    || cmd == c_403));		/* invalid channel */
  if (cmd == c_403)
    return -1;				/* staying */
  mychan = arg_chan;
  action (cp[0], cmd, i, cp[2], cp[3]);
  return 0;
}

int nick (char *arg_nick)
{
  commands cmd;
  char buf[64];
  char *cp[4];
  int i;

  type2 ("NICK ", arg_nick);
  do
    {
      cmd = igets (buf, 64, &i, cp);
      action (cp[0], cmd, i, cp[2], cp[3]);
    }
  while (! ((cmd == c_nick && !strcmp (cp[2], arg_nick))
	    || cmd == c_431
	    || cmd == c_432
	    || cmd == c_433
	    || (cmd == c_notice && !strcmp (cp[2], arg_nick))));
  if (cmd == c_431 || cmd == c_432 || cmd == c_433)
    return -1;
  mynick = arg_nick;
  action (cp[0], cmd, i, cp[2], cp[3]);
  return 0;
}

void say (char *cp)
{
  fputs ("MSG :", TxD);
  fputs (cp, TxD);
  putc ('\n', TxD);
  fflush (TxD);
}
     
void tell (char *who, char *cp)
{
  fputs ("PRIVMSG ", TxD);
  fputs (who, TxD);
  fputs (" :", TxD);
  fputs (cp, TxD);
  putc ('\n', TxD);
  fflush (TxD);
}

/*********************************************************************
 * Main(). Contains the top loop.
 */

int main (int argc, char *argv[]) 
{
  int ic;
  char buffer[256];
  char *vec[4];
  int narg;
  commands c;
  char login[L_cuserid + 1];
  char name[L_cuserid + 20];

  (void) cuserid (login);
  (void) strcpy (name, login);
  (void) strcat (name, "'s robot");
  
  ic = ircopen (name, login, SERVER, PORT);
  if (ic)
    {
      fprintf (stderr, " *** Cannot connect to IRC\n");
      exit (1);
    }

  if (birth (argc, argv))
    {
      fprintf (stderr, " *** Robot died during birth\n");
    }
  
  while (1)
    {
      c = igets (buffer, 256, &narg, vec);
      if (c == c_eof)
	break;
      if (c != c_zilch)
	action (vec[0], c, narg, vec[2], vec[3]);
    }
  

  if (0 != irclose ())
    {
      fprintf (stderr, " *** Error when closing connection\n");
      exit (1);
    }

  return 0;
}

---- cut here --------------------------------------------------------

/* File botrick.c
 * Aronsson was here
 * 7 MAY 1991
 */


#include <stdlib.h>
#include <stdio.h>
#include <strings.h>
#include <ctype.h>

#include "ircrobot.h"

static char *mynick = "Botrick";
static char *mychan = "+Stupid";

static  char sayline[256];

int birth (int argc, char *argv[])
{

  if (argc > 1)
    mychan = argv[--argc];
  if (argc > 0)				/* Will use argv[0] */
    mynick = argv[--argc];
  if (argc > 1)
    {
      fprintf (stderr, "Usage: %s [[nickname] channel]", argv[0]);
      return 1;
    }
  
  if (nick (mynick))
    {
      fprintf (stderr, " *** Cannot use /NICK %s\n", mynick);
      return 1;
    }

  if (join (mychan))
    fprintf (stderr, " *** Cannot /JOIN %s (who cares?)\n", mychan);

  return 0;
}


void informs (char *what)
{
  printf ("NOTICE: %s\n", what);
}

void invites (char *who, char *chan)
{
  printf ("%s INVITES me to %s\n", who, chan);
}

void joins (char *who)
{
  sprintf (sayline, "Hi, %s, welcome to %s!", who, mychan);
  say (sayline);
}

void left (char *who)
{
  sprintf (sayline, "Bye, %s, see you later!", who);
  tell (who, sayline);
}

static int count = 0;

void says (char *who, char *what)
{
  count += 1;
  count %= 17;
  if (!count)
    {
      sprintf (sayline, "%.4s: Why do you say ``%.20s''?", who, what);
      say (sayline);
    }
}

void tells (char *who, char *what)
{
  tell (who, "Is that true?");
}

void quits (char *who)
{
  sprintf (sayline, "Hey! Where did %s go?", who);
  say (sayline);
}

void topic (char *who, char *what)
{
  if (who)
    say ("Why did you change the topic?");
}


Valid HTML 4.0! Aronsson was here October 7, 2007.