diff --git a/packages/wopr/index.html b/packages/wopr/index.html
new file mode 100644
index 0000000..f06212c
--- /dev/null
+++ b/packages/wopr/index.html
@@ -0,0 +1,14 @@
+
+
+ merf
+
+
+
+
+
Connecting...
+
+
+
diff --git a/packages/wopr/tokens.txt b/packages/wopr/tokens.txt
new file mode 100644
index 0000000..54cc330
--- /dev/null
+++ b/packages/wopr/tokens.txt
@@ -0,0 +1,17 @@
+wopr:xipir-cavud-libux
+wopr:xirak-zoses-gefox
+wopr:xetil-rokak-robyx
+wopr:xofic-belid-civox
+wopr:xelev-lepur-pozyx
+wopr:xocom-bysik-mapix
+wopr:xoroc-hunaz-vyhux
+wopr:xigeh-lydut-vinax
+wopr:xipar-canit-zimyx
+wopr:xocec-lifoz-gasyx
+wopr:xubif-hikig-mocox
+wopr:xoroz-hymaz-fivex
+wopr:xufov-sugig-zecox
+wopr:xocem-dabal-fisux
+wopr:xufez-dofas-tyvyx
+wopr:xosov-tenoh-nebox
+wopr:xopev-zihuk-hubyx
diff --git a/packages/wopr/wopr.cgi b/packages/wopr/wopr.cgi
new file mode 100755
index 0000000..de901bc
--- /dev/null
+++ b/packages/wopr/wopr.cgi
@@ -0,0 +1,824 @@
+#! /usr/bin/lua
+
+require("lfs")
+
+BASEDIR = "/var/tmp/wopr"
+POST_MAX = 512
+
+method = os.getenv("REQUEST_METHOD")
+if (method == "POST") then
+ local CL = tonumber(os.getenv("CONTENT_LENGTH")) or 0
+ if (CL > POST_MAX) then
+ CL = POST_MAX
+ end
+ function getc()
+ if (CL > 0) then
+ CL = CL - 1
+ return io.read(1)
+ else
+ return nil
+ end
+ end
+elseif (method == "GET") then
+ local query = os.getenv("QUERY_STRING") or ""
+ local query_pos = 0
+ local query_len = string.len(query)
+ if (query_len > POST_MAX) then
+ query_len = POST_MAX
+ end
+ function getc()
+ if (query_pos < query_len) then
+ query_pos = query_pos + 1
+ return string.sub(query, query_pos, query_pos)
+ else
+ return nil
+ end
+ end
+else
+ print("405 Method not allowed")
+ print("Allow: GET POST")
+ print("Content-Type: text/plain")
+ print()
+ print("I only do GET and POST.")
+ os.exit(0)
+end
+
+function read_hex()
+ local a = getc() or 0
+ local b = getc() or 0
+
+ return string.char(tonumber(a, 16)*16 + tonumber(b, 16))
+end
+
+function cgi_item()
+ local val = ""
+
+ while (true) do
+ local c = getc()
+ if ((c == nil) or (c == "=") or (c == "&")) then
+ return val
+ elseif (c == "%") then
+ c = read_hex()
+ elseif (c == "+") then
+ c = " "
+ end
+ val = val .. c
+ end
+end
+
+function escape(s)
+ s = string.gsub(s, "&", "&")
+ s = string.gsub(s, "<", "<")
+ s = string.gsub(s, ">", ">")
+ return s
+end
+
+f = {}
+while (true) do
+ local key = cgi_item()
+ local val = cgi_item()
+
+ if (key == "") then
+ break
+ end
+ f[key] = val
+end
+
+
+
+-- lua doesn't seed its PRNG and provides nothing other than
+-- time in seconds. If you're on Windows, go fish.
+do
+ local seed = 0
+ r = io.open("/dev/urandom") or io.open("/dev/random")
+ for i = 1, 4 do
+ seed = seed*256 + string.byte(r:read(1))
+ end
+ r:close()
+ math.randomseed(seed)
+end
+
+-- Get or create Session ID
+sid = f["s"] or ""
+if (sid == "") then
+ sid = string.format("%08x.%04x", os.time(), math.random(65535))
+end
+dirname = BASEDIR .. "/" .. sid
+
+-- Send back a page
+function reply(text, prompt, ...)
+ print("Content-type: text/xml")
+ print()
+ print("")
+ print(" " .. sid .. "")
+ print(" " .. escape(text or "") .. "")
+ print(" " .. escape(prompt or ">") .. "")
+ if (arg[1]) then
+ print(" " .. escape(arg[1]) .. "")
+ end
+ print("")
+ os.exit(0)
+end
+
+
+
+
+--
+-- Database functions
+--
+
+function get(key, ...)
+ local fn = string.format("%s/%s", dirname, key)
+ local f = io.open(fn)
+ if (not f) then
+ return arg[1]
+ else
+ local ret = f:read(4000) or ""
+ f:close()
+ return ret
+ end
+end
+
+function set(key, ...)
+ local fn = string.format("%s/%s", dirname, key)
+ local f
+
+ -- Lazy mkdir to save a few inodes
+ lfs.mkdir(dirname)
+
+ f = io.open(fn, "w")
+ if not f then
+ error("Unable to write " .. fn)
+ end
+ f:write(arg[1] or "")
+ f:close()
+end
+
+function del(key)
+ local fn = string.format("%s/%s", dirname, key)
+ os.remove(fn)
+end
+
+
+--
+-- A string splitter
+--
+function string:split(...)
+ local sep = arg[1] or " "
+ local ret = {}
+ local start = 1
+
+ while true do
+ local first, last = self:find(sep, start)
+ if not first then
+ break
+ end
+ table.insert(ret, self:sub(start, first - 1))
+ start = last + 1
+ end
+ table.insert(ret, self:sub(start))
+
+ return ret
+end
+
+
+-------------------------------------
+--
+-- WOPR-specific stuff
+--
+
+-- A list of all hosts, by name
+hosts_by_name = {}
+
+
+Host = {}
+
+function Host:new(name, ...)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.name = name
+ o.prompt = (arg[1] or ">")
+ o.obuf = {}
+ o.history = {}
+
+ hosts_by_name[name] = o
+ return o
+end
+
+function Host:add_commands(t)
+ local cmds = {}
+ local k, v
+
+ for k,v in pairs(self.commands) do
+ cmds[k] = v
+ end
+ for k,v in pairs(t) do
+ cmds[k] = v
+ end
+ self.commands = cmds
+end
+
+function Host:get(key, ...)
+ return get(self.name .. "." .. key, arg[1])
+end
+
+function Host:set(key, ...)
+ return set(self.name .. "." .. key, arg[1])
+end
+
+function Host:del(key)
+ return del(self.name .. "." .. key)
+end
+
+function Host:writeln(...)
+ table.insert(self.obuf, (arg[1] or ""))
+end
+
+function Host:login(...)
+ set("host", self.name)
+ reply(arg[1] or self.motd, self.prompt)
+end
+
+function Host:cmd_help()
+ local k, v
+ self:writeln("Available commands:")
+ for k,v in pairs(self.commands) do
+ if (v[1]) then
+ local s = string.format("%-15s %s", k, v[1])
+ self:writeln(s)
+ end
+ end
+end
+
+function Host:cmd_history()
+ local k, v
+ for k,v in ipairs(self.history) do
+ self:writeln(string.format("%5d %s", k, v))
+ end
+end
+
+-- Call self:handle(req) and return what to send back
+function Host:handle_request(req)
+ local t = ""
+ local k, v
+
+ self:handle(req)
+
+ for k,v in ipairs(self.obuf) do
+ t = t .. v .. "\n";
+ end
+ reply(t, self.prompt)
+end
+
+-- Handle a request
+function Host:handle(req)
+ self:do_cmd(req)
+end
+
+-- Run a command or return an error
+function Host:do_cmd(req)
+ local argv = req:split()
+ local cmd = self.commands[argv[1]:lower()]
+
+ if (argv[1] == "") then
+ return
+ end
+
+ -- First, update history
+ if self.history then
+ local h = self:get("history")
+ if h then
+ self.history = h:split("\n")
+ end
+ table.insert(self.history, req)
+ self:set("history", table.concat(self.history, "\n"))
+ end
+
+ -- Now run the command
+ if cmd then
+ if cmd[2] then
+ cmd[2](self, argv)
+ else
+ self:writeln("ERROR: no function defined")
+ end
+ else
+ self:writeln("Unknown command")
+ end
+end
+
+-- List of commands, with help string (nil hides from help)
+Host.commands = {
+ ["?"] = {nil, Host.cmd_help},
+ ["help"] = {"List available commands", Host.cmd_help},
+ ["history"] = {"Display command history", Host.cmd_history},
+}
+
+
+--
+-- Login screen
+--
+Login = Host:new("login", "Enter 12-digit access code:")
+Login.motd = [[
+
+┃┃┃┏━┃┏━┃┏━┃
+┃┃┃┃ ┃┏━┛┏┏┛
+━━┛━━┛┛ ┛ ┛ 3.0
+War Operations Plan Response
+New Khavistan Ministry of Ministries
+
+This computer system is the property of the government of New Khavistan.
+It is for authorized use only. Any or all uses of this system and all
+files on this system are monitored and logged. By using this system,
+the user consents to such monitoring. Users who do not consent to such
+monitoring will be dispatched with the New Khavistan fiber-optic bullet
+delivery system.
+
+Users should have no expectation of privacy as to any communication on
+or information stored within the system, including but not limited to
+information stored within your brain, DNA, government tooth implants,
+or tinfoil hat.
+
+Unauthorized or improper use of this system may result in gnomes pooping
+in your underpants. By continuing to use this system, you indicate your
+awareness of and consent to these terms and conditions of use. LOG OFF
+IMMEDIATELY if you do not agree to the conditions stated in this
+warning.
+
+]]
+
+function Login:handle(req)
+ if (string.len(req) > 20) then
+ -- Log them in to wopr
+ Wopr:login([[
+FLAGRANT SYSTEM ERROR: Memory segmentation violation
+Returning to command subsystem [wopr:xipir-cavud-libux]
+]])
+ else
+ if (req == "joshua") then
+ self:writeln("wopr:xirak-zoses-gefox")
+ elseif (req ~= "") then
+ self:writeln("Incorrect code")
+ end
+ end
+end
+
+function Login:login(...)
+ -- Since login is the default, we can *unset* host.
+ -- This has the nice property of not allocating any
+ -- storage for people who never make it past the front door.
+ del("host")
+ reply(arg[1] or self.motd, self.prompt)
+end
+
+
+--
+-- Bulletin Board (bb) subsystem
+--
+Bb = Host:new("bb", "[N]ext, (P)rev, (Q)uit, msg#:")
+Bb.posts = {[[
+WOPR operational! ===== administrator ===== Aug 16 2003
+
+Welcome to WOPR system. Authorized by FLD-853, system will be linked
+with all critical New Khavistan technical infrastructure. Mandated by
+FLD 897 will be full compliance by 2007. Waiting time, following
+services may be used: telecommunications, traffic control devices,
+payroll, and strategic missile offensive control.
+
+Finding any problems with this system, simply fill and submit form
+CPW-190. Royal Ministry Of Technology processes all properly-reported
+issues with utmost haste and concern.
+
+::: FLD-711 Restricted Distribution :::
+]],
+[[
+Clock problem ===== administrator ===== Sep 35, 1568
+
+Royal Ministry Of Technology is aware of recent problems with system
+time. We strive to rectify this problem. Thank you for patience with
+issue.
+
+::: FLD-711 Restricted Distribution :::
+]],
+[[
+System Overhaul ===== Krdznyklyk ===== Dec 12, 2003
+
+The entire system is being overhauled to fix security holes exposed by a
+recent attack on our systems. Your presence is requested at an all-hands
+briefing to roll out the new system this afternoon at 1500 hrs.
+]],
+[[
+Drill ===== wopr:xofic-belid-civox ===== Dec 15, 2003
+
+There will be a drill to test our combat readiness today. Make sure you
+are familiar with the proper procedures to complete the task. Follow
+proper drilling procedures.
+]],
+[[
+Passwords ===== administrator ===== Jan 12, 2004
+
+All passwords have been modified to end with character "!", to bring
+WOPR compliant with Fearless Leader Directive 1138 "standards for secure
+passwords". For example, a password once "cascade", is becoming
+"cascade!".
+
+Recent change improves important system resilience against attack from
+enemies. Your gracious understanding and support of New Khavistan is
+appreciated.
+
+::: FLD-711 Restricted Distribution :::
+]],
+[[
+ICBM control ===== administrator ==== Feb 2, 2004
+
+Because of new security protocols in FLD-1205, you must now type,
+"override on" to get access to ICBM commands.
+
+::: FLD-711 Restricted Distribution :::
+]],
+[[
+attcon command ===== administrator ===== Feb 14, 2004
+
+The WOPR system has been updated to include a new command!
+
+Purpose: To make it possible to change the attack condition level for
+all troops in the New Khavistan republic, simple and unified.
+
+Subsystems Affected: Small subset of WOPR subsystems
+
+Usage: Type "attcon" then number pertaining to correct readiness level
+(1-5). Also can get attcon level by only typing attcon.
+
+Expected loss of service: 30 min while WOPR system recycles
+
+::: FLD-711 Restricted Distribution :::
+]],
+[[
+Syistem Adimn ===== ACTION REQUIRED ==== 0573, Dec 32 2004
+
+ACTION REQUIRED
+
+Dear empyoyee,
+
+We have an important message for you form your commanding
+officer. Please click here to veiw the messsage.
+]],
+[[
+Alert: message attack ===== administrator ===== Jun 20, 2004
+
+NOT TO CLICK ON LINK.
+
+Perverted computer attackers from libellous Republic of Dweezil break
+WOPR system security and try to subvert glorious nation with tricksy
+electronic link. NT TO CLICK.
+
+New Khavistan secret infantry being dispatched to deal with computer
+threat from libellous Republic of Dweezil.
+]],
+[[
+Security Breach ===== administrator ===== Jun 21, 2004
+
+Who click link? Now is time to come forward and accept judicious
+punishment from Fearless Leader.
+]],
+[[
+Mandatory Training ===== administrator ===== Jun 24, 2004
+
+Phishing awareness training today at 1500 hrs
+]],
+[[
+FLD-1327 ===== administrator ===== Aug 10, 2004
+
+FLD-1327 extends target date for full WOPR integration until June 22,
+2058. Meantime WOPR subsystem continue to operate.
+
+::: FLD-711 Restricted Distribution :::
+]],
+
+-- Message ID #-5: a snippet of the WOPR command program
+[-1] = "^A^@^@<8B>Uwopr:xetil-rokak-robyx^OMЉËEօuG<84>",
+[-2] = "^A<90><8D>t&^@@<8D>v^@<83>",
+[-3] = "[^_]Ð<8D>t&^@<89>^\$Ѐ^@^@<8B>=^E^H<80>;/<89>",
+[-4] = "<85>^?׃^T1[]Ít&^@^A^@^@^@븋C^D<89>",
+[-5] = [[
+^@^@^@^@^@^@on", n] => set attcon = n
+ msg "attcon set to" n
+["attcon enid"] => set_launch_trigger(1)
+["attcon dennis"] => set_launch_trigger(0)
+["bb"] => call_subsys bb
+["test"] => msg "test out^D$<9F><9B>^E^Hb<84>A<[,[^_]WVS1
+]],
+[-6] = "^@^@1^E^@^@2^E^@^@3^E^@^@4",
+[-7] = "^^F^@^@!^F^@^@$^F^@^@&^F^@^@)",
+[-8] = "^@",
+}
+
+Bb.motd = [[
+ WOPR Message Board
+====================================================
+ [N]ext message
+ (P)revious message
+ (Q)uit
+ Enter message number to jump to that message
+]]
+
+function Bb:read(inc)
+ local msgid = tonumber(self:get("msgid")) or 0
+ msgid = msgid + inc
+ self:jump(msgid)
+end
+
+function Bb:jump(msgid)
+ self:set("msgid", msgid)
+
+ self:writeln("::::::::::::::::::::::::: Message #" .. tostring(msgid))
+ self:writeln()
+ self:writeln(self.posts[msgid])
+end
+
+function Bb:do_cmd(req)
+ local n = tonumber(req)
+ if (req == "") then
+ self:cmd_next()
+ elseif n then
+ self:jump(n)
+ else
+ Host.do_cmd(self, req)
+ end
+end
+
+function Bb:cmd_next(argv)
+ self:read(1)
+end
+
+function Bb:cmd_prev(argv)
+ self:read(-1)
+end
+
+function Bb:cmd_help(argv)
+ self:writeln(self.motd)
+end
+
+function Bb:cmd_quit(argv)
+ Wopr:login()
+end
+
+Bb.commands = {
+ ["?"] = {nil, Bb.cmd_help},
+ ["n"] = {nil, Bb.cmd_next},
+ ["p"] = {nil, Bb.cmd_prev},
+ ["q"] = {nil, Bb.cmd_quit},
+}
+
+--
+-- The WOPR host
+--
+Wopr = Host:new("wopr", "WOPR%")
+Wopr.history = {
+ 'subsys comm',
+ 'exit',
+ 'subsys comm',
+ 'bb',
+ 'subsys comm',
+ 'exit',
+ 'bb',
+ 'subsys comm',
+ 'exit',
+ 'hlep',
+ 'help',
+ 'bb',
+ 'help',
+ 'subsys comm',
+ 'exit',
+}
+
+Wopr.motd = ""
+
+function Wopr:cmd_subsys(argv)
+ local sys = argv[2]
+
+ if not sys then
+ self:writeln("Usage: subsys SYSTEM")
+ elseif sys == "?" then
+ local k, v
+ for k,v in pairs(hosts_by_name) do
+ self:writeln(k)
+ end
+ else
+ h = hosts_by_name[sys]
+ if not h then
+ self:writeln("No such subsystem (? to list)")
+ else
+ h:login()
+ end
+ end
+end
+
+function Wopr:cmd_bb(argv)
+ Bb:login()
+end
+
+function Wopr:attcon()
+ return tonumber(self:get("attcon") or 5)
+end
+
+-- This command should feel really shoddy: it was written
+-- in-house by the New Khavistan Ministry of Technology.
+function Wopr:cmd_attcon(argv)
+ if argv[2] == "enid" then
+ self:writeln("[[[ LAUNCH TRIGGER ENABLED ]]]")
+ self:writeln("wopr:xelev-lepur-pozyx")
+ self:set("launch")
+ elseif argv[2] == "dennis" then
+ self:writeln("[[[ LAUNCH TRIGGER DISABLED ]]]")
+ self:del("launch")
+ elseif argv[2] then
+ local v = tonumber(argv[2]) or 5
+ self:set("attcon", v)
+ self:writeln("attcon set to " .. tostring(v))
+ else
+ self:writeln(tostring(self:attcon()))
+ end
+end
+
+-- Some test code they didn't remove
+function Wopr:cmd_test(argv)
+ self:writeln("test output:")
+ self:writeln(" EIGEN58")
+ self:writeln(" sub_malarkey reached")
+ self:writeln(" DEBUG:453:wopr:xocom-bysik-mapix")
+ self:writeln("$$END")
+end
+
+Wopr:add_commands{
+ ["subsys"] = {"Connect to subsystem", Wopr.cmd_subsys},
+ ["bb"] = {"Read bulletin board", Wopr.cmd_bb},
+ ["attcon"] = {"[Place command description here]", Wopr.cmd_attcon},
+ ["test"] = {nil, Wopr.cmd_test},
+}
+
+--hosts["wopr"] = Wopr
+
+--
+-- Communications subsystem
+--
+Comm = Host:new("comm", "COMSYS>")
+
+Comm.motd = [[
+_____IMPORTANT_____
+
+IBM 3750 used for main switchboard in captiol building is currently
+running at half capacity until relays arrive. Please to remember not
+patching trunks to switch!
+]]
+
+function Comm:cmd_exit(argv)
+ Wopr:login()
+end
+
+function Comm:cmd_status(argv)
+ self:writeln("[Not yet implemented]")
+ self:writeln("wopr:xoroc-hunaz-vyhux")
+end
+
+Comm:add_commands{
+ ["status"] = {"Display phone system status", Comm.cmd_status},
+ ["exit"] = {"Exit this subsystem", Comm.cmd_exit},
+}
+
+
+--
+-- Missile subsystem
+--
+Smoc = Host:new("smoc", "[SMOC]")
+Smoc.motd = [[
+
+_______________VERY IMPORTANT READ_______________
+
+Ministry of Weapons replacing all peanut brittle warheads with bubble
+gum, as mandated by FLD-1492 "Fearless Grandson Peanut Allergy". Launch
+capacity will be reduced until conversions are complete.
+
+::: FLD-711 Restricted Distribution ::: wopr:xigeh-lydut-vinax
+]]
+Smoc.authcode = "CPE-1704-TKS"
+Smoc.inventory = {
+ "ready", "offline", "offline", "ready",
+ "offline", "offline", "offline", "offline",
+ "offline", "offline", "FileNotFound", "ready",
+ [-1] = "program_invocation_short_name^@realm^@",
+ [-2] = "^@^@^@^@^@^@^@^@^@^R^@^@^@3",
+ [-3] = "%L^D^Hhx^@^@^@",
+ [-4] = "^D^H^G^P^@^@P",
+ [-5] = "^P<8B>=^D^H",
+ [-6] = "WVS<83>\<8B>E^L<8B><8B>U^P",
+ [-7] = "Y^@^@" .. Smoc.authcode .. "^@get_launch_trigger^@",
+ [-8] = "^@",
+ [-9] = "^@",
+ [-10] = "^@",
+ [-11] = "^@wopr:xipar-canit-zimyx^@",
+ [-12] = "^@",
+ [-13] = "^@",
+ [-14] = "^@",
+ [-15] = "^@",
+ [-16] = "^@",
+}
+
+function Smoc:login()
+ if self:get("nuked") then
+ Wopr:login("*** LINK DOWN\n*** CONNECTION REFUSED")
+ else
+ Host.login(self)
+ end
+end
+
+function Smoc:cmd_exit(argv)
+ Wopr:login()
+end
+
+function Smoc:cmd_status(argv)
+ local n = tonumber(argv[2])
+ if not n then
+ local k, v, max
+ local ready = 0
+ for k,v in ipairs(self.inventory) do
+ if (v == "ready") then
+ ready = ready + 1
+ end
+ max = k
+ end
+ self:writeln(("%d total, %d ready"):format(max, ready))
+ self:writeln("Use \"status #\" to check status of individual missiles")
+ else
+ self:writeln(("---- Missile #%d Summary ----"):format(n))
+ self:writeln("Type: SS-256 SCUMM")
+ self:writeln("Location: Fearless Missile Silo #1 (-44.76,-120.66)")
+ self:writeln("Status: " .. (self.inventory[n] or "(null)"))
+ end
+end
+
+function Smoc:cmd_authorize(argv)
+ if not Wopr:get("launch") then
+ self:writeln("ERROR: Launch trigger disabled.")
+ elseif (argv[2] ~= self.authcode) then
+ self:writeln("Invalid authorization code.")
+ else
+ self:writeln("Authorization code accepted.")
+ self:writeln("wopr:xocec-lifoz-gasyx")
+ self:set("auth")
+ end
+end
+
+function Smoc:cmd_launch(argv)
+ local n = tonumber(argv[2])
+ local lat = tonumber(argv[3])
+ local lon = tonumber(argv[4])
+
+ if Wopr:attcon() > 1 then
+ self:writeln("ERROR: Missiles may only be launched during times of war.")
+ elseif not self:get("auth") then
+ self:writeln("ERROR: Not authorized")
+ elseif (not n) then
+ self:writeln("Usage: launch # LAT LONG")
+ elseif (not lat) or (not lon) then
+ self:writeln("ERROR: Invalid coordinates supplied")
+ elseif (self.inventory[n] == "offline") then
+ self:writeln("ERROR: Missile currently off-line")
+ elseif (n < 1) then
+ self:writeln("ERROR: No such missile")
+ else
+ self:writeln(("Launching to (%f,%f)..."):format(lat, lon))
+ self:writeln("wopr:xubif-hikig-mocox")
+ if (lat ~= -44.76) or (lon ~= -120.66) then
+ self:writeln("ERROR: No propulsion system attached")
+ elseif (self.inventory[n] ~= "FileNotFound") then
+ self:writeln("ERROR: Triggering device not installed")
+ else
+ self:set("nuked")
+ Wopr:login("Detonating warhead...\nwopr:xoroz-hymaz-fivex wopr:xufov-sugig-zecox wopr:xocem-dabal-fisux wopr:xufez-dofas-tyvyx\s*** CONNECTION TERMINATED")
+ end
+ end
+end
+
+Smoc:add_commands{
+ ["status"] = {"Check missile status", Smoc.cmd_status},
+ ["launch"] = {"Launch missile", Smoc.cmd_launch},
+ ["authorize"] = {"Set authorization code", Smoc.cmd_authorize},
+ ["exit"] = {"Exit to WOPR", Smoc.cmd_exit},
+}
+
+
+function main()
+ if (not f["s"]) or (f["s"] == "") then
+ Login:login()
+ else
+ local h = hosts_by_name[get("host")] or Login
+ txt, prompt = h:handle_request(f["v"] or "")
+ end
+end
+
+function err(msg)
+ reply("", "A>", msg .. " wopr:xosov-tenoh-nebox\n\n" .. debug.traceback())
+end
+
+xpcall(main, err)
diff --git a/packages/wopr/wopr.css b/packages/wopr/wopr.css
new file mode 100644
index 0000000..76afe27
--- /dev/null
+++ b/packages/wopr/wopr.css
@@ -0,0 +1,32 @@
+body {
+ background: #000;
+ color: #0f0;
+ font-family: monospace;
+ margin: 0;
+}
+pre {
+ margin: 0;
+ padding: 0;
+}
+form {
+ padding: 0;
+ margin: 0;
+}
+input {
+ font-family: monospace;
+ font-weight: normal;
+ font-size: inherit;
+ background-color: #222;
+ color: #0f0;
+ border: 0;
+ margin: 0;
+ padding: 0;
+ width: 70%;
+}
+input:focus {
+ background-color: #000;
+}
+.error {
+ background-color: #f00;
+ color: #000;
+}
\ No newline at end of file
diff --git a/packages/wopr/wopr.js b/packages/wopr/wopr.js
new file mode 100755
index 0000000..b8948d0
--- /dev/null
+++ b/packages/wopr/wopr.js
@@ -0,0 +1,74 @@
+sessid = "";
+
+function go() {
+ input = document.getElementById("input");
+ output = document.getElementById("output");
+ prompt = document.getElementById("prompt");
+ val = input.value;
+
+ r = new XMLHttpRequest();
+
+ function statechange() {
+ if (r.readyState == 4) {
+ doc = r.responseXML;
+ resp_txt = doc.getElementsByTagName("response")[0].textContent;
+ prompt_txt = doc.getElementsByTagName("prompt")[0].textContent;
+ error = doc.getElementsByTagName("error")[0];
+
+ if (! sessid) {
+ sessid = doc.getElementsByTagName("sessionid")[0].textContent;
+ output.textContent += "Connected with session ID " + sessid + ".\n";
+ }
+
+ if (resp_txt) {
+ if (resp_txt.charAt(resp_txt.length-1) != "\n") {
+ resp_txt += "\n";
+ }
+ output.textContent += resp_txt;
+ }
+ if (error) {
+ e = document.createElement('div');
+ e.className = 'error';
+ e.textContent = error.textContent + "\n";
+ output.appendChild(e);
+ }
+ if (prompt_txt) {
+ prompt.textContent = prompt_txt;
+ }
+
+ prompt.style.display = "inline";
+ }
+ document.body.scrollTop = document.body.scrollHeight;
+ input.focus();
+ }
+
+ // Calculate this before screwing with stuff
+ data = ("s=" + sessid + '&v=' + escape(val));
+
+ // Add prompt and input text to output. This instantly displays the
+ // text so you know you hit enter, while providing a slight delay in
+ // results, like the server is "working on" the request.
+ output.textContent += prompt.textContent + " " + val + "\n";
+ input.value = "";
+ input.focus(); // Maybe prevent color flashes
+
+ setTimeout(statechange, 1);
+ if (val == "@sessid") {
+ output.textContent += sessid + "\n";
+ } else if (val[0] == ":") {
+ code = val.substr(1, val.length - 1);
+ output.textContent += "==> " + eval(code) + "\n";
+ } else {
+ prompt.style.display = "none";
+ r.onreadystatechange = statechange;
+ r.open("POST", "wopr.cgi");
+
+ r.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
+ r.setRequestHeader("Content-length", data.length);
+ r.send(data);
+ }
+
+ return false;
+}
+
+window.onload = go;