// Parser.C  -*- C++ -*-
// Copyright (c) 1997, 1998 Etienne BERNARD
// Copyright (C) 2002 Clinton Ebadi

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307, USA.

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <sys/types.h>
#include <netinet/in.h>

#include "StringTokenizer.H"
#include "Parser.H"
#include "UserCommands.H"
#include "Macros.H"
#include "Utils.H"
#include "ShitList.H"

static struct {
  char *name;
  void (*function)(ServerConnection *, Person *, String);
} functions[] =
{
  { "001",     Parser::parse001     }, /* RPL_WELCOME */
  { "302",     Parser::parse302     }, /* RPL_USERHOST */
  { "311",     Parser::parse311     }, /* RPL_WHOISUSER */
  { "315",     Parser::parse315     }, /* RPL_ENDOFWHO */
  { "324",     Parser::parse324     }, /* RPL_CHANNELMODEIS */
  { "332",     Parser::parse332     }, /* RPL_TOPIC */
  { "352",     Parser::parse352     }, /* RPL_WHOREPLY */
  { "353",     Parser::parse353     }, /* RPL_NAMESREPLY */
  { "366",     Parser::parse366     }, /* RPL_ENDOFNAMES */
  { "367",     Parser::parse367     }, /* RPL_BANLIST */
  { "401",     Parser::parse401     }, /* ERR_NOSUCHNICK */
  { "433",     Parser::parse433     }, /* ERR_NICKNAMEINUSE */
  { "437",     Parser::parse433     }, /* ERR_UNAVAILRESOURCE */
  { "471",     Parser::parse473     }, /* ERR_CHANNELISFULL */
  { "473",     Parser::parse473     }, /* ERR_INVITEONLYCHAN */
  { "474",     Parser::parse473     }, /* ERR_BANNEDFROMCHAN */
  { "475",     Parser::parse473     }, /* ERR_BADCHANNELKEY */
  { "ERROR",   Parser::parseError   },
  { "INVITE",  Parser::parseInvite  },
  { "JOIN",    Parser::parseJoin    },
  { "KICK",    Parser::parseKick    },
  { "MODE",    Parser::parseMode    },
  { "NICK",    Parser::parseNick    },
  { "NOTICE",  Parser::parseNotice  },
  { "PART",    Parser::parsePart    },
  { "PING",    Parser::parsePing    },
  { "PONG",    Parser::parsePong    },
  { "PRIVMSG", Parser::parsePrivmsg },
  { "QUIT",    Parser::parseQuit    },
  { "TOPIC",   Parser::parseTopic   },
  { "",        Parser::parseError   },
  { 0,         0                    }
};


void
Parser::parseLine(ServerConnection * cnx, String line)
{
  StringTokenizer st(line);
  Person * from = 0;

#ifdef USESCRIPTS
  cnx->bot->botInterp->RunHooks(Hook::RAW, line,
                   scm_listify (Utils::string2SCM(line),
                           SCM_UNDEFINED));
#endif

  if (line[0] == ':') {
    String fromMask = st.nextToken().subString(1);
    if (fromMask.find('!') != -1)
      from = new Person(cnx->bot, fromMask);
  }

  String command = st.nextToken();
  String rest = st.rest();

  for (int i = 0; functions[i].name != 0; i++)
    if (command == functions[i].name) {
      functions[i].function(cnx, from, rest);
      break;
    }

  delete from;
}

void
Parser::parse001(ServerConnection * cnx,
                 Person *from, String rest)
{
  String temp = "";
  StringTokenizer st(rest);
  String realNick = st.nextToken();

  if ((cnx->bot->nickName).toLower() != realNick.toLower ()) {
    // Yes, this can happen, and it was a very subtle bug
    cnx->bot->nickName = realNick;
    cnx->bot->userList->removeFirst();
    cnx->bot->userList->addUserFirst(realNick + "!" + cnx->bot->userHost, "*", 0, 3, true, -1, "");
    cnx->bot->lastNickNameChange = time(0);
    cnx->bot->rehash();
  }

  cnx->bot->connected = true;

  cnx->queue->sendUserMode(cnx->bot->nickName, "+i");
  cnx->queue->sendWhois(cnx->bot->nickName);

  for (std::map<String, wantedChannel *, std::less<String> >::iterator
         it = cnx->bot->wantedChannels.begin();
       it != cnx->bot->wantedChannels.end(); ++it)
    cnx->queue->sendJoin((*it).first, (*it).second->key);

  cnx->bot->logLine(String("Connected to server ") +
               cnx->bot->serverList->currentServer()->getHostName() +
               " (" +  String((long)cnx->bot->serverList->currentServer()->getPort()) +
               ").");
}

void
Parser::parse302(ServerConnection *cnx,
                 Person *from, String rest)
{
  unsigned long num = cnx->bot->receivedUserhostID++;
  StringTokenizer st(rest);

  st.nextToken(':');

  if (st.rest().length()) {
    st.nextToken('=');
    String parameters = st.rest();
    parameters = parameters.subString(1);
    cnx->bot->userhostMap[num] = parameters;
  } else
    cnx->bot->userhostMap[num] = "";
}

void
Parser::parse311(ServerConnection *cnx,
                 Person *from, String rest)
{
  StringTokenizer st(rest);
  st.nextToken();
  String nuh = st.nextToken() + "!";
  String uh = st.nextToken() + "@";
  uh = uh + st.nextToken();
  nuh = nuh + uh;
  cnx->bot->userList->addUserFirst(nuh, "*", 0, 3, true, -1, "");
  cnx->bot->userHost = uh;
}

void
Parser::parse315(ServerConnection *cnx,
                 Person *from, String rest)
{
  StringTokenizer st(rest);
  st.nextToken();
  String channel = st.nextToken();
  Channel *c = cnx->bot->channelList->getChannel(channel);
  if (!c)
    return;
  c->gotWho = true;
}

void
Parser::parse324(ServerConnection *cnx,
                 Person *from, String rest)
{
  StringTokenizer st(rest);
  st.nextToken();
  String channel = st.nextToken();
  if (Channel *c = cnx->bot->channelList->getChannel(channel))
    if (c) c->parseMode(from, st.rest());
}

void
Parser::parse332(ServerConnection *cnx,
                 Person *from, String rest)
{
  StringTokenizer st(rest);
  st.nextToken();
  String channel = st.nextToken();
  if (Channel *c = cnx->bot->channelList->getChannel(channel))
    if (c) c->channelTopic = st.rest().subString(1);
}

void
Parser::parse352(ServerConnection *cnx,
                 Person *from, String rest)
{
  StringTokenizer st(rest);
  st.nextToken();
  String ch = st.nextToken();
  String uh = st.nextToken() + "@";
  uh = uh + st.nextToken();
  st.nextToken();
  String n = st.nextToken();
  String m = st.nextToken();
  int mode = 0;
  
  for (int i = 0; i < m.length(); i++)
    switch (m[i]) {
    case 'H': break;
    case 'G': mode |= User::AWAY_MODE; break;
    case '*': mode |= User::IRCOP_MODE; break;
    case '@': mode |= User::OP_MODE; break;
    case '+': mode |= User::VOICE_MODE; break;
    }
  if (Channel *c = cnx->bot->channelList->getChannel(ch))
    if (c) c->addNick(n, uh, mode, cnx->bot->userList);
}

void
Parser::parse353(ServerConnection *cnx,
                 Person *from, String rest)
{
  int mode = 0;
  String nick;

  StringTokenizer st(rest);
  st.nextToken(); st.nextToken();

  Channel * c = cnx->bot->channelList->getChannel(st.nextToken());
  if (!c) return;
  StringTokenizer st2(st.nextToken(':'));

  while (st2.hasMoreTokens()) {
    nick = st2.nextToken();
    if (nick[0] == '@') {
      mode = User::OP_MODE;
      nick = nick.subString(1);
    } else if (nick[0] == '+') {
      mode = User::VOICE_MODE;
      nick = nick.subString(1);
    }
    c->addNick(nick, "", mode, 0, true);
  }
}

void
Parser::parse366(ServerConnection *cnx,
                 Person *from, String rest)
{
  StringTokenizer st(rest);
  st.nextToken();
  String ch = st.nextToken();
  if (Channel *c = cnx->bot->channelList->getChannel(ch))
    c->joined = true;
}

void
Parser::parse367(ServerConnection *cnx,
                 Person *from, String rest)
{
  StringTokenizer st(rest);
  st.nextToken();
  String ch = st.nextToken();
  if (Channel *c = cnx->bot->channelList->getChannel(ch))
    c->addBan(st.nextToken(), -1);
}

void
Parser::parse401(ServerConnection *cnx,
                 Person *from, String rest)
{
  StringTokenizer st(rest);
  st.nextToken();
  String nick = st.nextToken();

  if (cnx->bot->spyList.find(nick) != cnx->bot->spyList.end()) {
    delete cnx->bot->spyList[nick];
    cnx->bot->spyList.erase(nick);
  }
}

void
Parser::parse433(ServerConnection *cnx,
                 Person *from, String rest)
{
  if (cnx->bot->connected)
    return;

  if (cnx->bot->nickName.length() == 9) {
    int i;
    for (i = 0; i < cnx->bot->nickName.length() && cnx->bot->nickName[i] == '_'; i++)
       ;
     if (i < cnx->bot->nickName.length())
       cnx->bot->nickName = cnx->bot->nickName.subString(0, i-1) + "_" + cnx->bot->nickName.subString(i+1);
     else
       cnx->bot->nickName = cnx->bot->nickName.subString(0, 4) +
         String((long)(rand() % 10000));
   }
   else
     cnx->bot->nickName = cnx->bot->nickName + "_";

  cnx->queue->sendNick(cnx->bot->nickName);
}

void
Parser::parse473(ServerConnection *cnx,
                 Person *from, String rest)
{
  StringTokenizer st(rest);
  st.nextToken();

  cnx->bot->logLine(String("Unable to join channel ") +
               st.nextToken() + ".");
}

void
Parser::parseError(ServerConnection *cnx,
                   Person *from, String rest)
{
  cnx->bot->logLine(String("Error from server ") +
               cnx->bot->serverList->currentServer()->getHostName() +
               " (" + String((long)cnx->bot->serverList->currentServer()->getPort()) +
               ").");
  cnx->bot->nextServer();
}

void
Parser::parseInvite(ServerConnection *cnx,
                    Person *from, String rest)
{
  String nick = from->getNick();
  StringTokenizer st(rest);
  st.nextToken(':');
  String channel = st.rest();

#ifdef USESCRIPTS
  cnx->bot->botInterp->RunHooks(Hook::INVITE, nick + " " + channel,
                   scm_listify (Utils::string2SCM(nick),
                           Utils::string2SCM(channel),             
                           SCM_UNDEFINED));
#endif

  if (cnx->bot->wantedChannels.find(channel) !=
      cnx->bot->wantedChannels.end())
    cnx->queue->sendJoin(channel, cnx->bot->wantedChannels[channel]->key);
}

void
Parser::parseJoin(ServerConnection *cnx,
                  Person *from, String rest)
{
  StringTokenizer st(from->getAddress());
  String n = st.nextToken('!');
  String uh = st.nextToken();
  StringTokenizer st2(rest);
  String c = st2.nextToken(':');
  String mode;
  bool joinAndMode = false;

#ifdef USESCRIPTS
  cnx->bot->botInterp->RunHooks(Hook::JOIN, n + " " + c,
                   scm_listify (Utils::string2SCM(n),
                           Utils::string2SCM(c),             
                           SCM_UNDEFINED));
#endif

  // This part of code is for the combined JOIN & MODE of ircd 2.9
  if (c.find('\007') >= 0) {
    joinAndMode = true;
    StringTokenizer st3(c);
    c = st3.nextToken('\007');
    String m = st3.rest();
    mode = c + " +" + m;
    for (int i = 0; i < m.length(); i++)
      mode = mode + " " + n;    
  }

  if (n == cnx->bot->nickName) {
    cnx->bot->logLine(String("Joined channel ") + c + ".");
    if (cnx->bot->wantedChannels.find(c) != cnx->bot->wantedChannels.end())
      cnx->bot->channelList->addChannel(cnx, c, cnx->bot->wantedChannels[c]->keep);
    else
      cnx->bot->channelList->addChannel(cnx, c);
    cnx->queue->sendWho(c);
    cnx->queue->sendChannelMode(String("MODE ") + c + " b");
    cnx->queue->sendChannelMode(String("MODE ") + c);
  } else {
    Channel * ch = cnx->bot->channelList->getChannel(c);
    if (!ch)
      return;
    ShitEntry * se = cnx->bot->shitList->getShit(n+"!"+uh, c);
    if (se && se->isStillValid() &&
        se->getShitLevel() >= ShitEntry::SHIT_NOJOIN) {
      cnx->queue->sendChannelMode(c, "+b", se->getMask());
      cnx->queue->sendKick(c, n, se->getShitReason());
      return;
    }
    ch->addNick(n, uh, 0, cnx->bot->userList);
    if (ch->getUser(n)->getAop() && !(ch->getUser(n)->mode & User::OP_MODE) && cnx->bot->iAmOp(c)) {
      // This is a part of the antispoof code
      ch->getUser(n)->userkey = Utils::getKey();
      cnx->queue->sendCTCP(n, "PING", ch->getUser(n)->userkey + " " + c);
    }
  }

  if (joinAndMode)
    parseMode(cnx, 0, mode);
}

void
Parser::parseKick(ServerConnection *cnx,
                  Person *from, String rest)
{
  StringTokenizer st(rest);
  String channel = st.nextToken();
  String target = st.nextToken();
  String reason = st.rest().subString(1);

#ifdef USESCRIPTS
  cnx->bot->botInterp->RunHooks(Hook::KICK, target + " " + from->getNick() + " "
                   + channel + " " + reason,
                   scm_listify (Utils::string2SCM(target),
                           Utils::string2SCM(from->getNick()),
                           Utils::string2SCM(channel),
                           Utils::string2SCM(reason),
                           SCM_UNDEFINED));
#endif

  if (target == cnx->bot->nickName) {
    cnx->bot->logLine(from->getAddress() + " kicked me out of channel " +
                 channel + " (" + reason + ").");
    cnx->queue->sendJoin(channel, cnx->bot->channelList->getChannel(channel)->channelKey);
    cnx->bot->channelList->delChannel(channel);
  } else {
    if (!cnx->bot->channelList->getChannel(channel)) return;
    User *u = cnx->bot->channelList->getChannel(channel)->getUser(target);
    if (u && u->getProt() >= User::NO_KICK) {
      String fromNick = from->getNick();
      User *v = cnx->bot->channelList->getChannel(channel)->getUser(fromNick);
      if (v->getProt() < User::NO_KICK) {
        cnx->bot->logLine(from->getAddress() + " kicked " + target +
                     " (protected) out of channel " + channel +
                     " (" + reason + ").");
        cnx->queue->sendKick(channel, fromNick,
                        target + " \002is protected !\002");
      }
    }
    cnx->bot->channelList->getChannel(channel)->delNick(target);
  }
}

void
Parser::parseMode(ServerConnection *cnx,
                  Person *from, String rest)
{
   StringTokenizer st(rest);
   String ch = st.nextToken();
   String modes = st.rest();

#ifdef USESCRIPTS
   if (from)
     cnx->bot->botInterp->RunHooks(Hook::MODE, from->getNick() + " " + ch +
                      " " + modes,
                      scm_listify (Utils::string2SCM(from->getNick()),
                              Utils::string2SCM(ch),
                              Utils::string2SCM(modes),
                              SCM_UNDEFINED));
#endif


   if (Utils::isChannel(ch)) {
     Channel *c = cnx->bot->channelList->getChannel(ch);
     if (!c)
       return;
     if (from)
       c->parseMode(from, modes);
     else
       c->parseMode(0, modes);
   }       
}

void
Parser::parseNick(ServerConnection *cnx,
                  Person *from, String rest)
{
  String on_orig = from->getNick();
  String on = on_orig.toLower();
  String nn = rest.subString(1);
  String nn_lower = nn.toLower();

#ifdef USESCRIPTS
  cnx->bot->botInterp->RunHooks(Hook::NICKNAME, on_orig + " " + nn,
                   scm_listify (Utils::string2SCM(on_orig),
                           Utils::string2SCM(nn),
                           SCM_UNDEFINED));
#endif

  if ((cnx->bot->nickName).toLower() == on) {
    cnx->bot->userList->removeFirst();
    cnx->bot->userList->addUserFirst(nn + "!" + cnx->bot->userHost, "*", 0, 3, true, -1, "");
    cnx->bot->lastNickNameChange = time(0);
    cnx->bot->nickName = nn;
    cnx->bot->rehash();
  }

  if (cnx->bot->spyList.find(on) != cnx->bot->spyList.end()) {
    cnx->bot->spyList[nn_lower] = cnx->bot->spyList[on];
    cnx->bot->spyList.erase(on);
  }

  for (std::map<String, Channel *, std::less<String> >::iterator it =
         cnx->bot->channelList->begin();
       it != cnx->bot->channelList->end();
       ++it)
    if ((*it).second->hasNick(on))
      (*it).second->changeNick(on, nn_lower);
}

void
Parser::parseNotice(ServerConnection *cnx,
                    Person *from, String rest)
{
  String nick = "";

  if (from)
    nick = from->getNick();

  StringTokenizer st(rest);
  String to = st.nextToken();

  rest = st.rest().subString(1);

  if (rest[0] != '\001') {
#ifdef USESCRIPTS
    if (Utils::isChannel(to))
      cnx->bot->botInterp->RunHooks(Hook::PUBLIC_NOTICE, nick + " " +
                       to + " " + rest,
                       scm_listify (Utils::string2SCM(nick),
                               Utils::string2SCM(to),
                               Utils::string2SCM(rest),
                               SCM_UNDEFINED));
    else
      cnx->bot->botInterp->RunHooks(Hook::NOTICE, nick + " " + rest,
                       scm_listify (Utils::string2SCM(nick),
                               Utils::string2SCM(rest),
                               SCM_UNDEFINED));
#endif
    return;
  }

  rest = rest.subString(1, rest.length() - 2);
  StringTokenizer st2(rest);
  String command = st2.nextToken();
  rest = st2.rest();

#ifdef USESCRIPTS
  cnx->bot->botInterp->RunHooks(Hook::CTCP_REPLY, nick + " " + command +
                   " " + rest,
                   scm_listify (Utils::string2SCM(nick),
                           Utils::string2SCM(command),
                           Utils::string2SCM(rest),
                           SCM_UNDEFINED));
#endif

  if (command == "PING") {
    StringTokenizer st3(rest);
    rest = st3.nextToken();
    String c = st3.rest();
    if (cnx->bot->channelList->getChannel(c) &&
        cnx->bot->channelList->getChannel(c)->getUser(nick) &&
        cnx->bot->channelList->getChannel(c)->getUser(nick)->getAop() &&
        !(cnx->bot->channelList->getChannel(c)->getUser(nick)->mode & User::OP_MODE)
        && cnx->bot->channelList->getChannel(c)->getUser(nick)->userkey == rest)
      cnx->queue->sendChannelMode(c, "+o", nick);
  }
}

void
Parser::parsePrivmsg(ServerConnection *cnx,
                     Person *from, String rest)
{
  String nick = from->getNick();

  StringTokenizer st(rest);
  String to = st.nextToken();
  String fromUserhost = Utils::getUserhost(from->getAddress());

  rest = st.rest().subString(1);

  if (++(cnx->bot->ignoredUserhosts[fromUserhost])
      > Bot::MAX_MESSAGES) {
    if (cnx->bot->ignoredUserhosts[fromUserhost]
        == Bot::MAX_MESSAGES+1) {
#ifdef USESCRIPTS
      cnx->bot->botInterp->RunHooks(Hook::FLOOD, nick,
                       scm_listify (Utils::string2SCM(nick),
                               SCM_UNDEFINED));
#endif
      cnx->bot->ignoredUserhosts[fromUserhost] += Bot::IGNORE_DELAY;
      cnx->bot->logLine(from->getAddress() +
                   " is flooding me. We will ignore him/her/it.");
      if (!Utils::isChannel(to))
        from->sendNotice(String("\002You are now being ignored for ") +
                         String((long)Bot::IGNORE_DELAY) + " seconds.\002");
    }
    // The following lines reset the counter if you use the
    // command "!sorry" (if '!' is your command char).
    // This is not documented, I know. But one probably does
    // not want that every users can bypass the flood control
    // Of course, if you want this feature to remain 'secret',
    // do not use it in public.
    if (rest.toUpper() == String(cnx->bot->commandChar) + "SORRY") {
      cnx->bot->ignoredUserhosts[fromUserhost] = 0;
      from->sendNotice("\002Don't do it again!\002");
    }
    return;
  }

  if (rest[0] == '\001') {
    rest = rest.subString(1, rest.length() - 2);
    if (!Utils::isChannel(to))
      for (std::map<String, Person *, std::less<String> >::iterator it =
             cnx->bot->spyList.begin(); it != cnx->bot->spyList.end(); ++it)
        (*it).second->sendNotice(String("CTCP From ") + nick +
                                ": " + rest);
    Parser::parseCTCP(cnx, from, to, rest);
  }
  else {
    if ((rest.length() < 5 ||
        rest.subString(1, 5).toUpper() != "IDENT") &&
       (rest.length() < 8 ||
        rest.subString(1, 8).toUpper() != "PASSWORD") &&
        !Utils::isChannel(to))
      for (std::map<String, Person *, std::less<String> >::iterator it =
             cnx->bot->spyList.begin(); it != cnx->bot->spyList.end(); ++it)
        (*it).second->sendNotice(String("*") + nick + "* " + rest);
    Parser::parseMessage(cnx, from, to, rest);
  }
}

void
Parser::parsePart(ServerConnection *cnx,
                  Person *from, String rest)
{
  String n = from->getNick();
  StringTokenizer st(rest);
  String channel = st.nextToken();

#ifdef USESCRIPTS
  cnx->bot->botInterp->RunHooks(Hook::LEAVE, n + " " + channel,
                   scm_listify (Utils::string2SCM(n),
                           Utils::string2SCM(channel),
                           SCM_UNDEFINED));
#endif

  if (n.toLower() == cnx->bot->nickName.toLower()) {
    cnx->bot->logLine(String("Leaved channel ") + channel + ".");
    cnx->bot->channelList->delChannel(channel);
  } else {
    Channel * c = cnx->bot->channelList->getChannel(channel);
    if (!c) return;
    c->delNick(n);
    if (c->countOp == 0 && c->count == 1) {
      cnx->queue->sendPart(channel);
      cnx->queue->sendJoin(channel, cnx->bot->wantedChannels[channel]->key);
    }
  }
}

void
Parser::parsePing(ServerConnection * cnx,
                  Person *from, String rest)
{
  cnx->queue->sendPong(rest);
}

void
Parser::parsePong(ServerConnection *cnx,
                  Person *from, String rest)
{
  cnx->lag = (cnx->lag + 2 * (time(NULL) - cnx->pingTime)) / 3;
  cnx->bot->sentPing = false;
}

void
Parser::parseQuit(ServerConnection *cnx,
                  Person *from, String rest)
{
  String n = from->getNick();
  
#ifdef USESCRIPTS
  cnx->bot->botInterp->RunHooks(Hook::SIGNOFF, n + " " + rest,
                   scm_listify (Utils::string2SCM(n),
                           Utils::string2SCM(rest),
                           SCM_UNDEFINED));
#endif

  if (n == cnx->bot->nickName)
    cnx->bot->stop = true;

  for (std::map<String, Channel *, std::less<String> >::iterator it =
         cnx->bot->channelList->begin();
       it != cnx->bot->channelList->end();
       ++it)
    (*it).second->delNick(n);
}

void
Parser::parseTopic(ServerConnection *cnx,
                   Person *from, String rest)
{
  StringTokenizer st(rest);
  String channel = st.nextToken();
  String newTopic = st.rest().subString(1);
  Channel *c = cnx->bot->channelList->getChannel(channel);

#ifdef USESCRIPTS
  cnx->bot->botInterp->RunHooks(Hook::TOPIC, from->getNick() + " " + channel +
                   " " + newTopic,
                   scm_listify (Utils::string2SCM(from->getNick()),
                           Utils::string2SCM(channel),
                           Utils::string2SCM(newTopic),
                           SCM_UNDEFINED));
#endif

  if (!c) return;

  if (c->lockedTopic && from->getNick() != cnx->bot->nickName)
    cnx->queue->sendTopic(channel, c->channelTopic);

  c->channelTopic = newTopic;
}

void
Parser::parseCTCP(ServerConnection *cnx,
                  Person *from, String to,
                  String parameters)
{
  StringTokenizer st(parameters);
  String command = st.nextToken().toUpper();
  String nick = from->getNick();
  String rest;

  if (st.hasMoreTokens())
    rest = st.rest();
  else
    rest = "";

#ifdef USESCRIPTS
  cnx->bot->botInterp->RunHooks(Hook::CTCP, nick + " " + to + " " +
                   command + " " + rest,
                   scm_listify (Utils::string2SCM(nick),
                           Utils::string2SCM(to),
                           Utils::string2SCM(command),
                           Utils::string2SCM(rest),
                           SCM_UNDEFINED));
#endif

  if (command == "PING")
    cnx->queue->sendCTCPReply(nick, "PING", rest);
  else if (command == "VERSION")
    cnx->queue->sendCTCPReply(nick, "VERSION", cnx->bot->versionString);

  else if (command == "CLOCK") {
    time_t diff = time(NULL) - cnx->bot->startTime;
    cnx->queue->sendCTCPReply(nick, "CLOCK", String("elapsed time: ") +
                         String((long)(diff / 86400)) + "d" +
                         String((long)(diff % 86400) / 3600) +
                         "h" + String((long)(diff % 3600) / 60) +
                         "m" + String((long)(diff % 60)) + "s");
  } else if (command == "COMMAND")
    cnx->queue->sendCTCPReply(nick,
                         "COMMAND",
                         String(cnx->bot->commandChar));
  else if (command == "LAG")
    cnx->queue->sendCTCPReply(nick, "LAG",
                         String((long)cnx->lag) + " second(s)");
  else if (command == "DCC") {
    StringTokenizer st2(rest);
    command = st2.nextToken().toUpper();
    if (command == "CHAT") {
      // FIXME: Re-activate and debug DCC
//       st2.nextToken();
//       unsigned long address =
//         htonl(strtoul((const char *)st2.nextToken(), 0, 0));
//       int port = atoi((const char *)st2.rest());
//       if (port >= 1024 && Utils::getLevel(cnx->bot, from->getAddress()))
//         cnx->bot->addDCC(from, address, port);
    }
  }
#ifdef USESCRIPTS
  else if (command == "ACTION") {
    cnx->bot->botInterp->RunHooks(Hook::ACTION, from->getAddress() + " " +
                     to + " " + rest,
                     scm_listify (Utils::string2SCM(from->getAddress()),
                             Utils::string2SCM(to),
                             Utils::string2SCM(rest),
                             SCM_UNDEFINED));
  }
#endif
}

struct userFunctionsStruct userFunctionsInit[] =

{
  { "ACTION",      UserCommands::Action,      User::USER,         true  },
  { "ADDUSER",     UserCommands::AddUser,     User::FRIEND,       false },
  { "ADDSERVER",   UserCommands::AddServer,   User::FRIEND,       false },
  { "ADDSHIT",     UserCommands::AddShit,     User::FRIEND,       false },
  { "ALIAS",       UserCommands::Alias,       User::MASTER,       false },
  { "BAN",         UserCommands::Ban,         User::USER,         true  },
  { "BANLIST",     UserCommands::BanList,     User::USER,         true  },
  //  { "CHANGELEVEL", UserCommands::ChangeLevel, User::FRIEND,       false },
  { "CHANNELS",    UserCommands::Channels,    User::FRIEND,       false },
  { "CYCLE",       UserCommands::Cycle,       User::FRIEND,       true  },
  { "DCCLIST",     UserCommands::DCCList,     User::FRIEND,       false },
  { "DEBAN",       UserCommands::Deban,       User::USER,         true  },
  { "DELSERVER",   UserCommands::DelServer,   User::FRIEND,       false },
  { "DELUSER",     UserCommands::DelUser,     User::FRIEND,       false },
  { "DELSHIT",     UserCommands::DelShit,     User::FRIEND,       false },
  { "DEOP",        UserCommands::Deop,        User::TRUSTED_USER, true  },
  { "DIE",         UserCommands::Die,         User::MASTER,       false },
  { "DO",          UserCommands::Do,          User::MASTER,       false },
#ifdef USESCRIPTS
  { "EXECUTE",     UserCommands::Execute,     User::MASTER,       false },
#endif
  { "HELP",        UserCommands::Help,        User::NONE,         false },
  { "IDENT",       UserCommands::Ident,       User::NONE,         true  },
  { "INVITE",      UserCommands::Invite,      User::USER,         true  },
  { "JOIN",        UserCommands::Join,        User::FRIEND,       false },
  { "KEEP",        UserCommands::Keep,        User::FRIEND,       true  },
  { "KICK",        UserCommands::Kick,        User::USER,         true  },
  { "KICKBAN",     UserCommands::KickBan,     User::USER,         true  },
  { "LOAD",        UserCommands::Load,        User::FRIEND,       false },
#ifdef USESCRIPTS
  { "LOADSCRIPT",  UserCommands::LoadScript,  User::MASTER,       false },
#endif
  { "LOCK",        UserCommands::Lock,        User::FRIEND,       true  },
  { "MODE",        UserCommands::Mode,        User::FRIEND,       true  },
  { "MSG",         UserCommands::Msg,         User::USER,         false },
  { "NAMES",       UserCommands::Names,       User::USER,         true  },
  { "NEXTSERVER",  UserCommands::NextServer,  User::FRIEND,       false },
  { "NICK",        UserCommands::Nick,        User::FRIEND,       false },
  { "NSLOOKUP",    UserCommands::NsLookup,    User::USER,         false },
  { "OP",          UserCommands::Op,          User::TRUSTED_USER, true  },
  { "PART",        UserCommands::Part,        User::FRIEND,       true  },
  { "PASSWORD",    UserCommands::Password,    User::USER,         true  },
  { "RECONNECT",   UserCommands::Reconnect,   User::FRIEND,       false },
  { "RSPYMESSAGE", UserCommands::RSpyMessage, User::USER,         false },
  { "SAVE",        UserCommands::Save,        User::FRIEND,       false },
  { "SAY",         UserCommands::Say,         User::USER,         true  },
  { "SERVER",      UserCommands::Server,      User::FRIEND,       false },
  { "SERVERLIST",  UserCommands::ServerList,  User::FRIEND,       false },
  { "SETVERSION",  UserCommands::SetVersion,  User::MASTER,       false },
  { "SHITLIST",    UserCommands::ShitList,    User::FRIEND,       false },
  { "SPYLIST",     UserCommands::SpyList,     User::USER,         false },
  { "SPYMESSAGE",  UserCommands::SpyMessage,  User::USER,         false },
  { "STATS",       UserCommands::Stats,       User::FRIEND,       true  },
  { "TBAN",        UserCommands::TBan,        User::USER,         true  },
  { "TKBAN",       UserCommands::TKBan,       User::USER,         true  },
  { "TOPIC",       UserCommands::Topic,       User::USER,         true  },
  { "UNLOCK",      UserCommands::Unlock,      User::FRIEND,       true  },
  { "USERLIST",    UserCommands::UserList,    User::FRIEND,       false },
  { "WHO",         UserCommands::Who,         User::NONE,         true  },
  { "WHOIS",       UserCommands::Whois,       User::FRIEND,       true  },
  { "",            0,                         0,                  false }
};

void
Parser::parseMessage(ServerConnection *cnx,
                     Person *from, String to,
                     String parameters)
{
#ifdef USESCRIPTS
  if (Utils::isChannel(to))
    cnx->bot->botInterp->RunHooks(Hook::PUBLIC, from->getNick() + " " +
                     to + " " + parameters,
                     scm_listify (Utils::string2SCM(from->getNick()),
                             Utils::string2SCM(to),
                             Utils::string2SCM(parameters),
                             SCM_UNDEFINED));
  else
    cnx->bot->botInterp->RunHooks(Hook::MESSAGE, from->getNick() + " " + parameters,
                     scm_listify (Utils::string2SCM(from->getNick()),
                             Utils::string2SCM(parameters),
                             SCM_UNDEFINED));
#endif

  if (parameters[0] != cnx->bot->commandChar)
    return;

  StringTokenizer st(parameters);

  String command = st.nextToken().subString(1).toUpper();
  String rest = st.rest().trim();
  int level;
  bool identified = false;

  std::list<userFunction *>::iterator it;
  for (it = cnx->bot->userFunctions.begin();
       it != cnx->bot->userFunctions.end();
       ++it)
    if (command == (*it)->name) {
      if ((*it)->needsChannelName) {
        if (Utils::isChannel(rest)) {
          StringTokenizer st2(rest);
          to = st.nextToken();
          rest = st.rest();
        }
        if (!Utils::isChannel(to)) {
          from->sendNotice("\002You need to supply a channel name"
                          " for this command\002");
          return;
        }
        if (!cnx->bot->channelList->getChannel(to)) {
          from->sendNotice(String("\002I am not on channel\002 ") +
                          to);
          return;
        }
        level = Utils::getLevel(cnx->bot, from->getAddress(), to);
        User * u = 0;
        if (Channel *c = cnx->bot->channelList->getChannel(to))
          u = c->getUser(from->getNick());
        if (!u || !u->userListItem)
          identified = true;
        else
          identified = u->userListItem->passwd == "" || u->userListItem->identified > 0;
      } else {
        level = Utils::getLevel(cnx->bot, from->getAddress());
        identified = true;
      }
      if (level >= (*it)->minLevel) {
        cnx->bot->logLine(from->getAddress() + " did " + command +
                     " " + rest);
#ifdef USESCRIPTS
        if ((*it)->argsCount != -1) {
          Parser::parseScriptFunction(cnx, to, (*it)->needsChannelName,
                                      (*it)->scmFunc, (*it)->argsCount, rest);
        } else {
          (*it)->function(cnx, from, to, rest);
        }
#else
        (*it)->function(cnx, from, to, rest);
#endif
        break;
      } else {
        if (!identified)
          from->sendNotice(String("\002You are not identified on channel\002 ")+to);
      }
    }
}

#ifdef USESCRIPTS
void
Parser::parseScriptFunction(ServerConnection *cnx, String channel,
                            bool needsChannelName, SCM scmFunc,
                            int argsCount, String parameters)
{
  String param;
  SCM args_list = scm_listify (SCM_UNDEFINED);

  if (needsChannelName) {
    args_list = gh_append2(args_list,
                           scm_listify (Utils::string2SCM(channel),
                                   SCM_UNDEFINED));
    argsCount--;
  }

  StringTokenizer st(parameters);
  for (int i = argsCount; i > 0; i--) {
    if (i == 1)
      param = st.rest();
    else
      param = st.nextToken();
    args_list = gh_append2(args_list,
                           scm_listify (Utils::string2SCM(param),
                                   SCM_UNDEFINED));
  }

  struct wrapper_data wd;
  wd.func = scmFunc;
  wd.args = args_list;

  gh_catch(SCM_BOOL_T, (scm_catch_body_t) scm_apply_wrapper,
           (void *)&wd, (scm_catch_handler_t) Interp::ErrorHandler,
           0);
}
#endif


syntax highlighted by Code2HTML, v. 0.9.1