colloquy-1.35.30/
colloquy-1.35.30/data/lang/
colloquy-1.35.30/data/misc/
colloquy-1.35.30/docs/
-- lists
-- table layout:
-- 
-- lists[listname] = {
--   listname = "capitalised list name",
--   description = "description of the list",
--   owner = "who owns this list, and is always a list master",
--   created = "when this list was created",
--   flags = "flags - O = open, L = locked, P = permanent, A = anonymous",
--   members = {
--     "bob",
--     "god",
--     "foo"
--   },
--   masters = {
--     "bob",
--     "god"
--   }
--   used = time since the epoch that the list was last used.
-- }

function numberOfListsOwned(username)
  local username = strlower(username)
  local t = 0;
  for i, v in lists do
    if ( v.owner == username and not strfind(v.flags, "P") ) then
      t = t + 1;
    end;
  end;

  return t;
end;

function searchForUser(realName)
  -- searches for a user with realName
  local l, i, v = strlower(realName);
  for i, v in colloquy.connections do
    if (v.realUser and l == strlower(v.realUser)) then
      return v;
    end;
  end;
  return nil;
end;

function isListMaster(listname, username)
  listname = strlower(listname);
  username = strlower(username);
  local l = lists[listname];

  if l.owner == username or strfind((connection(username).privs or ""), "M", 1, 1) then
    return 1;
  end

  for i, v in (l.masters or {}) do
    if v == username then
      return 1;
    end;
  end;

  return nil;
end

function getListMembers(listname, talking)
   local r, i, v = {};
   
   for i, v in lists[listname].members do
      if (i ~= "n") then
        local bing = searchForUser(v);
          if (type(bing) == "table") then
           if (talking) then
             if (not listHasPaused(bing, listname)) then
               tinsert(r, bing);
             end;
           else
             tinsert(r, bing);
           end;
         end;
      end;
   end;

   return r;
end;

function listIsMember(user, list, real)
  local m, l, i, v = strlower(user), strlower(list);

  if (not lists[list]) then return nil end;
  if (not real and strfind(lists[list].flags, "O", 1, 1)) then return 0 end;

  for i, v in lists[l].members do
    if (type(v) == "string" and v == m) then return i end;
  end;

  return nil;
end;

function listByName(conn, list, talk)
  local found, i, v = {};
  local luser = strlower(conn.realUser);
  local llist = strlower(list);
  
  if (lists[llist] ~= nil ) then
    return llist;
  end;

  for i, v in lists do
    if (type(v) == "table") then
      if (strfind(i, llist, 1, 1) == 1) then
        if (listIsMember(luser, i) or not talk) then
          tinsert(found, i);
        end;
      end;
    end;
  end;

  for i, v in found do
    if (v == llist) then
      return v;
    end;
  end;

  if (getn(found) == 0) then
    if (lists[llist] ~= nil ) then
      return llist;
    end;
    return nil, "That list does not exist!";
  end;
  if (getn(found) > 1) then
    local r = list .. " is ambiguous - matches ";
    for i, v in found do
      if (type(v) == "string") then
        r = r .. lists[v].listname .. ", ";
      end;
    end;
    r = strsub(r, 1, -3) .. ".";
    return nil, r;
  end;
  return found[1];

end;

function commandListTell(connection, line, params)
   local p = split(params);
   local conn = colloquy.connections[connection];
      
   if (p[1] == nil or p[2] == nil) then
      send("Whispering nothing to a list is silly.", colloquy.connections[connection], S_ERROR);
      return nil;
   end;
   
   local l = strlower(p[1]);
   local l, err = listByName(conn, l, 1);
   if (l == nil) then
     send(err, conn, S_ERROR);
     return nil;
   end;
   
   if (lists[l] == nil) then
      send("No such list.", conn, S_ERROR);
      return nil;
   end;

   if (not listIsMember(conn.realUser, l) and not strfind(conn.privs or "", "M", 1, 1)) then
     send("You are not a member of that list, and it is not open!", conn, S_ERROR);
     return nil;
   end;

   if (strfind(lists[l].flags, "R", 1, 1)) then
     -- the list is read-only.  Check if they're the owner, or a master.
     if not isListMaster(l, conn.realUser) then
       send("That list is read-only.", conn, S_ERROR);
       return nil;
     end;
   end;

   if (listHasPaused(conn, lists[l].listname)) then
     listUnpause(conn, l);
   end;

   local m = getListMembers(l, 1);
   local t = strsub(line, strfind(line, p[1], 1, 1) + strlen(p[1]) + 1, strlen(line));
   local a = conn.username .. strrep(" ", 11);
   a = strsub(a, 1, 12) .. "%" .. t .. " {" .. lists[l].listname .. "}";
   sendTo(a, m, S_LISTTALK);
  
  if (not listIsMember(conn.realUser, l, 1)) then
    send(format("Whispered to list %s: '%s'", lists[l].listname, t), conn, S_DONETELL);
  end;

  lists[l].used = secs;
end;

function commandListEmote(connection, line, params)
   local p = split(params);
   local conn = colloquy.connections[connection];
      
   if (p[1] == nil or p[2] == nil) then
      send("Whispering nothing to a list is silly.", colloquy.connections[connection], S_ERROR);
      return nil;
     end;
   
   local l = strlower(p[1]);
   local l, err = listByName(conn, l, 1);
   if (l == nil) then
     send(err, conn, S_ERROR);
     return nil;
   end;
 
   if (lists[l] == nil) then
      send("No such list.", colloquy.connections[connection], S_ERROR);
      return nil;
   end;

   if (not listIsMember(conn.realUser, l) and not strfind(conn.privs, "M", 1, 1)) then
     send("You are not a member of that list, and it is not open!", conn, S_ERROR);
     return nil;
   end;

   if (strfind(lists[l].flags, "R", 1, 1)) then
     -- the list is read-only.  Check if they're the owner, or a master.
     if not isListMaster(l, conn.realUser) then
       send("That list is read-only.", conn, S_ERROR);
       return nil;
     end;
   end;

   if (listHasPaused(conn, lists[l].listname)) then
     listUnpause(conn, l);
   end;

   local m = getListMembers(l, 1);
   local t = strsub(line, strfind(line, p[1], 1, 1) + strlen(p[1]) + 1, strlen(line));
   local blah = " ";
   if (strfind(punctuation, strsub(t, 1, 1), 1, 1)) then
     blah = "";
   end;
   sendTo(format("%% %s%s%s {%s}", colloquy.connections[connection].username, blah, t, lists[l].listname) , m, S_LISTEMOTE);

   if (not listIsMember(conn.realUser, l, 1)) then
     send(format("REmote'd to list %s: '%s%s%s'", lists[l].listname, conn.username, blah, t), conn, S_DONETELL);
   end;

   lists[l].used = secs;
end;

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

   if (p[1] == nil) then
     send("Usage: .list <command> <parameters...>", conn, S_ERROR);
     return nil;
   end;

   if (p[1] == "info") then
     if (p[2] == nil) then
       return commandLists(connection, ".lists", "");
     else
       listInfo(conn, p[2]);
       return nil;
     end;
   end;

   if (p[1] == "create") then
     if (p[2] == nil) then
       send("Usage: .list create <listname>", conn, S_ERROR);
       return nil;
     else
       listCreate(conn, p[2]);
       return nil;
     end;
   end;

   if (p[1] == "delete") then
     if (p[2] == nil) then
       send("Usage: .list delete <listname>", conn, S_ERROR);
       return nil;
     else
       listDelete(conn, p[2]);
       return nil;
     end;
   end;

   if (p[1] == "join") then
     if (p[2] == nil) then
       send("Usage: .list join <listname>", conn, S_ERROR);
       return nil;
     else
       listJoin(conn, p[2]);
       return nil;
     end;
   end;

   if (p[1] == "leave") then
     if (p[2] == nil) then
       send("Usage: .list leave <listname>", conn, S_ERROR);
       return nil;
      else
        listLeave(conn, p[2]);
        return nil;
      end;
    end;

    if (p[1] == "invite") then
      if (p[2] == nil or p[3] == nil) then
        send("Usage: .list invite <listname> <username>", conn, S_ERROR);
        return nil;
      else
        listInvite(conn, p[2], p[3]);
        return nil;
      end;
    end;

    if (p[1] == "owner") then
      if (p[2] == nil or p[3] == nil) then
        send("Usage: .list owner <listname> <username>", conn, S_ERROR);
        return nil;
      else
        listOwner(conn, p[2], p[3]);
        return nil;
      end;
    end;

    if (p[1] == "description") then
      if (p[2] == nil or p[3] == nil) then
        send("Usage: .list description <listname> <description>", conn, S_ERROR);
        return nil;
      else
        listDescription(conn, p[2], strsub(params, strfind(params, p[2], 1, 1) + strlen(p[2]) + 1, -1));
        return nil;
      end;
    end;
    
    if (p[1] == "lock") then
      if (p[2] == nil) then
        send("Usage: .list lock <listname>", conn, S_ERROR);
        return nil;
      else
        listLock(conn, p[2]);
        return nil;
      end;
    end;

    if (p[1] == "unlock") then
      if (p[2] == nil) then
        send("Usage: .list unlock <listname>", conn, S_ERROR);
        return nil;
      else
        listUnlock(conn, p[2]);
        return nil;
      end;
    end;

    if (p[1] == "evict") then
      if (not p[2] or not p[3]) then
        send("Usage: .list evict <listname> <username>", conn, S_ERROR);
        return nil;
      else
        listEvict(conn, p[2], p[3]);
        return nil;
      end;
    end;

    if (p[1] == "open") then
      if (not p[2]) then
        send("Usage: .list open <listname>", conn, S_ERROR);
        return nil;
      else
        listOpen(conn, p[2]);
        return nil;
      end;
    end;

    if (p[1] == "close") then
      if (not p[2]) then
        send("Usage: .list close <listname>", conn, S_ERROR);
        return nil;
      else
        listClose(conn, p[2]);
        return nil;
      end;
    end;

    if (p[1] == "pause") then
      listPause(conn, p[2]);
      return nil;
    end;

    if (p[1] == "unpause") then
      listUnpause(conn, p[2]);
      return nil;
    end;

    if (p[1] == "permanent") then
      if not p[2] then
        send("Usage: .list permanent <listname>", conn, S_ERROR);
        return nil;
      end;
      listPermanent(conn, p[2]);
      return nil;
    end;
    
    if (p[1] == "unpermanent") then
      if not p[2] then
        send("Usage: .list unpermanent <listname>", conn, S_ERROR);
        return nil;
      end;
      listUnpermanent(conn, p[2]);
      return nil;
    end;

    if (p[1] == "anonymous") then
      if not p[2] then
        send("Usage: .list anonymous <listname>", conn, S_ERROR);
        return nil;
      end;
      listAnonymous(conn, p[2]);
      return nil;
    end;

    if (p[1] == "unanonymous") then
      if not p[2] then
        send("Usage: .list unanonymous <listname>", conn, S_ERROR);
        return nil;
      end;
      listUnanonymous(conn, p[2]);
      return nil;
    end;

    if (p[1] == "readonly") then
      if not p[2] then
        send("Usage: .list readonly <listname>", conn, S_ERROR);
        return nil;
      end;
      listReadOnly(conn, p[2]);
      return nil;
    end;

    if (p[1] == "readwrite") then
      if not p[2] then
        send("Usage: .list readwrite <listname>", conn, S_ERROR);
        return nil;
      end
      listReadWrite(conn, p[2]);
      return nil;
    end;

    if (p[1] == "master") then
      if (not p[2] or not p[3]) then
        send("Usage: .list master <listname> <username>", conn, S_ERROR);
        return nil;
      else
        listMaster(conn, p[2], p[3]);
        return nil;
      end;
    end;

    if (p[1] == "unmaster") then
      if (not p[2] or not p[3]) then
        send("Usage: .list unmaster <listname> <username>", conn, S_ERROR);
        return nil;
      else
        listUnmaster(conn, p[2], p[3]);
        return nil;
      end;
    end;

    if (p[1] == "rename") then
      listRename(conn, p[2], p[3]);
      return nil;
    end;

    send("Unknown .list command.", conn, S_ERROR);

end;

function listMember(list, user)
  -- returns a * if user is a member of list, or " " otherwise.
  if (lists[strlower(list)] == nil) then return " " end;
  local m, i, v = lists[strlower(list)].members;
  for i, v in m do
    if (type(v) == "string" and v == user) then
      return "*";
    end;
  end;
  return " ";
end;

function commandLists(connection, line, params) 

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

  if (p[1]) then
    listInfo(conn, p[1]);
    return nil;
  end;

  local sortedLists = {};
  local i, v;
  local u = strlower(conn.realUser);
  local t = 0;
  
  for i, v in lists do
    if (type(v) == "table" and strfind(v.flags, "L", 1, 1)) then
      tinsert(sortedLists, v.listname);
    end;
  end;

  t = getn(sortedLists);
  
  if (getn(sortedLists) > 0) then
    send("Available locked lists are: ('*' marks ones currently subscribed to)", conn, S_LISTSHDR);
    sort(sortedLists);
    for i, v in sortedLists do
      if (type(v) == "string") then
        sortedLists[i] = listMember(v, u) .. v;
      end;
    end;
    local rl = columns(sortedLists, (conn.width-6)/17, 17);
    for i=1,getn(rl) do
      send("  " .. rl[i], conn, S_LISTS);
    end;
  end;

  sortedLists = {};
  
  for i, v in lists do
    if (type(v) == "table" and not strfind(v.flags, "L", 1, 1)) then
      tinsert(sortedLists, v.listname);
    end;
  end;
  
  t = t + getn(sortedLists);

  if (getn(sortedLists) > 0) then
    send("Available unlocked lists are: ('*' marks ones currently subscribed to)", conn, S_LISTSHDR);
    sort(sortedLists);  
    for i, v in sortedLists do
      if (type(v) == "string") then
        sortedLists[i] = listMember(v, u) .. v;
      end;
    end;
    local rl = columns(sortedLists, (conn.width-6)/17, 17);
    for i=1,getn(rl) do
      send("  " .. rl[i], conn, S_LISTS);
    end;
  end;

  if (t == 0) then
    send("There are no lists.", conn, S_ERROR);
  else
    send(tostring(t) .. " total.", conn, S_DONE);
  end;

end;

function listInfo(conn, params)
  local l = strlower(params);

  local l, err = listByName(conn, l);
  if (not l) then
    send(err, conn, S_ERROR);
    return nil;
  end;

  if (lists[l] == nil) then
    send("This list does not exist!", conn, S_ERROR);
    return nil;
  end;

  send(format("%-15.15s %s", "Name:", lists[l].listname), conn, S_LISTINFO);
  if (lists[l].description ~= "") then
    send(format("%-15.15s %s", "Description:", lists[l].description), conn, S_LISTINFO);
  end;
  if (lists[l].flags ~= "" and lists[l].flags ~= nil) then
    local f = lists[l].flags;
    f = gsub(f, "A", "Anonymous ");
    f = gsub(f, "L", "Locked ");
    f = gsub(f, "O", "Open ");
    f = gsub(f, "P", "Permanent ");
    f = gsub(f, "R", "Read-Only ");
    send(format("%-15.15s %s", "Flags:", f), conn, S_LISTINFO);
  end;
  send(format("%-15.15s %s", "Created:", lists[l].created), conn, S_LISTINFO);
  send(format("%-15.15s %s", "Owner:", lists[l].owner), conn, S_LISTINFO);

  local on, off =  "", "";
  local i, v;

  if not strfind(lists[l].flags, "A", 1, 1) then
    for i, v in lists[l].members do
      if (type(v) == "string") then
        local vconn = connection(v);
        local lmaster;
        if (vconn) then
          lmaster = isListMaster(l, vconn.realUser);
          local pconn = listHasPaused(vconn, lists[l].listname);
          if lmaster then
            on = on .. "*";
          end
          if (pconn) then
            on = on .. "(" .. v .. ") ";
          elseif (vconn.veryIdle) then
            on = on .. "[" .. v .. "] ";
          else
            on = on .. v .. " ";
          end;
        else
          if lmaster then
            off = off .. "*";
          end;
          off = off .. v .. " ";
        end;
      end;
    end;
  end;

  if (lists[l].used) then
    send(format("%-15.15s %s", "Last used:", strsub(timeToString(secs - lists[l].used), 1, -2) .. " ago."), conn, S_LISTINFO);
  end;
  if (on ~= "") then
    send(format("%-15.15s %s", "Users online:", on), conn, S_LISTINFO);
  end;
  if (off ~= "") then
    send(format("%-15.15s %s", "Users offline:", off), conn, S_LISTINFO);
  end;
end;

function listCreate(conn, params)
  local l = strlower(params);
  if (lists[l] ~= nil) then
    send("That list already exists!", conn, S_ERROR);
    return nil;
  end;

  if ( numberOfListsOwned(conn.realUser) >= colloquy.listQuota ) then
    send("You have exhausted your list quota.  Either delete some old lists, or ask a master to make some of them permanent.", conn, S_ERROR);
    return nil;
  end;

  if (strlen(gsub(l, "[%w%-]", "")) > 0 or (strlen(params) > 15)) then
    send("That isn't a valid list name.", conn, S_ERROR);
    return nil;
  end;

  lists[l] = {
    listname = params,
    description = "",
    flags = "",
    owner = strlower(conn.realUser),
    created = date("%a %b %e %H:%M:%S %Y"),
    members = {
      strlower(conn.realUser)
    }
  };

  send("List created.", conn, S_DONE);

  saveLists();

end;

function listDelete(conn, params)
  local l = strlower(params);
  local l, err = listByName(conn, l, 1);
  if (not l) then
    send(err, conn, S_ERROR);
    return nil;
  end;

  
  if (lists[l] == nil) then
    send("That list does not exist!", conn, S_ERROR);
    return nil;
  end;

  if ((strlower(conn.realUser) ~= lists[l].owner) and (conn.privs == nil or strfind(conn.privs, "M", 1, 1) == nil)) then
    send("You cannot delete a list you do not own.", conn, S_ERROR);
    return nil;
  end;

  updateInvitations("%" .. l, "");

  sendTo(format("%s has deleted the list. {%s}", conn.username, lists[l].listname), getListMembers(l), S_LISTDELETE);
  if (not listIsMember(conn.realUser, l, 1)) then
    send(format("%s has deleted the list. {%s}", conn.username, lists[l].listname), conn, S_LISTDELETE);
  end;

  lists[l] = nil;
  saveLists();
  
end;

function listJoin(conn, params)
  local l = strlower(params);
  local l, err = listByName(conn, l);
  if (not l) then
    send(err, conn, S_ERROR);
    return nil;
  end;


  if (lists[l] == nil) then
    send("That list does not exist!", conn, S_ERROR);
    return nil;
  end;

  local u, i, v = strlower(conn.realUser);
  for i, v in lists[l].members do
    if (type(v) == "string" and v == u) then
      send("You are already a member of that list.", conn, S_ERROR);
      return nil;
    end;
  end;

  if (lists[l].flags and strfind(lists[l].flags, "L", 1, 1) and (not checkInvitation(conn, "%" .. l))) then
    if ( not (conn.privs and strfind(conn.privs, "M", 1, 1))) then
      send("That list is locked.", conn, S_ERROR);
      return nil;
    end;
  end;

  tinsert(lists[l].members, u);
  removeInvitation(conn, "%" .. l);
  if strfind(lists[l].flags, "A", 1, 1) then
    -- this list is anonymous.  Don't tell the list that this person has joined.
    send(format("You have joined the list anonymously. {%s}", lists[l].listname), conn, S_LISTJOIN);
  else
    sendTo(format("%s has joined the list. {%s}", conn.username, lists[l].listname), getListMembers(l), S_LISTJOIN);
  end;

  saveLists();
  
end;

function listLeave(conn, params)
  local l = strlower(params);
  local l, err = listByName(conn, l, 1);
  if (not l) then
    send(err, conn, S_ERROR);
    return nil;
  end;


  if (lists[l] == nil) then
    send("That list does not exist!", conn, S_ERROR);
    return nil;
  end;

  if (lists[l].owner == strlower(conn.realUser)) then
    send("You cannot leave a list you own.", conn, S_ERROR);
    return nil;
  end;

  local u, i, v = strlower(conn.realUser);
  for i, v in lists[l].members do
    if (type(v) == "string" and v == u) then
      
      if strfind(lists[l].flags, "A", 1, 1) then
        -- this list is anonymous.  Don't tell the list that this person has left.
        send(format("You have left the list anonymously. {%s}", lists[l].listname), conn, S_LISTLEAVE);
      else
        sendTo(format("%s has left the list. {%s}", conn.username, lists[l].listname), getListMembers(l), S_LISTLEAVE);
      end;

      tremove(lists[l].members, i);

      return nil;
    end;
  end;

  send("You are not a member of that list.", conn, S_ERROR);
  saveLists();
  
end;

function listInvite(conn, list, user)
  local l = strlower(list);
  local u = userByName(user);
  local master;  -- is this a master override?
  
  local l, err = listByName(conn, l);
  if (not l) then
    send(err, conn, S_ERROR);
    return nil;
  end;

  
  if (lists[l] == nil) then
    send("That list does not exist!", conn, S_ERROR);
    return nil;
  end;

  if (u == nil) then
    send("No such user.", conn, S_ERROR);
    return nil;
  end;

  if (type(u) == "string") then
    send(u, conn, S_ERROR);
    return nil;
  end;
  
  if (lists[l].flags and strfind(lists[l].flags, "L", 1, 1)) then
    local m, i, v, f;
    m = strlower(conn.realUser);
    for i, v in lists[l].members do
      if (type(v) == "string" and v == m) then
        f = 1;
        break;
      end;
    end;

    if (f ~= 1) then
      send("Only list members can invite users to locked lists.", conn, S_ERROR);
      return nil;
    end;
  end;

  local n = strlower(u.realUser);
  local f = nil;
  local un = strlower(conn.realUser);

  local m, i, v = lists[l].members;
  for i, v in m do
    if (type(v) == "string" and v == n) then
      send("User is already a member.", conn, S_ERROR);
      return nil;
    end;
    if (v == un) then
      f = 1;
    end;
  end;

  addInvitation(u, "%" .. l);

  if strfind(lists[l].flags, "A", 1, 1) then
    -- this list is anonymous.  Don't tell the whole list about it.
    send(format("You invite %s to the list anonymously. {%s}", u.username, lists[l].listname), conn, S_LISTINVITE);
  else
    sendTo(format("%s invites %s to the list. {%s}", conn.username, u.username, lists[l].listname), getListMembers(l), S_LISTINVITE);
  end;

  if (not listIsMember(conn.realUser, l, 1)) then
    if strfind(lists[l].flags, "A", 1, 1) then
      send(format("You invite %s to the list anonymously. {%s}", u.username, lists[l].listname), conn, S_LISTINVITE);
    else
      send(format("%s invites %s to the list. {%s}", conn.username, u.username, lists[l].listname), conn, S_LISTINVITE);
    end;
  end;

  local an = "";
  if strfind(lists[l].flags, "A", 1, 1) then
    an = " anonymously"
  end;

  send(format("%s invites you to %%%s%s.  To respond, type .list join %s", conn.username, lists[l].listname, an, lists[l].listname), u, S_LISTINVITE);

end;

function listOwner(conn, list, user)
  local l = strlower(list);
  local u = userByName(user);
  local l, err = listByName(conn, l, 1);
  if (not l) then
    send(err, conn, S_ERROR);
    return nil;
  end;


  if (lists[l] == nil) then
    send("That list does not exist!", conn, S_ERROR);
    return nil;
  end;

  if (u == nil) then
    send("No such user.", conn, S_ERROR);
    return nil;
  end;

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

  if (lists[l].owner ~= strlower(conn.realUser)) then
    if (not (conn.privs and strfind(conn.privs, "M", 1, 1))) then
      send("You cannot change the owner of lists you do not own.", conn, S_ERROR);
      return nil;
    end;
  end;

  local n = strlower(u.realUser);
  if ( numberOfListsOwned(n) >= colloquy.listQuota ) then
    send(format("%s has exhausted their list quota, and cannot take ownership.", u.username), conn, S_ERROR);
    return nil;
  end;

  local m, i, v = lists[l].members;
  for i, v in m do
    if (type(v) == "string" and (v == n)) then
      sendTo(format("%s makes %s the list owner. {%s}", conn.username, u.username, lists[l].listname), getListMembers(l), S_LISTOWNER);
      if (not listIsMember(conn.realUser, l, 1)) then
        send(format("%s makes %s the list owner. {%s}", conn.username, u.username, lists[l].listname), conn, S_LISTOWNER);
      end;

      lists[l].owner = n;
      saveLists();

      return nil;
    end;
  end;

  send("Only a list's members can be made the owner.", conn, S_ERROR);
end;

function listDescription(conn, list, desc)
  local l = strlower(list);
  local l, err = listByName(conn, l, 1);
  if (not l) then
    send(err, conn, S_ERROR);
    return nil;
  end;


  if (lists[l] == nil) then
    send("That list does not exist!", conn, S_ERROR);
    return nil;
  end;

  if not isListMaster(l, conn.realUser) then
    send("You cannot change the descriptions of lists you are not a master of.", conn, S_ERROR);
    return nil;
  end;

  lists[l].description = desc;
  sendTo(format("%s has changed the list's description to '%s'. {%s}", conn.username, desc, lists[l].listname), getListMembers(l), S_LISTDESC);

  if (not listIsMember(conn.realUser, l, 1)) then
    send(format("%s has changed the list's description to '%s'. {%s}", conn.username, desc, lists[l].listname), conn, S_LISTDESC);
  end;

  saveLists();
  
end;

function listLock(conn, list)
  local l = strlower(list);
  local l, err = listByName(conn, l, 1);
  if (not l) then
    send(err, conn, S_ERROR);
    return nil;
  end;


  if (lists[l] == nil) then
    send("That list does not exlist!", conn, S_ERROR);
    return nil;
  end;
  
  if not isListMaster(l, conn.realUser) then
    send("You cannot lock lists that you are not a master of.", conn, S_ERROR);
  end;

  if (lists[l].flags and (strfind(lists[l].flags, "L", 1, 1))) then
    send("That list is already locked.", conn, S_ERROR);
    return nil;
  end;

  if (not lists[l].flags) then lists[l].flags = "" end;

  lists[l].flags = lists[l].flags .. "L";

  sendTo(format("%s has locked the list. {%s}", conn.username, lists[l].listname), getListMembers(l), S_LISTLOCK);
  if (not listIsMember(conn.realUser, l, 1)) then
    send(format("%s has locked the list. {%s}", conn.username, lists[l].listname), conn, S_LISTLOCK);
  end;

  saveLists();

end;

function listUnlock(conn, list)
  local l = strlower(list);
  local l, err = listByName(conn, l, 1);
  if (not l) then
    send(err, conn, S_ERROR);
    return nil;
  end;

  if (lists[l] == nil) then
    send("That list does not exist!", conn, S_ERROR);
    return nil;
  end;

  if not isListMaster(l, conn.realUser) then
    send("You cannot unlock lists that you are not a master of.", conn, S_ERROR);
    return nil;
  end;

  if (lists[l].flags and (not strfind(lists[l].flags, "L", 1, 1))) then
    send("That list is already unlocked.", conn, S_ERROR);
    return nil;
  end;

  if (not lists[l].flags) then lists[l].flags = "" end;
  lists[l].flags = gsub(lists[l].flags, "L", "");

  updateInvitations("%" .. l, "");

  sendTo(format("%s has unlocked the list. {%s}", conn.username, lists[l].listname), getListMembers(l), S_LISTUNLOCK);
  if (not listIsMember(conn.realUser, l, 1)) then
    send(format("%s has unlocked the list. {%s}", conn.username, lists[l].listname), conn, S_LISTUNLOCK);
  end;

  saveLists();

end;

function listEvict(conn, list, user)
  local l = strlower(list);
  local l, err = listByName(conn, l, 1);
  if (not l) then
    send(err, conn, S_ERROR);
    return nil;
  end;


  if (lists[l] == nil) then
    send("That list does not exist!", conn, S_ERROR);
    return nil;
  end;

  if not isListMaster(l, conn.realUser) then
    send("You cannot evict users from lists you are not a master of.", conn, S_ERROR);
    return nil;
  end;

  local m, i, v;
  m = userByName(user);
  if (m == nil) then
    send("No such user.", conn, S_ERROR);
    return nil;
  end;

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

  local mm = strlower(m.username);

  if (mm == lists[l].owner) then
    send("You cannot evict the list owner.", conn, S_ERROR);
    return nil;
  end;

  for i, v in lists[l].members do
    if (type(v) == "string" and v == mm) then
      sendTo(format("%s has evicted %s from the list. {%s}", conn.username, m.username, lists[l].listname), getListMembers(l), S_LISTEVICT);
      if (not listIsMember(conn.realUser, l, 1)) then
        send(format("%s has evicted %s from the list. {%s}", conn.username, m.username, lists[l].listname), conn, S_LISTEVICT);
      end;

      tremove(lists[l].members, i);
      saveLists();
      return nil;
    end;
  end;

  send("User isn't on that list.", conn, S_ERROR);

end;

function listOpen(conn, list)
  local l = strlower(list);
  local l, err = listByName(conn, l, 1);
  if (not l) then
    send(err, conn, S_ERROR);
    return nil;
  end;

  if not isListMaster(l, conn.realUser) then
    send("You cannot open a list that you are not a master of.", conn, S_ERROR);
    return nil;
  end;

  if (strfind(lists[l].flags, "O", 1, 1)) then
    send("That list is already open!", conn, S_ERROR);
    return nil;
  end;

  lists[l].flags = lists[l].flags .. "O";

  sendTo(format("%s has opened the list. {%s}", conn.username, lists[l].listname), getListMembers(l), S_LISTOPEN);
  if (not listIsMember(conn.realUser, l, 1)) then
    send(format("%s has opened the list. {%s}", conn.username, lists[l].listname), conn, S_LISTOPEN);
  end;


  saveLists();

end;

function listPermanent(conn, list)
  local l = strlower(list);
  local l, err = listByName(conn, l, 1);
  if (not l) then
    send(err, conn, S_ERROR);
    return nil;
  end;

  if (not (conn.privs and strfind(conn.privs, "M", 1, 1))) then
    send("Only masters can do that.", conn, S_ERROR);
    return nil;
  end;

  if (strfind(lists[l].flags, "P", 1, 1)) then
    send("That list is already permanent!", conn, S_ERROR);
    return nil;
  end;

  lists[l].flags = lists[l].flags .. "P";

  sendTo(format("%s has made the list permanent. {%s}", conn.username, lists[l].listname), getListMembers(l), S_LISTPERM);
  if (not listIsMember(conn.realUser, l, 1)) then
    send(format("%s has made the list permanent. {%s}", conn.username, lists[l].listname), conn, S_LISTPERM);
  end;
 
  saveLists();

end;

function listUnpermanent(conn, list)
  local l = strlower(list);
  local l, err = listByName(conn, l, 1);
  if (not l) then
    send(err, conn, S_ERROR);
    return nil;
  end;

  if (not (conn.privs and strfind(conn.privs, "M", 1, 1))) then
    send("Only masters can do that.", conn, S_ERROR);
    return nil;
  end;

  if (not strfind(lists[l].flags, "P", 1, 1)) then
    send("That list isn't permanent!", conn, S_ERROR);
    return nil;
  end;

  lists[l].flags = gsub(lists[l].flags, "P", "");

  sendTo(format("%s has made the list non-permanent. {%s}", conn.username, lists[l].listname), getListMembers(l), S_LISTUNPERM);
  if (not listIsMember(conn.realUser, l, 1)) then
    send(format("%s has made the list non-permanent. {%s}", conn.username, lists[l].listname), conn, S_LISTUNPERM);
  end;
 
  saveLists();

end;

function listAnonymous(conn, list)
  local l = strlower(list);
  local l, err = listByName(conn, l, 1);
  if (not l) then
    send(err, conn, S_ERROR);
    return nil;
  end;

  if (not (conn.privs and strfind(conn.privs, "M", 1, 1))) then
    send("Only masters can do that.", conn, S_ERROR);
    return nil;
  end;

  if (strfind(lists[l].flags, "A", 1, 1)) then
    send("That list is already anonymous!", conn, S_ERROR);
    return nil;
  end;

  lists[l].flags = lists[l].flags .. "A";

  sendTo(format("%s has made the list anonymous. {%s}", conn.username, lists[l].listname), getListMembers(l), S_LISTANON);
  if (not listIsMember(conn.realUser, l, 1)) then
    send(format("%s has made the list anonymous. {%s}", conn.username, lists[l].listname), conn, S_LISTANON);
  end;
 
  saveLists();

end;

function listUnanonymous(conn, list)
  local l = strlower(list);
  local l, err = listByName(conn, l, 1);
  if (not l) then
    send(err, conn, S_ERROR);
    return nil;
  end;

  if (not (conn.privs and strfind(conn.privs, "M", 1, 1))) then
    send("Only masters can do that.", conn, S_ERROR);
    return nil;
  end;

  if (not strfind(lists[l].flags, "A", 1, 1)) then
    send("That list isn't anonymous!", conn, S_ERROR);
    return nil;
  end;

  lists[l].flags = gsub(lists[l].flags, "A", "");

  sendTo(format("%s has made the list non-anonymous. {%s}", conn.username, lists[l].listname), getListMembers(l), S_LISTUNANON);
  if (not listIsMember(conn.realUser, l, 1)) then
    send(format("%s has made the list non-anonymous. {%s}", conn.username, lists[l].listname), conn, S_LISTUNANON);
  end;
 
  saveLists();

end;

function listReadOnly(conn, list)
  local l = strlower(list);
  local l, err = listByName(conn, l, 1);
  if (not l) then
    send(err, conn, S_ERROR);
    return nil;
  end;

  if not isListMaster(l, conn.realUser) then
    send("You cannot make a list read-only that you are not a master of.", conn, S_ERROR);
    return nil;
  end;

  if (strfind(lists[l].flags, "R", 1, 1)) then
    send("That list is already read-only!", conn, S_ERROR);
    return nil;
  end;

  lists[l].flags = lists[l].flags .. "R";

  sendTo(format("%s has made the list read-only. {%s}", conn.username, lists[l].listname), getListMembers(l), S_LISTREAD);
  if (not listIsMember(conn.realUser, l, 1)) then
    send(format("%s has made the list read-only. {%s}", conn.username, lists[l].listname), conn, S_LISTREAD);
  end;

  saveLists();

end;

function listReadWrite(conn, list)
  local l = strlower(list);
  local l, err = listByName(conn, l, 1);
  if (not l) then
    send(err, conn, S_ERROR);
    return nil;
  end;

  if not isListMaster(l, conn.realUser) then
    send("You cannot make a list read-write that you are not a master of.", conn, S_ERROR);
    return nil;
  end;

  if (not strfind(lists[l].flags, "R", 1, 1)) then
    send("That list isn't read-only!", conn, S_ERROR);
    return nil;
  end;

  lists[l].flags = gsub(lists[l].flags, "R", "");

  sendTo(format("%s has made the list read-write. {%s}", conn.username, lists[l].listname), getListMembers(l), S_LISTUNREAD);
  if (not listIsMember(conn.realUser, l, 1)) then
    send(format("%s has made the list read-write. {%s}", conn.username, lists[l].listname), conn, S_LISTUNREAD);
  end;

  saveLists();

end;


function listClose(conn, list)
  local l = strlower(list);
  local l, err = listByName(conn, l, 1);
  if (not l) then
    send(err, conn, S_ERROR);
    return nil;
  end;

  if not isListMaster(l, conn.realUser) then
    send("You cannot close a list that you are not a master of.", conn, S_ERROR);
    return nil;
  end;

  if (not strfind(lists[l].flags, "O", 1, 1)) then
    send("That list is already closed!", conn, S_ERROR);
    return nil;
  end;

  lists[l].flags = gsub(lists[l].flags, "O", "");

  sendTo(format("%s has closed the list. {%s}", conn.username, lists[l].listname), getListMembers(l), S_LISTCLOSE);
  if (not listIsMember(conn.realUser, l, 1)) then
    send(format("%s has closed the list. {%s}", conn.username, lists[l].listname), conn, S_LISTCLOSE);
  end;
   
  saveLists();

end;

function listPause(conn, list)
  if (not list) then
    if (not conn.pausedLists) then
      send("You have no paused lists.", conn, S_DONE);
      return nil;
    else
      send("Paused lists: " .. conn.pausedLists, conn, S_DONE);
    end;
    return nil;
  end;

  local llist = strlower(list);
  local rlist, err = listByName(conn, list);

  if (not rlist) then
    send(err, conn, S_ERROR);
    return nil;
  end;

  if (not listIsMember(conn.realUser, rlist)) then
    send("You cannot pause lists you are not a member of.", conn, S_ERROR);
    return nil;
  end;

  rlist = lists[rlist].listname;

  if (conn.pausedLists and strfind(conn.pausedLists, rlist .. " ", 1, 1)) then
    send("You already have that list paused!", conn, S_ERROR);
    return nil;
  end;

  if (not conn.pausedLists) then
    conn.pausedLists = "";
  end;

  conn.pausedLists = conn.pausedLists .. rlist .. " ";
  send("Paused list " .. rlist .. ".", conn, S_DONE);
end;

function listUnpause(conn, list)
  if (not list) then
    if (not conn.pausedLists) then
      send("You have no paused lists.", conn, S_DONE);
      return nil;
    else
      send("Paused lists: " .. conn.pausedLists, conn, S_DONE);
    end;
    return nil;
  end;

  local llist = strlower(list);
  local rlist, err = listByName(conn, list);

  if (not rlist) then
    send(err, conn, S_ERROR);
    return nil;
  end;

  if (not listIsMember(conn.realUser, rlist)) then
    send("You cannot unpause lists you are not a member of.", conn, S_ERROR);
    return nil;
  end;

  rlist = lists[rlist].listname;

  if (not conn.pausedLists or not strfind(conn.pausedLists, rlist .. " ", 1, 1)) then
    send("You do not have that list paused!", conn, S_ERROR);
    return nil;
  end;

  conn.pausedLists = gsub(conn.pausedLists, rlist .. " ", "");
  if (conn.pausedLists == "") then
    conn.pausedLists = nil;
  end;

  send("Unpaused list " .. rlist .. ".", conn, S_DONE);
end;

function listRename(conn, oldName, newName)
  if (oldName == nil or newName == nil) then
    send("Usage: .List Rename <list> <newname>", conn, S_ERROR);
    return nil;
  end;

  local oldName = strlower(oldName);
  local l, err = listByName(conn, oldName, 1);
  if (not l) then
    send(err, conn, S_ERROR);
    return nil;
  end;

  oldName = strlower(l)

  if not isListMaster(l, conn.realUser) then
    send("You cannot rename a list that you are not a master of.", conn, S_ERROR);
    return nil;
  end;

  if (lists[strlower(newName)]) then
    send("There is already a list with that name.", conn, S_ERROR);
    return nil;
  end;

  sendTo(format("%s has renamed %%%s to %%%s. {%s}", conn.username, lists[oldName].listname, newName, newName), getListMembers(oldName), S_LISTRENAME);
  if (not listIsMember(conn.realUser, l, 1)) then
    send(format("%s has renamed %%%s to %%%s. {%s}", conn.username, lists[oldName].listname, newName, newName), conn, S_LISTRENAME);
  end;


  lists[strlower(newName)] = lists[oldName];
  lists[oldName] = nil;
  lists[strlower(newName)].listname = newName;

  saveLists();

end;

function listMaster(conn, list, user)
  local l, err = listByName(conn, list, 1);
  if (not l) then
    send(err, conn, S_ERROR);
    return nil;
  end;

  if not isListMaster(l, conn.realUser) then
    send("You cannot make somebody a master on a list you are not a master of.", conn, S_ERROR);
    return nil;
  end;

  local m, i, v;
  m = userByName(user);
  if (m == nil) then
    send("No such user.", conn, S_ERROR);
    return nil;
  end;

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

  local mm = strlower(m.username);

  if isListMaster(l, m.realUser) then
    send(format("%s is already a master of that list.", m.username), conn, S_ERROR);
    return nil;
  end;

  if not listIsMember(m.realUser, l) then
    send(format("%s is not on that list.", m.username), conn, S_ERROR);
    return nil;
  end

  if not lists[l].masters then
    lists[l].masters = {};
  end;

  tinsert(lists[l].masters, strlower(m.realUser));
  sendTo(format("%s has made %s a list master. {%s}", conn.username, m.username, lists[l].listname), getListMembers(l), S_LISTMASTER);
  if not listIsMember(conn.realUser, l, 1) then
    send(format("%s has made %s a list master. {%s}", conn.username, m.username, lists[l].listname), conn, S_LISTMASTER);
  end;

  saveLists();

end;

function listUnmaster(conn, list, user)
  local l, err = listByName(conn, list, 1);
  if (not l) then
    send(err, conn, S_ERROR);
    return nil;
  end;

  if not isListMaster(l, conn.realUser) then
    send("You cannot unmaster somebody on a list you are not a master of.", conn, S_ERROR);
    return nil;
  end;

  local m, i, v;
  m = userByName(user);
  if (m == nil) then
    send("No such user.", conn, S_ERROR);
    return nil;
  end;

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

  local mm = strlower(m.username);

  if not isListMaster(l, m.realUser) then
    send(format("%s is not a master of that list.", m.username), conn, S_ERROR);
    return nil;
  end;

  if not listIsMember(m.realUser, l) then
    send(format("%s is not on that list.", m.username), conn, S_ERROR);
    return nil;
  end

  for i, v in lists[l].masters or {} do
    if v == strlower(m.realUser) then
      tremove(lists[l].masters, i);
      break;
    end;
  end;
  
  sendTo(format("%s has unmastered %s. {%s}", conn.username, m.username, lists[l].listname), getListMembers(l), S_LISTMASTER);
  if not listIsMember(conn.realUser, l, 1) then
    send(format("%s has unmastered %s. {%s}", conn.username, m.username, lists[l].listname), conn, S_LISTMASTER);
  end;

  saveLists();

end;

function listHasPaused(conn, list)
  if (conn.pausedLists) then
    return strfind(conn.pausedLists, lists[strlower(list)].listname .. " ", 1, 1);
  else
    return nil;
  end;
end;

function saveLists()
  local f = openfile(colloquy.listsFile, "w");
  local i, v;

  write(f, "lists = {\n");

  for i, v in lists do
    if (type(v) == "table") then
      write(f, "  ['" .. i .. "'] = {\n");
      write(f, '    listname = "' .. v.listname .. '",\n');
      write(f, '    description = ' .. format("%q", v.description) .. ',\n');
      write(f, '    flags = "' .. v.flags .. '",\n');
      write(f, '    owner = "' .. v.owner .. '",\n');
      write(f, '    created = "' .. v.created .. '",\n');
      write(f, '    used = ' .. (v.used or secs) .. ',\n');
      write(f, '    members = {\n');
      local j;

      for j = 1,getn(v.members) do
        write(f, '      "' .. v.members[j] .. '"');
        if (j ~= getn(v.members)) then
          write(f, ",\n");
        end;
      end;

      write(f, '\n    },\n');

      write(f, '    masters = {\n');
      for j = 1,getn(v.masters or {}) do
        write(f, '      "' .. v.masters[j] .. '"');
        if (j ~= getn(v.masters)) then
          write(f, ",\n");
        end;
      end;

      write(f, '\n    },\n');

      write(f, '  },\n');
    end;
  end;
  write(f, "};\n");
  closefile(f);
end;