colloquy-1.35.30/
colloquy-1.35.30/data/lang/
colloquy-1.35.30/data/misc/
colloquy-1.35.30/docs/
-- command table for colloquy

function commandQuit(connection, line, message)
   local conn = colloquy.connections[connection];
   if (message == "") then
      message = "";
   else
      message = "(" .. message .. ")";
   end;
   sendGM(conn, S_QUIT, "cquitBye");
   disconnectUser(connection, message);
end;

function commandShout(connection, line, message)
   local tmp;
   local conn = colloquy.connections[connection];

   if (not message or message == "") then
     sendGM(conn, S_ERROR, "cshoutUsage");
     return nil;
   end;

   if (strfind(conn.restrict, "G", 1, 1) or strfind(conn.restrict, "B", 1, 1)) then
      sendGM(conn, S_ERROR, "cshoutGagged");
      return nil;
   end;

   if (message ~= "") then
    if (strfind(conn.restrict, "C", 1, 1)) then
      message = censor(message);
    end;

    if (strsub(message, 1, 1) == ":" or strsub(message, 1, 1) == ";") then
      tmp = "! " .. conn.username;
      if (strsub(message, 2, 2) == ":") then
        if (strlower(strsub(conn.username, -1, -1)) == "s") then
          tmp = tmp .. "' " .. strsub(message, 3, -1);
        else
          tmp = tmp .. "'s " .. strsub(message, 3, -1);
        end;
      else
        tmp = tmp .. " " .. strsub(message, 2, -1);
      end;
    else
      tmp = conn.username;
      tmp = tmp .. strrep(" ", 11);
      tmp = strsub(tmp, 1, 12) .. "!" .. message;
    end;
    conn.talkBytes = conn.talkBytes + strlen(message);
    sendToAll(tmp, S_SHOUT);
  end;
end;

function commandSay(connection, line, message, esc)
   local conn = colloquy.connections[connection];
 
   if (message ~= "") then
      if (strsub(message, 1, 1) == "'") then
        -- ah, they want to escape this...
        message = strsub(message, 2);
      end;

      if (not esc and conn.query) then
        local type = strsub(conn.query.format, 1, 1);
        if (type == ">") then
          -- turn this into a whisper..
          local params = format("%s %s", conn.query.data.username, message);
          commandTell(connection, ".tell " .. params, params);
          return nil;
        elseif (type == "@") then
          -- turn into another whisper...
          local params = format("@%s %s", conn.query.data, message);
          commandTell(connection, ".tell " .. params, params);
          return nil;
        elseif (type == "%") then
          -- turn into a list whisper...
          local params = format("%s %s", conn.query.data.listname, message);
          commandListTell(connection, ">>" .. params, params);
          return nil;
        end;
      end;  
      if (strfind(conn.restrict, "C", 1, 1)) then
        message = censor(message);
      end;
      conn.talkBytes = conn.talkBytes + strlen(message);
      sendToGroup(format("%-11.11s :%s", conn.username, message), conn.group, S_TALK);
   end;
end;

function commandLook(connection, line, params)
   local t, i, v;
   local conn = colloquy.connections[connection];
   local sorted = {};
   local p = split(params or "");
   local g = conn.group;
   local lsort = function(a, b) return (strlower(a) < strlower(b)) end;
   
   if (p[1]) then
     local err;
     g, err = groupByName(p[1]);
     if (not g) then
       send(err, conn, S_ERROR);
       return nil;
     end;
   end;

   local lg = strlower(g);

   for i, v in colloquy.connections do
     if (strlower(v.group) == lg and not v.veryIdle) then
       local m = v.username;

       if (v.privs and strfind(v.privs, "M")) then
         m = m .. "(M)";
       elseif (v.privs and v.privs ~= "") then
         m = m .. "(P)";
       elseif (v.status > 1) then
         m = m .. "(U)";
       end;

       if (not v.invis) then
         tinsert(sorted, m);
       end;
     end;
   end;

   if (getn(sorted) > 0) then
     sendGM(conn, S_LOOKHDR, "clookActive", g);
     sort(sorted, lsort);
     local rl = columns(sorted, (conn.width - 19) / 14, 13);
     for i = 1, getn(rl) do
       send("  " .. rl[i], conn, S_LOOK);
     end;
   end;
    
   sorted = {}
   
   for i, v in colloquy.connections do
     if (strlower(v.group) == lg and v.veryIdle) then
       local m = v.username;

       if (v.privs and strfind(v.privs, "M")) then
         m = m .. "(M)";
       elseif (v.privs and v.privs ~= "") then
         m = m .. "(P)";
       elseif (v.status > 1) then
         m = m .. "(U)";
       end;

       if (not v.invis) then
         tinsert(sorted, m);
       end;
     end;
   end;

   if (getn(sorted) > 0) then
     sendGM(conn, S_LOOKHDR, "clookIdle", g);
     sort(sorted, lsort);
     local rl = columns(sorted, (conn.width - 19) / 14, 13);
     for i = 1, getn(rl) do
       send("  " .. rl[i], conn, S_LOOK);
     end;
   end;
end;

function commandEmote(connection, line, message, esc)
   local tmp;
   local conn = colloquy.connections[connection];

   if (message == "") then
     sendGM(conn, S_ERROR, "cemoteUsage");
     return nil;
   end;

   if (message ~= "") then
    if (not esc and conn.query) then
      local type = strsub(conn.query.format, 1, 1);
      if (type == ">") then
        -- turn this into a whisper..
        local params = format("%s %s", conn.query.data.username, message);
        commandRemote(connection, "<" .. params, params);
        return nil;
      elseif (type == "@") then
        -- turn into another whisper...
        local params = format("@%s %s", conn.query.data, message);
        commandRemote(connection, "<" .. params, params);
        return nil;
      elseif (type == "%") then
        -- turn into a list whisper...
        local params = format("%s %s", conn.query.data.listname, message);
        commandListEmote(connection, "<<" .. params, params);
        return nil;
      end;
     end;  
     
     if (strfind(conn.restrict, "C", 1, 1)) then
       message = censor(message);
     end;
     tmp = conn.username;
     if (strfind(punctuation, strsub(message, 1, 1), 1, 1)) then
       tmp = tmp .. message;
     else
       tmp = tmp .. " " .. message;
     end;
     conn.talkBytes = conn.talkBytes + strlen(message);

     sendToGroup(tmp, conn.group, S_EMOTE);
   end;
end;

function commandPemote(connection, line, message, esc)
  local tmp;
  local conn = colloquy.connections[connection];

  if (message == "") then
    sendGM(conn, S_ERROR, "cpemoteUsage");
    return nil;
  else
    if (not esc and conn.query) then
      local type = strsub(conn.query.format, 1, 1);
      local s = "'s ";
      if (strlower(strsub(conn.username, -1, -1)) == "s") then
        s = "' ";
      end;

      if (type == ">") then
        -- turn this into a remote...
        local params = format("%s %s%s", conn.query.data.username, s, message);
        commandRemote(connection, ".remote " .. params, params);
        return nil;
      elseif (type == "@") then
        -- turn this into a group whisper...
        local params = format("@%s %s%s", conn.query.data, s, message);
        commandRemote(connection, ".remote " .. params, params);
        return nil;
      elseif (type == "%") then
        -- turn this into a list whisper...
        local params = format("%s %s%s", conn.query.data.listname, s, message);
        commandListEmote(connection, ".remote " .. params, params);
        return nil;
      end;
    end;

    if (strfind(conn.restrict, "C", 1, 1)) then
      message = censor(message);
    end;

    tmp = conn.username;
    if (strlower(strsub(tmp, -1, -1))) == "s" then
      tmp = tmp .. "' ";
    else
      tmp = tmp .. "'s ";
    end;
    tmp = tmp .. message;
    conn.talkBytes = conn.talkBytes + strlen(message);
    sendToGroup(tmp, conn.group, S_EMOTE);
  end;
end;

function commandMark(connection, line)
   local t = date("%H:%M");
   send(strrep("-", colloquy.connections[connection].width - 4 - strlen(t) - 1) .. " " .. t, colloquy.connections[connection], S_MARK);
end;

function commandHelp(connection, line, thing)
   local t, i, v = " "; 
   local c = {};
   local conn = colloquy.connections[connection];

   if (thing == "") then
     thing = gm(conn, "chelpGeneral");
   end;

   if (thing == gm(conn, "chelpCommands")) then
      local longest = 0;

      for i, v in commTable do
        if (i ~= "n") then
          if (v.name ~= nil and v.name ~= "" and v.allow(connection)) then --t = t .. v.name .. " " end;
            tinsert(c, v.name);
            if (strlen(v.name) > longest) then
              longest = strlen(v.name);
            end;
          end;
        end;
      end;

      sort(c);
      local rl = columns(c, floor((conn.width-6 - longest) / (longest + 1)) + 1, longest)

      sendGM(conn, S_HELP, "chelpAvailable");
      for i=1,getn(rl) do
        send("  " .. rl[i], conn, S_HELP);
      end;
   else
      thing = strlower(thing);
      thing = gsub(thing, "[%.%/]", "");
      thing = gsub(thing, "%s+", " ");
      if thing == "" then
        sendGM(conn, S_ERROR, "chelpNoHelp");
        return nil;
      end;
      local f = openfile(colloquy.help .. thing, "r");
      if (f == nil) then
        sendGM(conn, S_ERROR, "chelpNoHelp");
        return nil;
      end;
      local s = read(f, "*l");
      while (s ~= nil) do
        send(s, conn, S_HELP);
        s = read(f, "*l");
      end;
      closefile(f);
   end;
end;

function commandGroup(connection, line, groupname)
   local tmp;
   local conn = colloquy.connections[connection];

   if (isBot(connection)) then
     sendGM(conn, S_ERROR, "cgroupBot");
     return nil;
   end;

   if (groupname == "" or groupname == nil) then groupname = gm(conn, "PublicGroup") end;

   groupname = gsub(groupname, "[%c ]", "");
   if (strlen(groupname) > 15) then
      groupname = strsub(groupname, 1, 15);
   end;

   if (strupper(groupname) == strupper(conn.group)) then
      sendGM(conn, S_ERROR, "cgroupAlready", groupname);
      return nil;
   end;

   if (colloquy.lockedGroups[strlower(groupname)] and not checkInvitation(conn, "@" .. strlower(groupname))) then
     sendGM(conn, S_ERROR, "cgroupLocked", groupname);
     return nil;
   end;

   if (strfind(groupname, "[%s,@%%]")) then
     sendGM(conn, S_ERROR, "cgroupInvalid", groupname);
     return nil;
   end;


   local oldGroup = conn.group;

   conn.group = "";

   if (not conn.invis) then
      sendGMGroup(oldGroup, S_GROUP, "cgroupHasMoved", conn.username, groupname);
      sendGMGroup(groupname, S_GROUP, "cgroupEnters", conn.username);
   end;

   conn.group = groupname;
   checkGroupToUnlock(oldGroup);

   sendGM(conn, S_GROUP, "cgroupYouEnter", groupname);
   commandLook(connection, ".look", "");
   removeInvitation(conn, "@" .. strlower(groupname));

end;

function commandSpy(connection, line, groupname)
   local tmp;
   local conn = colloquy.connections[connection];

   if (groupname == "" or groupname == nil) then groupname = "Public" end;

   groupname = gsub(groupname, "[%c ]", "");
   if (strlen(groupname) > 15) then
      groupname = strsub(groupname, 1, 15);
   end;

   if (strupper(groupname) == strupper(conn.group)) then
      sendGM(conn, S_ERROR, "cspyAlready", groupname);
      return nil;
   end;

   local oldGroup = conn.group;
   
   conn.group = "";

   if (not conn.invis) then
      sendGMGroup(oldGroup, S_GROUP, "cspyHasMoved", conn.username, groupname);
      sendGMGroup(groupname, S_GROUP, "cspyEnters", conn.username);
   end;

   conn.group = groupname;

   log(format("S  %s[%s] spies on group %s", conn.username, conn.realUser, groupname));
   
   sendGM(conn, S_GROUP, "cspyYouEnter", groupname);
   commandLook(connection, ".look");
   removeInvitation(conn, "@" .. strlower(groupname));
end;

function commandJoin(connection, line, username)
   local tmp, i, v;
   local conn = colloquy.connections[connection];
   
   if (username == "") then
      sendGM(conn, S_ERROR, "cjoinUsage");
      return nil;
   end;

   username = gsub(username, "[%c ]", "");
   if (strlen(username) > 10) then
      username = strsub(username, 1, 10);
   end;

   local expansion = userByName(username);
   if (expansion == nil) then
      sendGM(conn, S_ERROR, "cjoinNoUser", username);
      return nil;
   elseif (type(expansion) == "string") then
      send(expansion, conn, S_ERROR);
      return nil;
   end;

   commandGroup(connection, ".group " .. expansion.group, expansion.group);

end;

function commandUnknown(connection, line, command)
   send("Unknown command.", colloquy.connections[connection], S_ERROR);
end;

function commandGroups(connection, line)
   local groups = {};
   local i, v, r;
   local conn = colloquy.connections[connection];
   local alreadyExists = function(name, table)
     local i, v, n;
   
     n = strlower(name);
     for i, v in table do
       if (strlower(i) == n) then
         return v;
       end;
    end;
  end;
   
  for i, v in colloquy.connections do
    local t = alreadyExists(v.group, groups);
    if (t == nil and not v.invis and not (v.restrict and strfind(v.restrict, "B", 1, 1)) and v.group ~= "") then
      groups[v.group] = {};
      t = groups[v.group];
    end;
    
    local m;
    if (v.group ~= "") then
      m = v.username;
      if (not v.invis and not (v.restrict and strfind(v.restrict, "B", 1, 1))) then tinsert(t, m); end;
    end;
  end;

  sendGM(conn, S_GROUPSHDR, "cgroupCurrent");

  for i, v in groups do
    local j, g;
    r = " " .. i; 
    if (colloquy.lockedGroups[strlower(i)]) then
      r = r .. " (L)";
    end;
    r = r .. strrep(" ", 21);
    r = (strsub(r, 1, 21));
    for j, g in v do
      if (j ~= "n") then
      r = r .. g .. " "
    end;
   end;
   send(r, conn, S_GROUPS);
 end;
end;

function commandGName(connection, line, new)
   local tmp, i, v;
   local conn = colloquy.connections[connection];
   
   if (new == "") then
      sendGM(conn, S_ERROR, "cgnameUsage");
      return nil;
   end;

   if (isBot(connection)) then
     sendGM(conn, S_ERROR, "cgnameBot");
     return nil;
   end;

   if (strlower(conn.group) == "public" and not allowM(connection)) then
      sendGM(conn, S_ERROR, "cgnamePublic");
      return nil;
   end;

   if (strfind(new, "[%s,@%%]")) then
     sendGM(conn, S_ERROR, "cgnameInvalid");
     return nil;
   end;

   new = gsub(new, "[%c ]", "");
   if (strlen(new) > 15) then
      new = strsub(new, 1, 15);
   end;

   tmp = strlower(new);
   local old = conn.group;

   -- check if this groupname is already in use...
   local merging = nil;
   for i, v in colloquy.connections do
      if (i ~= "n") then
        if (strlower(v.group) == tmp and not allowM(connection)) then
          sendGM(conn, S_ERROR, "cgnameAlready");
          return nil;
        elseif (strlower(v.group) == tmp and allowM(connection)) then
          merging = v.group;
        end;
      end;
   end;

   tmp = strlower(conn.group);

   for i, v in colloquy.connections do
      if (i ~= "n") then
        if (strlower(v.group) == tmp) then
           v.group = new;
        end;
      end;
   end;

   if (merging) then
     sendGMGroup(new, S_GNAME, "cgnameMerge", conn.username, old, merging);
   else
     sendGMGroup(new, S_GNAME, "cgnameChange", conn.username, new);
   end;

   log(format("GN %s[%s] changes group %s name to %s", conn.username, conn.realUser, old, new));
   updateInvitations("@" .. old, "@" .. new)
   if (colloquy.lockedGroups[old]) then
     colloquy.lockedGroups[old] = nil;
     colloquy.lockedGroups[new] = 1;
   end;

end;

function commandInfo(connection, line, user)
  local i, v, t;
  local conn = colloquy.connections[connection];
  local guest, master;
  
  if conn.status < 2 then
    guest = 1
  end;

  master = allowM(conn.socket.socket);
   
  t = " ";

  if (user == "") then
    -- list all the users...
    local u = {}
    for i, v in users do
      if (i ~= "n") then
        tinsert(u, i);
      end;
    end;

    sort(u);

    sendGM(conn, S_INFOHDR, "cinfoAvailable");
    local l = {};

    for i = 1, getn(u) do
      tinsert(l, u[i]);
    end;

    local r = columns(l, ((conn.width-6)/18), 17);

    for i=1, getn(r) do
      send("  " .. r[i], conn, S_INFOLIST);
    end;
  else
    -- info one just one user...
    i = strlower(user);
    if (users[i] == nil) then
      sendGM(conn, S_ERROR, "cinfoNoUser", user);
      return nil;
    end;
      
    -- let's work out what lists they are on...
    local sublists, st = "", {};
    do
      local o, v;
      local j, k;
      for o, v in lists do
        if (type(v) == "table" and not strfind(v.flags or "", "A", 1, 1)) then
          for j, k in v.members do
            if (type(k) == "string" and k == i) then
              tinsert(st, o);
            end;
          end;
        end;
      end;
      sort(st);
      if (getn(st) > 0) then
        for o=1,getn(st) do
          sublists = sublists .. lists[st[o]].listname .. " ";
        end;
      end;
    end;
  
    local field = function(n, v, conn, removeStar)
      if removeStar then
        v = gsub(v, "^%*", "")
        v = gsub(v, "^%!", "")
      end
      send(format("%-14.14s %s", n .. ":", v), conn, S_INFO);
    end
    
    local showable = function(v)
      if not v then return nil end;
      if %guest and strfind(v, "^[%*]") then return nil end
      if not %master and strfind(v, "^[%!]") then return nil end
      return 1;
    end

    local f = function(f)
      return gm(%conn, "cinfo" .. f);
    end;

    local ud = users[i];

    field(f "User", i, conn);
    if showable(ud.name) then field(f "RealName", ud.name, conn, 1) end;
    if ud.banned then field(f "Banned", ud.banned, conn) end;
    if ud.aliases then field(f "Aliases", ud.aliases, conn) end;
    if allowM(conn.socket.socket) and ud.authenticator then
      field(f "Authenticator", ud.authenticator, conn);
    end
    if ud.privs then field(f "Privs", ud.privs, conn) end;
    if showable(ud.sex) then field(f "Sex", ud.sex, conn, 1) end;
    if showable(ud.birthday) then
      field(f "Birthday", ud.birthday, conn, 1);
      field(f "Age", calculateAge(ud.birthday), conn);
    end
    if showable(ud.email) then field(f "Email", ud.email, conn, 1) end;
    if showable(ud.homepage) then field(f "Homepage", ud.homepage, conn, 1) end;
    if showable(ud.occupation) then field(f "Occupation", ud.occupation, conn, 1) end;
    if showable(ud.location) then field(f "Location", ud.location, conn, 1) end;
    if showable(ud.interests) then field(f "Interests", ud.interests, conn, 1) end;
    if showable(ud.comments) then field(f "Comments", ud.comments, conn, 1) end;
    if showable(ud.around) then field(f "NextAround", ud.around, conn, 1) end;
    if sublists ~= "" then field(f "OnLists", sublists, conn) end;
    if ud.created then field(f "Created", ud.created, conn) end;
    if ud.lastSite then field(f "LastSite", ud.lastSite, conn) end;
    if ud.lastLogon then field(f "LastLogon", ud.lastLogon, conn) end;
    if (ud.lastQuit and ud.lastQuit ~= "") then field(f "LastQuit", ud.lastQuit, conn) end;
    if ud.talkBytes then field(f "TalkBytes", prettyBytes(ud.talkBytes), conn) end;
    if ud.timeon then field(f "TimeOn", timeToString(ud.timeon), conn) end;
  end;
end;

function commandStats(connection, line, user)
  local conn = colloquy.connections[connection];
  local of = format;
  local format = function(field, value)
    local t = gm(%conn, "cstats" .. field) .. ":" .. strrep(" ", 24);
    t = strsub(t, 1, 24);
    return t .. value;
  end;

  send(format("TalkerName", colloquy.talkerName), conn, S_STATS);
  send(format("Version", colloquy.version .. " (" .. colloquy.date .. ") (c) Rob Kendrick (" .. _VERSION .. ")"), conn, S_STATS);
  send(format("Compiled", __DATE__ .. " (" .. colloquy.os .. ")"), conn, S_STATS);
  send(format("Started", colloquy.startTime), conn, S_STATS);
  send(format("UpFor", timeToString(secs - colloquy.startClock)), conn, S_STATS);
  send(format("Daytime", colloquy.daytime), conn, S_STATS);
  send(format("MaxDayUsers", colloquy.daytimeMax or gm(conn, "cstatsNone")), conn, S_STATS);
  send(format("MaxNightUsers", colloquy.nighttimeMax or gm(conn, "cstatsNone")), conn, S_STATS);
  send(format("MaxIdle", gm(conn, "cstatsMinutes", colloquy.maxIdle)), conn, S_STATS);
  send(format("MaxGuests", colloquy.maxGuests or gm(conn, "cstatsNone")), conn, S_STATS);
  if colloquy.guestTimeout then
    send(format("GuestTimeout", gm(conn, "cstatsSeconds", colloquy.guestTimeout)), conn, S_STATS);
  else
    send(format("GuestTimeout", gm(conn, "cstatsNone")), conn, S_STATS);
  end;
  send(format("CacheStats", of("%d/%d %s, %d %s, %d%% %s.", msgCacheSize, msgCacheMax, gm(conn, "cstatsUsed"),
                                                        msgCacheLastRemoved, gm(conn, "cstatsRemoved"),
                                                        (msgCacheHits * 100)/(msgCacheHits+msgCacheMisses), gm(conn, "cstatsHits")
                                                        )), conn, S_STATS);
  local round = function(a)
    if (strfind(a, ".", 1, 1)) then
      return strsub(a, 1, strfind(a, ".", 1, 1) + 3);
    end;
    return a;
  end;
   
  local cpuUsage = clock() / ( (secs - colloquy.startClock) / ( 60 * 60 * 24) );
  if (cpuUsage < 1 or ((secs - colloquy.startClock) / (60 * 60 * 24) < 1)) then
    cpuUsage = round(clock()) .. " seconds, ";
    send(format("ResUsage", gm(conn, "cstatsUsage1", round(clock()), tostring(gcinfo()))), conn, S_STATS);
  else
    cpuUsage = round(cpuUsage) .. " secs/day, ";
    send(format("ResUsage", gm(conn, "cstatsUsage2", round(cpuUsage), tostring(gcinfo()))), conn, S_STATS);
  end;
 
  send(format("DataSent", prettyBytes(dataSent)), conn, S_STATS);
  send(format("DataRead", prettyBytes(dataRead)), conn, S_STATS);
  send(format("Bandwidth", round(dataSent / (secs - colloquy.startClock)) .. " bytes/sec out, " .. round(dataRead / (secs - colloquy.startClock)) .. " bytes/sec in."), conn, S_STATS);
end;

function commandSet(connection, line, params)
   local i, v;
   local p = split(params);
   local conn = colloquy.connections[connection];
   if (not p[1]) then
     tmp = gm(conn, "csetOptions");
     tmp = tmp .. format("%s%s%s%s%s%s%s%s%s",
                         gm(conn, "csetOptBeep", y(strfind(conn.flags, "B", 1, 1), gm(conn, "csetOn"), gm(conn, "csetOff"))),
                         gm(conn, "csetOptCR", y(strfind(conn.flags, "C", 1, 1), gm(conn, "csetOn"), gm(conn, "csetOff"))),
                         gm(conn, "csetOptEcho", y(strfind(conn.flags, "E", 1, 1), gm(conn, "csetOn"), gm(conn, "csetOff"))),
                         gm(conn, "csetOptStrip", y(strfind(conn.flags, "D", 1, 1), gm(conn, "csetOn"), gm(conn, "csetOff"))),
                         gm(conn, "csetOptPrompts", y(strfind(conn.flags, "P", 1, 1), gm(conn, "csetOn"), gm(conn, "csetOff"))),
                         gm(conn, "csetOptShouts", y(strfind(conn.flags, "S", 1, 1), gm(conn, "csetOn"), gm(conn, "csetOff"))),
                         gm(conn, "csetOptMessages", y(strfind(conn.flags, "M", 1, 1), gm(conn, "csetOn"), gm(conn, "csetOff"))),
                         gm(conn, "csetOptLists", y(strfind(conn.flags, "L", 1, 1), gm(conn, "csetOn"), gm(conn, "csetOff"))),
                         gm(conn, "csetOptIdling", y(strfind(conn.flags, "I", 1, 1), gm(conn, "csetOn"), gm(conn, "csetOff"))));

     if (conn.termType == "dumb") then
       tmp = tmp .. gm(conn, "csetOptTerminal", gm(conn, "csetOptTermDumb"));
     elseif (conn.termType == "colour") then
       tmp = tmp .. gm(conn, "csetOptTerminal", gm(conn, "csetOptTermColour"));
     elseif (conn.termType == "client") then
       tmp = tmp .. gm(conn, "csetOptTerminal", gm(conn, "csetOptTermClient"));
     end;

     if (strfind(conn.flags, "W")) then
       tmp = tmp .. gm(conn, "csetOptWidth", gm(conn, "csetOptWidthAuto", conn.width + 1));
     elseif (conn.noWrap) then
       tmp = tmp .. gm(conn, "csetOptWidth", gm(conn, "csetOptWidthZero"));
     else
       tmp = tmp .. gm(conn, "csetOptWidth", gm(conn, "csetOptWidthOther", conn.width));
     end;

     tmp = tmp .. gm(conn, "csetOptLanguage", conn.lang.NAME);

     send(tmp, conn, S_DONE);

     return nil;
   end;

   for i = 1, getn(setCommands) do
     v = setCommands[i];
     if (strlower(p[1]) == v.name) then
       v.code(connection, line, params)
       return nil;
     end;
   end;

   sendGM(conn, S_ERROR, "csetUnknown", p[1]);
end;

function setLanguage(connection, line, params)
  local p = split(params);
  local conn = colloquy.connections[connection];
  
  if (p[2] == nil) then
    -- get a list of current languages
    local languages = pdir(colloquy.langs)
    languages.lf = ""
    foreachi(languages, function(i,v)
                          if strsub(v, 1, 1) == "." then return end
                          %languages.lf = %languages.lf .. gsub(v, "%.lua$", "") .. " "
                        end)
    send(gm(conn, "csetlanguageUsage"), conn, S_ERROR)
    send(gm(conn, "csetlanguageAvailable", languages.lf), conn, S_ERROR);
    return nil;
  end;

  if (getlang(p[2])) then
    conn.lang = getlang(p[2]);
    send(gm(conn, "csetlanguageChanged"), conn, S_DONE);
    return nil;
  end

  send(gm(conn, "csetlanguageUnknown", p[2]), conn, S_ERROR);

end

function setStrip(connection, line, params)

  local p = split(params);
  local conn = colloquy.connections[connection];

  if (p[2] == nil) then
     sendGM(conn, S_ERROR, "csetstripUsage");
     return nil;
  end

  if (strlower(p[2]) == gm(conn, "On")) then
     conn.flags = gsub(conn.flags, "d", "D");
     if (strfind(conn.flags, "D", 1, 1) == nil) then
       conn.flags = conn.flags .. "D";
     end
     sendGM(conn, S_DONE, "csetstripOn");
  else
     conn.flags = gsub(conn.flags, "D", "d");
     sendGM(conn, S_DONE, "csetstripOff");
  end;

end;

function setEcho(connection, line, params)
  local p = split(params);
  local conn = colloquy.connections[connection];

  if (p[2] == nil) then
    sendGM(conn, S_ERROR, "csetechoUsage");
    return nil;
  end;

  if (strfind(conn.flags, "e", 1, 1) == nil and strfind(conn.flags, "E", 1, 1) == nil) then
    conn.flags = conn.flags .. "e";
  end;

  if (strlower(p[2]) == gm(conn, "On")) then
    conn.flags = gsub(conn.flags, "e", "E");
    sendGM(conn, S_DONE, "csetechoOn");
    conn.socket.echo = 1;
  else
    conn.flags = gsub(conn.flags, "E", "e");
    sendGM(conn, S_DONE, "csetechoOff");
    conn.socket.echo = nil;
  end;
end;

function setWidth(connection, line, params)
   local p = split(params);
   local conn = colloquy.connections[connection];

   if (p[2] == nil or (tonumber(p[2]) == nil) and p[2] ~= "auto") then
     sendGM(conn, S_ERROR, "csetwidthUsage");
     return nil;
   end;

   if (strlower(p[2]) == gm(conn, "csetwidthAuto")) then
     if (conn.termType == "colour") then
        -- ask the terminal to let us autonegotiate screen size
        send(gm(conn, "csetwidthDoneAuto") .. "\255\253\31", conn, S_DONE);
        conn.flags = gsub(conn.flags, "w", "W");
        conn.noWrap = nil;
     else
       sendGM(conn, S_ERROR, "csetwidthNoColour");
       return nil;
     end;
   elseif (tonumber(p[2]) < 79 and tonumber(p[2]) > 0) then
     sendGM(conn, S_ERROR, "csetwidthTooSmall");
     return nil;
   else
     conn.flags = gsub(conn.flags, "W", "w");
     if (tonumber(p[2]) == 0) then
       -- they don't want wrapping, but some functions need to know how
       -- to format tables, so set it to eight, and set the conn.noWrap
       -- flag.
       conn.width = 79;
       conn.noWrap = 1;
       sendGM(conn, S_DONE, "csetwidthDoneNone");
     else
       conn.width = p[2];
       conn.noWrap = nil;
       sendGM(conn, S_DONE, "csetwidthDone", conn.width);
     end;
   end;
end;

function setPrompts(connection, line, params)
   local p = split(params);
   local conn = colloquy.connections[connection];
   
   if (p[2] == nil) then
     sendGM(conn, S_ERROR, "csetpromptsUsage");
     return nil
   end;

   if (strfind(conn.flags, "p", 1, 1) == nil and strfind(conn.flags, "P", 1, 1) == nil) then
     conn.flags = conn.flags .. "p";
   end;

   if (strlower(p[2]) == gm(conn, "On")) then
      conn.flags = gsub(conn.flags, "p", "P");
      sendGM(conn, S_DONE, "csetpromptsOn");
   else
      conn.flags = gsub(conn.flags, "P", "p");
      sendGM(conn, S_DONE, "csetpromptsOff");
   end;
end;

function setPrivs(connection, line, params)
  local conn = colloquy.connections[connection];
   
  if (strfind(conn.privs or "", "P", 1, 1) == nil) then
    sendGM(conn, S_ERROR, "NoPriv");
    return nil;
  else
    local p = split(params);
    if (p[3] == nil or p[2] == nil) then
      sendGM(conn, S_ERROR, "csetprivsUsage");
      return nil;
    end;

    local u = strlower(p[2]);
    local i, v;
    
    for i, v in colloquy.connections do
      if (i ~= "n" and strlower(v.username) == u) then
        -- right, found the user...
        if (v.privs and strfind(v.privs, "Z", 1, 1)) then
          sendGM(conn, S_ERROR, "Immune", v.username);
          return nil;
        end;
        
        v.privs = "";
        v.status = 2;
        if (p[3] ~= "-") then
          local j, k, m;
          m = strlower(conn.realUser);
          for j, k in users do
            if (j == m) then
              local n;
              for n = 1, strlen(p[3]) do
                if (strfind(k.privs, strsub(p[3], n, n), 1, 1)) then
                  v.privs = v.privs .. strsub(p[3], n, n);
                end;
              end;
            end;
          end;
        end;
        log(format("P  %s[%s] sets %s[%s] privs to %s", conn.username, conn.realUser, v.username, v.realUser, p[3]));
        sendGM(conn, S_DONE, "csetprivsChanged", v.username, v.privs);
        return nil;
      end;
    end;
    sendGM(conn, S_ERROR, "UnknownUser", p[2]);
   end;
end;

function setTimewarn(connection, line, params)
   local conn = colloquy.connections[connection];
   
   local p = split(params);

   if (p[2] == nil) then
      sendGM(conn, S_ERROR, "csettimeUsage");
      return nil;
   end;

   local n = tonumber(p[2]);
   if (type(n) ~= "number") then
      sendGM(conn, S_ERROR, "csettimeUsage");
      return nil;
   end;

   if (n > 0 and n < 1) then n = 1 end;

   n = floor(n);

   conn.timeWarn = n;
   conn.timeTick = secs;

   if (n > 0) then
      sendGM(conn, S_DONE, "csettimeDone", n);
   else
      sendGM(conn, S_DONE, "csettimeNone");
   end;
end;

function setCR(connection, line, params)
   local p = split(params);
   local conn = colloquy.connections[connection];

   if (p[2] == nil) then
     sendGM(conn, S_ERROR, "csetcrUsage");
     return nil;
   end;

   if (strlower(p[2]) == gm(conn, "On")) then
      conn.flags = gsub(conn.flags, "c", "C");
      sendGM(conn, S_DONE, "csetcrOn");
  else
      conn.flags = gsub(conn.flags, "C", "c");
      sendGM(conn, S_DONE, "csetcrOff");
   end;
end;

function setTerm(connection, line, params)
   local p = split(params);
   local conn = colloquy.connections[connection];
   
   if (p[2] == nil) then
      sendGM(conn, S_ERROR, "csettermUsage");
      return nil;
   end;

   local t = strlower(p[2]);

   if (t == strlower(gm(conn, "csetOptTermDumb"))) then
      conn.termType = "dumb";
      sendGM(conn, S_DONE, "csettermDone", gm(conn, "csetOptTermDumb"));
   elseif (t == strlower(gm(conn, "csetOptTermColour"))) then
      conn.termType = "colour";
      sendGM(conn, S_DONE, "csettermDone", gm(conn, "csetOptTermColour"));
   elseif (t == strlower(gm(conn, "csetOptTermClient"))) then
      conn.termType = "client";
      sendGM(conn, S_DONE, "csettermDone", gm(conn, "csetOptTermClient"));
   else
     sendGM(conn, S_ERROR, "csettermUnknown", p[2]);
   end;

end;

function setColour(connection, line, params)
   local p = split(params);
   local conn = colloquy.connections[connection];
   local col = function(t)
      local f, b = getColouringName(%conn, t)
      return f .. " on " .. b
   end;
   
   if (p[2] == nil or p[3] == nil or p[4] == nil) then
     send("Current colour settings: ", conn, S_DONE);
     send(" Talk:        " .. col("talk"), conn, S_DONE);

     send(" Tell:        " .. col("tell"), conn, S_DONE);

     send(" List:        " .. col("list"), conn, S_DONE);
     
     send(" ListName:    " .. col("listname"), conn, S_DONE);

     send(" Shout:       " .. col("shout"), conn, S_DONE);

     send(" Message:     " .. col("message"), conn, S_DONE);

     send(" Nick:        " .. col("nick"), conn, S_DONE);

     send(" Me:          " .. col("me"), conn, S_DONE);
     
     send("Use .Set Colour <Type> <Foreground> <Background> to change them.", conn, S_DONE);

     return nil;
   end;

   local t = strlower(p[2]);
   local c = strlower(p[3]);
   local bc = strlower(p[4]);
   local br, brb;

   if (strsub(c, 1, 2) == "br") then
      br = 1;
      c = strsub(c, 3, -1);
   end;

   if (strsub(bc, 1, 2) == "br") then
     brb = 1;
     bc = strsub(bc, 3, -1);
   end
      
   if (not equal(t, { "talk", "tell", "list", "listname", "shout", "message", "nick", "me", "talkback", "tellback", "listback", "listnameback", "shoutback", "messageback", "nickback", "meback" })) then
      send("Unknown type.  Valid types are: talk, tell, list, listname, shout, message, nick, me.", conn, S_ERROR);
      return nil;
   end;

   if (not equal(c, { "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", "none" } )) then
      send("Unknown colour.  Valid colours are: BrBlack, BrRed, BrGreen, BrYellow, BrBlue, BrMagenta, BrCyan, BrWhite, Black, Red, Green, Yellow, Blue, Magenta, Cyan, White.",  conn, S_ERROR);
      return nil;
   end;

   if (not equal(bc, { "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", "none" } )) then
      send("Unknown colour.  Valid colours are: BrBlack, BrRed, BrGreen, BrYellow, BrBlue, BrMagenta, BrCyan, BrWhite, Black, Red, Green, Yellow, Blue, Magenta, Cyan, White.",  conn, S_ERROR);
      return nil;
   end;
   setColouring(conn, t, strlower(p[3]), strlower(p[4]));

   send("Colour changed.", conn, S_DONE);

   saveUsers(colloquy.users);
end;

function setBeep(connection, line, params)
   local p = split(params);
   local conn = colloquy.connections[connection];

   if (p[2] == nil) then
     sendGM(conn, S_ERROR, "csetbeepUsage");
     return nil;
   end;

   if (strlower(p[2]) == gm(conn, "On")) then
      conn.flags = gsub(conn.flags, "b", "B");
      sendGM(conn, S_DONE, "csetbeepOn");
  else
      conn.flags = gsub(conn.flags, "B", "b");
      sendGM(conn, S_DONE, "csetbeepOff");
   end;

   saveUsers(colloquy.users);
end;

function setInfo(connection, line, params)
   local p = split(params);
   local conn = colloquy.connections[connection];

   if (conn.status < 2) then
     sendGM(conn, S_ERROR, "csetinfoGuest");
     return nil;
   end;

   if (p[2] == nil) then
      sendGM(conn, S_ERROR, "csetinfoUsage");
      return nil;
   end;

   local t = strlower(p[2]);

   local f = function(a) return gm(%conn, "csetinfoF" .. a) end;

   if (not equal(t, {f"location", f"occupation", f"interests", f"comments", f"around", f"email", f"homepage"}) ) then
      sendGM(conn, S_ERROR, "csetinfoInvalid", p[2]);
      return nil;
   end;

   local u = users[strlower(conn.realUser)];
   if (not u) then
     send("Sorry, I havn't a clue who you are.", conn, S_ERROR);
     return nil;
   end;

   local v;
   if (p[3]) then
     v = strsub(line, strfind(line, p[2], 1, 1) + strlen(p[2]) + 1, -1);
   end;

   if (t == f"location") then
     u.location = v;
   elseif (t == f"occupation") then
     u.occupation = v;
   elseif (t == f"interests") then
     u.interests = v;
   elseif (t == f"comments") then
     u.comments = v;
   elseif (t == f"around") then
     u.around = v;
   elseif (t == f"email") then
     u.email = v;
   elseif (t == f"homepage") then
     u.homepage = v;
   end;
   
   saveUsers(colloquy.users);

   if (v) then
     sendGM(conn, S_DONE, "csetinfoChanged", t, v);
   else
     sendGM(conn, S_DONE, "csetinfoUnset", t);
   end;

end;

function setHeard(connection, line, params)
  local p = split(params);
  local f = strlower(p[1]);
  local conn = colloquy.connections[connection];

  if (f == gm(conn, "csetheardShouts")) then
    if (not p[2]) then
      if (strfind(conn.flags, "S", 1, 1)) then
        sendGM(conn, S_DONE, "csetheardShoutsOn");
      else
        sendGM(conn, S_DONE, "csetheardShoutsOff");
      end;
      return nil;
    end;

    local s = strlower(p[2]);
    if (s == gm(conn, "Off")) then
      conn.flags = exchange(conn.flags, "S", "s");
      sendGM(conn, S_DONE, "csetheardShoutsOff");
    else
      conn.flags = exchange(conn.flags, "s", "S");
      sendGM(conn, S_DONE, "csetheardShoutsOn");
    end;
    return nil;
  
  elseif (f == gm(conn, "csetheardMessages")) then
    if (not p[2]) then
      if (strfind(conn.flags, "M", 1, 1)) then
        sendGM(conn, S_DONE, "csetheardMessagesOn");
      else
        sendGM(conn, S_DONE, "csetheardMessagesOff");
      end;
      return nil;
    end;
    local s = strlower(p[2]);
    if (s == gm(conn, "Off")) then
      conn.flags = exchange(conn.flags, "M", "m");
      sendGM(conn, S_DONE, "csetheardMessagesOff");
    else
      conn.flags = exchange(conn.flags, "m", "M");
      sendGM(conn, S_DONE, "csetheardMessagesOn");
    end;
    return nil;
  elseif (f == gm(conn, "csetheardLists")) then
    if (not p[2]) then
      if (strfind(conn.flags, "L", 1, 1)) then
        sendGM(conn, S_DONE, "csetheardListsOn");
      else
        sendGM(conn, S_DONE, "csetheardListsOff");
      end;
      return nil;
    end;
    local s = strlower(p[2]);
    if (s == gm(conn, "Off")) then
      conn.flags = exchange(conn.flags, "L", "l");
      sendGM(conn, S_DONE, "csetheardListsOff");
    else
      conn.flags = exchange(conn.flags, "m", "M");
      sendGM(conn, S_DONE, "csetheardListsOn");
    end;
    return nil;
  elseif (f == gm(conn, "csetheardIdling")) then
    if (not p[2]) then
      if (strfind(conn.flags, "I", 1, 1)) then
        sendGM(conn, S_DONE, "csetheardIdlingOn");
      else
        sendGM(conn, S_DONE, "csetheardIdlingOff");
      end;
      return nil;
    end;
    local s = strlower(p[2]);
    if (s == gm(conn, "Off")) then
      conn.flags = exchange(conn.flags, "I", "i");
      sendGM(conn, S_DONE, "csetheardIdlingOff");
    else
      conn.flags = exchange(conn.flags, "i", "I");
      sendGM(conn, S_DONE, "csetheardIdlingOn");
    end;
    return nil;
  end;
end;

function commandClosedown(connection, line)
    log(format("C  %s[%s] closed the talker down.", colloquy.connections[connection].username, colloquy.connections[connection].realUser));
    local i, v;

    for i, v in colloquy.connections do
      if (i ~= "n") then
        send("Talker closed down by " .. colloquy.connections[connection].username .. ".", v, S_DISCONNECT);
        disconnectUser(i, " - Closedown");
      end;
    end;
    colloquy.quit = 1;
end;

function commandForce(connection, line, thing)
   local username, command;
   local conn = colloquy.connections[connection];
   
      if (strfind(thing, " ") ~= nil) then
        username = strsub(thing, 1, strfind(thing, " ") - 1);
        command = strsub(thing, strfind(thing, " ") + 1, strlen(thing));
      else
        username, command = "", "";
      end;

      if (username == "" or command == "") then
        sendGM(conn, S_ERROR, "cforceUsage");
        return nil;
      end;

      local u, i, v = strlower(username);

      for i, v in colloquy.connections do
        if (i ~= "n") then
          if (u == strlower(v.username)) then
            if (allowZ(i)) then
              send(colloquy.connections[i].username .. " has immunity.", conn, S_ERROR);
              return nil;
            end;

            log(format("F  %s[%s] forces %s[%s]: %s", conn.username, conn.realUser, v.username, v.realUser, command));
            parseInput(i, command)
            sendGM(conn, S_DONE, "cforceDone", v.username, command);
            return nil;
          end;
        end;
      end;
      sendGM(conn, S_ERROR, "UnknownUser", username);
end;

function commandHelpUser(connection, line, thing)
  local username, command;
  local conn = colloquy.connections[connection];

  if (strfind(thing, " ") ~= nil) then
    username = strsub(thing, 1, strfind(thing, " ") - 1);
    command = strsub(thing, strfind(thing, " ") + 1, strlen(thing));
  else
    username, command = "", "";
  end

  if (username == "" or command == "") then
    sendGM(conn, S_ERROR, "chelpuserUsage");
    return nil;
  end

  local u, i, v = strlower(username);

  for i, v in colloquy.connections do
    if (i ~= "n") then
      if (u == strlower(v.username)) then
        log(format("H  %s[%s] helps %s[%s]: %s", conn.username, conn.realUser, v.username, v.realUser, command));
        parseInput(i, ".help " .. command)
        sendGM(conn, S_DONE, "chelpuserDone", v.username, command);
        return nil;
      end;
    end;
  end;
  sendGM(conn, S_ERROR, "UnknownUser", username);
end;

function commandSaveData(connection, line, file)
   if (file == "") then file = colloquy.users end;
   saveUsers(file);
   sendGM(colloquy.connections[connection], S_DONE, "csavedataDone", file);
   log(format("SD %s[%s] saves data to %s", colloquy.connections[connection].username, colloquy.connections[connection].realUser, file));
end;

function commandPassword(connection, line, params)
   local old, new, cold;
   local conn = colloquy.connections[connection];

   if (conn.status < 2) then
      sendGM(conn, S_ERROR, "cpasswordGuest");
      return nil;
   end;

   if (strfind(params, " ") ~= nil) then
      old = strsub(params, 1, strfind(params, " ") - 1);
      new = strsub(params, strfind(params, " ") + 1, strlen(params));
   else
      old, new = "", "";
   end;

   if (old == "" or new == "") then
      sendGM(conn, S_ERROR, "cpasswordUsage");
      return nil;
   end;

   local pr, message = changePassword(conn.realUser, old, new);
   if (not pr) then
     sendGM(conn, S_ERROR, "cpasswordFail");
   else
     sendGM(conn, S_DONE, "cpasswordDone");
   end;
end;

function commandLua(connection, line, command)
      log(format("L  %s[%s] executes %s", colloquy.connections[connection].username, colloquy.connections[connection].realUser, command));
      if (strsub(command, 1, 1) == "=") then
        -- they just want to view a variable
        local s = strsub(command, 2, -1);
        send(format("%s = %s", s, tostring(dostring("return " .. s))), colloquy.connections[connection], S_DONE);
      else
        dostring(command);
        sendGM(colloquy.connections[connection], S_DONE, "cluaDone", command);
      end;
end;

function commandNewUser(connection, line, params)
      local conn = colloquy.connections[connection];
      local p = split(params);
      if (p[1] == nil or p[2] == nil) then
        sendGM(conn, S_ERROR, "cnewuserUsage");
        return nil;
      end;

      if (users[strlower(p[1])]) then
        sendGM(conn, S_ERROR, "cnewuserAlready", p[1]);
        return nil;
      end;

      log(format("U  %s[%s] creates '%s' with password '%s'", colloquy.connections[connection].username, colloquy.connections[connection].realUser, p[1], p[2]));
      users[strlower(p[1])] = {
        password2 = crypt(strlower(p[1]) .. p[2]),
        created = date() .. " by " .. colloquy.connections[connection].username,
        flags = "",
        restrict = "",
      };
      saveUsers(colloquy.users);
      sendGM(conn, S_DONE, "cnewuserDone", strlower(p[1]));
end;

function commandDeleteUser(connection, line, params)
      local p = split(params);
      local conn = colloquy.connections[connection];
      if (p[1] == nil) then
        sendGM(conn, S_ERROR, "cdeleteuserUsage");
        return nil;
      end;
      if (users[strlower(p[1])] == nil) then
        sendGM(conn, S_ERROR, "UnknownUser", p[1]);
        return nil;
      end;

      if (users[strlower(p[1])].privs ~= nil and strfind(users[strlower(p[1])].privs, "Z", 1, 1)) then
        sendGM(conn, S_ERROR, "Immune", strlower(p[1]));
        return nil;
      end;
      log(format("U  %s[%s] deletes %s", colloquy.connections[connection].username, colloquy.connections[connection].realUser, params));
      users[strlower(p[1])] = nil;
      saveUsers(colloquy.users);
      sendGM(conn, S_DONE, "cdeleteuserDone", strlower(p[1]));
end;

function commandTell(connection, line, text)
   -- new .tell command!  Can take lists of people to send a message to... for example:
   -- >bob,gavin,rick Ogg smells.
   -- <@public,-ogg thinks Ogg smells.
   -- etc...

   local p = split(text);               -- split words into a table, so we have who to whisper to as p[1]...
   local dest = {};                     -- table of connection tables that this will be sent to (for normal tells, just one, multitells, multiple.)
   local whoAdd = {};                   -- table of people to add (later intergrated into dest{})
   local whoSub = {};                   -- table of people to subtract (later intergrated into dest{})
   local who = {};                      -- table used for extracting the entries out of p[1]
   local conn = colloquy.connections[connection];
   local sep = ">"                      -- seperator to use... > for tells, ] for a tell to a group, | for a multitell.
   local tmp;
   local i, v;
   local destString = " <";              -- string that contains a pretty list of who a multitell was sent to.
   local namedGroups = "";              -- string containing a space seperated list of named lists.
    
   if (p[2] == nil and (p[1] == nil)) then
     sendGM(conn, S_ERROR, "ctellUsage");
     return nil;
   end;

   -- is this a reply, rather than a new tell?
   if (strsub(p[1], 1, 1) == "!" or strsub(p[1], 1, 1) == "|") then
     if (not conn.replyTo) then
       sendGM(conn, S_ERROR, "ctellNone");
       return nil;
     end;
     p[1] = strsub(p[1], 2, -1);
     tinsert(p, 1, conn.replyTo);
     if (strfind(conn.flags, "D", 1, 1)) then  -- is this user a dunce?
       text = conn.replyTo .. " " .. gsub(strsub(text, 2, -1), "^(%s+)", "");
     else
       text = conn.replyTo .. " " .. strsub(text, 2, -1);
     end;
     p = split(text);
   else
     -- it's not - let's check to see if the last character of p[1] is , and if it is, strip the following
     -- space, to allow ">bob, @public, -gavin Hello!"
     while (strsub(p[1], -1, -1) == ",") do
       local commapos = strfind(text, ", ", 1, 1);
       if( not commapos ) then
          sendGM(conn, S_ERROR, "cremoteUsage");
          return nil;
       end
       local notspace = strfind(text, "%S", commapos + 1);
       local lComma = strsub(text, 1, commapos);
       local rComma = strsub(text, notspace, -1);
       text = lComma .. rComma;
       p = split(text);
     end;
   end;

   if (p[2] == nil or p[1] == nil) then
     sendGM(conn, S_ERROR, "ctellUsage");
     return nil;
   end;

   -- extract the entries out of p[1] into who{}
   tmp = p[1];
   if (strfind(tmp, ",", 1, 1)) then
     -- there are multiple things here, extract them.
     -- here's a quick bodge. :)
     if (strsub(tmp, -1, -1) ~= ",") then tmp = tmp .. "," end;
     sep = "|";
     repeat
       local n = strsub(tmp, 1, strfind(tmp, ",", 1, 1) - 1);
       tinsert(who, n);
       tmp = strsub(tmp, strfind(tmp, ",", 1, 1) + 1, -1);
     until (not strfind(tmp, ",", 1, 1));
   else
     -- there aren't multiple things in this list - just do a vanilla tell.
     who[1] = tmp;
     if (strsub(tmp, 1, 1) == "@") then
       -- they only want to whisper to one group - do a group tell...
       local groupExists = nil;
       local group = strlower(strsub(tmp, 2, -1));
       local err;
       if (strlen(group) == 0) then
         -- There's no group name - this means they want to say something to their current group.
         commandSay(connection, "", strsub(text, strfind(text, " ", 1, 1) + 1, -1), "escape!");
         return nil;
       end; 
       
       group, err = groupByName(group);

       if (not group) then  -- changed from groupExists, and err
         send(err, conn, S_ERROR);
         return nil;
       end;
           
       sep = "]";
       local message = strsub(conn.username .. strrep(" ", 11), 1, 12) .. sep .. strsub(text, strfind(text, " ", 1, 1) + 1, -1);
       
       sendToGroup(message, group, S_TELL);
       sendGM(conn, S_DONETELL, "ctellToGroup", group, strsub(text, strfind(text, " ", 1, 1) + 1, -1));

       -- now update everybody in the target group's replyTo.
       for i, v in colloquy.connections do
         if (i ~= "n" and strlower(v.group) == group) then v.replyTo = conn.username end;
       end;

       return nil;
     end;
   end;
   
   -- now work through the list, inserting entries into dest{}, raising an error if something can't be done.
   for i, v in who do
     if (i ~= "n") then
       local t = whoAdd;
       if (strsub(v, 1, 1) == "-") then
         t = whoSub;
         v = strsub(v, 2, -1);
       end;
       
       if (strsub(v, 1, 1) == "@") then
         -- right, they've supplied a group name - add everybody in that group to the current table...
         local group, err = groupByName(strlower(strsub(v, 2, -1)));

         if (not group) then
           if (err == "No such group.") then err = gm(conn, "UnknownGroup", strsub(v, 2, -1)) end;
           send(err, conn, S_ERROR);
           return nil;
         end;

         -- check to see if they've already mentioned this group...
         if (strfind(namedGroups, group .. " ", 1, 1)) then
           sendGM(conn, S_ERROR, "ctellMultipleGroup", group);
           return nil;
         end;

         namedGroups = namedGroups .. group .. " ";

         if (t == whoSub) then
           destString = destString .. "-";
         end;

         destString = destString .. "@" .. group .. ", ";

         group = strlower(group);
          
         local i, v;
         for i, v in colloquy.connections do
           if (i ~= "n" and strlower(v.group) == group) then
             tinsert(t, v);
           end;
         end;
       
       else

         local expansion = userByName(v);
         if (expansion == nil) then
           sendGM(conn, S_ERROR, "ctellNoUser", v);
           return nil;
         end;

         if (type(expansion) == "string") then
           send(expansion, conn, S_ERROR);
           return nil;

         end;
         if (in(expansion, t)) then
           sendGM(conn, S_ERROR, "ctellMultipleUser", v);
           return nil;
         end;
         tinsert(t, expansion);
         if (t == whoSub) then
           destString = destString .. "-";
         end;
         destString = destString .. expansion.username .. ", ";
       end;
     end;
   end;

   dest = whoAdd;

   destString = strsub(destString, 1, -3) .. ">";

   -- now go though whoSub, and remove them from dest{}
   for i, v in whoSub do
     if (i ~= "n") then
       local where = in(v, dest);
       if (where) then
         tremove(dest, where);
       else
         sendGM(conn, S_ERROR, "ctellNoRemove", v.username);
         return nil;
       end;
     end;
   end;

   local message = strsub(conn.username .. strrep(" ", 11), 1, 12) .. sep .. strsub(text, strfind(text, " ", 1, 1) + 1, -1);
   if (sep == "|") then
     sendTo(message .. destString, dest, S_MULTITELL);
     if (not in(conn, dest)) then
       destString = gsub(destString, "^%s+", "");
       sendGM(conn, S_DONETELL, "ctellDone", destString, strsub(text, strfind(text, " ", 1, 1) + 1, -1));
     end;
     local replyTo = gsub(destString, "[ %>%<]", "");
     if (not in(conn, dest)) then
       replyTo = replyTo .. "," .. conn.username;
     end;
     for i, v in dest do
       if (i ~= "n") then v.replyTo = replyTo; end;
     end;

   elseif (sep == ">") then
     sendTo(message, dest, S_TELL);
     sendGM(conn, S_DONETELL, "ctellDone", dest[1].username,strsub(text, strfind(text, " ", 1, 1) + 1, -1));
     dest[1].replyTo = conn.username;
   end;
end;

function commandRemote(connection, line, text)
   local p = split(text);               -- split words into a table, so we have who to whisper to as p[1]...
   local dest = {};                     -- table of connection tables that this will be sent to (for normal tells, just one, multitells, multiple.)
   local whoAdd = {};                   -- table of people to add (later intergrated into dest{})
   local whoSub = {};                   -- table of people to subtract (later intergrated into dest{})
   local who = {};                      -- table used for extracting the entries out of p[1]
   local conn = colloquy.connections[connection];
   local sep = ">"                      -- seperator to use... > for tells, ] for a tell to a group, | for a multitell.
   local tmp;
   local i, v;
   local destString = " <";              -- string that contains a pretty list of who a multitell was sent to.
   local namedGroups = "";              -- string containing a space seperated list of named lists.

   if (p[2] == nil and p[1] == nil) then
     sendGM(conn, S_ERROR, "cremoteUsage");
     return nil;
   end;

   -- is this a reply, rather than a new tell?
   if (strsub(p[1], 1, 1) == "!" or strsub(p[1], 1, 1) == "|") then
     if (not conn.replyTo) then
       sendGM(conn, S_ERROR, "cremoteNone");
       return nil;
     end;
     p[1] = strsub(p[1], 2, -1);
     tinsert(p, 1, conn.replyTo);
     if (strfind(conn.flags, "D", 1, 1)) then  -- is this user a dunce?
       text = conn.replyTo .. " " .. gsub(strsub(text, 2, -1), "^(%s+)", "");
     else
       text = conn.replyTo .. " " .. strsub(text, 2, -1);
     end;
   else
     -- it's not - let's check to see if the last character of p[1] is , and if it is, strip the following
     -- space, to allow ">bob, @public, -gavin Hello!"
     while (strsub(p[1], -1, -1) == ",") do
       local commapos = strfind(text, ", ", 1, 1);
       if( not commapos ) then
         sendGM(conn, S_ERROR, "cremoteUsage");
         return nil;
       end
       local notspace = strfind(text, "%S", commapos + 1);
       local lComma = strsub(text, 1, commapos);
       local rComma = strsub(text, notspace, -1);
       text = lComma .. rComma;
       p = split(text);
     end;
   end;
   
   if (p[2] == nil) then
     sendGM(conn, S_ERROR, "cremoteUsage");
     return nil;
   end;

   -- extract the entries out of p[1] into who{}
   tmp = p[1];
   if (strfind(tmp, ",", 1, 1)) then
     -- there are multiple things here, extract them.
     -- here's a quick bodge. :)
     if (strsub(tmp, -1, -1) ~= ",") then tmp = tmp .. "," end;
     sep = "|";
     repeat
       local n = strsub(tmp, 1, strfind(tmp, ",", 1, 1) - 1);
       tinsert(who, n);
       tmp = strsub(tmp, strfind(tmp, ",", 1, 1) + 1, -1);
     until (not strfind(tmp, ",", 1, 1));
   else
     who[1] = tmp;
     if (strsub(tmp, 1, 1) == "@") then
       -- they only want to whisper to one group - do a group tell...
       local groupExists = nil;
       local group = strlower(strsub(tmp, 2, -1));
       local err;

       if (strlen(group) == 0) then
         commandEmote(connection, "", strsub(text, strfind(text, " ", 1, 1) + 1, -1), "escape!");
         return nil;
       end; 
       
       group, err = groupByName(group);

       if (not group) then
         send(err, conn, S_ERROR);
         return nil;
       end;
           
       sep = "]";
       local message = sep .. " " .. conn.username;
       local t = strsub(text, strfind(text, " ", 1, 1) + 1, -1);
       if (strfind(punctuation, strsub(t, 1, 1), 1, 1)) then
         message = message .. t;
       else
         message = message .. " " .. t;
         t = " " .. t;
       end;

       sendToGroup(message, group, S_REMOTE);
       sendGM(conn, S_DONETELL, "cremoteToGroup", group, conn.username .. t);
       for i, v in colloquy.connections do
         if (i ~= "n" and strlower(v.group) == group) then v.replyTo = conn.username end;
       end;

       return nil;
     end;
   end;
   
   -- now work through the list, inserting entries into dest{}, raising an error if something can't be done.
   for i, v in who do
     if (i ~= "n") then
       local t = whoAdd;
       if (strsub(v, 1, 1) == "-") then
         t = whoSub;
         v = strsub(v, 2, -1);
       end;
       
       if (strsub(v, 1, 1) == "@") then
         -- right, they've supplied a group name - add everybody in that group to the current table...
         local group, err = groupByName(strlower(strsub(v, 2, -1)));

         if (not group) then
           if (err == "No such group.") then err = gm(conn, "UnknownGroup", strsub(v, 2, -1)) end;
           send(err, conn, S_ERROR);
           return nil;
         end;  

         -- check to see if they've already mentioned this group...
         if (strfind(namedGroups, group .. " ", 1, 1)) then
           sendGM(conn, S_ERROR, "cremoteMultipleGroup", group);
           return nil;
         end;

         namedGroups = namedGroups .. group .. " ";

         if (t == whoSub) then
           destString = destString .. "-";
         end;

         destString = destString .. "@" .. group .. ", ";

         group = strlower(group);

         local i, v;
         for i, v in colloquy.connections do
           if (i ~= "n" and strlower(v.group) == group) then
             tinsert(t, v);
           end;
         end;
       
       else

         local expansion = userByName(v);
         if (expansion == nil) then
           sendGM(conn, S_ERROR, "cremoteNoUser", v);
           return nil;
         end;

         if (type(expansion) == "string") then
           send(expansion, conn, S_ERROR);
           return nil;

         end;
         if (in(expansion, t)) then
           sendGM(conn, S_ERROR, "cremoteNoUser", v);
           return nil;
         end;
         tinsert(t, expansion);
         if (t == whoSub) then
           destString = destString .. "-";
         end;
         destString = destString .. expansion.username .. ", ";
       end;
     end;
   end;

   dest = whoAdd;

   destString = strsub(destString, 1, -3) .. ">";

   -- now go though whoSub, and remove them from dest{}
   for i, v in whoSub do
     if (i ~= "n") then
       local where = in(v, dest);
       if (where) then
         tremove(dest, where);
       else
         sendGM(conn, S_ERROR, "cremoteNoRemove", v.username);
         return nil;
       end;
     end;
   end;

   local message = sep .. " " .. conn.username;
   local t = strsub(text, strfind(text, " ", 1, 1) + 1, -1);
   if (strfind(punctuation, strsub(t, 1, 1), 1, 1)) then
     message = message .. t;
   else
     message = message .. " " .. t;
     t = " " .. t;
   end;
   
   if (sep == "|") then
     sendTo(message .. destString, dest, S_MULTITELL);
     if (not in(conn, dest)) then
       sendGM(conn, S_DONETELL, "cremoteDone", gsub(destString, "^%s+", ""), conn.username .. t);
     end;
     local replyTo = gsub(destString, "[ %>%<]", "");
     if (not in(conn, dest)) then
       replyTo = replyTo .. "," .. conn.username;
     end;
     for i, v in dest do
       if (i ~= "n") then v.replyTo = replyTo; end;
     end;

   elseif (sep == ">") then
     sendTo(message, dest, S_REMOTE);
     sendGM(conn, S_DONETELL, "cremoteDone", dest[1].username, conn.username .. t);
     dest[1].replyTo = conn.username;
   end;
end;

function commandUserInfo(connection, line, params)
   local conn = colloquy.connections[connection];
      local p = split(params);
      if (p[1] == nil or p[2] == nil or p[3] == nil) then
        sendGM(conn, S_ERROR, "cuserinfoUsage");
        return nil;
      end;

      local u = strlower(p[1]);
      local i, v, s, c;
      s = strsub(params, strfind(params, p[2] .. " ") + strlen(p[2]) + 1, strlen(params));
      c = strlower(p[2]);
      if (p[3] == "-") then
        s = nil;
      end;

      local f = function(n) return gm(%conn, "cuserinfoCat" .. n) end;
      local unset = gm(conn, "cuserinfoUnset");

      for i, v in users do
        if (i == u) then
          if (c == f "username") then
            if (users[strlower(p[3])]) then
              sendGM(conn, S_ERROR, "cuserinfoAlready", strlower(p[3]));
              return nil;
            end;
            users[strlower(p[3])] = v;
            users[u] = nil;
            -- now work though all the lists, changing their name over
            for i, v in lists do
              if (v.owner == u) then
                v.owner = strlower(p[3]);
              end;
              for j, k in v.members do
                if (k == u) then
                  v.members[j] = strlower(p[3]);
                  break;
                end;
              end;
            end;
            sendGM(conn, S_DONE, "cuserinfoDone", f "username", s or unset);
            saveUsers(colloquy.users);
          elseif (c == f "password") then
            v.password = nil
            v.password2 = crypt(strlower(p[1])..p[3]);
            saveUsers(colloquy.users);
            sendGM(conn, S_DONE, "cuserinfoDone", f "password", s or unset);
          elseif (c == f "name") then
            v.name = s;
            saveUsers(colloquy.users);
            sendGM(conn, S_DONE, "cuserinfoDone", f "name", s or unset);
          elseif (c == f "birthday") then
            v.birthday = s;
            saveUsers(colloquy.users);
            sendGM(conn, S_DONE, "cuserinfoDone", f "birthday", s or unset);
          elseif (c == f "location") then
            v.location = s;
            saveUsers(colloquy.users);
            sendGM(conn, S_DONE, "cuserinfoDone", f "location", s or unset);
          elseif (c == f "occupation") then
            v.occupation = s;
            saveUsers(colloquy.users);
            sendGM(conn, S_DONE, "cuserinfoDone", f "occupation", s or unset);
          elseif (c == f "interests") then
            v.interests = s;
            saveUsers(colloquy.users);
            sendGM(conn, S_DONE, "cuserinfoDone", f "interests", s or unset);
          elseif (c == f "comments") then
            v.comments = s;
            saveUsers(colloquy.users);
            sendGM(conn, S_DONE, "cuserinfoDone", f "comments", s or unset);
          elseif (c == f "around") then
            v.around = s;
            saveUsers(colloquy.users);
            sendGM(conn, S_DONE, "cuserinfoDone", f "around", s or unset);
          elseif (c == f "homepage") then
            v.homepage = s;
            saveUsers(colloquy.users);
            sendGM(conn, S_DONE, "cuserinfoDone", f "homepage", s or unset);
          elseif (c == f "email") then
            v.email = s;
            saveUsers(colloquy.users);
            sendGM(conn, S_DONE, "cuserinfoDone", f "email", s or unset);
          elseif (c == f "sex") then
            v.sex = s;
            saveUsers(colloquy.users);
            sendGM(conn, S_DONE, "cuserinfoDone", f "sex", s or unset);
          elseif (c == f "aliases") then
            v.aliases = s;
            saveUsers(colloquy.users);
            sendGM(conn, S_DONE, "cuserinfoDone", f "aliases", s or unset);
          elseif (c == f "privs") then
            -- this is a complex one -- only allow somebody to set privs they've got.
            local j;
            v.privs = "";
            if (s ~= nil) then
              for j = 1, strlen(s) do
                local k = strsub(s, j, j);
                if (strfind(colloquy.connections[connection].privs, k) ~= nil) then
                  v.privs = v.privs .. k;
                end;
              end;
            else
              v.privs = nil;
            end;
            saveUsers(colloquy.users);
            sendGM(conn, S_DONE, "cuserinfoDone", f "privs", s or unset);
          elseif (c == f "auth") then
            v.authenticator = s;
            saveUsers(colloquy.users);
            sendGM(conn, S_DONE, "cuserinfoDone", f "auth", s or unset);
          elseif (c == f "quitmsg") then
            v.quitmsg = s;
            saveUsers(colloquy.users);
            sendGM(conn, S_DONE, "cuserinfoDone", f "quitmsg", s or unset);
          else
            sendGM(conn, S_ERROR, "cuserinfoCatunknown", c);
            return nil;
          end;
          log(format("U  %s[%s] changes %s for %s to '%s'", colloquy.connections[connection].username, colloquy.connections[connection].realUser, c, i, s or "(nil)"));
          return nil;
         end;
      end;

      sendGM(conn, S_ERROR, "UnknownUser", u);
end;

function commandExamine(connection, line, user)
   local conn = colloquy.connections[connection];
   if (user == nil or user == "") then
      sendGM(conn, S_ERROR, "cexamineUsage");
      return nil;
   end;

   local expansion = userByName(user);
   if (expansion == nil) then
      sendGM(conn, S_ERROR, "UnknownUser", user);
      return nil;
   elseif (type(expansion) == "string") then
      send(expansion, colloquy.connections[connection], S_ERROR);
      return nil;
   end;
   
   local i, v, u, tmp;
   local format = function(field, value)
     local t = gm(%conn, "cexamineF" .. field) .. ":" .. strrep(" ", 15);
     t = strsub(t, 1, 15);
     return t .. value .. "";
   end;
   
   v = expansion;
   
   if (not expansion.invis) then
      
      tmp = v.username;
      if (strlower(v.username) ~= strlower(v.realUser)) then
        tmp = tmp .. gm(conn, "cexamineOnAs", v.realUser);
      end;

      send(format("User", tmp), colloquy.connections[connection], S_EXAMINE);
      
      local j, k;
      for j, k in users do
        if (j ~= "n" and j == strlower(v.realUser)) then
          if (k.name) then
            send(format("Name", k.name), colloquy.connections[connection], S_EXAMINE);
          end;
        end;
      end;


      tmp = "";
      if (v.privs ~= nil and strfind(v.privs, "M")) then
        tmp = gm(conn, "cexamineMaster", v.privs);
      elseif (v.privs ~= nil and v.privs ~= "") then
        tmp = gm(conn, "cexaminePrived", v.privs);
      elseif (v.status == 2) then
        tmp = gm(conn, "cexamineNormal");
      else
        tmp = gm(conn, "cexamineGuest");
      end;
      send(format("Status", tmp), colloquy.connections[connection], S_EXAMINE);
      
      if (v.restrict ~= nil and v.restrict ~= "") then
        local e = v.restrict;
         e = gsub(e, "G", gm(conn, "cexamineGagged"));
         e = gsub(e, "C", gm(conn, "cexamineCensored"));
         e = gsub(e, "B", gm(conn, "cexamineBot"));
         send(format("Restrictions", e), colloquy.connections[connection], S_EXAMINE);
      end;
      send(format("Group", v.group), colloquy.connections[connection], S_EXAMINE);
      if (colloquy.connections[connection].privs and strfind(colloquy.connections[connection].privs, "M", 1, 1) and v.invitations) then 
        local t, j, k = "";
        for j, k in v.invitations do
          t = t .. j .. " ";
        end; 
        send(format("Invitations", t), colloquy.connections[connection], S_EXAMINE)
      end;
      if (v.pausedLists) then
        send(format("PausedLists", v.pausedLists), colloquy.connections[connection], S_EXAMINE);
      end;
      send(format("Site", v.site), colloquy.connections[connection], S_EXAMINE);
      if (v.via) then send(format("Via", v.via), colloquy.connections[connection], S_EXAMINE); end;
      send(format("OnSince", v.onSince), colloquy.connections[connection], S_EXAMINE);
      send(format("OnFor", timeToString(floor(secs - v.conTime))), colloquy.connections[connection], S_EXAMINE);
      send(format("TalkBytes", prettyBytes(v.talkBytes or 0)), colloquy.connections[connection], S_EXAMINE);
      send(format("IdleFor", timeToString(floor(secs - v.idle))), colloquy.connections[connection], S_EXAMINE);
      if (v.idleReason) then
        send(format("Idle", v.idleReason), colloquy.connections[connection], S_EXAMINE);
      end;
      send(format("TotalIdle", timeToString(floor(v.totalIdle))), colloquy.connections[connection], S_EXAMINE);
   end;
end;

function commandIdle(connection, line, params)
  local conn = colloquy.connections[connection];
  local tmp;
  local listeners = idleListeners()

  if (params ~= nil and params ~= "") then
    tmp =  " (" .. params .. ")";
    conn.idleReason = params;
  else
    tmp = "";
    conn.idleReason = "";
  end;

  if (conn.veryIdle) then
    sendGMList(listeners, S_IDLE, "cidleReidle", conn.username, tmp);
    conn.veryIdle = 0;
  else
    sendGMList(listeners, S_IDLE, "cidleIdle", conn.username, tmp);
  end

  if not strfind(conn.flags, "I", 1, 1) then 
    if conn.veryIdle then 
      sendGM(conn, S_IDLE, "cidleYouReidle", tmp);
      conn.veryIdle = 0
    else 
      sendGM(conn, S_IDLE, "cidleYouIdle", tmp);
    end
  end

end;

function commandName(connection, line, params)
   local p = split(params);
   local conn = colloquy.connections[connection];
      if (p[1] == nil or p[2] == nil) then
        sendGM(conn, S_ERROR, "cnameUsage");
        return nil;
      end;
      
      local i, v, u, nu;
      u = strlower(p[1]);
      nu = p[2];
      if (strlen(nu) > 10) then nu = strsub(nu, 1, 10) end;
      
      if (strlen(gsub(nu, "%w", "")) > 0 or (strlen(nu) > 10)) then
        sendGM(conn, S_ERROR, "cnameInvalid");
        return nil;
      end;

      for i, v in colloquy.connections do
        if (i ~= "n" and u == strlower(v.username)) then
          -- we've found the user to be renamed... check if it's already in use...
          local j, k;
            for j, k in colloquy.connections do
              if (j ~= "n" and (strlower(p[1]) ~= strlower(p[2])) and strlower(k.username) == strlower(nu)) then
                sendGM(conn, S_ERROR, "cnameAlready");
                return nil;
              end;
            end;
    
            local tmp = "";
    
            if (i == connection) then
              sendGMAll(S_NAME, "cnameMyNameDone", v.username, nu);
            else
              if (allowZ(i)) then
                sendGM(conn, S_ERROR, "Immune", v.username);
                return nil;
              end;
             sendGMAll(S_NAME, "cnameDone", conn.username, v.username, nu);
           end;
    
           log(format("N  %s[%s] changed name of %s[%s] to %s", colloquy.connections[connection].username, colloquy.connections[connection].realUser, v.username, v.realUser, nu));
           v.username = nu;

           return nil;
         end;
       end;
      sendGM(conn, S_ERROR, "UnknownUser", p[1]);
end;

function commandNameself(connection, line, params)
   local conn = colloquy.connections[connection];
   if (conn.status < 2) then
     sendGM(conn, S_ERROR, "cnameselfGuest");
     return nil;
   end;
   local p = split(params);

   if (p[1] ~= nil) then
     if (strlower(p[1]) == strlower(conn.realUser)) then
       -- they're the same username at least...
       if (p[1] == conn.username) then
         sendGM(conn, S_ERROR, "cnameselfAlreadyNamed", conn.username);
         return nil;
       else
         for i, v in colloquy.connections do
           if (i ~= "n" and strlower(v.username) == strlower(conn.realUser) and v.realUser ~= conn.realUser) then
             sendGM(conn, S_ERROR, "cnameselfAlready");
             return nil;
           end;
         end;
         conn.username = p[1];
         conn.realUser = p[1];
         sendGM(conn, S_DONE, "cnameselfChanged", conn.username);
         return nil;
       end;
     else
       sendGM(conn, S_ERROR, "cnameselfNotSame", p[1], conn.realUser);
       return nil;
     end;
   else
     if (conn.username == conn.realUser) then
       sendGM(conn, S_ERROR, "cnameselfAlreadyNamed", conn.username);
       return nil;
     end;
     for i, v in colloquy.connections do
       if (i ~= "n" and strlower(v.username) == strlower(conn.realUser) and v.realUser ~= conn.realUser) then
         sendGM(conn, S_ERROR, "cnameselfAlready");
         return nil;
       end;
     end;
     sendGMAll(S_NAME, "cnameselfAllChange", conn.username, conn.realUser);
     log(format("N  %s changed name back to %s", conn.username, conn.realUser));
     conn.username = conn.realUser;
   end;
end;

function commandWarn(connection, line, params)
   local p = split(params);
   local conn = colloquy.connections[connection];
      if (p[1] == nil or p[2] == nil) then
        sendGM(conn, S_ERROR, "cwarnUsage");
        return nil;
      end;
      
      local i, v, u, m;
      u = strlower(p[1]);

      if (p[2] ~= nil) then
        m = strsub(params, strfind(params, " ") + 1, strlen(params));
      end;


      for i,v in colloquy.connections do
        if (i ~="n" and u == strlower(v.username)) then
          log(format("W  %s[%s] warns %s[%s]: %s", colloquy.connections[connection].username, colloquy.connections[connection].realUser, v.username, v.realUser, m));
          sendGMAll(S_WARN, "cwarnDone", conn.username, v.username, m);
          return nil;
        end;
      end;

      sendGM(conn, S_ERROR, "UnknownUser", p[1]);
end;

function commandKick(connection, line, params)
   local p = split(params);
   local conn = colloquy.connections[connection];
      if (p[1] == nil) then
        sendGM(conn, S_ERROR, "chickUsage");
        return nil;
      end;
      
      local i, v, u, m;
      u = strlower(p[1]);
      
      if (p[2] ~= nil) then
        m = strsub(params, strfind(params, " ") + 1, strlen(params));
      end;

      for i,v in colloquy.connections do
        if (i ~="n" and u == strlower(v.username)) then
          if (allowZ(i)) then
            sendGM(conn, S_ERROR, "Immune", v.username);
            return nil;
          end;

          local tmp = "K " .. colloquy.connections[connection].username .. " " .. p[1];
          if (p[2] ~= nil) then
            tmp = tmp .. " " .. m;
          end;
          log(format("K  %s[%s] kicks %s[%s]: %s", colloquy.connections[connection].username, colloquy.connections[connection].realUser, v.username, v.realUser, p[2] or "(no reason)"));

          tmp = "- Kicked by " .. colloquy.connections[connection].username;
          if (p[2] ~= nil) then
            tmp = tmp .. " (" .. m .. ")";
          end;

          if (p[2] ~= nil) then
            sendGM(colloquy.connections[i], S_ERROR, "ckickMessage", "(" .. m .. ")");
          else
            sendGM(colloquy.connections[i], S_ERROR, "ckickMessage", gm(colloquy.connections[i], "ckickDefault"));
          end;

          disconnectUser(i, tmp);
          return nil;
        end;
      end;
      sendGM(conn, S_ERROR, "UnknownUser", p[1]);
end;

function commandInvis(connection, line)
   colloquy.connections[connection].invis = 1;
   sendGM(colloquy.connections[connection], S_DONE, "cinvisDone");
   log(format("I  %s[%s]", colloquy.connections[connection].username, colloquy.connections[connection].realUser));
end;

function commandVis(connection, line)
   colloquy.connections[connection].invis = nil;
   sendGM(colloquy.connections[connection], S_DONE, "cvisDone");
   log(format("i  %s[%s]", colloquy.connections[connection].username, colloquy.connections[connection].realUser));
end;

function commandRequest(connection, line, params)
   local conn = colloquy.connections[connection];
   if (params == "" or params == nil) then
      sendGM(conn, S_ERROR, "crequestUsage");
      return nil;
   end;

   if colloquy.noFork then
     sendGM(conn, S_ERROR, "crequestNoFork");
     return nil;
   end

   params = gsub(params, "^[^A-Za-z0-9]", "");

   local f = writeto('|mail -a "Reply-To: ' .. y(users[strlower(colloquy.connections[connection].realUser)].email, users[strlower(colloquy.connections[connection].realUser)].email, colloquy.email) .. '" -s "[colloquy] Request from ' .. colloquy.connections[connection].realUser .. '" ' .. colloquy.email);
   write(f, params);
   closefile(f);
   sendGM(conn, S_DONE, "crequestDone");
end;

function commandTime(connection, line)
   sendGM(colloquy.connections[connection], S_TIME, "ctimeDone", date("%a %b %e %H:%M:%S %Y"));
end;

function commandWho(connection, line, params)
   local i, v, l, count;
   local p = split(params);
   local conn = colloquy.connections[connection];
   
   if (p[1]) then
     sendGM(conn, S_WHOHDR, "cwhoGroup", p[1]);
   else
     sendGM(conn, S_WHOHDR, "cwhoAll", colloquy.talkerName);
   end;
   sendGM(conn, S_WHOHDR, "cwhoColumns");

   local sorted = {};
   local lg;
   for i, v in colloquy.connections do
     if (type(v) == "table") then tinsert(sorted, v) end;
   end;

   sort(sorted, function(a, b)
                  if (not b) then return 1 end;

                  local la, lb = strlower(a.group), strlower(b.group);

                  if (la == lb) then
                    return (strlower(a.username) < strlower(b.username));
                  elseif (la == "public") then
                    return 1;
                  elseif (lb == "public") then
                    return nil;
                  elseif (la == "bots-r-us") then
                    return nil;
                  elseif (lb == "bots-r-us") then
                    return 1;
                  else
                    return (strlower(a.group) < strlower(b.group));
                  end;
                end);

   count = 0;
   for i=1,getn(sorted) do
      v=sorted[i];
      
      if (i ~= "n") then
        if (v.status == 0) then
          l = "--- Still connecting ---              "
        else
           if (not (v.invis or (v.restrict and strfind(v.restrict, "B", 1, 1))) or (conn.privs and strfind(conn.privs, "M"))) and (p[1] == nil or (strlower(v.group) == strlower(p[1]))) then
              local flags = "";
            
              if (v.privs and strfind(v.privs, "M", 1, 1)) then flags = "M"
              elseif (v.restrict and strfind(v.restrict, "B", 1, 1)) then flags = "B"
              elseif (v.privs ~= "" and v.privs ~= nil) then flags = "P"
              elseif (v.status > 1) then flags = "U" end;

              if (v.invis) then flags = flags .. "I" end;
              if (v.restrict ~= "" and v.restrict ~= nil) then flags = flags .. gsub(v.restrict, "B", "") end;

              local idle;

              if (v.veryIdle) then
                idle = gm(conn, "cwhoIdle");
              else
                if (v.idle) then
                  idle = timeToWhoString(secs - v.idle);
                else
                  idle = "00:00";
                end;
              end;

              local locked = colloquy.lockedGroups[strlower(v.group)];
              if (locked) then locked = " (L)" else locked = "" end;
              send(format("%-10.10s %-1.1s %-19.19s %-6.6s %-5.5s %-27.27s", v.username, y(strlower(v.username) == strlower(v.realUser), "", "*"), v.group .. locked, flags, idle, v.site), conn, S_WHO);
              count = count + 1;
           end;
        end;
      end;
   end;
   sendGM(conn, S_WHOHDR, "cwhoTotal", count);
end;

function commandLWho(connection, line, params)
   local i, v, l, count;
   local p = split(params);
   local conn = colloquy.connections[connection];
   local lname;

   if (p[1]) then
     lname, l = listByName(conn, p[1]);
     if (not lname) then
       send(l, conn, S_ERROR);
       return nil;
     end;
       
     sendGM(conn, S_WHOHDR, "clwhoList");
   else
     sendGM(conn, S_ERROR, "clwhoUsage");
     return nil;
   end;
   sendGM(conn, S_WHOHDR, "clwhoColumns");

   local sorted = {};
   local lg;
   for i, v in colloquy.connections do
     if (type(v) == "table") then tinsert(sorted, v) end;
   end;

   sort(sorted, function(a, b)
                  if (not b) then return 1 end;
                  
                  local la, lb = strlower(a.group), strlower(b.group);

                  if (la == lb) then
                    return (strlower(a.username) < strlower(b.username));
                  elseif (la == "public") then
                    return 1;
                  elseif (lb == "public") then
                    return nil;
                  elseif (la == "bots-r-us") then
                    return nil;
                  elseif (lb == "bots-r-us") then
                    return 1;
                  else
                    return (strlower(a.group) < strlower(b.group));
                  end;
                end);

   count = 0;
   for i=1,getn(sorted) do
      v=sorted[i];
      
      if (i ~= "n") then
        if (v.status == 0) then
          l = "--- Still connecting ---              "
        else
           if (not (v.invis or (v.restrict and strfind(v.restrict, "B", 1, 1))) or (conn.privs and strfind(conn.privs, "M"))) and (listIsMember(v.realUser, lname, 1)) then
              local flags = "";
            
              if (v.privs and strfind(v.privs, "M", 1, 1)) then flags = "M"
              elseif (v.restrict and strfind(v.restrict, "B", 1, 1)) then flags = "B"
              elseif (v.privs ~= "" and v.privs ~= nil) then flags = "P"
              elseif (v.status > 1) then flags = "U" end;

              if (v.invis) then flags = flags .. "I" end;
              if (v.restrict ~= "" and v.restrict ~= nil) then flags = flags .. gsub(v.restrict, "B", "") end;

              local idle;

              if (v.veryIdle) then
                idle = "IDLE";
              else
                if (v.idle) then
                  idle = timeToWhoString(secs - v.idle);
                else
                  idle = "00:00";
                end;
              end;

              local locked = colloquy.lockedGroups[strlower(v.group)];
              if (locked) then locked = " (L)" else locked = "" end;
              send(format("%-10.10s %-1.1s %-19.19s %-6.6s %-5.5s %-27.27s", v.username, y(strlower(v.username) == strlower(v.realUser), "", "*"), v.group .. locked, flags, idle, v.site), conn, S_WHO);
              count = count + 1;
           end;
        end;
      end;
   end;
   sendGM(conn, S_WHOHDR, "clwhoTotal", count);
end;

function commandGag(connection, line, params)
   local p = split(params);
   local conn = colloquy.connections[connection];

   if (p[1] == nil) then
      sendGM(conn, S_ERROR, "cgagUsage");
      return nil;
   end;

   local u = strlower(p[1]);
   local i, v;
   
   for i, v in colloquy.connections do
      if (i ~= "n") then
        if (strlower(v.username) == u) then
          if (allowZ(i)) then
            sendGM(conn, S_ERROR, "Immune", v.username);
            return nil;
          end;

          if (strfind(v.restrict, "G", 1, 1)) then
            sendGM(conn, S_ERROR, "cgagAlready", v.username);
            return nil;
          end;
          v.restrict = v.restrict .. "G";
          sendGMAll(S_GAG, "cgagGag", v.username, conn.username);
          log(format("G  %s[%s] gagged %s[%s]", colloquy.connections[connection].username, colloquy.connections[connection].realUser, v.username, v.realUser));
          return nil;
         end;
      end;
   end;

   sendGM(conn, S_ERROR, "UnknownUser", p[1]);

end;

function commandCensor(connection, line, params)
   local p = split(params);
   local conn = colloquy.connections[connection];

   if (p[1] == nil) then
      sendGM(conn, S_ERROR, "ccensorUsage");
      return nil;
   end;

   local u = strlower(p[1]);
   local i, v;
   
   for i, v in colloquy.connections do
      if (i ~= "n") then
        if (strlower(v.username) == u) then
          if (allowZ(i)) then
            sendGM(conn, S_ERROR, "Immune", v.username);
            return nil;
          end;

          if (strfind(v.restrict, "C", 1, 1)) then
            sendGM(conn, S_ERROR, "ccensorAlready", v.username);
            return nil;
          end;
          v.restrict = v.restrict .. "C";
          sendGMAll(S_GAG, "ccensorCensor", v.username, conn.username);
          log(format("MC %s[%s] censors %s[%s]", colloquy.connections[connection].username, colloquy.connections[connection].realUser, v.username, v.realUser));
         return nil;
        end;
      end;
   end;

   sendGM(conn, S_ERROR, "UnknownUser", p[1]);
end;

function commandUngag(connection, line, params)
   local p = split(params);
   local conn = colloquy.connections[connection];

   if (p[1] == nil) then
      sendGM(conn, S_ERROR, "cungagUsage");
      return nil;
   end;

   local u = userByName(p[1]);

   if (not u) then
     sendGM(conn, S_ERROR, "UnknownUser", p[1]);
     return nil;
   elseif (type(u) == "string") then
     send(u, conn, S_ERROR);
     return nil;
   elseif (u.realUser == conn.realUser) then
     sendGM(conn, S_ERROR, "cungagSelf");
     return nil;
   end;

   if (not strfind(u.restrict, "G", 1, 1)) then
     sendGM(conn, S_ERROR, "cungagAlready", u.username);
     return nil;
   end;

   u.restrict = gsub(u.restrict, "G", "");
   sendGMAll(S_UNGAG, "cungagUngag", u.username, conn.username);
   log(format("g  %s[%s] ungags %s[%s]", conn.username, conn.realUser, u.username, u.realUser));
end;

function commandUncensor(connection, line, params)
   local p = split(params);
   local conn = colloquy.connections[connection];
   
   if (p[1] == nil) then
      sendGM(conn, S_ERROR, "cuncensorUsage");
      return nil;
   end;

   local u = strlower(p[1]);

   if (u == strlower(colloquy.connections[connection].username)) then
      sendGM(conn, S_ERROR, "cuncensorSelf");
      return nil;
   end;

   local i, v;
   
   for i, v in colloquy.connections do
      if (i ~= "n") then
        if (strlower(v.username) == u) then
          if (strfind(v.restrict, "C", 1, 1) == nil) then
            sendGM(conn, S_ERROR, "cuncensorAlready", v.username);
            return nil;
          end;
          v.restrict = gsub(v.restrict, "C", "")
          sendGMAll(S_UNGAG, "cuncensorUncensor", v.username, conn.username);
          log(format("mc %s[%s] uncensors %s[%s]", colloquy.connections[connection].username, colloquy.connections[connection].realUser, v.username, v.realUser));
          return nil;
        end;
      end;
   end;

   sendGM(conn, S_ERROR, "UnknownUser", p[1]);
end;

function commandBanUser(connection, line, params)
   local p = split(params);
   local conn = colloquy.connections[connection];

   if (p[1] == nil or p[2] == nil) then
      sendGM(conn, S_ERROR, "cbanuserUsage");
      return nil;
   end;

   local i, v, u;
   u = strlower(p[1]);

   for i, v in users do
      if (i ~= "n") then
        if (i == u) then
          if (v.privs ~= nil and strfind(v.privs, "Z", 1, 1)) then
            sendGM(conn, S_ERROR, "Immune", i);
            return nil;
          end;
          v.banned = strsub(params, strfind(params, p[1]) + strlen(p[1]) + 1, -1) .. " [" .. colloquy.connections[connection].realUser .. "]";
          sendGM(conn, S_DONE, "cbanuserDone", i);
          log(format("B  %s[%s] bans %s: %s", colloquy.connections[connection].username, colloquy.connections[connection].realUser, u, v.banned));
          saveUsers(colloquy.users);
          return nil;
        end;
      end;
   end;

   sendGM(conn, S_ERROR, "UnknownUser", p[1]);
end;

function commandUnbanUser(connection, line, params)
   local p = split(params);
   local conn = colloquy.connections[connection];

   if (p[1] == nil) then
      sendGM(conn, S_ERROR, "cunbanuserUsage");
      return nil;
   end;

   local i, v, u;
   u = strlower(p[1]);

   for i, v in users do
      if (i ~= "n") then
        if (i == u) then
          if (v.banned ~= nil and v.banned ~= "") then
            v.banned = "";
            sendGM(conn, S_DONE, "cunbanuserDone", i);
            log(format("b  %s[%s] unbans %s", colloquy.connections[connection].username, colloquy.connections[connection].realUser, u));
            saveUsers(colloquy.users);
          else
            sendGM(conn, S_ERROR, "cunbanuserAlready", i);
          end;
          return nil;
        end;
      end;
   end;

   sendGM(conn, S_ERROR, "UnknownUser", p[1]);
end;

function commandLockTalker(connection, line, params)
   local conn = colloquy.connections[connection];
   
   sendGMAll(S_TALKERLOCK, "clocktalkerDone", conn.username);
   log(format("B  %s[%s] locked the talker", conn.username, conn.realUser));
   colloquy.locked = 1;
end;

function commandUnlockTalker(connection, line, params)
   local conn = colloquy.connections[connection];

   sendGMAll(S_TALKERUNLOCK, "cunlocktalkerDone", conn.username);
   log(format("b  %s[%s] unlocked the talker", conn.username, conn.realUser));   
   colloquy.locked = nil;
end;

function commandAlert(connection, line, params)
   local p = split(params);
   local conn = colloquy.connections[connection];
   
   if (p[1] == nil) then
      sendGM(conn, S_ERROR, "calertUsage");
      return nil;
   end;

   log(format("A  %s[%s] %s", conn.username, conn.realUser, params));
   sendGMAll(S_ALERT, "calertDone", params);
end;

function commandLogin(connection, line, params)
   local p = split(params);
   local c = colloquy.connections[connection];
  
   if (p[2] == nil) then
     sendGM(c, S_ERROR, "cloginUsage");
     return nil;
   end;

   local user = strlower(p[1]);

   if (users[user] == nil) then
     sendGM(c, S_ERROR, "UnknownUser", p[1]);
     return nil;
   end;

   local u = users[user];

   local i, v;
  
  for i, v in colloquy.connections do
     if (i ~= "n" and strlower(c.username) ~= user) then
       if (strlower(v.username) == user) then
         sendGM(c, S_ERROR, "cloginAlready", v.username);
         return nil;
       end;
     end;
   end;

   local pr, message = checkPassword(user, p[2]);
   if (not pr) then
     u.failed = (u.failed or 0) + 1;
     send(message or gm(c, "cloginPassword"), c, S_ERROR);
     log(format("!+ Authentication for %s failed (%s) by %s[%s].", p[1], message or "incorrect password", c.username, c.realUser));
     return nil;
   end;

   if u.banned and u.banned ~= "" then
     sendGM(c, S_ERROR, "cloginBanned", p[1], u.banned);
     log(format("!+ Banned user %s tried to logon from %s[%s].", p[1], c.username, c.realUser));
     return nil;
   end
 
   if (u.timeon == nil) then
      sendGM(c, S_ERROR, "cloginNoNormal", p[1]);
      return nil;
   end;

   disconnectUser(c.socket.socket, "Logged on as " .. p[1], 1);
 
   log(format("-  %s[%s] logged on as...", c.username, c.realUser));
   log(format("+  %s", p[1]));

   local oldName = c.username;
   
   c.username = p[1];
   c.realUser = p[1];
   c.status = 2;
   c.privs = u.privs;
   c.flags = u.flags;
   c.termType = u.termType;
   c.colours = u.colours;
   c.restrict = u.restrict;
   c.timeWarn = u.timeWarn;
   c.aliases = u.aliases;
   u.connected = secs;

   if (u.termType == "colour" and strfind(c.flags, "W")) then
     send("\255\253\31", c, S_RAW);
   end;

   commandSet(connection, ".set", "");

   sendGMAll(S_LOGIN, "cloginDone", oldName, p[1]);
   if (u.failed and u.failed > 0) then
     sendGM(c, S_DONE, "cloginFailures", u.failed);
     u.failed = 0;
   end;

end;

function commandComment(connection, line, params)
  local conn = colloquy.connections[connection];
  if (params == nil or params == "") then
    if (conn.comment) then
      conn.comment = nil;
      sendGM(conn, S_DONE, "ccommentRemove");
    else
      sendGM(conn, S_ERROR, "ccommentNone");
    end;
    return nil;
  else
   sendGM(conn, S_DONE, "ccommentSet", params);
   conn.comment = params;
  end;
end;

function commandComments(connection, line, params)
  local i, v, p;
  local conn = colloquy.connections[connection];
  p = 0;
  for i, v in colloquy.connections do
    if (i ~= "n" and v.comment ~= nil) then
      send(v.username .. ": " .. v.comment, conn, S_COMMENT);
      p = p + 1;
    end;
  end;
  if (p == 0) then
   sendGM(conn, S_COMMENT, "ccommentsNone");
  end;
end;

function commandWake(connection, line, params)
  local p = split(params);
  local conn = colloquy.connections[connection];

  if (p[1] == nil) then
    sendGM(conn, S_ERROR, "cwakeUsage");
    return nil;
  end;

  local u = userByName(p[1]);
  if (u == nil) then
    sendGM(conn, S_ERROR, "UnknownUser", p[1]);
    return nil;
  end;

  if (type(u) == "string") then
    send(u, conn, S_ERROR);
    return nil;
  end;

  sendGM(u, S_WAKE, "cwakeAttempts", conn.username);
  sendGM(conn, S_DONE, "cwakeDone", u.username);

end;

function commandShowLog(connection, line, params)
  local conn = colloquy.connections[connection];
  if colloquy.noFork then
    sendGM(conn, S_ERROR, "NotAvail");
    return nil;
  end;

  local f = readfrom("|tail " .. colloquy.logName);
  local conn, l = colloquy.connections[connection];
  
  repeat
   l = read(f, "*l");
   if (l) then
     send(l, conn, S_LOOK);
   end;

  until (l == nil);

  closefile(f);

end;

function commandLock(connection, line, params)
  local conn = colloquy.connections[connection];

  if (strlower(conn.group) == "public") then
    sendGM(conn, S_ERROR, "clockPublic");
    return nil;
  end;

  if (colloquy.lockedGroups[strlower(conn.group)]) then
    sendGM(conn, S_ERROR, "clockAlready");
    return nil;
  end;

  colloquy.lockedGroups[strlower(conn.group)] = 1;
  sendGMGroup(conn.group, S_DONE, "clockDone", conn.username);
end;

function commandUnlock(connection, line, params)
  local conn = colloquy.connections[connection];

  if (strlower(conn.group) == "public") then
    sendGM(conn, S_ERROR, "cunlockPublic");
    return nil;
  end;

  if (not colloquy.lockedGroups[strlower(conn.group)]) then
    sendGM(conn, S_ERROR, "cunlockAlready");
    return nil;
  end;

  colloquy.lockedGroups[strlower(conn.group)] = nil;
  updateInvitations("@" .. strlower(conn.group), "");
  
  sendGMGroup(conn.group, S_DONE, "cunlockDone", conn.username);
end;

function commandInvite(connection, line, params)

  local conn = colloquy.connections[connection];
  local p = split(params);

  if (not p[1]) then
    sendGM(conn, S_ERROR, "cinviteUsage");
    return nil;
  end;

  local u, spec = getUserMultiple(params);
  if (u == nil) then
    send(spec, conn, S_ERROR);
    return nil;
  end;
 
  -- check if everybody in the spec isn't already in this group.
  local g = strlower(conn.group);
  for i, v in u do
    if (i ~= "n") then
      if (g == strlower(v.group)) then
        sendGM(conn, S_ERROR, "cinviteAlready", v.username);
        return nil;
      end;
    end;
  end;

  sendGMGroup(conn.group, S_INVITE, "cinviteDone", conn.username, spec);
  for i, v in u do
    if (i ~= "n") then
      addInvitation(v, "@" .. strlower(conn.group));
      sendGM(v, S_INVITE, "cinviteUser", conn.username, conn.group, conn.username);
    end;
  end;
end;

function commandIdlers(connection, line, params)
   local i, v, l, count;
   local p = split(params);
   local conn = colloquy.connections[connection];
   
   if (p[1]) then
     sendGM(conn, S_WHOHDR, "cidlersGroup", p[1]);
   else
     sendGM(conn, S_WHOHDR, "cidlersAll", colloquy.talkerName);
   end;
   sendGM(conn, S_WHOHDR, "cidlersHeader")

   local sorted = {};
   local lg;
   for i, v in colloquy.connections do
     if (type(v) == "table" and v.veryIdle) then tinsert(sorted, v) end;
   
    end;

   sort(sorted, function(a, b)
                  if (not b) then return 1 end;
                  
                  local la, lb = strlower(a.group), strlower(b.group);

                  if (la == lb) then
                    return (strlower(a.username) < strlower(b.username));
                  elseif (la == "public") then
                    return 1;
                  elseif (lb == "public") then
                    return nil;
                  elseif (la == "bots-r-us") then
                    return nil;
                  elseif (lb == "bots-r-us") then
                    return 1;
                  else
                    return (la < lb);
                  end;
                end);

   count = 0;
   for i=1,getn(sorted) do
      v=sorted[i];
      
      if (i ~= "n") then
        if (v.status == 0) then
          l = "--- Still connecting ---              "
        else
           if (not v.invis or (conn.privs and strfind(conn.privs, "M"))) and (p[1] == nil or (strlower(v.group) == strlower(p[1]))) then
             send(format("%-10.10s %-8.8s %-55.55s", v.username, timeToShortString(secs - v.idle), v.idleReason),
                  conn, S_WHO);
             count = count + 1;        
           end;
         end;
      end;
   end;
   sendGM(conn, S_WHOHDR, "cidlersTotal", count);
end;

function commandEvict(connection, line, params)

  local conn = colloquy.connections[connection];
  local p = split(params);

  if (not p[1]) then
    sendGM(conn, S_ERROR, "cevictUsage");
    return nil;
  end;

  local ruser = userByName(p[1]);
  if (type(ruser) == "nil") then
    sendGM(conn, S_ERROR, "UnknownUser", p[1]);
    return nil;
  elseif (type(ruser) == "string") then
    send(ruser, conn, S_ERROR);
    return nil;
  end;

  if (ruser.username == conn.username) then
    sendGM(conn, S_ERROR, "cevictSelf");
    return nil;
  end;

  if (strlower(ruser.group) == "public" and (not conn.privs or not strfind(conn.privs, "E", 1, 1))) then
    sendGM(conn, S_ERROR, "cevictPublic");
    return nil;
  elseif (strlower(ruser.group) == "public" and (conn.privs and strfind(conn.privs, "E", 1, 1))) then
    sendGM(ruser, S_EVICT, "cevictEvictee", conn.username);
    ruser.group = "";
    sendGMGroup("limbo", S_EVICT, "cevictOthers", ruser.username, conn.username);
    ruser.group = "Limbo";
    commandLook(ruser.socket.socket);
    sendGMGroup("public", S_EVICT, "cevictDone", conn.username, ruser.username);
    log(format("E  %s[%s] evicts %s[%s] from %s", conn.username, conn.realUser, ruser.username, ruser.realUser, "public"));
    return nil;
  end;

  if (strlower(conn.group) ~= strlower(ruser.group)) then
    if (conn.privs and strfind(conn.privs, "E", 1, 1)) then
      local o = ruser.group;
      sendGM(ruser, S_EVICT, "cevictEvictee", conn.username);
      ruser.group = "";
      sendGMGroup("public", S_EVICT, "cevictOthers", ruser.username, conn.username);
      ruser.group = "Public";
      commandLook(ruser.socket.socket);
      sendGMGroup(o, S_EVICT, "cevictDone", conn.username, ruser.username);
      sendGM(conn, S_DONE, "Done");
      log(format("E  %s[%s] evicts %s[%s] from %s", conn.username, conn.realUser, ruser.username, ruser.realUser, o));
      return nil;
    else
      sendGM(conn, S_ERROR, "cevictGroup");
      return nil;
    end;
  end;

  sendGM(ruser, S_EVICT, "cevictEvictee", conn.username);
  ruser.group = "";
  sendGMGroup("public", S_EVICT, "cevictOthers", ruser.username, conn.username);
  ruser.group = "Public";
  commandLook(ruser.socket.socket);
  sendGMGroup(o, S_EVICT, "cevictDone", conn.username, ruser.username);
end;

function commandQuery(connection, line, params)
  local p = split(params);
  local conn = colloquy.connections[connection];

  if (not p[1]) then
    if (conn.query) then
      sendGM(conn, S_DONE, "cqueryEnd")
      conn.query = nil;
    else
      sendGM(conn, S_ERROR, "cqueryNone");
    end;
    return nil;
  end;

  local t = strsub(p[1], 1, 1);
  local u;
  if (t == "%") then
    -- they want to query a list...
    local ll = strlower(strsub(p[1], 2, -1));
    local l, err = listByName(conn, ll, 1);
    
    if (l == nil) then
      send(err, conn, S_ERROR);
      return nil;
    end;
    
    if (lists[l] == nil) then
      sendGM(conn, S_ERROR, "UnknownList", l);
      return nil;
    end;
 
    if (not listIsMember(conn.realUser, l)) then
      sendGM(conn, S_ERROR, "cqueryNoList");
      return nil;
    end;

    conn.query = { format = "%" .. lists[l].listname, data = lists[l] };
    sendGM(conn, S_DONE, "cqueryList", lists[l].listname);
    return nil;
  elseif (t == "@") then
    -- They want to query a group...
    -- Also, perhaps write a groupByName to handle contractions?
    local group, err = groupByName(strsub(p[1], 2, -1));
    if (not group) then
      send(err, conn, S_ERROR);
      return nil;
    end;
    conn.query = { format = "@" .. group, data = group };
    sendGM(conn, S_DONE, "cqueryGroup", group);
    return nil;
  else
    -- is it a user?
    u = userByName(p[1]);
    if (type(u) == "string") then
      send(u, conn, S_ERROR);
      return nil;
    elseif (u == nil) then
      sendGM(conn, S_ERROR, "UnknownUser", p[1]);
      return nil;
    end;
    conn.query = { format = ">" .. u.username, data = u };
    sendGM(conn, S_DONE, "cqueryUser", u.username);
  end;
  
end;  

function commandBan(connection, line, params)
  local conn = colloquy.connections[connection];
  local p = split(params);
  
  if (p[1] ~= nil and p[2] == nil) then
    sendCM(conn, S_ERROR, "cbanUsage");
    return nil;
  end;

  if (p[1] == nil and p[2] == nil) then
    sendGM(conn, S_INFO, "cbanHeader");
    local i, v;
    for i, v in colloquy.banMasks do
      if (type(v) == "table") then
        send(format(" %s (%s)", v.mask, v.reason), conn, S_INFO);
      end;
    end;
    return nil;
  end;

  tinsert(colloquy.banMasks, { mask = p[1], reason = strsub(params, strfind(params, p[2], 1, 1), -1) .. " [" .. conn.realUser .. "]" } );
  log(format("B  %s[%s] bans %s : %s", conn.username, conn.realUser, p[1], strsub(params, strfind(params, p[2], 1, 1), -1)));
  saveBans(colloquy.banFile);
  sendGM(conn, S_DONE, "cbanDone", p[1]);
end;

function commandUnban(connection, line, params)
  local conn = colloquy.connections[connection];
  local p = split(params);
  
  if (p[1] == nil) then
    sendGM(conn, S_ERROR, "cunbanUsage");
    return nil;
  end;

  local i, v;
  for i, v in colloquy.banMasks do
    if (type(v) == "table" and v.mask == p[1]) then
      tremove(colloquy.banMasks, i);
      log(format("b  %s[%s] unbans %s : %s", conn.username, conn.realUser, p[1], v.reason));
      saveBans(colloquy.banFile);
      sendGM(conn, S_DONE, "cunbanDone", p[1]);
      return nil;
    end;
  end;
  
  sendGM(conn, S_ERROR, "cunbanNone", p[1]);
end;

function commandMOTD(connection, line, params)
  sendFile("data/misc/motd", {colloquy.connections[connection]});
end;

function commandIgnore(connection, line, params)
  local p = split(params);
  local conn = colloquy.connections[connection];
  local silent = (strlower(p[2] or "")) == gm(conn, "cignoreSilently");

  if (p[1] == nil) then
    -- no parameters - print out who they're ignoring
    sendGM(conn, S_DONE, "cignoreIgnoring");
    if (conn.ignoring) then
      local t = {};
      foreach(conn.ignoring, function(i, v) tinsert(%t, i.username) end);
      sort(t);
      foreachi(t, function(i, v) send(" " .. v, %conn, S_DONE) end);
    else
      sendGM(conn, S_DONE, "cignoreNobody");
    end;
    local t, i, v = {};
    for i, v in colloquy.connections do
      if (v.ignoring and v.ignoring[conn]) then
        tinsert(t, v.username);
      end;
    end;
    sort(t);
    sendGM(conn, S_DONE, "cignoreIgnored");
    if (getn(t) > 0) then
      foreachi(t, function(i, v) send(" " .. v, %conn, S_DONE) end);
    else
      sendGM(conn, S_DONE, "cignoreNobody");
    end;
    return nil;
  end;

  local u = userByName(p[1]);
  if (not u) then
    sendGM(conn, S_ERROR, "UnknownUser", p[1]);
    return nil;
  end;

  if (type(u) == "string") then
    send(u, conn, S_ERROR);
    return nil;
  end;

  if (u == conn) then
    sendGM(conn, S_ERROR, "cignoreSelf");
    return nil;
  end;

  if (conn.ignoring and conn.ignoring[u]) then
    sendGM(conn, S_ERROR, "cignoreAlready", u.username);
    return nil;
  end;

  if (not conn.ignoring) then
    conn.ignoring = {};
  end;

  conn.ignoring[u] = 1;

  sendGM(conn, S_DONE, "cignoreDone", u.username);
  local suffix = "";
  if (silent and u.privs and strfind(u.privs, "M", 1, 1)) then silent = nil; suffix = format(" (%s)", gm(u, "cignoreSilently")) end;
  if (not silent) then
    sendGM(u, S_DONE, "cignoreIgnoree", conn.username, suffix);
  end;
end;

function commandUnignore(connection, line, params)
  local p = split(params);
  local conn = colloquy.connections[connection];
  local silent = (strlower(p[2] or "")) == gm(conn, "cingoreSilently");
  
  if (not p[1]) then
    sendGM(conn, S_ERROR, "cunignoreUsage");
    return nil;
  end;

  local u = userByName(p[1]);
  if (not u) then
    sendGM(conn, S_ERROR, "UnknownUser", p[1]);
    return nil;
  end;

  if (type(u) == "string") then
    send(u, conn, S_ERROR);
    return nil;
  end;

  if (not conn.ignoring or not conn.ignoring[u]) then
    sendGM(conn, S_ERROR, "cunignoreAlready", u.username);
    return nil;
  end;

  conn.ignoring[u] = nil;
  if (empty(conn.ignoring)) then
    conn.ignoring = nil;
  end;

  sendGM(conn, S_DONE, "cunignoreDone", u.username);
  local suffix = "";
  if (silent and u.privs and strfind(u.privs, "M", 1, 1)) then silent = nil; suffix = format(" (%s)", gm(u, "cignoreSilently")) end;
  if (not silent) then
    sendGM(u, S_DONE, "cunignoreIgnoree", conn.username, suffix);
  end;
end;

function commandWhoAmI(connection, line, params)
  local conn = colloquy.connections[connection];
  
  if (conn.realUser ~= conn.username) then
    sendGM(conn, S_DONE, "cwhoamiOther", conn.realUser, conn.username);
  else
    sendGM(conn, S_DONE, "cwhoamiNormal", conn.realUser);
  end;
end;

function commandBot(connection, line, params)
   local p = split(params);
   local conn = colloquy.connections[connection];
   
   if (p[1] == nil) then
      sendGM(conn, S_ERROR, "cbotUsage");
      return nil;
   end;

   local u = strlower(p[1]);
   local i, v;

   if (strsub(u, -3, -1) ~= "bot") then
     sendGM(conn, S_ERROR, "cbotNot", p[1]);
     return nil;
   end;
   
   for i, v in colloquy.connections do
      if (i ~= "n") then
        if (strlower(v.username) == u) then
          if (allowZ(i)) then
            sendGM(conn, S_ERROR, "Immune", v.username);
            return nil;
          end;

          if (strfind(v.restrict, "B", 1, 1)) then
            sendGM(conn, S_ERROR, "cbotAlready", v.username);
            return nil;
          end;
          v.restrict = v.restrict .. "B";
          sendGMAll(S_GAG, "cbotDone", v.username, conn.username);
          log(format("MB %s[%s] bots %s[%s]", colloquy.connections[connection].username, colloquy.connections[connection].realUser, v.username, v.realUser));
         return nil;
        end;
      end;
   end;

   sendGM(conn, S_ERROR, "UnknownUser", p[1]);
end;

function commandUnbot(connection, line, params)
   local p = split(params);
   local conn = colloquy.connections[connection];

   if (p[1] == nil) then
      sendGM(conn, S_ERROR, "cunbotUsage");
      return nil;
   end;

   local u = strlower(p[1]);
 
   if (strsub(u, -3, -1) ~= "bot") then
     sendGM(conn, S_ERROR, "cbotNot", p[1]);
     return nil;
   end;
 
   local i, v;
   
   for i, v in colloquy.connections do
      if (i ~= "n") then
        if (strlower(v.username) == u) then
          if (strfind(v.restrict, "B", 1, 1) == nil) then
            sendGM(conn, S_ERROR, "cunbotAlready", v.username);
            return nil;
          end;
          v.restrict = gsub(v.restrict, "B", "")
          sendGMAll(S_UNGAG, "cunbotDone", v.username, conn.username);
          log(format("mb %s[%s] has been unmade a bot by %s[%s]", colloquy.connections[connection].username, colloquy.connections[connection].realUser, v.username, v.realUser));
          return nil;
        end;
      end;
   end;

   sendGM(conn, S_ERROR, "UnknownUser", p[1]);
end;

function commandBots(connection, line, params)
  local total = 0;
  local occ;
  local conn = colloquy.connections[connection];
  for i, v in colloquy.connections do
    if (v.restrict and strfind(v.restrict, "B", 1, 1)) then
      if (total == 0) then
        send(format("%-15.15s %s", gm(conn, "cbotsName"), gm(conn, "cbotsUse")), conn, S_BOTHDR);
      end;
      if (users[strlower(v.realUser)] and users[strlower(v.realUser)].occupation) then
        occ = users[strlower(v.realUser)].occupation;
      else
        occ = gm(conn, "cbotsUseless");
      end;
      send(format("%-15.15s %s", v.username, occ), colloquy.connections[connection], S_BOT);
      total = total + 1;
    end;
  end;
  if (total > 0) then
    sendGM(conn, S_DONE, "cbotsTotal", total);
  else
    sendGM(conn, S_ERROR, "cbotsNone");
  end;
end;

function commandLaston(connection, line, params)
  local conn = colloquy.connections[connection];
  local p = split(params);
  local i;

  if (p[1] == nil) then
    sendGM(conn, S_ERROR, "clastonUsage");
    return nil;
  end;

  if (strsub(p[1], 1, 1) == "%") then
    -- they want to know about a list
    i = listByName(conn, strlower(strsub(p[1], 2, -1)));
    -- OK - first of all, we get the list of realUsers who are on a list.
    -- Then, one by one, go through and see if a user who is connected has
    -- the same realUser.  When we find one, print out a match, and remove
    -- it from the list.  After we've examined all active connections, look
    -- up the remaining entries in the list members in the users table to
    -- find out when they last connected.
    -- User       * Last on
    local members = {};
    local count = 0;
    
    if (i == nil) then
      sendGM(conn, S_ERROR, "UnknownList", p[1]);
      return nil;
    end;

    for i, v in lists[i].members do
      if (type(i) == "number") then tinsert(members, v) end;
    end;

    sendGM(conn, S_DONE, "clastonHeader");

    for i = 1, getn(members) do
      for k, j in colloquy.connections do
        if (strlower(j.realUser) == members[i]) then
          local u = strlower(j.realUser);
          local tmp;
          tmp = gm(conn, "clastonConnected", j.group, timeToShortString(floor(secs - j.idle)));
          send(format("%-10.10s %-1.1s %s", j.username, y(strlower(j.username) == strlower(j.realUser), "", "*"), tmp), conn, S_DONE);
          count = count + 1;
          members[i] = nil;
        end
      end;
    end;

    -- right, we've dumped out the stuff for currently connected
    -- users, and removed them from the table.  Now look up the remaining
    -- in the users table.
    
    for i = 1, getn(members) do
      if (members[i]) then
        local tmp;
        if (not users[members[i]]) then
          tmp = gm(conn, "clastonNoExist");
        elseif (not users[members[i]].lastLogon) then
          tmp = gm(conn, "clastonNever");
        else
          tmp = users[members[i]].lastLogon;
        end;
        send(format("%-10.10s %-1.1s %s", members[i], "", tmp), conn, S_DONE);
        count = count + 1;
      end;
    end;

    sendGM(conn, S_DONE, "clastonTotal", count);

    return nil;
  else
    -- they want to know about a single user
    i = strlower(p[1]);
    if (users[i] == nil) then
      sendGM(conn, S_ERROR, "UnknownUser", i);
      return nil;
    end;
    if (users[i].connected) then
      i = userByName(i);
      sendGM(conn, S_DONE, "clastonUser", i.username, i.group, timeToShortString(floor(secs - i.idle)));
    else
      if not users[i].lastLogon then
        sendGM(conn, S_DONE, "clastonUserNever", i);
      else
        sendGM(conn, S_DONE, "clastonUserConn", i, users[i].lastLogon);
      end;
    end;
  end;

end;

function commandGuest(connection, line, params)
   local p = split(params);
   local conn = colloquy.connections[connection];

   if (p[1] == nil) then
      sendGM(conn, S_ERROR, "cguestUsage");
      return nil;
   end;

   local u = userByName(p[1]);
   if (type(u) == "string") then
     send(u, conn, S_ERROR);
     return nil;
   end;

   if (u == nil) then
     sendGM(conn, S_ERROR, "UnknownUser", p[1]);
     return nil;
   end;

   if (u.status ~= 1) then
     sendGM(conn, S_ERROR, "cguestAlready", u.username);
     return nil;
   end;

   if (users.guest == nil) then
     sendGM(conn, S_ERROR, "cguestNoGuest");
     return nil;
   end;

   u.status = 2;
   u.realUser = "guest"

   sendGMAll(S_LOGIN, "cguestDone", u.username, conn.username);
   log(format("MG %s[%s] guests %s[%s]", conn.username, conn.realUser, u.username, u.realUser));
end;

function commandXyzzy(connection, line, params)
   send("Nothing happens.", colloquy.connections[connection], S_ERROR);
end;

--------------------------------------------------------------------------------------------------------------------------
function allowAll(connection)
   return 1;
end;

function allowConn(connection)
  return colloquy.connections[connection].status > 0;
end;

function allow(connection, p)
   if (colloquy.connections[connection].privs == nil) then return nil end;
   return strfind(colloquy.connections[connection].privs, p, 1, 1);
end;

function allowUsers(connection)
   return colloquy.connections[connection].status > 1;
end;

function allowA(connection)
   return allow(connection, "A");
end;

function allowB(connection)
   return allow(connection, "B");
end;

function allowC(connection)
   return allow(connection, "C");
end;

function allowF(connection)
   return allow(connection, "F");
end;

function allowG(connection)
   return allow(connection, "G");
end;

function allowH(connection)
   return allow(connection, "H");
end;

function allowI(connection)
   return allow(connection, "I");
end;

function allowK(connection)
   return allow(connection, "K");
end;

function allowL(connection)
   return allow(connection, "L");
end;

function allowM(connection)
   return allow(connection, "M");
end;

function allowN(connection)
   return allow(connection, "N");
end;

function allowS(connection)
   return allow(connection, "S");
end;

function allowU(connection)
   return allow(connection, "U");
end;

function allowW(connection)
   return allow(connection, "W");
end;

function allowZ(connection)
   return allow(connection, "Z");
end;

function isBot(connection)
  if (not colloquy.connections[connection].restrict) then return nil end;
  return strfind(colloquy.connections[connection].restrict, "B", 1, 1);
end;

if (not commTable) then
  dofile("parser.lua");
end;

setCommands = {};
tinsert(setCommands, { name = "cr", code = setCR });
tinsert(setCommands, { name = "beep", code = setBeep });
tinsert(setCommands, { name = "timewarn", code = setTimewarn });
tinsert(setCommands, { name = "prompts", code = setPrompts });
tinsert(setCommands, { name = "privs", code = setPrivs });
tinsert(setCommands, { name = "term", code = setTerm });
tinsert(setCommands, { name = "echo", code = setEcho });
tinsert(setCommands, { name = "colour", code = setColour });
tinsert(setCommands, { name = "info", code = setInfo });
tinsert(setCommands, { name = "shouts", code = setHeard });
tinsert(setCommands, { name = "messages", code = setHeard });
tinsert(setCommands, { name = "lists", code = setHeard });
tinsert(setCommands, { name = "idling", code = setHeard });
tinsert(setCommands, { name = "width", code = setWidth });
tinsert(setCommands, { name = "strip", code = setStrip });
tinsert(setCommands, { name = "language", code = setLanguage });