ldd-CC/sysmail.lua

1529 lines
40 KiB
Lua

--[[
sysMail
by LDDestroier
Make sure that the server has the keys of all clients!
To do:
+ update read prompt
+ implement auto-update
--]]
local mainPath = ".sysmail" -- where everything is based
local yourID = os.getComputerID() -- duhhhhh
local onlyUseWiredModems = false -- if true, will refuse to use wireless modems
local defaultTimer = 3 -- will wait this amount of seconds for a server response
local maximumMailLines = 16 -- will cap all emails to this amount of lines
local scr_x, scr_y = term.getSize()
local config = {
channel = 1024,
keyPath = fs.combine(mainPath, "keys"),
mailPath = fs.combine(mainPath, "mail"),
apiPath = fs.combine(mainPath, "api"),
nameFile = fs.combine(mainPath, "names"),
attachmentPath = "attachments"
}
--
local getTableLength = function(tbl)
local output = 0
for k,v in pairs(tbl) do
output = output + 1
end
return output
end
-- used for picking attachments
local lddfm = {scroll = 0, ypaths = {}}
lddfm.scr_x, lddfm.scr_y = term.getSize()
lddfm.setPalate = function(_p)
if type(_p) ~= "table" then _p = {} end
lddfm.p = { --the DEFAULT color palate
bg = _p.bg or colors.gray, -- whole background color
d_txt = _p.d_txt or colors.yellow, -- directory text color
d_bg = _p.d_bg or colors.gray, -- directory bg color
f_txt = _p.f_txt or colors.white, -- file text color
f_bg = _p.f_bg or colors.gray, -- file bg color
p_txt = _p.p_txt or colors.black, -- path text color
p_bg = _p.p_bg or colors.lightGray, -- path bg color
close_txt = _p.close_txt or colors.gray, -- close button text color
close_bg = _p.close_bg or colors.lightGray,-- close button bg color
scr = _p.scr or colors.lightGray, -- scrollbar color
scrbar = _p.scrbar or colors.gray, -- scroll tab color
}
end
lddfm.setPalate()
lddfm.foldersOnTop = function(floop,path)
local output = {}
for a = 1, #floop do
if fs.isDir(fs.combine(path,floop[a])) then
table.insert(output,1,floop[a])
else
table.insert(output,floop[a])
end
end
return output
end
lddfm.filterFileFolders = function(list,path,_noFiles,_noFolders,_noCD,_doHidden)
local output = {}
for a = 1, #list do
local entry = fs.combine(path,list[a])
if fs.isDir(entry) then
if entry == ".." then
if not (_noCD or _noFolders) then table.insert(output,list[a]) end
else
if not ((not _doHidden) and list[a]:sub(1,1) == ".") then
if not _noFolders then table.insert(output,list[a]) end
end
end
else
if not ((not _doHidden) and list[a]:sub(1,1) == ".") then
if not _noFiles then table.insert(output,list[a]) end
end
end
end
return output
end
lddfm.isColor = function(col)
for k,v in pairs(colors) do
if v == col then
return true, k
end
end
return false
end
lddfm.clearLine = function(x1,x2,_y,_bg,_char)
local cbg, bg = term.getBackgroundColor()
local x,y = term.getCursorPos()
local sx,sy = term.getSize()
if type(_char) == "string" then char = _char else char = " " end
if type(_bg) == "number" then
if lddfm.isColor(_bg) then bg = _bg
else bg = cbg end
else bg = cbg end
term.setCursorPos(x1 or 1, _y or y)
term.setBackgroundColor(bg)
if x2 then --it pains me to add an if statement to something as simple as this
term.write((char or " "):rep(x2-x1))
else
term.write((char or " "):rep(sx-(x1 or 0)))
end
term.setBackgroundColor(cbg)
term.setCursorPos(x,y)
end
lddfm.render = function(_x1,_y1,_x2,_y2,_rlist,_path,_rscroll,_canClose,_scrbarY)
local px,py = term.getCursorPos()
local x1, x2, y1, y2 = _x1 or 1, _x2 or lddfm.scr_x, _y1 or 1, _y2 or lddfm.scr_y
local rlist = _rlist or {"Invalid directory."}
local path = _path or "And that's terrible."
ypaths = {}
local rscroll = _rscroll or 0
for a = y1, y2 do
lddfm.clearLine(x1,x2,a,lddfm.p.bg)
end
term.setCursorPos(x1,y1)
term.setTextColor(lddfm.p.p_txt)
lddfm.clearLine(x1,x2+1,y1,lddfm.p.p_bg)
term.setBackgroundColor(lddfm.p.p_bg)
term.write(("/"..path):sub(1,x2-x1))
for a = 1,(y2-y1) do
if rlist[a+rscroll] then
term.setCursorPos(x1,a+(y1))
if fs.isDir(fs.combine(path,rlist[a+rscroll])) then
lddfm.clearLine(x1,x2,a+(y1),lddfm.p.d_bg)
term.setTextColor(lddfm.p.d_txt)
term.setBackgroundColor(lddfm.p.d_bg)
else
lddfm.clearLine(x1,x2,a+(y1),lddfm.p.f_bg)
term.setTextColor(lddfm.p.f_txt)
term.setBackgroundColor(lddfm.p.f_bg)
end
term.write(rlist[a+rscroll]:sub(1,x2-x1))
ypaths[a+(y1)] = rlist[a+rscroll]
else
lddfm.clearLine(x1,x2,a+(y1),lddfm.p.bg)
end
end
local scrbarY = _scrbarY or math.ceil( (y1+1)+( (_rscroll/(#_rlist-(y2-(y1+1))))*(y2-(y1+1)) ) )
for a = y1+1, y2 do
term.setCursorPos(x2,a)
if a == scrbarY then
term.setBackgroundColor(lddfm.p.scrbar)
else
term.setBackgroundColor(lddfm.p.scr)
end
term.write(" ")
end
if _canClose then
term.setCursorPos(x2-4,y1)
term.setTextColor(lddfm.p.close_txt)
term.setBackgroundColor(lddfm.p.close_bg)
term.write("close")
end
term.setCursorPos(px,py)
return scrbarY
end
lddfm.coolOutro = function(x1,y1,x2,y2,_bg,_txt,char)
local cx, cy = term.getCursorPos()
local bg, txt = term.getBackgroundColor(), term.getTextColor()
term.setTextColor(_txt or colors.white)
term.setBackgroundColor(_bg or colors.black)
local _uwah = 0
for y = y1, y2 do
for x = x1, x2 do
_uwah = _uwah + 1
term.setCursorPos(x,y)
term.write(char or " ")
if _uwah >= math.ceil((x2-x1)*1.63) then sleep(0) _uwah = 0 end
end
end
term.setTextColor(txt)
term.setBackgroundColor(bg)
term.setCursorPos(cx,cy)
end
lddfm.scrollMenu = function(amount,list,y1,y2)
if #list >= y2-y1 then
lddfm.scroll = lddfm.scroll + amount
if lddfm.scroll < 0 then
lddfm.scroll = 0
end
if lddfm.scroll > #list-(y2-y1) then
lddfm.scroll = #list-(y2-y1)
end
end
end
lddfm.makeMenu = function(_x1,_y1,_x2,_y2,_path,_noFiles,_noFolders,_noCD,_noSelectFolders,_doHidden,_p,_canClose)
if _noFiles and _noFolders then
return false, "C'mon, man..."
end
if _x1 == true then
return false, "arguments: x1, y1, x2, y2, path, noFiles, noFolders, noCD, noSelectFolders, doHidden, palate, canClose" -- a little help
end
lddfm.setPalate(_p)
local path, list = _path or ""
lddfm.scroll = 0
local _pbg, _ptxt = term.getBackgroundColor(), term.getTextColor()
local x1, x2, y1, y2 = _x1 or 1, _x2 or lddfm.scr_x, _y1 or 1, _y2 or lddfm.scr_y
local keysDown = {}
local _barrY
while true do
list = lddfm.foldersOnTop(lddfm.filterFileFolders(fs.list(path),path,_noFiles,_noFolders,_noCD,_doHidden),path)
if (fs.getDir(path) ~= "..") and not (_noCD or _noFolders) then
table.insert(list,1,"..")
end
_res, _barrY = pcall( function() return lddfm.render(x1,y1,x2,y2,list,path,lddfm.scroll,_canClose) end)
if not _res then
error(_barrY)
end
local evt = {os.pullEvent()}
if evt[1] == "mouse_scroll" then
lddfm.scrollMenu(evt[2],list,y1,y2)
elseif evt[1] == "mouse_click" then
local butt,mx,my = evt[2],evt[3],evt[4]
if (butt == 1 and my == y1 and mx <= x2 and mx >= x2-4) and _canClose then
--lddfm.coolOutro(x1,y1,x2,y2)
term.setTextColor(_ptxt) term.setBackgroundColor(_pbg)
return false
elseif ypaths[my] and (mx >= x1 and mx < x2) then --x2 is reserved for the scrollbar, breh
if fs.isDir(fs.combine(path,ypaths[my])) then
if _noCD or butt == 3 then
if not _noSelectFolders or _noFolders then
--lddfm.coolOutro(x1,y1,x2,y2)
term.setTextColor(_ptxt) term.setBackgroundColor(_pbg)
return fs.combine(path,ypaths[my])
end
else
path = fs.combine(path,ypaths[my])
lddfm.scroll = 0
end
else
term.setTextColor(_ptxt) term.setBackgroundColor(_pbg)
return fs.combine(path,ypaths[my])
end
end
elseif evt[1] == "key" then
keysDown[evt[2]] = true
if evt[2] == keys.enter and not (_noFolders or _noCD or _noSelectFolders) then --the logic for _noCD being you'd normally need to go back a directory to select the current directory.
--lddfm.coolOutro(x1,y1,x2,y2)
term.setTextColor(_ptxt) term.setBackgroundColor(_pbg)
return path
end
if evt[2] == keys.up then
lddfm.scrollMenu(-1,list,y1,y2)
elseif evt[2] == keys.down then
lddfm.scrollMenu(1,list,y1,y2)
end
if evt[2] == keys.pageUp then
lddfm.scrollMenu(y1-y2,list,y1,y2)
elseif evt[2] == keys.pageDown then
lddfm.scrollMenu(y2-y1,list,y1,y2)
end
if evt[2] == keys.home then
lddfm.scroll = 0
elseif evt[2] == keys["end"] then
if #list > (y2-y1) then
lddfm.scroll = #list-(y2-y1)
end
end
if evt[2] == keys.h then
if keysDown[keys.leftCtrl] or keysDown[keys.rightCtrl] then
_doHidden = not _doHidden
end
elseif _canClose and (evt[2] == keys.x or evt[2] == keys.q or evt[2] == keys.leftCtrl) then
--lddfm.coolOutro(x1,y1,x2,y2)
term.setTextColor(_ptxt) term.setBackgroundColor(_pbg)
return false
end
elseif evt[1] == "key_up" then
keysDown[evt[2]] = false
end
end
end
local alphasort = function(tbl)
table.sort(tbl, function(a,b)
if type(a) == "table" then
return string.lower(a.time) > string.lower(b.time)
else
return string.lower(a) > string.lower(b)
end
end)
return tbl
end
local readFile = function(path)
if fs.exists(path) then
local file = fs.open(path, "r")
local contents = file.readAll()
file.close()
return contents
else
return nil
end
end
local writeFile = function(path, contents)
if fs.isReadOnly(path) then
return false
else
local file = fs.open(path, "w")
file.write(contents)
file.close()
return true
end
end
local cwrite = function(text, y)
local cx, cy = term.getCursorPos()
term.setCursorPos(math.floor(scr_x / 2 - #text / 2 + 1), y or cy)
term.write(text)
end
local dialogueBox = function(msg, timeout)
local height = 7
local baseY = scr_y / 2 - height / 2
term.setTextColor(colors.white)
term.setBackgroundColor(colors.gray)
for y = 1, height do
term.setCursorPos(1, (scr_y / 2) - (baseY / 2) + (y - 1))
term.clearLine()
end
cwrite(("="):rep(scr_x), baseY)
cwrite(msg, baseY + height / 2)
cwrite(("="):rep(scr_x), baseY + height - 1)
local evt
local tID = os.startTimer(timeout or 2)
repeat
evt = {os.pullEvent()}
until (evt[1] == "key") or (evt[1] == "timer" and evt[2] == tID)
term.setBackgroundColor(colors.black)
end
local keyList, names = {}, {}
local makeKey = function(ID, key)
return writeFile(fs.combine(config.keyPath, ID), key)
end
local getKey = function(ID)
return readFile(fs.combine(config.keyPath, ID))
end
local readNames = function()
return textutils.unserialize(readFile(config.nameFile) or "{}") or {}
end
local writeNames = function(_names)
return writeFile(config.nameFile, textutils.serialize(_names or names))
end
-- keyList[id] = key
-- names[id] = name
-- get personal key file
keyList[yourID] = ""
if fs.exists(fs.combine(config.keyPath, tostring(yourID))) then
keyList[yourID] = readFile(fs.combine(config.keyPath, tostring(yourID)))
else
for i = 1, 64 do
keyList[yourID] = keyList[yourID] .. string.char(math.random(11, 255))
end
writeFile(fs.combine(config.keyPath, tostring(yourID)), keyList[yourID])
end
local getAllKeys = function()
local list = fs.list(config.keyPath)
local output = {}
for i = 1, #list do
if tonumber(list[i]) then
output[tonumber(list[i])] = getKey(list[i])
end
end
return output
end
names = readNames()
keyList = getAllKeys()
local apiData = {
["aeslua"] = {
path = "aeslua.lua",
url = "https://raw.githubusercontent.com/LDDestroier/CC/master/API/aeslua.lua", -- thanks SquidDev
useLoadAPI = true,
}
}
for name, data in pairs(apiData) do
data.path = fs.combine(config.apiPath, data.path)
if not fs.exists(data.path) then
local net = http.get(data.url)
if net then
local file = fs.open(data.path, "w")
file.write(net.readAll())
file.close()
net.close()
else
error("Could not download " .. name)
end
end
if data.useLoadAPI then
local res = os.loadAPI(data.path)
--error(res)
else
_ENV[name] = dofile(data.path)
end
end
local function interpretArgs(tInput, tArgs)
local output = {}
local errors = {}
local usedEntries = {}
for aName, aType in pairs(tArgs) do
output[aName] = false
for i = 1, #tInput do
if not usedEntries[i] then
if tInput[i] == aName and not output[aName] then
if aType then
usedEntries[i] = true
if type(tInput[i+1]) == aType or type(tonumber(tInput[i+1])) == aType then
usedEntries[i+1] = true
if aType == "number" then
output[aName] = tonumber(tInput[i+1])
else
output[aName] = tInput[i+1]
end
else
output[aName] = nil
errors[1] = errors[1] and (errors[1] + 1) or 1
errors[aName] = "expected " .. aType .. ", got " .. type(tInput[i+1])
end
else
usedEntries[i] = true
output[aName] = true
end
end
end
end
end
for i = 1, #tInput do
if not usedEntries[i] then
output[#output+1] = tInput[i]
end
end
return output, errors
end
local argList = {
["--server"] = false
}
local argData, argErrors = interpretArgs({...}, argList)
local isServer = argData["--server"]
local serverName = argData[1] or "server"
if ccemux and (not peripheral.find("modem")) then
ccemux.attach("top", "wireless_modem")
end
local modem
local getModem = function(doNotPickWireless)
local output, periphList
if not peripheral.find("modem") and ccemux then
ccemux.attach("top", "wireless_modem")
end
for try = 1, 40 do
periphList = peripheral.getNames()
for i = 1, #periphList do
if peripheral.getType(periphList[i]) == "modem" then
output = peripheral.wrap(periphList[i])
if not (doNotPickWireless and output.isWireless()) then
output.open(config.channel)
return output
end
end
end
if try == 1 then
write("Looking for modem...")
else
write(".")
end
sleep(0.15)
end
error("No modems were found after 40 tries. That's as many as four tens. And that's terrible.")
end
-- allowed IDs
local userIDs = {}
-- all data recorded
local DATA = {}
local transmit = function(msg, msgID)
modem = getModem(onlyUseWiredModems)
modem.transmit(config.channel, config.channel, {
msg = msg,
encrypted = false,
msgID = msgID
})
end
local encTransmit = function(msg, msgID, recipient, encID)
modem = getModem(onlyUseWiredModems)
local key = keyList[encID or recipient]
if not key then
error("You do not possess the key of the recipient.")
else
modem.transmit(config.channel, config.channel, {
msg = aeslua.encrypt(key, textutils.serialize(msg)),
encrypted = true,
msgID = msgID,
recipient = recipient
})
end
end
local receive = function(msgID, specifyCommand, encID, timer)
local evt, msg, tID
if timer then
tID = os.startTimer(timer)
end
modem = getModem()
while true do
evt = {os.pullEvent()}
if evt[1] == "modem_message" then
if type(evt[5]) == "table" then
if evt[5].encrypted then
if true then
if encID then
msg = aeslua.decrypt(keyList[encID], evt[5].msg)
else
for id, key in pairs(keyList) do
if msg then break end
if id ~= encID then
msg = aeslua.decrypt(key, evt[5].msg)
end
end
end
if msg then
msg = textutils.unserialize(msg)
end
end
else
msg = evt[5].msg
end
if (not msgID) or (evt[5].msgID == msgID) then
if (not specifyCommand) or (msg.command == specifyCommand) then
return msg, evt[5].encrypted, evt[5].msgID
end
end
end
elseif evt[1] == "timer" and evt[2] == tID then
return nil, nil, nil
end
end
end
local getNameID = function(name)
for k,v in pairs(names) do
if v == name then
return k
end
end
return nil
end
local client = {} -- all client-specific commands
local server = {} -- all server-specific commands
---- ----
---- CLIENT COMMANDS ----
---- ----
-- if you want a super duper secure network, manually enter the server ID into this
client.findServer = function(srv)
local msgID = math.random(1, 2^30)
srv = type(srv) == "number" and srv or getNameID(srv)
assert(tonumber(srv) or (not srv), "invalid server")
transmit({
id = yourID,
command = "find_server"
}, msgID)
local reply, isEncrypted = receive(msgID, "find_server_respond", srv, defaultTimer)
if type(reply) == "table" then
if reply.server then
return reply.server
end
end
return nil
end
-- Registers your ID to a name.
client.register = function(srv, username)
local msgID = math.random(1, 2^30)
assert(srv, "register( server, username )")
srv = type(srv) == "number" and srv or getNameID(srv)
assert(srv, "invalid server")
encTransmit({
id = yourID,
command = "register",
name = username
}, msgID, srv, yourID)
local reply, isEncrypted = receive(msgID, "register_respond", yourID, defaultTimer)
if reply then
return reply.result
else
return false
end
end
-- Gets a list of all registered ID names
client.getNames = function(srv)
local msgID = math.random(1, 2^30)
assert(srv, "getNames( server )")
srv = type(srv) == "number" and srv or getNameID(srv)
assert(srv, "invalid server")
encTransmit({
id = yourID,
command = "get_names"
}, msgID, srv, yourID)
local reply, isEncrypted = receive(msgID, "get_names_respond", yourID, defaultTimer)
if type(reply) == "table" then
return reply.names
else
return nil
end
end
-- Sends an email to a recipient ID.
client.sendMail = function(srv, recipient, subject, message, attachments)
assert(srv, "sendMail( server, recipient, subject, message, attachments )")
srv = type(srv) == "number" and srv or getNameID(srv)
assert(srv, "invalid server")
assert(type(subject) == "string", "invalid subject")
assert(type(message) == "string", "invalid message")
local msgID = math.random(1, 2^30)
if type(recipient) == "string" then
recipient = getNameID(recipient)
end
assert(recipient, "invalid recipient")
encTransmit({
command = "send_mail",
id = yourID,
recipient = recipient,
subject = subject,
message = message,
attachments = attachments
}, msgID, srv, yourID)
local reply, isEncrypted = receive(msgID, "send_mail_respond", yourID, defaultTimer)
if (isEncrypted and type(reply) == "table") then
return reply.result
else
return false
end
end
client.getMail = function(srv)
local msgID = math.random(1, 2^30)
assert(srv, "getMail( server )")
srv = type(srv) == "number" and srv or getNameID(srv)
assert(srv, "invalid server")
encTransmit({
command = "get_mail",
id = yourID,
}, msgID, srv, yourID)
local reply, isEncrypted = receive(msgID, "get_mail_respond", yourID, defaultTimer)
if (isEncrypted and type(reply) == "table") then
if reply.mail then
return alphasort(reply.mail)
end
end
end
client.deleteMail = function(srv, mail)
local msgID = math.random(1, 2^30)
assert(srv, "deleteMail( server, mailEntryNumber )")
srv = type(srv) == "number" and srv or getNameID(srv)
assert(srv, "invalid server")
assert(type(mail) == "number", "invalid mail entry")
encTransmit({
command = "delete_mail",
id = yourID,
mail = mail,
}, msgID, srv, yourID)
local reply, isEncrypted = receive(msgID, "delete_mail_respond", yourID, defaultTimer)
if (isEncrypted and type(reply) == "table") then
return reply.result
else
return false
end
end
---- ----
---- SERVER COMMANDS ----
---- ----
-- check whether or not a name is valid to be used
server.checkValidName = function(name)
if type(name) == "string" then
if #name >= 3 or #name <= 24 then
return true
end
end
return false
end
-- check whether or not an ID is registered
server.checkRegister = function(id)
-- I make the code this stupid looking in case I add other stipulations
if names[id] or getNameID(names[id]) then
return true
else
return false
end
end
server.registerID = function(id, name)
local path = fs.combine(config.mailPath, tostring(id))
if ((not server.checkRegister(name)) or getNameID(name) == id) then
fs.makeDir(path)
names[id] = name
writeNames()
return true, names[id]
else
return false, "name already exists"
end
end
-- records a full email to file
server.recordMail = function(sender, _recipient, subject, message, attachments)
local time = os.epoch("utc")
local recipient
if _recipient == "*" then
recipient = fs.list(config.mailPath)
elseif type(_recipient) ~= "table" then
recipient = {tostring(_recipient)}
end
local msg = textutils.serialize({
sender = sender,
time = time,
read = false,
subject = subject,
message = message,
attachments = attachments
})
local requiredSpace = #msg + 2
if fs.getFreeSpace(config.mailPath) < requiredSpace then
return false, "Cannot write mail, not enough space!"
end
local path, file
for i = 1, #recipient do
server.registerID(recipient[i])
path = fs.combine(config.mailPath, recipient[i])
file = fs.open(fs.combine(path, tostring(time)), "w")
file.write(msg)
file.close()
end
return true
end
-- returns every email in an ID's inbox
server.getMail = function(id)
local output, list = {}, {}
local mails = fs.list(fs.combine(config.mailPath, tostring(id)))
local file
for k,v in pairs(mails) do
list[v] = k
end
for k,v in pairs(list) do
file = fs.open(fs.combine(config.mailPath, "/" .. id .. "/" .. k), "r")
if file then
output[#output + 1] = textutils.unserialize(file.readAll())
file.close()
end
end
return output
end
server.deleteMail = function(id, del)
local mails = alphasort( fs.list(fs.combine(config.mailPath, tostring(id))) )
if mails[del] then
fs.delete(fs.combine(config.mailPath, tostring(id) .. "/" .. mails[del]))
return true
else
return false
end
end
server.setName = function(newName)
if server.checkValidName(newName) then
names[yourID] = newName
end
end
-- receives messages and sends the appropriate response
server.makeServer = function(verbose)
local msg, isEncrypted, msgID
local say = function(text, id)
if verbose then
return print(text .. (id and (" (" .. id .. ")") or ""))
end
end
if verbose then
term.clear()
term.setCursorPos(1,1)
print("Make sure client keys are copied to key folder!")
end
say("SysMail server started.")
while true do
msg, isEncrypted, msgID = receive(nil, nil, nil, 5)
if not msg then
keyList = getAllKeys()
else
if not isEncrypted then
if msg.command == "find_server" then
transmit({
command = msg.command .. "_respond",
server = yourID,
}, msgID, msg.id, yourID)
say("find_server")
end
elseif type(msg.id) == "number" and type(msg.command) == "string" then
if msg.command == "register" then
if (
type(msg.id) == "number" and
type(msg.name) == "string"
) then
local reply
local result, name = server.registerID(msg.id, msg.name)
if result then
reply = {
command = msg.command .. "_respond",
result = result,
name = name,
}
say("user " .. tostring(msg.id) .. " registered as " .. name)
else
reply = {
command = msg.command .. "_respond",
result = result,
}
say("user " .. tostring(msg.id) .. " failed to register as " .. tostring(msg.name) .. ": " .. name)
end
encTransmit(reply, msgID, msg.id, msg.id)
end
elseif not server.checkRegister(msg.id) then
encTransmit({
command = msg.command .. "_respond",
result = false,
errorMsg = "not registered"
}, msgID, msg.id, msg.id)
say("unregistered user attempt to use")
else
-- all the real nice stuff
if msg.command == "find_server" then
encTransmit({
command = msg.command .. "_respond",
server = yourID,
result = true
}, msgID, msg.id, msg.id)
say("find_server (aes)")
elseif msg.command == "get_names" then
encTransmit({
command = msg.command .. "_respond",
names = names,
result = true,
}, msgID, msg.id, msg.id)
say("get_names", msg.id)
elseif msg.command == "send_mail" then
if (
msg.recipient and
type(msg.subject) == "string" and
type(msg.message) == "string"
) then
local reply = {
command = msg.command .. "_respond",
result = server.recordMail(msg.id, msg.recipient, msg.subject, msg.message, msg.attachments)
}
encTransmit(reply, msgID, msg.id, msg.id)
say("send_mail", msg.id)
end
elseif msg.command == "get_mail" then
local mail = server.getMail(msg.id)
local reply = {
command = msg.command .. "_respond",
result = true,
mail = mail,
}
encTransmit(reply, msgID, msg.id, msg.id)
say("get_mail", msg.id)
elseif msg.command == "delete_mail" then
local result = false
if type(msg.mail) == "number" then
result = server.deleteMail(msg.id, msg.mail, yourID)
end
encTransmit({
command = msg.command .. "_respond",
result = result,
}, msgID, msg.id, msg.id)
say("delete_mail", msg.id)
end
end
end
end
end
end
local clientInterface = function(srv)
local scr_x, scr_y = term.getSize()
local inbox = {}
local refresh = function()
dialogueBox("Refreshing...", 0)
inbox = client.getMail(srv)
end
local explode = function(div, str, replstr, includeDiv)
if div == '' then
return false
end
local pos, arr = 0, {}
for st, sp in function() return string.find(str, div, pos, false) end do
table.insert(arr, string.sub(replstr or str, pos, st - 1 + (includeDiv and #div or 0)))
pos = sp + 1
end
table.insert(arr, string.sub(replstr or str, pos))
return arr
end
srv = srv or tonumber( client.findServer(argData[1]) )
if not srv then
error("No server was found!")
end
if not names[yourID] then
term.setBackgroundColor(colors.black)
term.setTextColor(colors.white)
term.clear()
local attempt
cwrite("Enter your name:", 3)
while true do
term.setCursorPos(2, 5)
term.write(":")
attempt = read()
if server.checkValidName(attempt) then
names[yourID] = attempt
writeNames()
break
else
term.clear()
cwrite("Bad name! Enter your name:", 3)
end
end
end
client.register(srv, names[yourID])
for k,v in pairs(client.getNames(srv) or {}) do
names[k] = v
end
term.clear()
refresh()
local keyWrite = function(text, pos)
local txcol = term.getTextColor()
term.write(text:sub(1, pos - 1))
term.setTextColor(colors.yellow)
term.write(text:sub(pos, pos))
term.setTextColor(txcol)
term.write(text:sub(pos + 1))
end
local writeHeader = function(left, right, y)
if y then
term.setCursorPos(1, y)
end
term.setTextColor(colors.lightGray)
term.write(left)
term.setTextColor(colors.white)
term.write(" " .. right)
end
local area_inbox = function()
local scroll = 0
local render = function(scroll)
local y = 1
term.setBackgroundColor(colors.black)
term.clear()
for i = 1 + scroll, scroll + scr_y - 1 do
if inbox[i] then
term.setCursorPos(1, y)
term.setTextColor(colors.white)
term.write(names[inbox[i].sender]:sub(1, 10))
term.setCursorPos(11, y)
term.setTextColor(colors.white)
term.write(inbox[i].subject:sub(1, 18))
term.setCursorPos(30, y)
term.setTextColor(colors.gray)
term.write(inbox[i].message:sub(1, scr_x - 30))
end
y = y + 1
end
term.setCursorPos(1, scr_y)
term.setBackgroundColor(colors.gray)
term.clearLine()
term.setTextColor(colors.white)
--term.write(names[yourID] .. ": ")
keyWrite("Quit ", 1)
keyWrite("New ", 1)
keyWrite("Refresh ", 1)
term.setCursorPos(scr_x - #names[yourID], scr_y)
term.setTextColor(colors.lightGray)
term.write(names[yourID])
end
-- logic(k)
local barCommands = {
[keys.q] = {1, scr_y, 4},
[keys.n] = {6, scr_y, 3},
[keys.r] = {10, scr_y, 7},
}
local evt, key, mx, my
local adjY -- mouse Y adjusted for scroll
while true do
render(scroll)
inbox = alphasort(inbox)
evt, key, mx, my = os.pullEvent()
if evt == "mouse_click" then
adjY = my + scroll
if inbox[adjY] then
return "view_mail", {adjY}
else
for key, data in pairs(barCommands) do
if my == data[2] and mx >= data[1] and mx <= data[1] + data[3] - 1 then
os.queueEvent("key", key)
break
end
end
end
elseif evt == "mouse_scroll" then
scroll = math.min(math.max(0, scroll + key), math.max(0, #inbox - (scr_y - 1)))
elseif evt == "key" then
if key == keys.n then
return "new_mail"
elseif key == keys.r then
return "refresh"
elseif key == keys.q then
return "exit"
end
end
end
end
local niftyRead = function(prebuffer, startX, startY, startCursorMX, startCursorMY, allowEnter, maxLines, maxLength, history)
local cx, cy = term.getCursorPos()
local histPos = 0
startX, startY = startX or cx, startY or cy
local scroll = 0
local buffer = {{}}
local unassemble = function(pBuffer)
local output = {{""}}
local y = 1
local x = 1
for i = 1, #pBuffer do
if pBuffer:sub(i,i) == "\n" then
x = 1
y = y + 1
output[y] = {""}
else
output[y][x] = pBuffer:sub(i,i)
x = x + 1
end
end
return output
end
if prebuffer then
buffer = unassemble(prebuffer)
end
local curY = startCursorMY and math.max(1, math.min(startCursorMY - (startY - 1), #buffer)) or 1
local curX = startCursorMX and math.max(1, math.min(startCursorMX - (startX - 1), #buffer[curY])) or 1
local biggestHeight = math.max(1, #buffer)
local getLength = function()
local output = 0
for ln = 1, #buffer do
output = output + #buffer[ln]
if ln ~= #buffer then -- account for newline chars
output = output + 1
end
end
return output
end
local render = function()
for y = startY, startY + (biggestHeight - 1) do
term.setCursorPos(startX, y)
term.write((" "):rep(maxLength))
end
term.setCursorPos(startX, startY)
local x, y, words = startX, startY
for ln = scroll + 1, #buffer + scroll do
if buffer[ln] then
words = explode(" ", table.concat(buffer[ln]), nil, true)
for i = 1, #words do
if x + #words[i] > scr_x and y < maxLines then
x = startX
y = y + 1
end
term.setCursorPos(x, y)
term.write(words[i])
x = x + #words[i]
end
term.write(" ")
if ln ~= #buffer then
y = y + 1
x = startX
end
end
end
term.setCursorPos(curX + startX - 1, curY + startY - 1)
biggestHeight = math.max(#buffer, biggestHeight)
end
local assemble = function(buffer)
local output = ""
for ln = 1, #buffer do
output = output .. table.concat(buffer[ln])
if ln ~= #buffer then
output = output .. "\n"
end
end
return output
end
if history then
history[0] = assemble(buffer)
end
local evt, key, mx, my
local keysDown = {}
term.setCursorBlink(true)
while true do
render()
evt, key, mx, my = os.pullEvent()
if evt == "char" then
if getLength() < maxLength then
table.insert(buffer[curY], curX, key)
curX = curX + 1
if histPos == 0 and history then
history[histPos] = assemble(buffer)
end
end
elseif evt == "key_up" then
keysDown[key] = nil
elseif evt == "mouse_click" then
if key == 1 then
if my - (startY - 1) > maxLines or my < startY then
term.setCursorBlink(false)
return assemble(buffer), "mouse_click", mx, my
else
curY = math.max(1, math.min(my - (startY - 1), #buffer))
curX = math.max(1, math.min(mx - (startX - 0), #buffer[curY]))
end
end
elseif evt == "key" then
keysDown[key] = true
if key == keys.left then
if curX == 1 then
if curY > 1 then
curY = curY - 1
curX = #buffer[curY] + 1
end
elseif curX > 1 then
curX = curX - 1
end
elseif key == keys.right then
if curX == #buffer[curY] + 1 then
if curY < #buffer then
curY = curY + 1
curX = 1
end
elseif curX < #buffer[curY] then
curX = curX + 1
end
elseif key == keys.up then
if history then
if histPos < #history then
histPos = histPos + 1
buffer = unassemble(history[histPos])
curY = #buffer
curX = #buffer[curY] + 1
end
else
if curY > 1 then
curY = curY - 1
curX = math.min(curX, #buffer[curY] + 1)
else
curX = 1
end
end
elseif key == keys.down then
if history then
if histPos > 0 then
histPos = histPos - 1
buffer = unassemble(history[histPos])
curY = #buffer
curX = #buffer[curY] + 1
end
else
if curY < #buffer then
curY = curY + 1
curX = math.min(curX, #buffer[curY] + 1)
else
curX = #buffer[curY] + 1
end
end
elseif key == keys.enter then
if allowEnter and not (keysDown[keys.leftAlt] or keysDown[keys.rightAlt]) and #buffer < maxLines then
curY = curY + 1
table.insert(buffer, curY, {})
for i = curX, #buffer[curY - 1] do
buffer[curY][#buffer[curY] + 1] = buffer[curY - 1][i]
buffer[curY - 1][i] = nil
end
curX = 1
elseif not allowEnter then
term.setCursorBlink(false)
return assemble(buffer), "key", keys.enter
end
elseif key == keys.tab or (key == keys.q and (keysDown[keys.leftAlt] or keysDown[keys.rightAlt])) then
term.setCursorBlink(false)
return assemble(buffer), "key", key
elseif key == keys.backspace then
if curX > 1 then
table.remove(buffer[curY], curX - 1)
curX = curX - 1
elseif curY > 1 then
curX = #buffer[curY - 1] + 1
for i = 1, #buffer[curY] do
buffer[curY - 1][#buffer[curY - 1] + 1] = buffer[curY][i]
end
table.remove(buffer, curY)
curY = curY - 1
end
elseif key == keys.delete then
if buffer[curY][curX] then
table.remove(buffer[curY], curX)
end
end
end
end
end
local area_new_mail = function(recipient, subject, message)
recipient = recipient or ""
subject = subject or ""
message = message or ""
local attachments = {}
sleep(0.05)
local mode = "recipient"
render = function()
term.setTextColor(colors.white)
term.setBackgroundColor(colors.black)
term.clear()
writeHeader("To:", recipient, 1)
writeHeader("Subject:", subject, 2)
writeHeader("Attachments:", "", 3)
for name, contents in pairs(attachments) do
term.write(name .. " ")
end
term.setCursorPos(1, 4)
term.setBackgroundColor(colors.gray)
term.setTextColor(colors.lightGray)
term.clearLine()
cwrite("(Alt+Enter = SEND, Alt+Q = QUIT)")
term.setTextColor(colors.white)
term.setBackgroundColor(colors.black)
if mode ~= "message" then
term.setCursorPos(1, 5)
write(message)
end
end
local mx, my, evt = 1, 1
local _mx, _my, userList
while true do
render()
if mode == "message" then
message, evt, _mx, _my = niftyRead(message, 1, 5, mx, my, true, maximumMailLines, 512)
elseif mode == "subject" then
subject, evt, _mx, _my = niftyRead(subject, 10, 2, mx, 1, false, 1, 64)
elseif mode == "recipient" then
names = client.getNames(srv) or names
userList = {}
for k,v in pairs(names) do
userList[#userList + 1] = v
end
recipient, evt, _mx, _my = niftyRead(recipient, 5, 1, mx, 1, false, 1, 24, userList)
end
if evt == "mouse_click" then
mx, my = _mx, _my
if my == 1 then
mode = "recipient"
elseif my == 2 then
mode = "subject"
elseif my == 3 then
local newAttachment = lddfm.makeMenu(1, 4, scr_x, scr_y, "", false, false, false, true, false, nil, true)
if newAttachment then
local name = fs.getName(newAttachment)
if attachments[name] then
attachments[name] = nil
else
attachments[name] = readFile(newAttachment)
end
end
elseif my >= 5 then
mode = "message"
end
elseif evt == "key" then
if _mx == keys.enter or _mx == keys.tab then
if mode == "recipient" then
mode = "subject"
elseif mode == "subject" then
mode = "message"
elseif mode == "message" and _mx == keys.enter then
local recip
names = client.getNames(srv) or names
if tonumber(recipient) then
recip = tonumber(recipient)
if not names[recip] then
recip = nil
end
else
recip = getNameID(recipient)
end
if recip then
client.sendMail(srv, recip, subject, message, attachments)
dialogueBox("Message sent!", 2)
refresh()
return
else
dialogueBox("There's no such recipient.", 2)
end
end
elseif _mx == keys.q then
return
end
end
end
niftyRead(nil, 1, 1, nil, true)
end
local area_view_mail = function(mailEntry)
local scroll = 0
local mail = inbox[mailEntry]
local render = function(scroll)
term.setBackgroundColor(colors.black)
term.setTextColor(colors.lightGray)
term.clear()
local y
writeHeader("From:", names[mail.sender], 1)
writeHeader("Subject:", mail.subject, 2)
if getTableLength(mail.attachments) > 0 then
writeHeader("Attachments:","",3)
for name, contents in pairs(mail.attachments) do
term.write(name .. " ")
end
y = 5
else
y = 4
end
term.setTextColor(colors.gray)
term.setCursorPos(1, y - 1)
term.write(("="):rep(scr_x))
term.setTextColor(colors.white)
local words = {}
local lines = explode("\n", mail.message, nil, true)
for i = 1, #lines do
local inWords = explode(" ", lines[i], nil, true)
for ii = 1, #inWords do
words[#words+1] = inWords[ii]
end
if i ~= #lines then
words[#words+1] = "\n"
end
end
local buffer = {""}
for i = 1, #words do
if words[i] == "\n" then
buffer[#buffer+1] = ""
elseif #buffer[#buffer] + #words[i] > scr_x then
buffer[#buffer+1] = words[i]
else
buffer[#buffer] = buffer[#buffer] .. words[i]
end
end
for i = scroll + 1, scroll + scr_y - y do
if buffer[i] then
term.setCursorPos(1, y)
term.write(buffer[i])
end
y = y + 1
end
term.setCursorPos(1, scr_y)
term.setBackgroundColor(colors.gray)
term.setTextColor(colors.white)
term.clearLine()
keyWrite("Quit ", 1)
keyWrite("Reply ", 1)
if getTableLength(mail.attachments) > 0 then
keyWrite("DL.Attachments ", 4)
end
keyWrite("Delete ", 1)
return #buffer
end
local downloadAttachments = function()
local path = fs.combine(config.attachmentPath, names[mail.sender])
for name, contents in pairs(mail.attachments) do
writeFile(fs.combine(path, name), contents)
end
return path
end
local barCommands = {
[keys.q] = {1, scr_y, 4},
[keys.r] = {6, scr_y, 5},
}
if getTableLength(mail.attachments) > 0 then
barCommands[keys.a] = {12, scr_y, 14}
barCommands[keys.d] = {27, scr_y, 6}
else
barCommands[keys.d] = {12, scr_y, 6}
end
local evt, key, mx, my, msgHeight
while true do
msgHeight = render(scroll)
evt, key, mx, my = os.pullEvent()
if evt == "key" then
if key == keys.r then
area_new_mail(names[mail.sender], "Re: " .. mail.subject, "\n\n~~~\nAt UTC epoch " .. mail.time .. ", " .. names[mail.sender] .. " wrote:\n\n" .. mail.message)
elseif key == keys.d then
client.deleteMail(srv, mailEntry)
refresh()
return
elseif key == keys.a and getTableLength(mail.attachments) > 0 then
local path = downloadAttachments()
dialogueBox("DL'd to '" .. path .. "/'")
elseif key == keys.q then
return "exit"
end
elseif evt == "mouse_click" then
if my == 3 and getTableLength(mail.attachments) > 0 then
local path = downloadAttachments()
dialogueBox("DL'd to '" .. path .. "/'")
else
for key, data in pairs(barCommands) do
if my == data[2] and mx >= data[1] and mx <= data[1] + data[3] - 1 then
os.queueEvent("key", key)
break
end
end
end
elseif evt == "mouse_scroll" then
scroll = math.min(math.max(0, scroll + key), math.max(0, msgHeight - (scr_y - 5)))
end
end
end
local res, output
while true do
res, output = area_inbox()
if res == "exit" then
term.setCursorPos(1, scr_y)
term.setBackgroundColor(colors.black)
term.clearLine()
sleep(0.05)
return
elseif res == "refresh" then
refresh()
elseif res == "view_mail" then
area_view_mail(table.unpack(output or {}))
elseif res == "new_mail" then
area_new_mail(table.unpack(output or {}))
end
end
end
if isServer then
names[yourID] = names[yourID] or serverName
writeNames()
server.makeServer(true)
elseif shell then
clientInterface()
end
return {client = client, server = server}