// UserCommands.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 <fstream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <ctype.h>
#ifndef _X_OPEN_SOURCE
#define _X_OPEN_SOURCE
#endif
#ifndef _X_OPEN_SOURCE_EXTENDED
#define _X_OPEN_SOURCE_EXTENDED 1
#endif
#ifndef __USE_XOPEN
#define __USE_XOPEN
#endif
#include <unistd.h>

#include "Commands.H"
#include "UserCommands.H"
#include "DCCConnection.H"
#include "Parser.H"
#include "Macros.H"
#include "StringTokenizer.H"
#include "Utils.H"
#include "ServerList.H"
#include "Server.H"

#ifdef NOCRYPT
char * crypt(const char *p, const char *s) { return p; }
#endif

void
UserCommands::Action(ServerConnection *cnx, Person *from,
                     String channel, String rest)
{
  Message m = Commands::Action(cnx->bot, channel, rest);
  if (m.getCode() != 0)
    from->sendNotice(m.getMessage());
}

void
UserCommands::AddUser(ServerConnection *cnx, Person *from,
                      String channel, String rest)
{
  StringTokenizer st(rest);
  String mask, who, maskChannel, level, prot,
    aop, expiration, passwd;

  mask = who = st.nextToken();
  maskChannel = st.nextToken();
  level = st.nextToken();
  prot = st.nextToken();
  aop = st.nextToken();
  expiration = st.nextToken();
  passwd = st.nextToken();

  if (mask == "" || maskChannel == "" || level == "" ||
      prot == "" || aop == "") {
    from->sendNotice("\002Invalid syntax for this command.\002");
    return;
  }

//   if (!Utils::isWildcard(mask)) {
//     mask = cnx->bot->getUserhost(channel, who);
//     if (mask == "") {
//       from->sendNotice(String("\002I can not find\002 ") + who);
//       return;
//     }
//     mask = Utils::makeWildcard(mask);
//   }
  
//   if (cnx->bot->userList->isInUserList(mask, maskChannel)) {
//     from->sendNotice(who + " \002is already in userlist on channel(s)\002 " +
// 		     maskChannel);
//     return;
//   }

  int l, p;
  bool a;
  time_t e;
  
  l = atoi((const char *)level);
  if (l < 0 || l > User::FRIEND)
    return;
  if (l > Utils::getLevel(cnx->bot, from->getAddress())) {
    from->sendNotice("\002You can not give a level greater than yours.\002");
    return;
  }
  p = atoi((const char *)prot);
  if (p < 0 || p > User::NO_DEOP)
    return;
  a = (bool)atoi((const char *)aop);
  if (a != 0 && a != 1)
    return;

  e = Utils::strToTime(expiration);

  if (!e)
    e = -1;

  // cnx->bot->userList->addUser(mask, maskChannel, l, p, a, e, passwd); 

  Message m = 
    Commands::AddUser (cnx->bot, who, maskChannel, l, p, a, e, passwd);
  if (!m.getCode ())
    {
      from->sendNotice(String("\002Added\002 ") + mask +
		       " \002on channels\002 " + maskChannel);
      from->sendNotice(String("\002Level:\002 ") +
		       Utils::levelToStr(l) +
		       "  \002Protection:\002 " +
		       Utils::protToStr(p) +
		       "  \002Auto-op:\002 " +
		       Utils::boolToStr(a));
    }
  else
    from->sendNotice(m.getMessage ());

  //  cnx->bot->rehash();
}

void
UserCommands::AddServer(ServerConnection *cnx, Person *from,
                        String channel, String rest)
{
  if (rest.length() == 0) {
    from->sendNotice("\002You must supply a server name.\002");
    return;
  }

  StringTokenizer st(rest);
  String serverName = st.nextToken();
  int port = 6667;

  if (st.hasMoreTokens()) {
    String temp = st.nextToken();
    port = atoi((const char *)temp);
  }

  Message m = Commands::AddServer(cnx->bot, serverName, port);
  if (m.getCode() != 0)
    from->sendNotice(m.getMessage());
  else
    from->sendNotice(String("\002Server\002 ") +
                     serverName + " \002on port\002 " +
                     String((long)port) + " \002has been added "
                     "to the server list.\002");
}

// FIXME: does this work now
void
UserCommands::AddShit(ServerConnection *cnx, Person *from,
                      String channel, String rest)
{
  StringTokenizer st(rest);
  String mask, who, maskChannel, level, expiration, reason;

  mask = who = st.nextToken();
  maskChannel = st.nextToken();
  level = st.nextToken();
  expiration = st.nextToken();
  reason = st.rest().trim();

  /*  if (mask == "" || maskChannel == "" || level == "") {
    from->sendNotice("\002Invalid syntax for this command.\002");
    return;
  }
  */

  /*
  if (reason == "")
    reason = "You're on my shitlist, lamer";
  */

  /*
  if (!Utils::isWildcard(mask)) {
    mask = cnx->bot->getUserhost(channel, who);
    if (mask == "") {
      from->sendNotice(String("\002I can not find\002 ") + who);
      return;
    }
    mask = Utils::makeWildcard(mask);
    if (cnx->bot->shitList->getShit(mask, maskChannel)) {
      from->sendNotice(mask + " \002is already in shitlist on channel(s)\002 " +
                       maskChannel);
      return;
    }
  }
  */
  int l;
  time_t e;

  l = atoi((const char *)level);
  if (l < 0 || l > ShitEntry::SHIT_NODEBAN)
    return;

  e = Utils::strToTime(expiration);

  if (!e)
    e = -1;

  /*
  if (cnx->bot->userList->getMaxProt(mask, maskChannel) > 0) {
    from->sendNotice(String("\002I can not add\002 ") + who +
                     " \002into the shitlist (protection).");
    return;
  }

  cnx->bot->shitList->addShit(mask, maskChannel, l, e, reason);
  */

  Message M = Commands::AddShit (cnx->bot, mask, maskChannel, l, e, reason);
  if (!M.getCode ())
    from->sendNotice(String("\002Added\002 ") + mask +
		     " \002on channels\002 " + maskChannel +
		     " \002into the shitlist.\002");
  else
    from->sendNotice (M.getMessage ());
}

// FIXME: Convert (and change ?)
void
UserCommands::Alias(ServerConnection *cnx, Person *from,
                    String channel, String rest)
{
  StringTokenizer st(rest);
  String newF = st.nextToken().toUpper();
  String oldF = st.nextToken().toUpper();
  std::list<userFunction *>::iterator it;

  if (newF == "" || oldF == "") {
    from->sendNotice("\002Invalid syntax for this command.\002");
    return;
  }

  // First, we check that the "new" function does not exist
  for (it = cnx->bot->userFunctions.begin(); it !=
         cnx->bot->userFunctions.end(); ++it)
    if (newF == (*it)->name) {
      from->sendNotice(newF + " \002is already an alias.\002");
      return;
    }

  // Next, we check that the "old" function exist
  bool found = false;
  userFunction *u;
  for (it = cnx->bot->userFunctions.begin(); it !=
         cnx->bot->userFunctions.end(); ++it)
    if (oldF == (*it)->name) {
      found = true;
      u = *it;
      break;
    }
  if (!found) {
    from->sendNotice(String("\002I don't know the\002 ") + oldF +
                     " \002command.");
    return;
  }

  // Fine, we do the binding
  cnx->bot->userFunctions.push_back(new
                               userFunction((char *)(const char 
                                                            *)newF,
                                                   u->function,
                                                   u->minLevel,
                                                   u->needsChannelName));

  from->sendNotice("\002Alias added.\002");
}

void
UserCommands::Ban(ServerConnection *cnx, Person *from,
                  String channel, String rest)
{
  if (rest.length() == 0) {
    if (from)
      from->sendNotice("\002No nick/mask specified.\002");
    return;
  }

  Message m = Commands::Ban(cnx->bot, channel, rest);
  if (m.getCode() != 0 && from)
    from->sendNotice(m.getMessage());
}

void
UserCommands::BanList(ServerConnection *cnx, Person *from,
                      String channel, String rest)
{
  time_t current = time(0);
  Channel *c = cnx->bot->channelList->getChannel(channel);
  from->sendNotice(String("\002Banlist for channel\002 ") +
                   channel + "\002:\002");
  from->sendNotice("\002Mask                           Expires (seconds)\002");
  for (std::vector<BanEntry *>::iterator it = c->channelBanlist.begin();
       it != c->channelBanlist.end(); ++it)
    if ((*it)->getExpirationDate() == -1)
      from->sendNotice((*it)->getMask().pad(30) + " -1");
    else
      from->sendNotice((*it)->getMask().pad(30) + " " + 
                       String((long)((*it)->getExpirationDate()-current)));
  from->sendNotice("\002End of banlist.\002");
}

// void
// UserCommands::ChangeLevel(ServerConnection *cnx, Person *from,
//                           String channel, String rest)
// {
//   StringTokenizer st(rest);
  
//   String who;
//   String mask = who = st.nextToken();
//   String maskChannel = st.nextToken();
//   String level = st.nextToken();
  
//   if (mask == "" || maskChannel == "" || level == "") {
//     from->sendNotice("\002Invalid syntax for this command.\002");
//     return;
//   }
  
//   if (!Utils::isWildcard(mask)) {
//     mask = cnx->bot->getUserhost(channel, who);
//     if (mask == "") {
//       from->sendNotice(String("\002I can not find\002 ") + who);
//       return;
//     }
//     mask = from->getNick() + "!" + mask;
//   }
  
//   UserListItem *uli = cnx->bot->userList->getUserListItem(mask, maskChannel);
  
//   if (!uli) {
//     from->sendNotice(String("\002I can not find\002 ") + who +
//                      " \002on channel(s)\002 " + maskChannel +
//                      " \002in my userlist.\002");
//     return;
//   }
  
//   int l = atoi((const char *)level);
  
//   if (l < User::NONE || l > User::MASTER) {
//     from->sendNotice("\002This is not a valid level.\002");
//     return;
//   }
  
//   if (l > Utils::getLevel(cnx->bot, from->getAddress())) {
//     from->sendNotice("\002You can not give a level greater than yours.\002");
//     return;
//   }
  
//   if (Utils::getLevel(cnx->bot, from->getAddress()) < uli->level) {
//     from->sendNotice("\002You can not change the level for a person "
//                      "whose level is greater than yours.\002");
//     return;
//   }
  
//   uli->level = l;
//   from->sendNotice("\002Level changed.\002");
//   cnx->bot->rehash();
// }

void
UserCommands::Channels(ServerConnection *cnx, Person *from,
                       String channel, String rest)
{
  String result = "";

  for (std::map<String, Channel *, std::less<String> >::iterator it =
         cnx->bot->channelList->begin(); 
       it != cnx->bot->channelList->end(); ++it)
    result = result + (*it).first + " ";

  if (result == "")
    from->sendNotice("\002I am not on any channel.\002");
  else
    from->sendNotice(String("\002I am currently on channel(s):\002 ") +
                      + result);
}

void
UserCommands::Cycle(ServerConnection *cnx, Person *from,
                    String channel, String rest)
{
  Message m = Commands::Cycle(cnx->bot, channel);
  if (m.getCode() != 0)
    from->sendNotice(m.getMessage());
}

void
UserCommands::DCCList(ServerConnection *cnx, Person *from,
                      String channel, String rest)
{
  time_t current_time = time(0);

  from->sendNotice("\002DCClist:\002");
  from->sendNotice("\002Hostname                         Last used\002");

  for (std::list<DCCConnection *>::iterator it =
         cnx->bot->dccConnections.begin();
       it != cnx->bot->dccConnections.end();
       ++it) {
    from->sendNotice((*it)->nuh.pad(32) + " " +
                     String((long)(current_time -
                                   (*it)->lastSpoken)));
  }

  from->sendNotice("\002End of dcclist.\002");
}

void
UserCommands::Deban(ServerConnection *cnx, Person *from,
                    String channel, String rest)
{
  if (rest.length() == 0) {
    from->sendNotice("\002No nick/mask specified.\002");
    return;
  }

  Message m = Commands::Deban(cnx->bot, channel, rest);
  if (m.getCode() != 0)
    from->sendNotice(m.getMessage());
}

void
UserCommands::DelServer(ServerConnection *cnx, Person *from,
                        String channel, String rest)
{
  if (rest.length() == 0) {
    from->sendNotice("\002You need to supply a server number"
                     " for this command.\002");
    return;
  }

  int serverNumber = atoi(rest);

  Message m = Commands::DelServer(cnx->bot, serverNumber);
  if (m.getCode() != 0)
    from->sendNotice(m.getMessage());
  else
    from->sendNotice(String("Deleted server ") +
                     cnx->bot->serverList->get(serverNumber)->getHostName() +
                     " (" + String((long)cnx->bot->serverList->get(serverNumber)->getPort()) +
                     ").");
}

// FIXME: Convert
void
UserCommands::DelUser(ServerConnection *cnx, Person *from,
                      String channel, String rest)
{
  StringTokenizer st(rest);

  String who;
  String mask = who = st.nextToken();
  String maskChannel = st.nextToken();

  if (mask == "" || maskChannel == "") {
    from->sendNotice("\002Invalid syntax for this command.\002");
    return;
  }

  if (!Utils::isWildcard(mask)) {
    mask = cnx->bot->getUserhost(channel, who);
    if (mask == "") {
      from->sendNotice(String("\002I can not find\002 ") + who);
      return;
    }
    mask = Utils::makeWildcard(mask);
  }
  
  if (!cnx->bot->userList->isInUserList(mask, maskChannel)) {
    from->sendNotice(mask + " \002is not in userlist on channel(s)\002 " +
                     maskChannel);
    return;
  }

  cnx->bot->userList->removeUser(mask, maskChannel);
  from->sendNotice(who + " \002has been removed from the userlist.\002");
  cnx->bot->rehash();
}

// FIXME: Convert
void
UserCommands::DelShit(ServerConnection *cnx, Person *from,
                      String channel, String rest)
{
  StringTokenizer st(rest);
  
  String who;
  String mask = who = st.nextToken();
  String maskChannel = st.nextToken();

  if (mask == "" || maskChannel == "") {
    from->sendNotice("\002Invalid syntax for this command.\002");
    return;
  }

  if (!Utils::isWildcard(mask)) {
    mask = cnx->bot->getUserhost(channel, who);
    if (mask == "") {
      from->sendNotice(String("\002I can not find\002 ") + who);
      return;
    }
    mask = Utils::makeWildcard(mask);
  }
  
  if (!cnx->bot->shitList->getShit(mask, maskChannel)) {
    from->sendNotice(mask + " \002is not in shitlist on channel(s)\002 " +
                     maskChannel);
    return;
  }

  cnx->bot->shitList->delShit(mask, maskChannel);
  from->sendNotice(who + " \002has been removed from the shitlist.\002");
}

void
UserCommands::Deop(ServerConnection *cnx, Person *from,
                   String channel, String rest)
{
  String target = rest;

  if (target.length() == 0)
    target = from->getNick();

  Message m = Commands::Deop(cnx->bot, channel, target);
  if (m.getCode() != 0)
    from->sendNotice(m.getMessage());
}

void
UserCommands::Die(ServerConnection *cnx, Person *from,
                  String channel, String rest)
{
  String reason;

  if (rest.length() == 0)
    reason = "Leaving";
  else
    reason = rest;

  Commands::Die(cnx->bot, reason);
}

void
UserCommands::Do(ServerConnection *cnx, Person *from,
                 String channel, String rest)
{
  if (rest.length() != 0)
    Commands::Do(cnx->bot, rest);
}

#ifdef USESCRIPTS
void
UserCommands::Execute(ServerConnection *cnx, Person *from,
                      String channel, String rest)
{
  if (rest.length() != 0)
    Interp::Execute(cnx->bot, rest);
}
#endif

void
UserCommands::Help(ServerConnection *cnx, Person *from,
                   String channel, String rest)
{
  if (rest.length() == 0) {
    from->sendNotice("\002Available topics for you are:\002");
    int level = Utils::getLevel(cnx->bot, from->getAddress());
    String result = "";
    int length = 0;
    std::list<userFunction *>::iterator it;
    for (it = cnx->bot->userFunctions.begin(); it != cnx->bot->userFunctions.end(); ++it)
      if ((*it)->minLevel <= level) {
        result = result + (*it)->name + " ";
        length += strlen((*it)->name) + 1;
        if (length >= 256) {
          from->sendNotice(result);
          result = ""; length = 0;
        }
    }
    if (result != "")
      from->sendNotice(result);
    from->sendNotice("\002Use\002 HELP <command> \002for"
                     " help about\002 <command>");
    return;
  }

  StringTokenizer st(rest);
  String command = st.nextToken().toUpper();
  std::ifstream helpFile(cnx->bot->helpFileName);

  if (!helpFile) {
    from->sendNotice(String("\002Error: I can not find the "
                            "help file\002 ") +
                     cnx->bot->helpFileName);
    return;
  }

  String buf;
  while (!helpFile.eof()) {
    helpFile >> buf;
    if (buf.subString(0, command.length()) == String(":") + command) {
      buf = buf.subString(1);
      from->sendNotice(String("\002Help for\002 ") + command +
                       "\002:\002");
      while (buf != "") {
        from->sendNotice(buf);
        helpFile >> buf;
      }
      from->sendNotice("\002End of help.\002");
      return;
    }
  }

  from->sendNotice(String("\002No such topic (\002") +
                   command + "\002).\002");
}

void
UserCommands::Ident(ServerConnection *cnx, Person *from,
                    String channel, String rest)
{
  Channel *c = cnx->bot->channelList->getChannel(channel);

  if (rest.length() <= 2) {
    from->sendNotice("\002No password specified or password "
                     "too short.\002");
    return;
  }

  User * u = c->getUser(from->getNick());
  if (!u) {
    from->sendNotice(String("\002You can identify yourself on"
                     " channel\002 ") + channel +
                     " \002only if you are on the channel.\002");
    return;
  }

  if (u->userListItem && u->userListItem->identified) {
    from->sendNotice(String("\002You are already identified on"
                     " channel\002 ") + channel);
    return;
  }

  if (!u->userListItem) {
    from->sendNotice("\002You are not in my userlist.\002");
    return;
  }

  if (u->userListItem->passwd ==
      crypt((const char *)rest, (const char *)u->userListItem->passwd)) {
    // For each channel, we increment identification counter
    for (std::map<String, Channel *, std::less<String> >::iterator it =
           cnx->bot->channelList->begin();
         it != cnx->bot->channelList->end(); ++it)
      u->userListItem->identified++;

    from->sendNotice("\002You are now identified.\002");
  } else
    from->sendNotice("\002This is a wrong password.\002");
}

void
UserCommands::Invite(ServerConnection *cnx, Person *from,
                     String channel, String rest)
{
  Message m = Commands::Invite(cnx->bot, channel, rest);
  if (m.getCode() < 0)
    from->sendNotice(m.getMessage());
  from->sendNotice(String("\002Inviting\002 ") + rest + " \002on channel\002 " + channel);
}

// FIXME: Convert
void
UserCommands::Join(ServerConnection *cnx, Person *from,
                   String channel, String rest)
{
  StringTokenizer st(rest);
  channel = st.nextToken();

  if (!Utils::isValidChannelName(channel)) {
    from->sendNotice(String("\002") + channel +
                     " is not a valid channel name\002");
    return;
  }

  // We change the key only if we are not on the channel. We don't trust
  // the user...
  if (!cnx->bot->channelList->getChannel(channel)) {
    if (cnx->bot->wantedChannels[channel])
      cnx->bot->wantedChannels[channel]->key = st.rest();
    else {
      cnx->bot->wantedChannels[channel] = new wantedChannel("", "", st.rest());
    }
  }
  cnx->queue->sendJoin(channel, cnx->bot->wantedChannels[channel]->key);
}

// FIXME: Convert
void
UserCommands::Keep(ServerConnection *cnx, Person *from,
                   String channel, String rest)
{
  String nick = from->getNick();

  if (rest.length() == 0) {
    from->sendNotice("\002No channel modes specified.\002");
    return;
  }

  cnx->bot->channelList->getChannel(channel)->keepModes = rest;
  from->sendNotice(String("\002Keeping modes\002 ") +
                    rest + " \002on channel\002 " +
                    channel);
}

// FIXME: Convert
void
UserCommands::Kick(ServerConnection *cnx, Person *from,
                   String channel, String rest)
{
  String nick = from->getNick();

  Channel * c = cnx->bot->channelList->getChannel(channel);

  if (rest.length() == 0) {
    from->sendNotice("\002No nick specified.\002");
    return;
  }

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

  if (Utils::isWildcard(who)) {
    User * u = c->getUser(nick);
    if (!u)
      return;
    if (u->getLevel() < User::TRUSTED_USER) {
      from->sendNotice("\002You need an higher level to "
                       "use wildcards.\002");
      return;
    }
  }

  if (!cnx->bot->iAmOp(channel)) {
    from->sendNotice(String("\002I am not channel op on\002 ") +
                     channel);
    return;
  }

  if (Utils::isWildcard(who)) {
    Mask m(who);
    for (std::map<String, User *, std::less<String> >::iterator it =
           c->channelMemory.begin();
         it != c->channelMemory.end();
         ++it)
      if (m.matches((*it).second->nick + "!" +
                    (*it).second->userhost) &&
          (*it).second->getProt() < User::NO_KICK)
        cnx->queue->sendKick(channel, (*it).second->nick, st.rest());
  } else {
    User * u = c->getUser(who);
    if (!u) {
      from->sendNotice(String("\002I can not find\002 ") +
                       who + " \002on\002 " + channel);
      return;
    }
    if (u->getProt() < User::NO_KICK)
      cnx->queue->sendKick(channel, who, st.rest());
    else
      from->sendNotice(String("\002I can not kick\002 ") +
                        who + " \002on\002 " + channel +
                        " \002(protected).\002");
  }
}

// FIXME: Convert
void
UserCommands::KickBan(ServerConnection *cnx, Person *from,
                      String channel, String rest)
{
  StringTokenizer st(rest);

  Ban(cnx, 0, channel, st.nextToken());
  Kick(cnx, from, channel, rest);
}

void
UserCommands::Load(ServerConnection *cnx, Person *from,
                   String channel, String rest)
{
  cnx->bot->shitList->read();
  cnx->bot->userList->read();
  cnx->bot->userList->addUserFirst(cnx->bot->nickName + "!" + cnx->bot->userHost, "*", 0, 3, true, -1, "");
  cnx->bot->rehash();
  from->sendNotice("\002Userlist and shitlist loaded.\002");
}

#ifdef USESCRIPTS
void
UserCommands::LoadScript(ServerConnection *cnx, Person *from,
                         String channel, String rest)
{
  if (rest.length() != 0)
    Interp::LoadScript(cnx->bot, rest);
}
#endif

// FIXME: Convert
void
UserCommands::Lock(ServerConnection *cnx, Person *from,
                   String channel, String rest)
{
  cnx->bot->channelList->getChannel(channel)->lockedTopic = true;
  from->sendNotice(String("\002Locking topic for channel\002 ") +
                   channel);
}

// FIXME: Convert
void
UserCommands::Mode(ServerConnection *cnx, Person *from,
                   String channel, String rest)
{
  String nick = from->getNick();

  if (rest.length() == 0) {
    from->sendNotice("\002I need a parameter for this command.\002");
    return;
  }

  if (!cnx->bot->iAmOp(channel)) {
    from->sendNotice(String("\002I am not channel op on\002 ") +
                     channel);
    return;
  }
                      
  cnx->queue->sendChannelMode(String("MODE ") + channel + " " + rest);
}

void
UserCommands::Msg(ServerConnection *cnx, Person *from,
                   String channel, String rest)
{
  StringTokenizer st(rest);
  String who = st.nextToken();
  String message = st.rest();

  Message m = Commands::Msg(cnx->bot, who, message);
  if (m.getCode() != 0)
    from->sendNotice(m.getMessage());
}

void
UserCommands::Names(ServerConnection *cnx, Person *from,
                     String channel, String rest)
{
  String nick = from->getNick();
  String result = "";
  int length = 0;
  Channel *c = cnx->bot->channelList->getChannel(channel);
  std::map<String, User *, std::less<String> >::iterator it;

  from->sendNotice(String("\002Names on ") +
                    channel + ":\002");

  for (it = c->channelMemory.begin();
       it != c->channelMemory.end(); ++it) {
    result = result +
      ((*it).second->mode & User::OP_MODE ? "@" :
       ((*it).second->mode & User::VOICE_MODE ? "+" : "")) +
      (*it).second->nick + " ";
    length += (*it).first.length() + 1;
    if (length >= 256) {
      from->sendNotice(result);
      result = ""; length = 0;
    }
  }

  if (result != "")
    from->sendNotice(result);

  from->sendNotice("\002End of names.\002");
}

// FIXME: Convert
void
UserCommands::NextServer(ServerConnection *cnx, Person *from,
                         String channel, String rest)
{
  if (cnx->bot->serverList->size() == 0) {
    from->sendNotice("\002Server list is empty !\002");
    return;
  }

  if (cnx->bot->serverList->size() == 1) {
    from->sendNotice("\002Server list has only one item. Use"
                     " \"reconnect\" to force reconnection.\002");
    return;
  }

  if (cnx->bot->canChangeServer()) {
    cnx->queue->sendQuit("Changing server");
    cnx->bot->nextServer();
  }
  else
    from->sendNotice("\002I can not change server without"
                     " losing op on a channel I am on.\002");
}

// FIXME: Convert
void
UserCommands::Nick(ServerConnection *cnx, Person *from,
                   String channel, String rest)
{
  // We parse the parameters
  StringTokenizer st(rest);
  String nick = st.nextToken();

  if (rest == "") {
    from->sendNotice(String("\002No nickname given.\002"));
    return;
  }

  if (!Utils::isValidNickName(nick)) {
    from->sendNotice(String("\002") + nick +
                     " is not a valid nickname\002");
    return;
  }

  cnx->bot->wantedNickName = nick;
  cnx->queue->sendNick(nick);
}

void
UserCommands::NsLookup(ServerConnection *cnx, Person *from,
                       String channel, String rest)
{
  String target;

  if (rest.length() == 0) {
    from->sendNotice("\002You need to supply a nick or an "
                     "host for this command.\002");
    return;
  }

  if (rest.find('.') == -1) {
    StringTokenizer st(cnx->bot->getUserhost("", rest));
    st.nextToken('@');
    target = st.rest();
    if (target.length() == 0) {
      from->sendNotice(String("\002I could not find\002 ") +
                        target);
      return;
    }
  } else
    target = rest;

  struct hostent * host;
  struct in_addr iaddr;

  if (isdigit(target[0])) { // An IP ?
    iaddr.s_addr = inet_addr((const char *)target);
    host = gethostbyaddr((char *)(&iaddr),
                         sizeof(struct in_addr),
                         AF_INET);
    if (host) {
      from->sendNotice(target +
                        " \002is the IP address of\002 " +
                        host->h_name);
      return;
    }
  } else {
    host = gethostbyname((const char *)target);
    if (host) {
      memcpy((char *)&iaddr, (char *)host->h_addr,
            host->h_length);
      from->sendNotice(target + " \002has\002 " +
                        inet_ntoa(iaddr) + " \002for IP address.\002");
      return;
    }
  }

  from->sendNotice(String("\002I could not find host\002 ") +
                    target);
}

// FIXME: Convert
void
UserCommands::Op(ServerConnection *cnx, Person *from,
                 String channel, String rest)
{
  String nick = from->getNick();

  if (!cnx->bot->iAmOp(channel)) {
    from->sendNotice(String("\002I am not channel op on\002 ") +
                     channel);
    return;
  }

  if (Utils::isWildcard(rest)) {
    from->sendNotice("\002Mass op is not allowed.\002");
    return;
  }

  String target;

  if (rest.length() == 0)
    target = nick;
  else
    target = rest;

  User *u = cnx->bot->channelList->getChannel(channel)->getUser(target);
  if (!u) {
    from->sendNotice(String("\002I cannot find\002 ") + target + " \002on channel\002 " + channel);
    return;
  }

  ShitEntry *se = cnx->bot->shitList->getShit(target, channel);
  if (se && se->isStillValid() && se->getShitLevel() >= ShitEntry::SHIT_NOOP) {
    from->sendNotice(String("\002I can not op\002 ")+target+" \002on channel\002 "+channel+" \002(shitlist).\002");
    return;
  }

  if (!(u->mode & User::OP_MODE))
    cnx->queue->sendChannelMode(channel, "+o", target);
  else {
    if (target == nick)
      from->sendNotice(String("\002You are already channel "
                              "operator on\002 ") + channel);
    else
      from->sendNotice(target + " \002is already channel "
                       "operator on\002 " + channel);
  }
}

// FIXME: Convert
void
UserCommands::Part(ServerConnection *cnx, Person *from,
                   String channel, String rest)
{
  wantedChannel *w = cnx->bot->wantedChannels[channel];
  cnx->bot->wantedChannels.erase(channel);
  delete w;
  cnx->queue->sendPart(channel);
}

void
UserCommands::Password(ServerConnection *cnx, Person *from,
                       String channel, String rest)
{
  Channel *c = cnx->bot->channelList->getChannel(channel);

  if (rest.length() == 0) {
    from->sendNotice("\002No password.\002");
    return;
  }

  User * u = c->getUser(from->getNick());
  if (!u) {
    from->sendNotice(String("\002To change your password for\002") +
                     channel + "\002, you need to be on the "
                     "channel.\002");
    return;
  }

  if (!u->userListItem) {
    from->sendNotice("\002You are not in my userlist.\002");
    return;
  }

  if (rest.toLower() == "none") {
    u->userListItem->passwd = "";
    from->sendNotice("\002Password cleared.\002");
    return;
  }
  
  static char saltChars[] = "abcdefghijklmnopqrstuvwxyz"
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./";
  char salt[3];
  srand(time(0));
  salt[0] = saltChars[rand() % 64];
  salt[1] = saltChars[rand() % 64];
  salt[2] = '\0';

  u->userListItem->passwd = crypt((const char *)rest, salt);
  from->sendNotice("\002Password changed.\002");
}

void
UserCommands::Save(ServerConnection *cnx, Person *from,
                   String channel, String rest)
{
  cnx->bot->userList->save();
  cnx->bot->shitList->save();
  from->sendNotice("\002Userlist and shitlist saved.\002");
}

// FIXME: Convert
void
UserCommands::Say(ServerConnection *cnx, Person *from,
                  String channel, String rest)
{
  if (rest.length() != 0)
    cnx->queue->sendPrivmsg(channel, rest);
}

// FIXME: Convert
void
UserCommands::Server(ServerConnection *cnx, Person *from,
                     String channel, String rest)
{
  int serverNumber;
  String nick = from->getNick();

  if (rest.length() == 0) {
    from->sendNotice("\002You need to supply a server number for this command.\002");
    return;
  }

  serverNumber = atoi(rest);

  if (serverNumber < 0 || serverNumber >= cnx->bot->serverList->size()) {
    from->sendNotice(String((long)serverNumber) +
                      " \002is an invalid server number (see the serverlist).\002");
    return;
  }

  if (cnx->bot->canChangeServer()) {
    cnx->queue->sendQuit("Changing server");
    cnx->queue->flush();
    cnx->bot->connect(serverNumber);
  }
  else
    from->sendNotice("\002I can not change server without losing op on a channel I am on.\002");
}

void
UserCommands::ServerList(ServerConnection *cnx, Person *from,
                     String channel, String rest)
{
  String nick = from->getNick();
  
  from->sendNotice("\002Server list:\002");
  long l = 0;
  String s;
  for (std::vector<class Server *>::iterator it =
         cnx->bot->serverList->v.begin();
       it != cnx->bot->serverList->v.end();
       ++it) {
    s = (*it) == cnx->server ? "\026" : "";
    from->sendNotice(s + String(l++) + s + " " +
                     (*it)->getHostName() +
                     " (" + String((long)((*it)->getPort())) +
                     ")");
  }
  from->sendNotice("\002End of server list.\002");
}

void
UserCommands::SetVersion(ServerConnection *cnx, Person *from,
                         String channel, String rest)
{
  Message m = Commands::SetVersion(cnx->bot, rest);
  if (m.getCode() < 0)
    from->sendNotice(m.getMessage());
}

void
UserCommands::ShitList(ServerConnection *cnx, Person *from,
                       String channel, String rest)
{
  from->sendNotice("\002Shitlist:\002");
  from->sendNotice("\002Mask                      Channel    Level    Expires   Reason\002");

  for (std::list<ShitEntry *>::iterator it = cnx->bot->shitList->l.begin();
       it != cnx->bot->shitList->l.end();
       it++)
    from->sendNotice((*it)->shitMask.getMask().pad(25) + " " +
                     (*it)->shitChannelMask.getMask().pad(10) + " " +
                     String((long)(*it)->shitLevel).pad(9) + " " +
                     String((long)(*it)->expirationDate).pad(9) + " " +
                     (*it)->shitReason);

  from->sendNotice("\002End of shitlist.\002");
}

void
UserCommands::SpyList(ServerConnection *cnx, Person *from,
                     String channel, String rest)
{
  String nick = from->getNick();

  from->sendNotice("\002Spy list:\002");

  for (std::map<String, Person *, std::less<String> >::iterator it =
         cnx->bot->spyList.begin(); it != cnx->bot->spyList.end(); ++it)
    from->sendNotice((*it).first);

  from->sendNotice("\002End of spy list.\002");
}

void
UserCommands::SpyMessage(ServerConnection *cnx, Person *from,
                         String channel, String rest)
{
  String nick = from->getNick();

  if (cnx->bot->spyList.find(nick) != cnx->bot->spyList.end()) {
    from->sendNotice("\002You are already spying messages.\002");
    return;
  }

  Person *f = from->copy();
  f->keepAlive();
  cnx->bot->spyList[nick.toLower()] = f;
  from->sendNotice("\002You have been added to the spy list.\002");
}

// FIXME: Convert
void
UserCommands::Reconnect(ServerConnection *cnx, Person *from,
                        String channel, String rest)
{
  String nick = from->getNick();

  if (cnx->bot->canChangeServer()) {
    cnx->queue->sendQuit("Reconnecting");
    cnx->bot->reconnect();
  }
  else
    from->sendNotice("\002I can not change server without losing op on a channel I am on.\002");
}

void
UserCommands::RSpyMessage(ServerConnection *cnx, Person *from,
                        String channel, String rest)
{
  String nick = from->getNick().toLower();

  if (cnx->bot->spyList.find(nick) == cnx->bot->spyList.end()) {
    from->sendNotice("\002You are not in the spy list.\002");
    return;
  }

  delete cnx->bot->spyList[nick];
  cnx->bot->spyList.erase(nick);
  from->sendNotice("\002You have been removed from the "
                   "spy list.\002");
}

// FIXME: Convert
void
UserCommands::Unlock(ServerConnection *cnx, Person *from,
                   String channel, String rest)
{
  cnx->bot->channelList->getChannel(channel)->lockedTopic = false;
  from->sendNotice(String("\002Unlocking topic for channel\002 ") +
                   channel);
}

void
UserCommands::Stats(ServerConnection *cnx, Person *from,
                    String channel, String rest)
{
  String nick = from->getNick();

  from->sendNotice(String("\002Keeping modes\002 ") +
                    cnx->bot->channelList->getChannel(channel)->keepModes + " \002on channel\002 " +
                    channel);
}

// FIXME: Convert
void
UserCommands::TBan(ServerConnection *cnx, Person *from,
                   String channel, String rest)
{
  Channel * c = cnx->bot->channelList->getChannel(channel);

  StringTokenizer st(rest);
  String who = st.nextToken();
  String t = st.nextToken();
  String dest;

  if (who == "" || t == "") {
    if (from)
      from->sendNotice("\002Invalid syntax for this command.\002");
    return;
  }

  if (Utils::isWildcard(who) && from) {
    User * u = c->getUser(from->getNick());
    if (u && u->getLevel() < User::TRUSTED_USER) {
      if (from)
        from->sendNotice("\002You need an higher level to use"
                         " wildcards.\002");
      return;
    }
  }

  if (!cnx->bot->iAmOp(channel)) {
    if (from)
    from->sendNotice(String("\002I am not channel op on\002 ") +
                     channel);
    return;
  }

  if (!Utils::isWildcard(who))
    dest = cnx->bot->getUserhost(channel, who);
  else
    dest = who;

  if (dest == "") {
    if (from)
      from->sendNotice(String("\002I can not find\002 ") + who);
    return;
  }

  time_t w;

  if ((w = Utils::strToTime(t)) == 0) {
    if (from)
      from->sendNotice(t + " \002is an invalid time.\002");
    return;
  }

  dest = Utils::makeWildcard(dest);
  Mask m(dest);

  for (std::list<UserListItem *>::iterator it = cnx->bot->userList->l.begin();
       it != cnx->bot->userList->l.end();
       it++)
    if (m.matches((*it)->mask) &&
        (*it)->channelMask.matches(channel) &&
        (*it)->prot >= User::NO_BAN) {
      if (from)
        from->sendNotice(String("\002I can not ban\002 ") +
                         who + " \002on channel\002 " +
                         channel + " \002(protection).\002");
      return;
    }

  for (std::vector<BanEntry *>::iterator it = c->channelBanlist.begin();
       it != c->channelBanlist.end(); ++it)
    if (m.matches((*it)->banMask))
      cnx->queue->sendChannelMode(channel, "-b", (*it)->banMask.getMask());

  cnx->bot->channelList->getChannel(channel)->addBan(dest, w);
  cnx->queue->sendChannelMode(channel, "+b", dest);
  cnx->bot->todoList->addDeban(channel, dest, (time_t)w);
}

// FIXME: Convert
void
UserCommands::TKBan(ServerConnection *cnx, Person *from,
                   String channel, String rest)
{
  StringTokenizer st(rest);
  String who = st.nextToken();
  String t = st.nextToken();

  TBan(cnx, 0, channel, who + " " + t);
  Kick(cnx, from, channel, who + " " + st.rest());
}

// FIXME: Convert
void
UserCommands::Topic(ServerConnection *cnx, Person *from,
                    String channel, String rest)
{
  String nick = from->getNick();

  if (rest.length() == 0) {
    if (cnx->bot->channelList->getChannel(channel)->channelTopic == "")
      from->sendNotice(String("\002No topic is set for channel\002 ") +
                        channel + "\002.\002");
    else
      from->sendNotice(String("\002Topic for\002 ") +
                        channel + " \002is:\002 " +
                        cnx->bot->channelList->getChannel(channel)->channelTopic);
  } else {
    if (cnx->bot->channelList->getChannel(channel)->lockedTopic)
      from->sendNotice(String("\002Topic is locked on channel\002 ") +
                       channel);
    else
      cnx->queue->sendTopic(channel, rest);
  }
}

void
UserCommands::UserList(ServerConnection *cnx, Person *from,
                       String channel, String rest)
{
  from->sendNotice("\002Userlist:\002");
  from->sendNotice("\002Mask                      Channel    Level        Prot    Aop   Expires\002");

  for (std::list<UserListItem *>::iterator it = cnx->bot->userList->l.begin();
       it != cnx->bot->userList->l.end();
       it++)
    from->sendNotice((*it)->mask.getMask().pad(25) + " " +
                     (*it)->channelMask.getMask().pad(10) + " " +
                     Utils::levelToStr((*it)->level).pad(12) + " " +
                     Utils::protToStr((*it)->prot).pad(7) + " " +
                     Utils::boolToStr((*it)->aop).pad(5) + " " +
                     String((long)(*it)->expirationDate));

  from->sendNotice("\002End of userlist.\002");
}

void
UserCommands::Who(ServerConnection *cnx, Person *from,
                    String channel, String rest)
{
  String nick = from->getNick();
  User * u = cnx->bot->channelList->getChannel(channel)->getUser(nick);
  UserListItem * uli;

  if (u)
    uli = u->userListItem;
  else
    uli = cnx->bot->userList->getUserListItem(from->getAddress(),
                                         channel);

  if (uli) {
    from->sendNotice(String("\002You are\002 ") +
                      uli->mask.getMask() + " \002on\002 " +
                      uli->channelMask.getMask());
    from->sendNotice(String("\002Lvl:\002 ") +
                     Utils::levelToStr(uli->level) +
                     " \002Prot:\002 " +
                     Utils::protToStr(uli->prot) +
                     " \002Aop:\002 " +
                     Utils::boolToStr(uli->aop) +
                     " \002Expired:\002 " +
                     Utils::boolToStr(!uli->isStillValid()) +
                     " \002Ident:\002 " +
                     (uli && !uli->identified ? "\026" : "") +
                     Utils::boolToStr(uli && uli->identified) +
                     (uli && !uli->identified ? "\026" : ""));
  } else
    from->sendNotice(String("\002You are not in the userlist for\002 ") +
                      channel);
}

void
UserCommands::Whois(ServerConnection *cnx, Person *from,
                    String channel, String rest)
{
  String nick = from->getNick();

  if (!rest.length()) {
    from->sendNotice("\002No nick specified.\002");
    return;
  }

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

  User * u = cnx->bot->channelList->getChannel(channel)->getUser(otherNick);
  UserListItem * uli;

  if (u)
    uli = u->userListItem;
  else
    uli = cnx->bot->userList->getUserListItem(otherNick + "!" +
                                         cnx->bot->getUserhost(channel,
                                                          otherNick),
                                         channel);

  if (uli) {
    from->sendNotice(otherNick +
                     " \002is\002 " + uli->mask.getMask() +
                     " \002on\002 " + uli->channelMask.getMask());
    from->sendNotice(String("\002Lvl:\002 ") +
                     Utils::levelToStr(uli->level) +
                     " \002Prot:\002 " +
                     Utils::protToStr(uli->prot) +
                     " \002Aop:\002 " +
                     Utils::boolToStr(uli->aop) +
                     " \002Expired:\002 " +
                     Utils::boolToStr(!uli->isStillValid()) +
                     " \002Ident:\002 " +
                     Utils::boolToStr(uli && uli->identified));
  } else
    from->sendNotice(otherNick +
                      " \002is not in the userlist for\002 " +
                      channel);
}


syntax highlighted by Code2HTML, v. 0.9.1