<?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.22"
   >
</plugin>

<aliases>
  <alias match="^\s*atcp\s+debug\s*$"
         enabled="y" regexp="y" ignore_case="y"
         send_to="12" sequence="100" script="set_debug" />
</aliases>

<variables>
  <variable name="Library"><![CDATA[
    local atcp = {ID = "7c08e2961c5e20e5bdbf7fc5", nil, nil, nil}

    atcp.EnableModule = function (...)
      CallPlugin(atcp.ID, "EnableModule", table.concat({...}, ","))
    end

    atcp.Send = function (msg)
      CallPlugin(atcp.ID, "SendATCP", tostring(msg))
    end

    atcp.Filter = function(id, msg, text)
      if id == atcp.ID then
        local sep = text:find("|") or #text+1
        id, msg, text = "ATCP", text:sub(1, sep-1), text:sub(sep+1)
      end

      return id, msg, text
    end

    return atcp
  ]]></variable>
</variables>

<script><![CDATA[
-- this script is directly derived from Trevize's ATCP plugin,
-- and by extension Keldar's.

local 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", -- ???
}

local client_id =  "MUSHclient " .. Version()
local nexus_opts = {}
local commands = {}

local enabled_modules_info = ""

local 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 and not tonumber(average) then
    error("Invalid argument 'average' to ping command: '" .. (average or "") .. "'\10")
  end

  return "ping" .. average
end

-- splits an ATCP message into header and content lines
local 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

local parseATCP
do
  -- global, used to store remnants of chopped ATCP messages.
  -- concatenated to the beginning of the next packet.
  local leftovers = ""

  -- matches on an ATCP message, captures the internals
  local 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
end

OnPluginConnect = function()
  nexus_opts = {}
  commands = {}

  -- alert other plugins to EnableModule now.
  BroadcastPlugin(0, "Atcp.Load")

  -- do not authenticate if we're connected to Vadi's System
  if (GetInfo(61) == "127.0.0.1") then
    return
  end

  local msg = "hello " .. client_id .. "\10"
  local line = ""

  for k, v in pairs(nexus_opts) do
    line = line .. k .. ", "
    msg = msg .. k .. " " .. v .. "\10"
  end

  Note("[ATCP]: Modules enabled: " .. line:sub(1, line:len() - 2))
  enabled_modules_info = line

  SendPkt(codes["IAC DO ATCP"] .. codes["IAC SB ATCP"] .. msg .. codes["IAC SE"])
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
    if debugging then Note(msg .. ' ' .. line) end
--    Simulate('[ATCP]: ' .. msg .. ' ' .. line .. '\n')
    BroadcastPlugin(1, msg .. "|" .. line)
  end

  return packet
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
    v = mods[m]
    if v.version ~= nil then
      nexus_opts[m] = v.version

      -- import module commands
      for i, j in pairs(v) do
        if i ~= "version" then
          Note(i)
          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

set_debug = function ()
  debugging = not debugging
end

]]></script>

</muclient>