<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE muclient>

<muclient>
<plugin
   name="ATCP"
   author="Soludra"
   id="7c08e2961c5e20e5bdbf7fc5"
   language="Lua"
   purpose="ATCP data"
   date_written="2008-09-18"
   requires="4.35"
   version="1.0"
   >
</plugin>

<script><![CDATA[
-- this script is directly derived from Trevize's ATCP plugin,
-- and by extension Keldar's.

codes = {
  ["IAC WILL ATCP"] = "\255\251\200", -- server-sent
  ["IAC WONT ATCP"] = "\255\252\200", -- server-sent
  ["IAC DO ATCP"]   = "\255\253\200", -- client-sent, enables ATCP
  ["IAC SB ATCP"]   = "\255\250\200", -- begins an ATCP packet
  ["IAC SE"]        = "\255\240",     -- ends an ATCP packet
  ["IAC WILL EOR"]  = "\255\251\025", -- ???
}

client_id =  "MUSHclient " .. Version()
nexus_opts = {}
commands = {}

mods = {auth        = {version = "1"},
        char_name   = {version = "1"},
        char_vitals = {version = "1"},
        room_brief  = {version = "1"},
        room_exits  = {version = "1"},
        map_display = {version = "1"},
        composer    = {version = "1"},
        keepalive   = {version = "1"},
        topvote     = {version = "1"},
        ping        = {version = "1"},
       }

mods.auth.auth = function(words)
  local response = words[1]

  if type(response) ~= string or not tonumber(response) then
    error("Invalid argument 'response' to auth command: '" .. (response or "") .. "'\10")
  end

  return "auth " .. response .. " " .. client_id
end

mods.composer.olesetbuf = function(words)
  local text = words[1]

  if type(text) ~= string then
    error("Invalid argument 'text' to olesetbuf command: '" .. (text or "") .. "'\10")
  end

  return "olesetbuf\10" .. text
end

mods.keepalive.keepalive = function()
  return "keepalive"
end

mods.topvote.voted = function()
  return "voted"
end

mods.ping.ping = function(words)
  local average = words[1]

  if type(average) ~= string or not tonumber(average) then
    error("Invalid argument 'average' to ping command: '" .. (average or "") .. "'\10")
  end

  return "ping" .. average
end


OnPluginConnect = function()
  -- alert other plugins to EnableModule now.
  Note("Broadcast begin")
  BroadcastPlugin(0, "")
  Note("Broadcast end")
  Note()

  local msg = "hello " .. client_id .. "\10"
  local line = ""

  Note("Available modules (in 'mods'):")
  for k, _ in pairs(mods) do
    Note(k)
  end
  Note()

  for k, v in pairs(nexus_opts) do
    Note(k)
    line = line .. k .. ", "
    msg = msg .. k .. " " .. v .. "\10"
  end

  Note("Modules enabled (in 'nexus_opts'): " .. line:sub(1, line:len() - 2))

  SendPkt(codes["IAC DO ATCP"] .. codes["IAC SB ATCP"] .. msg .. codes["IAC SE"])
end

OnPluginDisconnect = function()
  nexus_opts = {}
  commands = {}
end

OnPluginPacketReceived = function(packet)
  if string.find (packet, codes["IAC WILL ATCP"]) then
    packet = string.gsub (packet, "(.-)" .. codes["IAC WILL ATCP"] .. "(.-)", "%1%2")
  end
  if string.find (packet, codes["IAC WONT ATCP"]) then
    packet = string.gsub (packet, "(.-)" .. codes["IAC WONT ATCP"] .. "(.-)", "%1%2")
  end
  if string.find (packet, codes["IAC WILL EOR"]) then
    packet = string.gsub (packet, "(.-)" .. codes["IAC WILL EOR"] .. "(.-)", "%1%2")
  end

  local packet, atcp, parsed = parseATCP(packet)
  -- pack up the messages and send them to listeners
  for msg, line in pairs(parsed) do
    BroadcastPlugin(1, msg .. "|" .. line)
  end

  return packet
end

-- splits an ATCP message into header and content lines
split_atcp = function(msg)
  -- find a Something.Something marker
  -- (i.e. Room.Brief, Char.Vitals)
                -- match1
  local start, en, head = string.find(msg, "^(%w+%.%w+)")
                                   -- PCRE: ^(\w+\.\w+)

  -- store the message's content
  -- (skips the one whitespace between the header and
  --  the content)
  local cont = string.sub(msg, en+2)

  -- make sure we got anything at all
  if start then
    return head, cont
  else
  -- was a dud message (nothing between the begin/end bytes)
    return nil
  end
end

-- global, used to store remnants of chopped ATCP messages.
-- concatenated to the beginning of the next packet.
leftovers = ""

-- matches on an ATCP message, captures the internals
PAT = codes["IAC SB ATCP"] .. "(.-)" .. codes["IAC SE"]

-- Parse a packet for ATCP messages.
-- Return values:
-- 1: packet stripped of ATCP
-- 2: table of captures from PAT
-- 3: table of {header, content} split captures
parseATCP = function(packet)
  -- tacks on any remainder from the last packet
  local packet = leftovers .. packet
  leftovers = ""

  local atcp, parsed = {}, {}

  -- passed to string.gsub() below
  -- used to handle each capture
  local replace_func = function(cap)
    head, cont = split_atcp(cap, parsed)

    -- make sure we got something
    if head ~= nil then
      parsed[head] = cont
      table.insert(atcp, cap)
    end
    return ""
  end

  -- catch atcp messages, remove inline
  packet = string.gsub (packet, PAT, replace_func)

  -- finds any leftovers from an unfinished ATCP message
  local s,e,msg = string.find (packet, "(\255[^\255]-\255?)$")
  if s then
    if (#msg == 1) or ( (#msg > 2) and string.find (msg, "^\255\250\200") ) 
       or ( (#msg > 1) and string.find (msg, "^\255\250") ) then 
      leftovers = msg
      packet = string.sub (packet, 1, s-1)
    end
  end
  return packet, atcp, parsed
end


EnableModule = function(lines)
  if type(lines) == "string" then
    lines = utils.split(lines, ",")
  elseif type(lines) ~= "table" then
    error("Invalid argument to EnableModule: " .. lines .. "\10")
  end
  -- lines should be a table at this point

  for _, m in ipairs(lines) do
    Note("EnableModule: " .. m)

    v = mods[m]
    if v.version ~= nil then
      nexus_opts[m] = v.version
      Note("  Enabled: " .. m .. " (version " .. nexus_opts[m] .. ")")

      -- import module commands
      for i, j in pairs(v) do
        if i ~= "version" then
          Note("Loading command " .. i .. ": " .. j)
          commands[i] = j
        end
      end
    end
  end
end

SendATCP = function (msg)
  if type(msg) ~= "string" then
    error("Invalid argument 'msg' to SendATCP - expected string")
  end

  words = utils.split(msg, " ")
  func = commands[words[1]]

  if not func then
    error("Invalid command '" .. words[1] .. "'\10  Is the appropriate module loaded?\10")
  end

  table.remove(words, 1)
  SendPkt(codes["IAC SB ATCP"] .. func(words) .. "\10" .. codes["IAC SE"])
end
]]></script> 

</muclient>
