/* small ircbot */
/* by erikw@acc.umu.se */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>


#define BUFSIZE 8192
#define FORK 0      // do we want to run as a daemon? 
#define NICKLENGTH 10


typedef struct {
  int  fd;                   // the filedescriptor to the irc server
  char server[50];
  int  port;
  char nick[NICKLENGTH];     // 9 is max acording to RFC 2813, but some servers accept more
  char username[20];
  char channel[210];
} botinfo;

/* globals */


char	log_time[15];


/* functions */

void daemonize(char *stin, char *stout, char *sterr);
void killbot(botinfo *bip);
char *logtime();

int sock_connect(botinfo *bip);
int init_irc(botinfo *bip);
int join_channel(botinfo *bip);
int read_data(botinfo *bip);
int parse_msg(char *buf, botinfo *bip);
int srv_msg(char *buf, botinfo *bip);
int process_srv_error(char *buf, botinfo *bip);
int process_srv_reply(char *buf, botinfo *bip);
int normal_msg(char *buf, botinfo *bip);
int privmsg_to_bot(char *buf, botinfo *bip);
int privmsg_to_channel(char *buf, botinfo *bip);
int bot_got_kicked(char *buf, botinfo *bip);
int send_privmsg(char *chan_nick, char *msg, botinfo *bip);
int send_notice(char *chan_nick, char *msg, botinfo *bip);
int nick_in_use(botinfo *bip);
int process_ctcp_msg(char *buf, botinfo *bip);

long print_tvprogs_from_file(botinfo *bip);  // temp funct


/* main() */

int main(int argc, char **argv) {

  botinfo 		bi;
  botinfo 		*bip;
  time_t	t =	time(NULL);
  char 			log[50];
  
  if (argc < 7) { // did we get all args we need?
    printf("Usage: %s <server> <port> <nick> <username> <ircchannel> <logfile>\n",argv[0]);
    exit(0);
  }
  bip = &bi;  // point the pointer bip to bi
 
  /* fill the struct with the data we just got */
  strncpy(bip->server,argv[1],50-1); // -1 to leave space for \0
  bip->port = atoi(argv[2]);
  strncpy(bip->nick,argv[3],NICKLENGTH-1);
  strncpy(bip->username,argv[4],20-1);
  strncpy(bip->channel,argv[5],210-1);
  /* done! */
  
  strncpy(log,argv[6],50);
  
  if (FORK == 1) {
    printf("Minibot\nStarting as daemon.\n");
    printf("Logging to %s\n",log);
    daemonize("/dev/null", log, log);
  }
    
  
  if ((sock_connect(bip) != 0)) {
    printf("%s Error connecting to %s:%d\n",logtime(),bip->server,bip->port);
    exit(-1);
  }
  
  
  init_irc(bip);
  join_channel(bip);
  
  sleep(1); 

  /* the main loop */
  while(1) {  
    /* once a second */
    if ((read_data(bip)) <= 0) {
      printf("Connection lost!\n");
      exit(-1);
    }
    if (t+60 < time(NULL)) {
      /* once a minute */
      t = time(NULL);
      print_tvprogs_from_file(bip);
    }
  }
  /* ... ends here */
  exit(0);
}

int sock_connect(botinfo *bip) {

  struct sockaddr_in    sin;
  struct hostent	*hent;
  
  if ((hent = gethostbyname(bip->server)) == NULL) {
    printf("Unable to resolv host.\n");
    return -1;
  }
       
  if ((bip->fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1 ) {
    printf("Unable to open socket.\n");
    exit(-1);
  }
  sin.sin_family = PF_INET;
  sin.sin_port = htons(bip->port);
  sin.sin_addr = *((struct in_addr *)hent->h_addr);
  memset(&(sin.sin_zero), '\0', 8);
  
  if (connect(bip->fd,(struct sockaddr *)&sin, sizeof(struct sockaddr)) == -1) {
    printf("Unable to connect.\n");
    return -1;
  }
  printf("%s Connected to %s:%d\n",logtime(),bip->server,bip->port);
  return 0;
}

int init_irc(botinfo *bip) {

  /* we need to send USER and NICK to the server to get in... */
  char 			output[200];
  
  sprintf(output,"USER %s %s %s %s\nNICK %s\n",bip->username,bip->username,bip->username,bip->username,bip->nick);
  if ((send(bip->fd,output,strlen(output),0)) == -1) {
    return -1;
  }
  printf("%s Trying to get nick: %s\n",logtime(),bip->nick);
  return 0;
}
  
int join_channel(botinfo *bip) {

  char			output[50];
  
  sprintf(output,"JOIN %s\n",bip->channel);
  if ((send(bip->fd,output,strlen(output),0)) == -1) {
    return -1;
  }
  return 0;
}

int send_privmsg(char *chan_nick, char *msg, botinfo *bip) {
  
  char output[1024]; // dunno how long msgs you are allowed to send
 
  sprintf(output,"PRIVMSG %s :%s\n",chan_nick, msg);
  if ((send(bip->fd,output,strlen(output),0)) == -1) {
    return -1;
  }
  return 0;
}

int read_data(botinfo *bip) {

   char		buf[BUFSIZE];
   struct	timeval t;
   fd_set	rfds;
   int		ret;
   char 	tmpbuf[BUFSIZE];
   
   memset(buf,0,BUFSIZE);
   memset(tmpbuf,0,BUFSIZE);
   
   /* we need to get the data non-blocking, timeout after 1 sec */
   t.tv_sec = 1; 
   t.tv_usec = 0;
   
   FD_ZERO(&rfds);
   FD_SET(bip->fd, &rfds);
   
   
   if (select (bip->fd+1 , &rfds, NULL, NULL, &t)) {
     ret = recv(bip->fd,buf ,BUFSIZE - 1,0);
     if (ret > 0 ) {           
       /* got data */
       char *p;
       int length=0;
       int offset=0;
       /* this routine will not work if buf is full, but that dosen't seem to be our problem  */
       while ((p = memchr(buf+offset+1, '\n', ret)) != NULL) {  // safe to use +1 (to get behind the \n)
	 /* split buf into lines and run parse_msg() on each line */ 
	 if (offset > ret) break;  // if we have processed all we got from recv() then break
	 p++;                      // we want p to get behind \n
	 length = p-(buf+offset);  // how long is the line we are about to process?
	 memset(tmpbuf,0,BUFSIZE); // null the the tmpbuf
	 memcpy(tmpbuf,buf+offset,length); // copy the line we just got to tmpbuf
	 offset += length;                 // we need this to keep track of where to begin next time
	 parse_msg(tmpbuf,bip);            // send the line to parse_msg()
       }
     return 1;
     }
     else if (ret == 0) {      
       // connection closed 
       return 0;
     }
     else if (ret == -1) {               
       // error from recv() 
       return -1;
     }

   }
   return 1;             // did not read data, still everything OK
}

int parse_msg(char *buf, botinfo *bip) {    // check if data is a server msg or normal msg
   
  char *p;
  char *q; 

  if (buf[0] == ':') {         // normal msgs begin with :
    /* server reply if the second field (separated with space) is a digit */
    p = strchr(buf,32);     // find the second field
    p++;                       // now p is pointing at the first char in the second field 
    if ((isdigit(*p)) != 0) {
      /* second field starts with a digit, its a server reply msg */
      process_srv_reply(buf, bip);
      return 0;
    }
    /* the message begins after the next : since we are already past the first one */
    q = strstr(p,":");
    q++;  
    if (*q == '1') {
      process_ctcp_msg(buf,bip);
      return 0;
    }
    /* its a normal msg */
    normal_msg(buf,bip);
    return 0;
  }
  else {
    srv_msg(buf,bip);
    return 0;
  }
  /* not supposed to get here */
  printf("%s Message slipped through parse_msg()\n",logtime());
  printf("   %s",buf);
  return -1;
}

int srv_msg(char *buf, botinfo *bip) {
  
  /* now we need to check what kind of message the server sent us */
  
  char *p;
  char output[50];
  
  if ((strncmp(buf,"PING",4)) == 0) {
    /* the server sent a PING and we need to reply with a PONG */
    p = strstr(buf,":"); // after the : is the servername
    sprintf(output,"PONG %s\n",p);
    send(bip->fd,output,strlen(output),0);
//  printf("TO SRV: %s",output);
    return 0;
  }
  else if ((strncmp(buf,"ERROR",5)) == 0) {
    /* we got an ERROR msg, send it to process_srv_error() */
    process_srv_error(buf,bip);
    return 0;
  }
  else if ((strncmp(buf,"NOTICE",6)) == 0) {
    /* NOTICE, add a function */
    return 0;
  }
  printf("%s Message slipped through srv_msg()\n",logtime());
  printf("   %s",buf);
  return -1;
}

int process_srv_error(char *buf, botinfo *bip) {
  
   if (strstr(buf,":Closing Link:")) {
     /* we lost the connection to the server */
     printf("%s Lost connection to server\n",logtime()); 
     
     close(bip->fd);
     /* lets reconnect and join the channel again */
     sock_connect(bip);
     init_irc(bip);
     join_channel(bip);
     return 0;
   }
   printf("%s Message slipped through process_srv_error()\n",logtime());
   printf("   %s",buf);
   return -1;
}

int process_srv_reply(char *buf, botinfo *bip) {
   
   char tmpbuf[BUFSIZE];
   char *p;
   char *q;
   int  errnum;
  
   /* now lets get the errornumber to an int ... */
   memcpy(tmpbuf,buf,BUFSIZE);
   p = strchr(tmpbuf,32);
   q = strchr(p+1,32);
   *q = '\0';

   errnum = atoi(p);
   /* now we can use a switch-statement */

   switch (errnum) {
     case 433:    
       /* nickname in use */
       nick_in_use(bip);
       return 0;
     /* add more solutions to different error numbers */
   }
   // printf("A server reply message. (errnum ==%d)\n",errnum);
   /* we don't know what to do with this message */  
   return -1;
}

int normal_msg(char *buf, botinfo *bip) {
  
  /* msg format    :nick!user@adress.com COMMAND (#channel|nick) :MSG   */

  char tmpbuf[BUFSIZE]; // we dont want to destroy the original string with \0
  char *q;
  char *r;
  
  memcpy(tmpbuf,buf,BUFSIZE); // copy whats in buf to tmpbuf
  q = strtok(tmpbuf,":");     // extract whats inbetween the :'s
  *(q + strlen(q) - 1) = '\0';  // remove the space in the end...
//printf("q = '%s'\n",q);       // we need to figure out what to do with the msg
    
  r = strchr(q,32);   // remove nick!user@adress.com 
  r++;                // now r is pointing on the COMMAND
                      // probably most safe to check with strncmp(), strstr()
		      // might fuckup if someone ha the same nick as a command
  printf("r == '%s'\n",r);  
  if ((strncmp(r,"PRIVMSG",7)) == 0) {  // a msg to the bot or the channel?
    r +=8;                         // now r is pointing on the nick or channel
//  printf("r == '%s'\n",r);
    if ((strncasecmp(r,bip->channel,strlen(bip->channel))) == 0) { 
      /* we got a privmsg to the channel */
      privmsg_to_channel(buf, bip);
      return 0;
    }
    else if ((strncasecmp(r,bip->nick,strlen(bip->nick))) == 0) {
      /* a privmsg to the bot! */
      privmsg_to_bot(buf, bip);
      return 0;
    }
    return -1; // i don't think this will ever happen
  }
  else if ((strncasecmp(r,"KICK",4)) == 0) {  // lets see if someone got kicked
    r += 6 + strlen(bip->channel); // r is pointing on the kicked users nick (KICK is 4 + 2 spaces)
    if ((strncasecmp(r,bip->nick,strlen(bip->nick))) == 0) {
      /* someone kicked the bot */
      bot_got_kicked(buf, bip);
      return 0;
    }
    else {
      /* someone else got kicked from the channel */
      return 0;
    }
  }
  else if ((strncasecmp(r,"JOIN",4)) == 0) {
    /* someone joined our channel, might be us */
    joined_channel(buf,bip);
    return 0;
  }
  else if ((strncasecmp(r,"PART",4)) == 0) {
    /* someone left our channel */
    /* add function here */
    return 0;
  }
  else if ((strncasecmp(r,"QUIT",4)) == 0) {
    /* someone left the server */
    /* add function here */
    return 0;
  }
  else if ((strncasecmp(r,"NOTICE",6)) == 0) {
    /* NOTICE */
    /* add function here */
    return 0;
  }
  /* nothing caught the message :(  */
  printf("%s Message slipped through normal_msg()\n",logtime());
  printf("   %s",buf);
  return -1;
}

int privmsg_to_bot(char *buf, botinfo *bip) {
  
  char *msg;
  char fromwho[NICKLENGTH+10];
  char *q;
  /* get the nick of the person who sent us the msg */
  memset(fromwho,0,NICKLENGTH+10);
  memcpy(fromwho,buf+1,NICKLENGTH+9);  // +1 to get past the first ":"
  q = strchr(fromwho,'!');             // after the nick there is a "!"
  *(q) = '\0';                         // null instead of the "!"
  
  
  msg = strstr(buf+1,":"); //get past the first ":" message begins after second
  msg++;   // the message begins here...
  
  printf("%s Message from %s: %s\n",logtime(),fromwho,msg);
  if ((strncasecmp(msg,"DIE",3)) == 0) {
    killbot(bip);
  }
  return 0;
}
int privmsg_to_channel(char *buf, botinfo *bip) {
  /* maybe we want to check for some triggers here */
/*  char *p;
  if (p = strstr(buf,"*PRE*")) {
    p += 6;
    sprintf(output,"Det här ska jag msga till ett nick: %s",p);
    send_privmsg("chilinut",p,bip);
    return 0;
  }
*/
  return 0;
}
  
int bot_got_kicked(char *buf, botinfo *bip) {
  
  /* passing pointer to buf if we want to look who kicked us */
  /* add nick of the user who kicked us to log */
  printf("%s Bot got kicked from %s\n",logtime(),bip->channel);
  join_channel(bip);
  return 0;
}

int nick_in_use(botinfo *bip) {
  /* we need to change our nick to get in */
  char output[20];
  
  printf("%s Nickname is in use, changing nick.\n",logtime());
  if (strlen(bip->nick) < NICKLENGTH) {
    strncat(bip->nick,"_",1);
    sprintf(output,"NICK %s\n",bip->nick);
    send(bip->fd,output,strlen(output),0);
    /* and then we need to join our channel */
    join_channel(bip);
    return 0;
  }
  /* unable to change nick because of strlen */
  return -1;
}
int process_ctcp_msg(char *buf, botinfo *bip) {
  /* CTCP messages are processed here */
  char *p;
  char *q;
  char *r;
  char tmpbuf[BUFSIZE];
  char nick[30];
  char output[100];
  
  memcpy(tmpbuf,buf,BUFSIZE);
  
  /* first of all we need to get the nick of the person that sent us the CTCP */
  r = &buf[1]; // we dont want the first ":"
  memset(nick,0,30); // null nick
  memcpy(nick,r,29); // the nick will be within this part 
  r = strstr(nick,"!");
  *r = '\0';
  
  /* now lets check what kind of CTCP we got */
  p = strstr(buf,":");
  q = strstr(p+1,":");
  q += 2;   // q is pointing on what type of ctcp 
//printf("q == %s\n",q); 
  if ((strncasecmp(q,"VERSION",7)) == 0) {
    sprintf(output,"%cVERSION minibot 0.1a%c",1,1);
    send_notice(nick,output,bip);
    return 0;
  }
  else if ((strncasecmp(q,"PING",4)) == 0) {
    /* now we need to send back the ping we got (including timestamps) */
    p = strchr(buf,1);
    q = strchr(p+1,1);
    q++;
    *q = '\0';
//  printf("p == '%s'\n",p);
    if (strchr(p,32)) {  
      /* message has timestamp, send reply! */
      send_notice(nick,p,bip);
      return 0;
    }
    /* message has no timestamp, discard */
    return 0;
  }
  else if ((strncasecmp(q,"ACTION",6)) == 0) {
    /* this is if someone does a /me message */
    return 0;
  }
  
  printf("%s Don't know what to do with this CTCP",logtime());
  printf("   %s",buf);
  return 0;
}

int send_notice(char *chan_nick, char *msg, botinfo *bip) {
  
  char output[1024]; // dunno how long msgs you are allowed to send
 
  sprintf(output,":%s NOTICE %s :%s\n", bip->nick, chan_nick, msg);
  if ((send(bip->fd,output,strlen(output),0)) == -1) {
    return -1;
  }
  return 0;
}

void daemonize(char *stin, char *stout, char *sterr) {
    
  if (fork()) {
    /* now we need to kill the parent */
    exit(0);
  }
  setsid(); // create a new session
  if (fork()) {
    exit(0);
  }
  /* lets redirect stdin, strout and stderr */
  
  dup2(open(stin, O_RDONLY), 0);
  dup2(open(stout, O_WRONLY | O_CREAT), 1);
  dup2(open(sterr, O_WRONLY | O_CREAT), 2);
  
  return;
}

void killbot(botinfo *bip) {

  send(bip->fd, "QUIT\n",5,0);
  sleep(1);
  close(bip->fd);
  printf("%s Shutdown requested...\n",logtime());
  exit(0);
}
long print_tvprogs_from_file(botinfo *bip) {
/* temp function */
  
  char  buf[300];
  static long po; 
  FILE   *fil;
  static int first=1;
  
  fil = fopen("watch.txt","r");
  if (first == 1) {
    while ((fgets(buf, 300, fil)) != NULL ) {
      if (buf[0] == '\n') break;
      send_privmsg(bip->channel, buf, bip);
    }
  first=0;
  po = ftell(fil);
  return;
  }
  fseek(fil,po-1,SEEK_CUR);

  while ((fgets(buf, 300, fil)) != NULL ) {
    if (buf[0] == '\n') break;
    send_privmsg(bip->channel, buf, bip);
  }
  po = ftell(fil);
  fclose(fil);
  return po;
}

char *logtime() {
  /* returns char* to date in format ex.:  Jan 5  [08:45] */
  time_t	t;
  struct tm	*tmt;
  
  t = time(NULL);
  tmt = gmtime(&t);
  
  switch (tmt->tm_mon) {
    case 0:
      memcpy(log_time,"Jan",3);
      break;
    case 1:
      memcpy(log_time,"Feb",3);
      break;
    case 2:
      memcpy(log_time,"Mar",3);
      break;
    case 3:
      memcpy(log_time,"Apr",3);
      break;
    case 4:
      memcpy(log_time,"May",3);
      break;
    case 5:
      memcpy(log_time,"Jun",3);
      break;
    case 6:
      memcpy(log_time,"Jul",3);
      break;
    case 7:
      memcpy(log_time,"Aug",3);
      break;
    case 8:
      memcpy(log_time,"Sep",3);
      break;
    case 9:
      memcpy(log_time,"Okt",3);
      break;
    case 10:
      memcpy(log_time,"Nov",3);
      break;
    case 11:
      memcpy(log_time,"Dec",3);
      break;
  }
  
  sprintf(log_time+3," %-d  [%d:%d]",tmt->tm_mday,tmt->tm_hour,tmt->tm_min);
  
  return log_time;
}

int joined_channel(char *buf, botinfo *bip) {

  char *who;
  who = &buf[0];
  who++;
  
  if ((strncasecmp(who,bip->nick,strlen(bip->nick))) == 0) {
    printf("%s Joined channel: %s\n",logtime(),bip->channel);
  }
  return 0;
}
   

