/*

  Here are defined all built-in irc !commands, they were not included in a
module. They are:

!modlist
List all loaded modules.

!modadd path
Load the module at the given <path>.

!moddel name
Unload the module with <name>, this must be a name returned by !modlist.

!quit [msg]
Make all bots in the current process quit with <msg>, else uses the default.

!set option [value]
If <value> is specified, sets <option> to it. Otherwise just show the content
of <option>. The available options (variables) depend on the modules loaded,
refer to their documentation for further help (note that some options are
part of the main code too).

!loadconf [filename]
Reload the current configuration file or use <filename>. Note that the changes
only take effect after reloading the module(s). USE WITH CARE, in particular,
don't change the bots' order (if multiple bots are defined of course).

!away [msg]
Put the bot away with <msg>, or without parameters, remove the away.

!nick nick
Change the bot's nick to <nick>.

!unbind !command
Disable the given <!command>. Note that it can only be recovered by restarting
the bot or reloading that command's module.

!join #channel
Add <#channel> to the channel list.

!part #channel
Remove <#channel> from the channel list.

!status
Show some bot status.

!topic [#channel] text
Change the topic in the current or in the specified <#channel>, to <text>

!opme
Give op to who executed the command. Can only be used inside a channel.

!deopme
Take op from who executed the command. Can only be used inside a channel.

!op nick [#channel]
Give op to <nick> on the specified <#channel>.

!deop nick [#channel]
Take op from <nick> on the specified <#channel>.

!kick nick [#channel] [reason]
Kick <nick> on the specified <#channel>, with <reason>.

!ban nick | mask [#channel]
Ban <nick> or <mask> on the specified <#channel>.

!unban nick | mask [#channel]
Unban <nick> or <mask> on the specified <#channel>.

!bk nick [#channel]
Deop, ban and kick <nick> on the specified <#channel>.

!invite nick [#channel]
Invite <nick> to <#channel>.

!voice nick [#channel]
Give voice to <nick> on the specified <#channel>.

!devoice nick [#channel]
Take voice from <nick> on the specified <#channel>.

!say [#channel | $nick] text
Send <text> to a <#channel> or <nick>.

!help
Show the command list, up to the level of who executed it.

!dcckill dcc_index
Terminate the DCC specified by <dcc_index>, use !dcclist to see the indexes.

!dcclist
Show the list of active DCCs (Chat and Send).

!chat
Make the bot start a DCC Chat with who executed it.

!get [file]
Without parameters, show the files available through DCC Send, else requests
the specified <file>.

!randkick
Kick a random non-op user where the command is made.

!time
Show current time and date.

!rose nick
Show a rose to <nick>.

!reverse text
Show <text> with the letters reversed.

!cop nick [#channel]
Use chanserv to op <nick> in the specified <#channel>.

!cdeop nick [#channel]
Use chanserv to deop <nick> in the specified <#channel>.

!useradd mask level [msg]
Add a user with <mask> and <level>. If <msg> is not specified, turns off the
join msg. If <mask> already exists, overwrites the old one.

!userdel mask
Delete the user with <mask>.

!userpass mask pass
Change user's password with <mask> to <pass>.

!usermsg mask msg | <none>
Change user's join msg with <mask> to <msg>, or turns it off with "<none>".

!usermask mask newmask
Change user's mask from <mask> to <newmask>.

!userlevel mask level
Change user's access level with <mask> to <level>.

!id
Show the currently identified users with the bot.

!userlist
Show the list of registered users in the bot.

!setpass password
Change to <password> the password of who executed it.

!setmsg msg | <none>
Change the join msg of who executed it to <msg>, or turn it off with "<none>".

!pass password
Authentifies with the bot. Cannot be used inside a channel.

!level
Show the access level of who executed it.

  The options to configure:

"dccget somefile.get"
Uses somefile.get's informations to configure !get. See example.get for more
details on this.

"dccport port"
Uses the specified port in dcc chats and sends. Very useful when the bot is
behind a firewall. The default is 0, which makes it use any available port.

"dccmotd somefile.motd"
Show this file to everyone who enters the dcc chat partyline.

*/

#include "scriptcmd.h"

#define SOURCE s->script.source
#define DEST s->script.dest
#define BUF s->script.buf
#define SEND_TEXT s->script.send_text

// !modlist
void
scriptcmd_modlist (CNetServer *s)
{
  if (s->bot->modules.count () == 0)
    {
      SEND_TEXT (DEST, "No modules loaded.");
      return;
    }
  my_strncpy (BUF[0], "Loaded Modules: ", MSG_SIZE);
  CModule *m;
  s->bot->modules.rewind ();
  while ((m = (CModule *)s->bot->modules.next ()) != NULL)
    {
      if (m->module->name != NULL)	// if a name is defined
        {
          if (strlen (BUF[0]) + strlen (m->module->name) + 2 < MSG_SIZE)
              snprintf (BUF[0] + strlen (BUF[0]),
                        MSG_SIZE - strlen (BUF[0]), "%s ",
                        m->module->name);
        }
      else				// else show "<unnamed>"
        {
          if (strlen (BUF[0]) + strlen ("<unnamed>") + 2 < MSG_SIZE)
              snprintf (BUF[0] + strlen (BUF[0]),
                        MSG_SIZE - strlen (BUF[0]), "%s ",
                        "<unnamed>");
        }
    }
  SEND_TEXT (DEST, "%s", BUF[0]);
}

// !modadd path
void
scriptcmd_modadd (CNetServer *s)
{
  strsplit (CMD[3], BUF, 1);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !modadd path");
      return;
    }
  CModule *mod = s->bot->add_module (BUF[1]);
  if (mod == NULL)
    {
      SEND_TEXT (DEST, "Failed to load module.");
      return;
    }
  CNetServer *server = NULL;
  const char *bufline;
  int server_cur = -1;

  s->bot->line = 0;
  s->bot->conf.rewind_text ();
  while ( (bufline = s->bot->conf.get_line ()) != NULL)
    {
      s->bot->line++;
      if (bufline[0] == 0 || bufline[0] == '#' || bufline[0] == 10)
        continue;
      strsplit (bufline, BUF, 2);
      if (strcasecmp (BUF[0], "bot") == 0)
        {
          server_cur++;
          if (server_cur < s->bot->server_num)
            server = s->bot->servers[server_cur];
        }
      if (server == NULL)
        continue;
      if (strcasecmp (BUF[0], "set") == 0)
        {
          if (!server->vars.var_set (BUF[1], BUF[2]))
            {
              snprintf (BUF[3], MSG_SIZE, "inexistant variable \"%s\".", BUF[1]);
              server->bot->conf_warn (BUF[3]);
            }
        }
      mod->module->conf (server, bufline);
    }
  SEND_TEXT (DEST, "Module loaded.");
}

// !moddel name
void
scriptcmd_moddel (CNetServer *s)
{
  strsplit (CMD[3], BUF, 1);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !moddel name");
      return;
    }
  if (s->bot->del_module (BUF[1]))
    SEND_TEXT (DEST, "Module unloaded.");
  else
    SEND_TEXT (DEST, "Failed to unload module.");
}

// !quit [reason]
void
scriptcmd_quit (CNetServer *s)
{
  strsplit (CMD[3], BUF, 1);
  for (int i = 0; i < s->bot->server_num; i++)
    if (BUF[1][0] == 0)
      s->bot->servers[i]->irc_quit (NULL);
    else
      s->bot->servers[i]->irc_quit (BUF[1]);
  mbot_exit ();
}

// !set option [value]
void
scriptcmd_set (CNetServer *s)
{
  strsplit (CMD[3], BUF, 2);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !set option [value]");
      return;
    }
  if (BUF[2][0] != 0)
    {
      if (s->vars.var_set (BUF[1], BUF[2]))
        {
          s->vars.var_get (BUF[1], BUF[2], MSG_SIZE);
          SEND_TEXT (DEST, "Value of \"%s\" changed to \"%s\".", BUF[1], BUF[2]);
        }
      else
        SEND_TEXT (DEST, "Invalid option.");
    }
  else
    {
      if (s->vars.var_get (BUF[1], BUF[2], MSG_SIZE))
        SEND_TEXT (DEST, "Value of \"%s\" is \"%s\".", BUF[1], BUF[2]);
      else
        SEND_TEXT (DEST, "Invalid option.");
    }
}

// !loadconf
void
scriptcmd_loadconf (CNetServer *s)
{
  strsplit (CMD[3], BUF, 2);

  if (BUF[1][0] != 0)
    {
      s->bot->conf_file = BUF[1];
      SEND_TEXT (DEST, "Configuration file set to \"%s\".", (c_char)s->bot->conf_file);
    }
  s->bot->conf.delete_lines ();
  if (s->bot->conf.read_file (s->bot->conf_file))
    SEND_TEXT (DEST, "Configuration loaded.");
  else
    SEND_TEXT (DEST, "Couldn't load configuration!");
}

// !away [msg]
void
scriptcmd_away (CNetServer *s)
{
  strsplit (CMD[3], BUF, 1);
  s->away = BUF[1];
  s->irc_away (s->away);
}

// !nick nick
void
scriptcmd_nick (CNetServer *s)
{
  strsplit (CMD[3], BUF, 2);
  if (BUF[1][0] == 0)
    SEND_TEXT(DEST, "usage: !nick nick");
  else
    s->irc_nick (BUF[1]);
}

// !unbind !command
void
scriptcmd_unbind (CNetServer *s)
{
  strsplit (CMD[3], BUF, 2);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !unbind !command");
      return;
    }
  if (s->script.unbind_cmd (BUF[1]))
    SEND_TEXT (DEST, "Command unbinded.");
  else
    SEND_TEXT (DEST, "Command %s does not exist.", BUF[1]);
}

// !join #channel
void
scriptcmd_join (CNetServer *s)
{
  strsplit (CMD[3], BUF, 3);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !join #channel [key]");
      return;
    }
  int chan_num = CHANNEL_INDEX (BUF[1]);
  if (chan_num == -1)		// if not already in the channel
    {
      if (s->channel_add (BUF[1], BUF[2]) == 1)		// add it
        {
          SEND_TEXT (DEST, "Maximum channels reached.");
          return;
        }
      chan_num = CHANNEL_INDEX (BUF[1]);	// search it's position again
    }
  else
    SEND_TEXT (DEST, "I'm already on %s.", BUF[1]);
  CHANNELS[chan_num]->irc_join ();		// join it
}

// !part #channel
void
scriptcmd_part (CNetServer *s)
{
  strsplit (CMD[3], BUF, 2);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !part #channel");
      return;
    }
  int i = CHANNEL_INDEX (BUF[1]);
  if (i != -1)
    {
      CHANNELS[i]->irc_part ();
      s->channel_del (BUF[1]);
    }
  else
    SEND_TEXT (DEST, "I'm not on %s.", BUF[1]);
}

// !status
void
scriptcmd_status (CNetServer *s)
{
  SEND_TEXT (DEST,
             "Status: %s - %s:%d - %s!%s (%s) - %d users, %d/%d channels, %d bytes read, %d written. Up since %s.",
             VERSION_STRING, (char *)s->host_current->host,
             s->host_current->port, (char *)s->nick, (char *)s->user,
             (char *)s->name, (USERS == NULL ? 0 : USERS->count ()),
             s->channel_num, CHANNEL_MAX, s->bytesin, s->bytesout,
             get_asctime (s->uptime, BUF[2], MSG_SIZE));
}

// !topic [#channel] text
void
scriptcmd_topic (CNetServer *s)
{
  strsplit (CMD[3], BUF, 1);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !topic [#channel] text");
      return;
    }
  int i;
  if (BUF[1][0] != '#')
    {
      i = CHANNEL_INDEX (CMD[2]);
      if (i != -1)
        CHANNELS[i]->irc_topic (BUF[1]);
      else
        SEND_TEXT (DEST, "%s", SCRIPT_INVALID_CHAN);
    }
  else
    {
      strsplit (CMD[3], BUF, 2);
      i = CHANNEL_INDEX (BUF[1]);
      if (i != -1)
        CHANNELS[i]->irc_topic (BUF[2]);
      else
        SEND_TEXT (DEST, "%s", SCRIPT_INVALID_CHAN);
    }
}

// !opme
void
scriptcmd_opme (CNetServer *s)
{
  if (CMD[2][0] != '#')
    {
      SEND_TEXT (DEST, "This command only works inside channels.");
      return;
    }
  mask2nick (CMD[0], BUF[0], NICK_SIZE);
  int i = CHANNEL_INDEX (CMD[2]);
  if (i != -1)
    CHANNELS[i]->irc_op (BUF[0]);
  else
    SEND_TEXT (DEST, "%s", SCRIPT_INVALID_CHAN);
}

// !deopme
void
scriptcmd_deopme (CNetServer *s)
{
  if (CMD[2][0] != '#')
    {
      SEND_TEXT (DEST, "This command only works inside channels.");
      return;
    }
  mask2nick (CMD[0], BUF[0], NICK_SIZE);
  int i = CHANNEL_INDEX (CMD[2]);
  if (i != -1)		// should never fail
    CHANNELS[i]->irc_deop (BUF[0]);
  else
    SEND_TEXT (DEST, "%s", SCRIPT_INVALID_CHAN);
}

// !op nick [#channel]
void
scriptcmd_op (CNetServer *s)
{
  strsplit (CMD[3], BUF, 3);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !op nick [#channel]");
      return;
    }
  if (BUF[2][0] != '#')
    my_strncpy (BUF[2], CMD[2], MSG_SIZE);
  int i = CHANNEL_INDEX (BUF[2]);
  if (i != -1)
    CHANNELS[i]->irc_op (BUF[1]);
  else
    SEND_TEXT (DEST, "%s", SCRIPT_INVALID_CHAN);
}

// !deop nick [#channel]
void
scriptcmd_deop (CNetServer *s)
{
  strsplit (CMD[3], BUF, 3);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !deop nick [#channel]");
      return;
    }
  if (BUF[2][0] != '#')
    strncpy (BUF[2], CMD[2], MSG_SIZE);
  if (strcasecmp (BUF[1], s->nick) != 0)
    {
      int i = CHANNEL_INDEX (BUF[2]);
      if (i != -1)
        CHANNELS[i]->irc_deop (BUF[1]);
      else
        SEND_TEXT (DEST, "%s", SCRIPT_INVALID_CHAN);
    }
  else
    SEND_TEXT (DEST, "%s", SCRIPT_HURT_BOT);
}

// !kick nick [#channel] [reason]
void
scriptcmd_kick (CNetServer *s)
{
  strsplit (CMD[3], BUF, 2);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !kick nick [#channel] [reason]");
      return;
    }
  if (strcasecmp (BUF[1], s->nick) == 0)	// doesn't kick the bot
    {
      SEND_TEXT (DEST, "%s", SCRIPT_HURT_BOT);
      return;
    }
  int i;
  if (BUF[2][0] != '#')
    {
      i = CHANNEL_INDEX(CMD[2]);
      if (i != -1)
        CHANNELS[i]->irc_kick (BUF[1], BUF[2]);
      else
        SEND_TEXT (DEST, "%s", SCRIPT_INVALID_CHAN);
    }
  else
    {
      strsplit (CMD[3], BUF, 3);
      i = CHANNEL_INDEX (BUF[2]);
      if (i != -1)
        CHANNELS[i]->irc_kick (BUF[1], BUF[3]);
      else
        SEND_TEXT (DEST, "%s", SCRIPT_INVALID_CHAN);
    }
}

// !ban nick | mask [#channel]
void
scriptcmd_ban (CNetServer *s)
{
  strsplit (CMD[3], BUF, 3);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT(DEST, "usage: !ban nick|mask [#channel]");
      return;
    }
  if (BUF[2][0] != '#')
    my_strncpy (BUF[2], CMD[2], MSG_SIZE);
  int i;
  if (strchr (BUF[1], '@') != NULL)		// check if it's a mask
    {
      i = CHANNEL_INDEX (BUF[2]);
      if (i != -1)
        CHANNELS[i]->irc_ban_mask (BUF[1]);
      else
        SEND_TEXT (DEST, "%s", SCRIPT_INVALID_CHAN);
      return;
    }
  if (strcasecmp (BUF[1], s->nick) != 0)	// doesn't ban the bot
    {
      i = CHANNEL_INDEX (BUF[2]);
      if (i != -1)
        CHANNELS[i]->irc_ban_nick (BUF[1]);
      else
        SEND_TEXT (DEST, "%s", SCRIPT_INVALID_CHAN);
    }
  else
    SEND_TEXT (DEST, "%s", SCRIPT_HURT_BOT);
}

// !unban nick | mask [#channel]
void
scriptcmd_unban (CNetServer *s)
{
  strsplit (CMD[3], BUF, 3);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !unban nick|mask [#channel]");
      return;
    }
  if (BUF[2][0] != '#')
    my_strncpy (BUF[2], CMD[2], MSG_SIZE);
  int i;
  if (strchr (BUF[1], '@') != NULL)		// check if it's a mask
    {
      i = CHANNEL_INDEX (BUF[2]);
      if (i != -1)
        CHANNELS[i]->irc_unban_mask (BUF[1]);
      else
        SEND_TEXT (DEST, "%s", SCRIPT_INVALID_CHAN);
      return;
    }
  i = CHANNEL_INDEX (BUF[2]);
  if (i != -1)
    CHANNELS[i]->irc_unban_nick (BUF[1]);
  else
    SEND_TEXT (DEST, "%s", SCRIPT_INVALID_CHAN);
}

// !bk nick [#channel] [reason]
void
scriptcmd_bk (CNetServer *s)
{
  strsplit (CMD[3], BUF, 2);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT(DEST, "usage: !bk nick [#channel] [reason]");
      return;
    }
  if (strcasecmp (BUF[1], s->nick) == 0)		// doesn't bk the bot
    {
      SEND_TEXT (DEST, "%s", SCRIPT_HURT_BOT);
      return;
    }
  if (BUF[2][0] != '#')		// if the channel was not specified
    {
      my_strncpy (BUF[3], BUF[2], MSG_SIZE);		// reason
      my_strncpy (BUF[2], CMD[2], MSG_SIZE);		// channel
    }
  else
    strsplit (CMD[3], BUF, 3);
  int i = CHANNEL_INDEX (BUF[2]);
  if (i == -1)			// if the channel exists
    {
      SEND_TEXT (DEST, "%s", SCRIPT_INVALID_CHAN);
      return;
    }
  int i2 = CHANNELS[i]->user_index (BUF[1]);
  if (i2 != -1)		// and the user too
    {
      // make mask
      make_generic_mask (CHANNELS[i]->users[i2]->mask, BUF[4]);
      // deop/ban
      CHANNELS[i]->irc_deopban (BUF[1], BUF[4]);
      // kick
      CHANNELS[i]->irc_kick (BUF[1], BUF[3][0] == 0 ? NULL : BUF[3]);
    }
  else
    SEND_TEXT (DEST, "Nick %s is not inside %s.", BUF[1], BUF[2]);
}

// !invite nick [#channel]
void
scriptcmd_invite (CNetServer *s)
{
  strsplit (CMD[3], BUF, 3);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !invite nick [#channel]");
      return;
    }
  if (BUF[2][0] != '#')
    my_strncpy (BUF[2], CMD[2], MSG_SIZE);
  int i = CHANNEL_INDEX (BUF[2]);
  if (i != -1)
    CHANNELS[i]->irc_invite (BUF[1]);
  else
    SEND_TEXT (DEST, "%s", SCRIPT_INVALID_CHAN);
}

// !voice nick [#channel] 
void
scriptcmd_voice (CNetServer *s)
{
  strsplit (CMD[3], BUF, 3);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !voice nick [#channel]");
      return;
    }
  if (BUF[2][0] != '#')
    my_strncpy (BUF[2], CMD[2], MSG_SIZE);
  int i = CHANNEL_INDEX (BUF[2]);
  if (i != -1)
    CHANNELS[i]->irc_voice (BUF[1]);
  else
    SEND_TEXT (DEST, "%s", SCRIPT_INVALID_CHAN);
}

// !devoice nick [#channel]
void
scriptcmd_devoice (CNetServer *s)
{
  strsplit (CMD[3], BUF, 3);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT(DEST, "usage: !devoice nick [#channel]");
      return;
    }
  if (BUF[2][0] != '#')
    my_strncpy (BUF[2], CMD[2], MSG_SIZE);
  if (strcasecmp (BUF[1], s->nick) != 0)
    {
      int i = CHANNEL_INDEX (BUF[2]);
      if (i != -1)
        CHANNELS[i]->irc_devoice (BUF[1]);
      else
        SEND_TEXT (DEST, "%s", SCRIPT_INVALID_CHAN);
    }
  else
    SEND_TEXT (DEST, "%s", SCRIPT_HURT_BOT);
}

// !say [#channel | $nick] text
void
scriptcmd_say (CNetServer *s)
{
  strsplit (CMD[3], BUF, 1);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !say [#channel | $nick] text");
      return;
    }
  if (BUF[1][0] == '#')
    {
      int i = 0;
      while (BUF[1][i] != ' ' && BUF[1][i] != 0)
        i++;
      my_strncpy (DEST, BUF[1], i);
      my_strncpy (BUF[2], BUF[1] + i + num_spaces (BUF[1]+i), MSG_SIZE);
      my_strncpy (BUF[1], BUF[2], MSG_SIZE);
    }
  if (BUF[1][0] == '$')
    {
      int i = 0;
      while (BUF[1][i] != ' ' && BUF[1][i] != 0)
        i++;
      my_strncpy (DEST, BUF[1], i);
      my_strncpy (DEST, DEST+1, CHANNEL_SIZE);
      if (strcasecmp (NICKSERV, DEST) == 0
          || strcasecmp (CHANSERV, DEST) == 0
          || strcasecmp (MEMOSERV, DEST) == 0
          || strcasecmp (OPERSERV, DEST) == 0)
        {
          SEND_TEXT (SOURCE, "Cannot send to IRC Services.");
          return;
        }
      my_strncpy (BUF[2], BUF[1] + i + num_spaces (BUF[1] + i), MSG_SIZE);
      my_strncpy (BUF[1], BUF[2], MSG_SIZE);
    }
  SEND_TEXT (DEST, "%s", BUF[1]);
}

// !help
void
scriptcmd_help (CNetServer *s)
{
  int level = (USERS == NULL ? 0 : USERS->match_mask_level (CMD[0]));
  bool has_user;
  SEND_TEXT (SOURCE, "Commands:");
  for (int i = level; i >= 0; i--)
    {
      CScript::cmd_type *c = s->script.cmds[i];
      snprintf (BUF[1], MSG_SIZE, "Level %d:", i);
      has_user = 0;
      while (c != NULL)
        {
          has_user = 1;
          snprintf (BUF[1] + strlen (BUF[1]), MSG_SIZE, " %s", c->cmd);
          c = c->next;
        }
      if (has_user)
        SEND_TEXT (SOURCE, "%s", BUF[1]);
    }
}

// !dcckill dcc_index
void
scriptcmd_dcckill (CNetServer *s)
{
  strsplit (CMD[3], BUF, 1);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !dcckill dcc_index");
      return;
    }
  int dcc_index = atoi (BUF[1]);
  if (dcc_index < 0 || dcc_index > DCC_NUM - 1)
    SEND_TEXT (DEST, "Invalid DCC index.");
  else
    {
      if (DCCS[dcc_index]->status == 5)
        {
          snprintf (BUF[0], MSG_SIZE, "%s killed %s.", SOURCE,
                    DCCS[dcc_index]->nick);
          s->script.send_partyline (BUF[0]);
        }
      DCCS[dcc_index]->dcc_stop ();
    }
}

// !dcclist
void
scriptcmd_dcclist (CNetServer *s)
{
  if (DCC_NUM == 0)
    {
      SEND_TEXT (DEST, "No DCCs running.");
      return;
    }
  for (int i = 0; i < DCC_NUM; i++)
    {
      switch (DCCS[i]->status)
        {
          case DCC_STOP:
            my_strncpy (BUF[1], "closing", MSG_SIZE);
            break;
          case DCC_SEND_INIT:
            my_strncpy (BUF[1], "Send (not connected)", MSG_SIZE);
            break;
          case DCC_SEND:
            my_strncpy (BUF[1], "Send", MSG_SIZE);
            break;
          case DCC_CHAT_INIT:
            my_strncpy (BUF[1], "Chat (not connected)", MSG_SIZE);
            break;
          case DCC_CHAT_AUTH:
            my_strncpy (BUF[1], "Chat (authenticating)", MSG_SIZE);
            break;
          case DCC_CHAT:
            my_strncpy (BUF[1], "Chat", MSG_SIZE);
            break;
          default:
            my_strncpy (BUF[1], "unknown", MSG_SIZE);
        }
      SEND_TEXT (DEST, "%d: DCC %s with %s", i, BUF[1], (c_char)DCCS[i]->mask);
    }
}

// !chat
void
scriptcmd_chat (CNetServer *s)
{
  if ((USERS == NULL ? 0 : USERS->match_mask_level(CMD[0])) < 1)
    {
      SEND_TEXT (SOURCE, "Sorry, you don't have access.");
      return;
    }
  for (int i = 0; i < DCC_NUM; i++)
    if (strcasecmp (DCCS[i]->nick, SOURCE) == 0
        && DCCS[i]->status >= 3 && DCCS[i]->status <= 5)
      {
        SEND_TEXT (SOURCE, "Another DCC Chat is in progress for you.");
        return;
      }
  if (DCC_NUM == DCC_MAX)
    {
      SEND_TEXT (SOURCE, "No free DCC slots available, please try later.");
      return;
    }
  int dcc_index = ((CMD[2][0] >= '0' && CMD[2][0] <= '9') ? atoi (CMD[2]) : -1);
  DCCS[DCC_NUM] = new CNetDCC (s, CMD[0], DCC_NUM, dcc_index);
  // if it gives error, work()'ll delete it
  if (DCCS[DCC_NUM]->dcc_chat_start ())
    SEND_TEXT (SOURCE, DCCS[DCC_NUM]->dcc_make_ctcp_chat (BUF[3], MSG_SIZE));
  DCC_NUM++;
}

// !get [file]
void
scriptcmd_get (CNetServer *s)
{
  ifstream f ((char *)s->dcc_file);
  if (!f)
    {
      SEND_TEXT (SOURCE, "Error opening configuration file %s: %s", (c_char)s->dcc_file, strerror (errno));
      s->write_botlog ("error opening configuration file %s: %s", (c_char)s->dcc_file, strerror (errno));
      return;
    }
  strsplit (CMD[3], BUF, 1);
  if (BUF[1][0] != 0)		// if a file was specified
    {
      for (int i = 0; i < DCC_NUM; i++)
        if (strcasecmp (DCCS[i]->nick, SOURCE) == 0 &&
            (DCCS[i]->status == 1 || DCCS[i]->status == 2))
          {
            SEND_TEXT (SOURCE, "Another DCC Send is in progress for you.");
            return;
          }
      if (DCC_NUM == DCC_MAX)
        {
          SEND_TEXT (SOURCE, "No free DCC slots available, please try later.");
          return;
        }
      char file[PATH_MAX+1];			// to store the requested file
      my_strncpy (file, BUF[1], PATH_MAX);

      while (f.getline (BUF[7], MSG_SIZE))
        if (BUF[7][0] != ':' && BUF[7][0] != '#')
          {
            strsplit (BUF[7], BUF, 2);
            if (strcasecmp (BUF[0], file) == 0)
              {
                int dcc_index = ((CMD[2][0] >= '0' && CMD[2][0] <= '9') ? atoi (CMD[2]) : -1);
                // create the dcc
                DCCS[DCC_NUM] = new CNetDCC (s, CMD[0], DCC_NUM, dcc_index);
                // if it gives error, work()'ll delete it
                if (DCCS[DCC_NUM]->dcc_send_start (BUF[1]))
                  SEND_TEXT (SOURCE, "%s", DCCS[DCC_NUM]->dcc_make_ctcp_send (BUF[1], MSG_SIZE));
                DCC_NUM++;
                return;
              }
          }
      SEND_TEXT (SOURCE, "Invalid file.");
    }
  else				// else show help
    {
      while (f.getline (BUF[0], MSG_SIZE))
        if (BUF[0][0] == ':')			// if it's help
          SEND_TEXT (SOURCE, "%s", BUF[0]+1);	// +1 to remove the ':'
    }
}

// look for ctcp dcc chat on privmsg
void
scriptcmd_dcc_event (CNetServer *s)
{
  if (CMD[3][0] == '' && strcmp(CMD[1], "PRIVMSG") == 0)
    {
      mask2nick (CMD[0], DEST, NICK_SIZE);
      if (strncmp (CMD[3], "DCC CHAT", 9) == 0)
        {
          s->irc_notice (DEST, "DCC REJECT CHAT chat");
          SEND_TEXT (DEST, "Please use the !chat command to start a DCC Chat.");
        }
    }
}

// !randkick's reason, change at will
#define RANDKICK_REASON "9 out of 10 people really hate this kick. how about you?"

// !randkick
void
scriptcmd_randkick (CNetServer *s)
{
  if (strcasecmp (CMD[2], s->nick) == 0)
    {
      SEND_TEXT(DEST, "!randkick can only be used inside channels.");
      return;
    }
  int i = CHANNEL_INDEX (CMD[2]);
  if (i == -1)
    return;
  struct CChannel::user_type *list[C_USERS_MAX];
  int user_num, list_num = 0;

  // build non-ops list
  for (user_num = 0; user_num < CHANNELS[i]->user_num; user_num++)
    if ( ! (CHANNELS[i]->users[user_num]->is_op) )
      list[list_num++] = CHANNELS[i]->users[user_num];

  // if there's at least one
  if (list_num != 0)
    {
      int rand_user = random_num (list_num);
      CHANNELS[i]->irc_kick (list[rand_user]->nick, RANDKICK_REASON);
    }
  else
    SEND_TEXT (DEST, "There's no one to kick.");
}

// !time
void
scriptcmd_time (CNetServer *s)
{
  SEND_TEXT (DEST, "%s", get_asctime (s->time_now, s->script.BUF[0], MSG_SIZE));
}

// !rose nick
void
scriptcmd_rose (CNetServer *s)
{
  strsplit (CMD[3], BUF, 1);
  if (BUF[1][0] == 0)
    SEND_TEXT (DEST, "usage: !rose nick");
  else
    SEND_TEXT (DEST, "ACTION %s offers this 7@3}-,-'-- to the beautiful %s :)",
               SOURCE, BUF[1]);
}

// !reverse text
void
scriptcmd_reverse (CNetServer *s)
{
  strsplit (CMD[3], BUF, 1);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !reverse text");
      return;
    }
  char c;
  size_t len = strlen (BUF[1]);
  for (size_t i = 0; i < len/2; i++)
    {
      c = BUF[1][i];
      BUF[1][i] = BUF[1][len-i-1];
      BUF[1][len-i-1] = c;
    }
  SEND_TEXT (DEST, "%s", BUF[1]);
}

// !cop nick [#channel]
void
scriptcmd_cop (CNetServer *s)
{
  strsplit (CMD[3], BUF, 3);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !cop nick [#channel]");
      return;
    }
  if (BUF[2][0] == 0)
    my_strncpy (BUF[2], CMD[2], MSG_SIZE);
  s->services.chan_op (BUF[2], BUF[1]);
}

// !cdeop nick [#channel]
void
scriptcmd_cdeop (CNetServer *s)
{
  strsplit (CMD[3], BUF, 3);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !cdeop nick [#channel]");
      return;
    }
  if (BUF[2][0] == 0)
    my_strncpy (BUF[2], CMD[2], MSG_SIZE);
  if (strcasecmp (BUF[1], s->nick) != 0)
    s->services.chan_deop (BUF[2], BUF[1]);
  else
    SEND_TEXT (DEST, "%s", SCRIPT_HURT_BOT);
}

// !useradd mask level [msg]
void
scriptcmd_useradd (CNetServer *s)
{
  strsplit (CMD[3], BUF, 3);
  if (BUF[2][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !useradd mask level [msg]");
      return;
    }
  if (USERS->match_mask_level (CMD[0]) > USERS->match_mask_reallevel (BUF[1]) &&
      USERS->match_mask_level (CMD[0]) > atoi (BUF[2]))
    {
      if (USERS->add_user (BUF[1], DEFAULT_USER_PASS, atoi (BUF[2]), BUF[3]))
        SEND_TEXT (DEST, "User added.");
      else
        SEND_TEXT (DEST, "Error adding user.");
    } 
  else
    SEND_TEXT (DEST, "Not enough access level.");
}

// !userdel mask
void
scriptcmd_userdel (CNetServer *s)
{
  strsplit (CMD[3], BUF, 2);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !userdel mask");
      return;
    }
  struct CListUsers::user_type *u = USERS->abs_mask2user (BUF[1]);
  if (u == NULL)
    {
      SEND_TEXT (DEST, "User does not exist.");
      return;
    }
  if (USERS->match_mask_level (CMD[0]) > u->level)
    {
      if (USERS->del_user (BUF[1]))
        SEND_TEXT (DEST, "User deleted.");
      else
        SEND_TEXT (DEST, "Error deleting user.");
    }
  else
    SEND_TEXT (DEST, "Not enough access level.");
}

// !userpass mask pass
void
scriptcmd_userpass (CNetServer *s)
{
  strsplit (CMD[3], BUF, 3);
  if (BUF[2][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !userpass mask pass");
      return;
    }
  struct CListUsers::user_type *u = USERS->abs_mask2user (BUF[1]);
  if (u == NULL)
    {
      SEND_TEXT (DEST, "User does not exist.");
      return;
    }
  if (USERS->match_mask_level (CMD[0]) > u->level )
    {
      USERS->abs_set_pass (BUF[1], BUF[2]);
      SEND_TEXT (DEST, "Password of %s set to %s.", BUF[1], BUF[2]);
    }
  else
    SEND_TEXT (DEST, "Not enough access level.");
}

// !usermsg mask msg|<none>
void
scriptcmd_usermsg (CNetServer *s)
{
  strsplit (CMD[3], BUF, 2);
  if (BUF[2][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !usermsg mask msg|<none>");
      return;
    }
  struct CListUsers::user_type *u = USERS->abs_mask2user (BUF[1]);
  if (u == NULL)
    {
      SEND_TEXT (DEST, "User does not exist.");
      return;
    }
  if (USERS->match_mask_level (CMD[0]) > u->level)
    {
      if (strcasecmp (BUF[2], "<none>") == 0)
        {
          USERS->abs_set_msg (BUF[1], "");
          SEND_TEXT (DEST, "Message disabled.");
        }
      else
        {
          USERS->abs_set_msg (BUF[1], BUF[2]);
          SEND_TEXT (DEST, "Message of %s set to %s.", BUF[1], BUF[2]);
        }
    } 
  else
    SEND_TEXT (DEST, "Not enough access level.");
}

// !usermask mask newmask
void
scriptcmd_usermask (CNetServer *s)
{
  strsplit (CMD[3], BUF, 3);
  if (BUF[2][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !usermask mask newmask");
      return;
    }
  struct CListUsers::user_type *u = USERS->abs_mask2user (BUF[1]);
  if (u == NULL)
    {
      SEND_TEXT (DEST, "User does not exist.");
      return;
    }
  if (USERS->match_mask_level (CMD[0]) > u->level)
    {
      USERS->abs_set_mask (BUF[1], BUF[2]);
      SEND_TEXT (DEST, "Mask %s changed to %s.", BUF[1], BUF[2]);
    }
  else
    SEND_TEXT (DEST, "Not enough access level.");
}

// !userlevel mask level
void
scriptcmd_userlevel (CNetServer *s)
{
  strsplit (CMD[3], BUF, 3);
  if (BUF[2][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !userlevel mask level");
      return;
    }
  struct CListUsers::user_type *u = USERS->abs_mask2user (BUF[1]);
  if (u == NULL)
    {
      SEND_TEXT (DEST, "User does not exist.");
      return;
    }
  if (atoi (BUF[2]) >= LEVEL_MIN && atoi (BUF[2]) <= LEVEL_MAX)
    {
      if (USERS->match_mask_level (CMD[0]) > u->level
          && USERS->match_mask_level (CMD[0]) > atoi (BUF[2]))
        {
          USERS->abs_set_level (BUF[1], atoi (BUF[2]));
          SEND_TEXT (DEST, "Level of %s set to %s.", BUF[1], BUF[2]);
        }
      else
        SEND_TEXT (DEST, "Not enough access level.");
    }
  else
    SEND_TEXT (DEST, "Invalid level.");
}

// !id
void
scriptcmd_id (CNetServer *s)
{
  my_strncpy (BUF[1], "Identified users:", MSG_SIZE);
  struct CListUsers::user_type *u;
  USERS->rewind ();
  while ((u = (struct CListUsers::user_type *)USERS->next ()) != NULL)
    if (u->id && strlen (BUF[1]) + u->cur_mask.getlen () + 1 < MSG_SIZE)
      sprintf (BUF[1] + strlen (BUF[1]), " %s", (char *)u->cur_mask);
  SEND_TEXT (DEST, "%s", BUF[1]);
}

// !userlist
void
scriptcmd_userlist (CNetServer *s)
{
  bool has_user;
  struct CListUsers::user_type *u;
  SEND_TEXT (SOURCE, "Users:");
  for (int i = LEVEL_MAX; i >= LEVEL_MIN; i--)
    {
      has_user = 0;
      snprintf (BUF[1], MSG_SIZE, "Level %d:", i);
      USERS->rewind ();
      while ((u = (struct CListUsers::user_type *)USERS->next ()) != NULL)
        if (u->level == i)
          if (strlen (BUF[1]) + u->mask.getlen () > MSG_SIZE)
            {
              SEND_TEXT (SOURCE, "%s", BUF[1]);
              my_strncpy (BUF[1], u->mask, MSG_SIZE);
            }
          else
            {
              snprintf (BUF[1] + strlen (BUF[1]), MSG_SIZE - strlen (BUF[1]),
                        " %s", (char *)u->mask);
              has_user = 1;
            }
      if (has_user)
        SEND_TEXT (SOURCE, "%s", BUF[1]);
    }
}

// !setpass password
void
scriptcmd_setpass (CNetServer *s)
{
  if (DEST[0] == '#')
    {
      SEND_TEXT (DEST, "Do not use this command on a channel.");
      return;
    }
  strsplit (CMD[3], BUF, 2);
  if (BUF[1][0] == 0)
    SEND_TEXT (DEST, "usage: !setpass password");
  else
    {
      USERS->match_set_pass (CMD[0], BUF[1]);
      SEND_TEXT (DEST, "Password set to %s - remember it for later use.", BUF[1]);
    }
}

// !setmsg msg | <none>
void
scriptcmd_setmsg (CNetServer *s)
{
  strsplit (CMD[3], BUF, 1);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !setmsg msg | <none>");
      return;
    }
  if (strcasecmp (BUF[1], "<none>") == 0)
    {
      USERS->match_set_msg (CMD[0], "");
      SEND_TEXT (DEST, "Message disabled.");
    }
  else
    {
      USERS->match_set_msg (CMD[0], BUF[1]);
      SEND_TEXT (DEST, "Message set to '%s'.", BUF[1]);
    }
}

// !pass password
void
scriptcmd_pass (CNetServer *s)
{
  if (DEST[0] == '#')
    {
      SEND_TEXT (DEST, "Do not use this command on a channel.");
      return;
    }
  strsplit (CMD[3], BUF, 1);
  if (BUF[1][0] == 0)
    {
      SEND_TEXT (DEST, "usage: !pass password");
      return;
    }
  if (USERS->match_check_pass (CMD[0], BUF[1]))
    {
      USERS->match_set_id (CMD[0], 1);
      mask2nick (CMD[0], BUF[2], NICK_SIZE);
      s->irc_watch (BUF[2], 1);
      SEND_TEXT (DEST, "Password correct.");
    }
  else
    SEND_TEXT (DEST, "Password incorrect.");
}

// !level
void
scriptcmd_level (CNetServer *s)
{
  snprintf (BUF[1], MSG_SIZE, "Your access level is %d",
            USERS->match_mask_reallevel (CMD[0]));
  if (USERS->match_mask_reallevel (CMD[0]) >= 1)
    {
      if (USERS->match_mask_level (CMD[0]) > 1)
        snprintf (BUF[1] + strlen (BUF[1]), MSG_SIZE - strlen (BUF[1]),
                  ", identified");
      else
        snprintf (BUF[1] + strlen (BUF[1]), MSG_SIZE - strlen (BUF[1]),
                  ", unidentified");
    }
  SEND_TEXT (DEST, "%s.", BUF[1]);
}

void
scriptcmd_ctcp_var (CNetServer *s, c_char name, char *data, size_t n)
{
  if (strcasecmp (name, "ctcp_version") == 0)
    {
      if (n != 0)
        my_strncpy (data, s->script.ctcp_version, n);
      else
        s->script.ctcp_version = data;
    }
  else if (strcasecmp (name, "ctcp_finger") == 0)
    {
      if (n != 0)
        my_strncpy (data, s->script.ctcp_finger, n);
      else
        s->script.ctcp_finger = data;
    }
  else if (strcasecmp (name, "ctcp_source") == 0)
    {
      if (n != 0)
        my_strncpy (data, s->script.ctcp_source, n);
      else
        s->script.ctcp_source = data;
    }
}
