mirror of
https://github.com/LDDestroier/CC/
synced 2024-12-12 11:10:28 +00:00
ec6ee3d166
Scrolling now has a limit, so you can't just scroll upwards infinitely. Refreshing now has a dialogue box showing up to let you know it hasn't hung itself. Clicking to the right of the text in a niftyRead prompt will move the cursor to the right position now. (I still need to fix up that niftyRead prompt, since it's not as clean as I'd like)
1529 lines
40 KiB
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}
|