Skip to content

Instantly share code, notes, and snippets.

@MCJack123
Last active August 18, 2023 22:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MCJack123/1734b90f8c8387b8f4644b3e2e41c1a3 to your computer and use it in GitHub Desktop.
Save MCJack123/1734b90f8c8387b8f4644b3e2e41c1a3 to your computer and use it in GitHub Desktop.
PIN-based door lock system for ComputerCraft, using a monitor for PIN entry
-- Save this as startup.lua on each client computer with a 1x1 monitor and modem attached. You can also optionally attach a speaker for audio feedback.
local secret = "" -- Set this to the same secret that was set in the server file.
local redstoneSide = "left" -- Set this to the side the redstone signal should be output on.
local openTime = 5 -- Set this to the number of seconds to keep the door open for.
local defaultOutput = false -- Set this to the default redstone state for the door. If set to true, this means power will be cut when unlocking.
-- This allows you to place a door sideways, and then have it stay closed even when power is applied externally.\
local accessLevel = 0 -- Set this to the minimum access level required by a PIN.
if not secret or secret == "" then error("Please set some keys inside the script before running.") end
local ok, err = pcall(function()
os.pullEvent = os.pullEventRaw
settings.set("shell.allow_disk_startup", false)
settings.save(".settings")
local sha256
do
local MOD = 2^32
local function rrotate(x, disp)
x = x % MOD
disp = disp % 32
local low = bit32.band(x, 2 ^ disp - 1)
return bit32.rshift(x, disp) + bit32.lshift(low, 32 - disp)
end
local k = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
}
local function str2hexa(s)
return (string.gsub(s, ".", function(c) return string.format("%02x", string.byte(c)) end))
end
local function num2s(l, n)
local s = ""
for i = 1, n do
local rem = l % 256
s = string.char(rem) .. s
l = (l - rem) / 256
end
return s
end
local function s232num(s, i)
local n = 0
for i = i, i + 3 do n = n*256 + string.byte(s, i) end
return n
end
local function preproc(msg, len)
local extra = 64 - ((len + 9) % 64)
len = num2s(8 * len, 8)
msg = msg .. "\128" .. string.rep("\0", extra) .. len
assert(#msg % 64 == 0)
return msg
end
local function initH256(H)
H[1] = 0x6a09e667
H[2] = 0xbb67ae85
H[3] = 0x3c6ef372
H[4] = 0xa54ff53a
H[5] = 0x510e527f
H[6] = 0x9b05688c
H[7] = 0x1f83d9ab
H[8] = 0x5be0cd19
return H
end
local function digestblock(msg, i, H)
local w = {}
for j = 1, 16 do w[j] = s232num(msg, i + (j - 1)*4) end
for j = 17, 64 do
local v = w[j - 15]
local s0 = bit32.bxor(rrotate(v, 7), rrotate(v, 18), bit32.rshift(v, 3))
v = w[j - 2]
w[j] = w[j - 16] + s0 + w[j - 7] + bit32.bxor(rrotate(v, 17), rrotate(v, 19), bit32.rshift(v, 10))
end
local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8]
for i = 1, 64 do
local s0 = bit32.bxor(rrotate(a, 2), rrotate(a, 13), rrotate(a, 22))
local maj = bit32.bxor(bit32.band(a, b), bit32.band(a, c), bit32.band(b, c))
local t2 = s0 + maj
local s1 = bit32.bxor(rrotate(e, 6), rrotate(e, 11), rrotate(e, 25))
local ch = bit32.bxor(bit32.band(e, f), bit32.band(bit32.bnot(e), g))
local t1 = h + s1 + ch + k[i] + w[i]
h, g, f, e, d, c, b, a = g, f, e, d + t1, c, b, a, t1 + t2
end
H[1] = bit32.band(H[1] + a)
H[2] = bit32.band(H[2] + b)
H[3] = bit32.band(H[3] + c)
H[4] = bit32.band(H[4] + d)
H[5] = bit32.band(H[5] + e)
H[6] = bit32.band(H[6] + f)
H[7] = bit32.band(H[7] + g)
H[8] = bit32.band(H[8] + h)
end
function sha256(msg)
msg = preproc(msg, #msg)
local H = initH256({})
for i = 1, #msg, 64 do digestblock(msg, i, H) end
return str2hexa(num2s(H[1], 4) .. num2s(H[2], 4) .. num2s(H[3], 4) .. num2s(H[4], 4) ..
num2s(H[5], 4) .. num2s(H[6], 4) .. num2s(H[7], 4) .. num2s(H[8], 4))
end
end
local speaker = peripheral.find("speaker")
local modem = peripheral.find("modem")
modem.open(74)
redstone.setOutput(redstoneSide, defaultOutput)
-- PrimeUI by JackMacWindows
-- Public domain/CC0
local expect = require "cc.expect".expect
-- Initialization code
local PrimeUI = {}
do
local coros = {}
local restoreCursor
--- Adds a task to run in the main loop.
---@param func function The function to run, usually an `os.pullEvent` loop
function PrimeUI.addTask(func)
expect(1, func, "function")
local t = {coro = coroutine.create(func)}
coros[#coros+1] = t
_, t.filter = coroutine.resume(t.coro)
end
--- Sends the provided arguments to the run loop, where they will be returned.
---@param ... any The parameters to send
function PrimeUI.resolve(...)
coroutine.yield(coros, ...)
end
--- Clears the screen and resets all components. Do not use any previously
--- created components after calling this function.
function PrimeUI.clear()
-- Reset the screen.
term.setCursorPos(1, 1)
term.setCursorBlink(false)
term.setBackgroundColor(colors.black)
term.setTextColor(colors.white)
term.clear()
-- Reset the task list and cursor restore function.
coros = {}
restoreCursor = nil
end
--- Sets or clears the window that holds where the cursor should be.
---@param win window|nil The window to set as the active window
function PrimeUI.setCursorWindow(win)
expect(1, win, "table", "nil")
restoreCursor = win and win.restoreCursor
end
--- Gets the absolute position of a coordinate relative to a window.
---@param win window The window to check
---@param x number The relative X position of the point
---@param y number The relative Y position of the point
---@return number x The absolute X position of the window
---@return number y The absolute Y position of the window
function PrimeUI.getWindowPos(win, x, y)
if win == term then return x, y end
while win ~= term.native() and win ~= term.current() and not win.setTextScale do
if not win.getPosition then return x, y end
local wx, wy = win.getPosition()
x, y = x + wx - 1, y + wy - 1
_, win = debug.getupvalue(select(2, debug.getupvalue(win.isColor, 1)), 1) -- gets the parent window through an upvalue
end
return x, y
end
--- Runs the main loop, returning information on an action.
---@return any ... The result of the coroutine that exited
function PrimeUI.run()
while true do
-- Restore the cursor and wait for the next event.
if restoreCursor then restoreCursor() end
local ev = table.pack(os.pullEvent())
-- Run all coroutines.
for _, v in ipairs(coros) do
if v.filter == nil or v.filter == ev[1] then
-- Resume the coroutine, passing the current event.
local res = table.pack(coroutine.resume(v.coro, table.unpack(ev, 1, ev.n)))
-- If the call failed, bail out. Coroutines should never exit.
if not res[1] then error(res[2], 2) end
-- If the coroutine resolved, return its values.
if res[2] == coros then return table.unpack(res, 3, res.n) end
-- Set the next event filter.
v.filter = res[2]
end
end
end
end
end
--- Creates a clickable button on screen with text.
---@param win window The window to draw on
---@param x number The X position of the button
---@param y number The Y position of the button
---@param text string The text to draw on the button
---@param action function|string A function to call when clicked, or a string to send with a `run` event
---@param fgColor color|nil The color of the button text (defaults to white)
---@param bgColor color|nil The color of the button (defaults to light gray)
---@param clickedColor color|nil The color of the button when clicked (defaults to gray)
function PrimeUI.button(win, x, y, text, action, fgColor, bgColor, clickedColor)
expect(1, win, "table")
expect(2, x, "number")
expect(3, y, "number")
expect(4, text, "string")
expect(5, action, "function", "string")
fgColor = expect(6, fgColor, "number", "nil") or colors.white
bgColor = expect(7, bgColor, "number", "nil") or colors.gray
clickedColor = expect(8, clickedColor, "number", "nil") or colors.lightGray
-- Draw the initial button.
win.setCursorPos(x, y)
win.setBackgroundColor(bgColor)
win.setTextColor(fgColor)
win.write(" " .. text .. " ")
-- Get the screen position and add a click handler.
PrimeUI.addTask(function()
local buttonDown = false
while true do
local event, button, clickX, clickY = os.pullEvent()
local screenX, screenY = PrimeUI.getWindowPos(win, x, y)
if event == "mouse_click" and button == 1 and clickX >= screenX and clickX < screenX + #text + 2 and clickY == screenY then
-- Initiate a click action (but don't trigger until mouse up).
buttonDown = true
-- Redraw the button with the clicked background color.
win.setCursorPos(x, y)
win.setBackgroundColor(clickedColor)
win.setTextColor(fgColor)
win.write(" " .. text .. " ")
elseif event == "mouse_up" and button == 1 and buttonDown then
-- Finish a click event.
if clickX >= screenX and clickX < screenX + #text + 2 and clickY == screenY then
-- Trigger the action.
if type(action) == "string" then PrimeUI.resolve("button", action)
else action() end
end
-- Redraw the original button state.
win.setCursorPos(x, y)
win.setBackgroundColor(bgColor)
win.setTextColor(fgColor)
win.write(" " .. text .. " ")
elseif event == "monitor_touch" and win.setTextScale and peripheral.getName(win) == button and clickX >= screenX and clickX < screenX + #text + 2 and clickY == screenY then
-- Redraw the button with the clicked background color.
win.setCursorPos(x, y)
win.setBackgroundColor(clickedColor)
win.setTextColor(fgColor)
win.write(" " .. text .. " ")
-- Trigger a click event.
if type(action) == "string" then PrimeUI.resolve("button", action)
else action() end
-- Pause to indicate clicked.
sleep(0.25)
-- Redraw the original button state.
win.setCursorPos(x, y)
win.setBackgroundColor(bgColor)
win.setTextColor(fgColor)
win.write(" " .. text .. " ")
end
end
end)
end
--- Draws a line of text, centering it inside a box horizontally.
---@param win window The window to draw on
---@param x number The X position of the left side of the box
---@param y number The Y position of the box
---@param width number The width of the box to draw in
---@param text string The text to draw
---@param fgColor color|nil The color of the text (defaults to white)
---@param bgColor color|nil The color of the background (defaults to black)
function PrimeUI.centerLabel(win, x, y, width, text, fgColor, bgColor)
expect(1, win, "table")
expect(2, x, "number")
expect(3, y, "number")
expect(4, width, "number")
expect(5, text, "string")
fgColor = expect(6, fgColor, "number", "nil") or colors.white
bgColor = expect(7, bgColor, "number", "nil") or colors.black
assert(#text <= width, "string is too long")
win.setCursorPos(x + math.floor((width - #text) / 2), y)
win.setTextColor(fgColor)
win.setBackgroundColor(bgColor)
win.write(text)
end
local monitor = peripheral.find("monitor")
monitor.setTextScale(0.5)
local w, h = monitor.getSize()
local w3 = w / 3
local h5 = h / 5
local text = ""
local function updateText(t)
text = text .. t
monitor.setCursorPos(1, h5 / 2 + 1)
monitor.setBackgroundColor(colors.black)
monitor.clearLine()
PrimeUI.centerLabel(monitor, 1, h5 / 2 + 1, w, ("\7"):rep(#text))
end
PrimeUI.clear()
monitor.setBackgroundColor(colors.black)
monitor.setTextColor(colors.white)
monitor.clear()
PrimeUI.centerLabel(monitor, 1, h5 / 2, w, "Enter PIN:")
PrimeUI.button(monitor, 0 * w3 + w3 / 2, 1 * h5 + h5 / 2, "1", function() updateText "1" end)
PrimeUI.button(monitor, 1 * w3 + w3 / 2, 1 * h5 + h5 / 2, "2", function() updateText "2" end)
PrimeUI.button(monitor, 2 * w3 + w3 / 2, 1 * h5 + h5 / 2, "3", function() updateText "3" end)
PrimeUI.button(monitor, 0 * w3 + w3 / 2, 2 * h5 + h5 / 2, "4", function() updateText "4" end)
PrimeUI.button(monitor, 1 * w3 + w3 / 2, 2 * h5 + h5 / 2, "5", function() updateText "5" end)
PrimeUI.button(monitor, 2 * w3 + w3 / 2, 2 * h5 + h5 / 2, "6", function() updateText "6" end)
PrimeUI.button(monitor, 0 * w3 + w3 / 2, 3 * h5 + h5 / 2, "7", function() updateText "7" end)
PrimeUI.button(monitor, 1 * w3 + w3 / 2, 3 * h5 + h5 / 2, "8", function() updateText "8" end)
PrimeUI.button(monitor, 2 * w3 + w3 / 2, 3 * h5 + h5 / 2, "9", function() updateText "9" end)
PrimeUI.button(monitor, 0 * w3 + w3 / 2, 4 * h5 + h5 / 2, "\x11", function()
text = text:sub(1, -2)
updateText ""
end, colors.white, colors.red)
PrimeUI.button(monitor, 1 * w3 + w3 / 2, 4 * h5 + h5 / 2, "0", function() updateText "0" end)
PrimeUI.button(monitor, 2 * w3 + w3 / 2, 4 * h5 + h5 / 2, "\x10", function()
local id = text
local found = false
local msg = sha256("?:" .. id .. tostring(math.floor(os.epoch("utc") / 1000) .. secret))
local channel = math.random(0, 65533)
while channel == 74 do channel = math.random(0, 65533) end
modem.open(channel)
modem.transmit(74, channel, "?" .. accessLevel .. ":" .. msg)
local timer = os.startTimer(3)
while true do
local ev2, side2, port, reply, message = os.pullEvent()
if ev2 == "timer" and side2 == timer then break
elseif ev2 == "modem_message" and port == channel and reply == 74 and type(message) == "string" and message:sub(1, 2) == "=:" then
if message == "=:" .. sha256("=:" .. id .. tostring(math.floor(os.epoch("utc") / 1000)) .. secret .. "true") or
message == "=:" .. sha256("=:" .. id .. tostring(math.floor(os.epoch("utc") / 1000) - 1) .. secret .. "true") then found = true break
elseif message == "=:" .. sha256("=:" .. msg .. secret .. "false") then found = false break end
end
end
modem.close(channel)
if found then
monitor.setCursorPos(1, h5 / 2 + 1)
monitor.setBackgroundColor(colors.black)
monitor.clearLine()
PrimeUI.centerLabel(monitor, 1, h5 / 2 + 1, w, "Success.", colors.green)
monitor.setTextColor(colors.white)
redstone.setOutput(redstoneSide, not defaultOutput)
if speaker then speaker.playNote("bit", 1, 24) end
sleep(openTime)
redstone.setOutput(redstoneSide, defaultOutput)
elseif speaker then
monitor.setCursorPos(1, h5 / 2 + 1)
monitor.setBackgroundColor(colors.black)
monitor.clearLine()
PrimeUI.centerLabel(monitor, 1, h5 / 2 + 1, w, "Denied.", colors.red)
monitor.setTextColor(colors.white)
for i = 1, 3 do
speaker.playNote("bit", 1, 12)
if i < 3 then sleep(0.2) end
end
sleep(1)
end
text = ""
updateText ""
end, colors.white, colors.green)
PrimeUI.run()
end)
if not ok then
printError(err)
sleep(5)
end
os.reboot()
-- Save this as startup.lua on the server computer with a modem attached to the computer. You can also optionally attach a drive to be able to create and erase ID cards.
local addIDPassword = "" -- Set this to a password that can be used to create a new ID card.
local secret = "" -- Set this to a randomly-generated secret code. This code should be copied to the secret variable for all clients.
if not addIDPassword or not secret or addIDPassword == "" or secret == "" then error("Please set some keys inside the script before running.") end
if not fs.exists("ids.lua") then
local file = fs.open("ids.lua", "w")
file.write("return {}")
file.close()
end
local ok, err = pcall(function()
os.pullEvent = os.pullEventRaw
settings.set("shell.allow_disk_startup", false)
settings.save(".settings")
local sha256
do
local MOD = 2^32
local function rrotate(x, disp)
x = x % MOD
disp = disp % 32
local low = bit32.band(x, 2 ^ disp - 1)
return bit32.rshift(x, disp) + bit32.lshift(low, 32 - disp)
end
local k = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
}
local function str2hexa(s)
return (string.gsub(s, ".", function(c) return string.format("%02x", string.byte(c)) end))
end
local function num2s(l, n)
local s = ""
for i = 1, n do
local rem = l % 256
s = string.char(rem) .. s
l = (l - rem) / 256
end
return s
end
local function s232num(s, i)
local n = 0
for i = i, i + 3 do n = n*256 + string.byte(s, i) end
return n
end
local function preproc(msg, len)
local extra = 64 - ((len + 9) % 64)
len = num2s(8 * len, 8)
msg = msg .. "\128" .. string.rep("\0", extra) .. len
assert(#msg % 64 == 0)
return msg
end
local function initH256(H)
H[1] = 0x6a09e667
H[2] = 0xbb67ae85
H[3] = 0x3c6ef372
H[4] = 0xa54ff53a
H[5] = 0x510e527f
H[6] = 0x9b05688c
H[7] = 0x1f83d9ab
H[8] = 0x5be0cd19
return H
end
local function digestblock(msg, i, H)
local w = {}
for j = 1, 16 do w[j] = s232num(msg, i + (j - 1)*4) end
for j = 17, 64 do
local v = w[j - 15]
local s0 = bit32.bxor(rrotate(v, 7), rrotate(v, 18), bit32.rshift(v, 3))
v = w[j - 2]
w[j] = w[j - 16] + s0 + w[j - 7] + bit32.bxor(rrotate(v, 17), rrotate(v, 19), bit32.rshift(v, 10))
end
local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8]
for j = 1, 64 do
local s0 = bit32.bxor(rrotate(a, 2), rrotate(a, 13), rrotate(a, 22))
local maj = bit32.bxor(bit32.band(a, b), bit32.band(a, c), bit32.band(b, c))
local t2 = s0 + maj
local s1 = bit32.bxor(rrotate(e, 6), rrotate(e, 11), rrotate(e, 25))
local ch = bit32.bxor(bit32.band(e, f), bit32.band(bit32.bnot(e), g))
local t1 = h + s1 + ch + k[j] + w[j]
h, g, f, e, d, c, b, a = g, f, e, d + t1, c, b, a, t1 + t2
end
H[1] = bit32.band(H[1] + a)
H[2] = bit32.band(H[2] + b)
H[3] = bit32.band(H[3] + c)
H[4] = bit32.band(H[4] + d)
H[5] = bit32.band(H[5] + e)
H[6] = bit32.band(H[6] + f)
H[7] = bit32.band(H[7] + g)
H[8] = bit32.band(H[8] + h)
end
function sha256(msg)
msg = preproc(msg, #msg)
local H = initH256({})
for i = 1, #msg, 64 do digestblock(msg, i, H) end
return str2hexa(num2s(H[1], 4) .. num2s(H[2], 4) .. num2s(H[3], 4) .. num2s(H[4], 4) ..
num2s(H[5], 4) .. num2s(H[6], 4) .. num2s(H[7], 4) .. num2s(H[8], 4))
end
end
local modem = peripheral.find("modem")
modem.open(74)
parallel.waitForAll(function()
while true do
local ev, side, port, reply, message = os.pullEvent("modem_message")
if port == 74 and type(message) == "string" and message:match "^%?%d*:" then
local level = tonumber(message:match "^%?(%d*):") or 0
message = message:gsub("^%?%d+:", "?:")
local found = false
local ids = dofile("ids.lua")
local delete = {}
modem.open(reply)
for i,id in ipairs(ids) do
if (id.expiration == 0 or id.expiration > os.epoch("utc")) and (id.uses ~= 0) and (id.level >= level) then
if message == "?:" .. sha256("?:" .. id.id .. tostring(math.floor(os.epoch("utc") / 1000)) .. secret) or
message == "?:" .. sha256("?:" .. id.id .. tostring(math.floor(os.epoch("utc") / 1000) - 1) .. secret) then
found = true
modem.transmit(reply, 74, "=:" .. sha256("=:" .. id.id .. tostring(math.floor(os.epoch("utc") / 1000)) .. secret .. "true"))
if id.uses > 0 then
ids[i].uses = id.uses - 1
local file = fs.open("ids.lua", "w")
file.write("return " .. textutils.serialize(ids))
file.close()
end
break
end
else table.insert(delete, i) end
end
if not found then modem.transmit(reply, 74, "=:" .. sha256("=:" .. message:sub(3) .. secret .. "false")) end
if reply ~= 74 then modem.close(reply) end
if #delete > 0 then
for _,i in ipairs(delete) do ids[i] = nil end
local file = fs.open("ids.lua", "w")
file.write("return " .. textutils.serialize(ids))
file.close()
end
end
end
end, function()
while true do
local w, h = term.getSize()
term.clear()
term.setCursorPos(w / 2 - 10, h / 2)
term.write("Enter admin password:")
term.setCursorPos(w / 2 - 10, h / 2 + 1)
local pass = read("\7")
if pass == addIDPassword then
while true do
term.clear()
term.setCursorPos(w / 2 - 8, h / 2 - 2)
term.write("Select an option:")
term.setCursorPos(w / 2 - 8, h / 2 - 1)
term.write("1. Add PIN")
term.setCursorPos(w / 2 - 8, h / 2)
term.write("2. Remove PIN")
term.setCursorPos(w / 2 - 8, h / 2 + 1)
term.write("3. Modify PIN")
term.setCursorPos(w / 2 - 8, h / 2 + 2)
term.write("4. List PINs")
term.setCursorPos(w / 2 - 8, h / 2 + 3)
term.write("5. Exit")
local ev, num = os.pullEvent("char")
if num == "2" then
term.clear()
term.setCursorPos(w / 2 - 5, h / 2 - 1)
print("Enter PIN:")
term.setCursorPos(w / 2 - 5, h / 2)
local id = read("\7")
local ids = dofile("ids.lua")
for i,v in ipairs(ids) do if v.id == id then ids[i] = nil break end end
local file = fs.open("ids.lua", "w")
file.write("return " .. textutils.serialize(ids))
file.close()
term.setCursorPos(w / 2 - 5, h / 2 + 1)
write("Removed PIN.")
sleep(3)
elseif num == "1" then
term.clear()
term.setCursorPos(w / 2 - 5, h / 2 - 1)
print("Enter PIN:")
term.setCursorPos(w / 2 - 5, h / 2)
local id = read("\7")
term.clear()
term.setCursorPos(1, 1)
print("To skip a value, just press enter without typing anything.")
write("Seconds valid? ")
local exp = read()
write("Maximum uses? ")
local uses = read()
write("Access level? ")
local level = tonumber(read()) or 0
local ids = dofile("ids.lua")
ids[#ids+1] = {id = id, expiration = exp == "" and 0 or os.epoch("utc") + (tonumber(exp) * 1000), uses = uses == "" and -1 or tonumber(uses), level = level}
local file = fs.open("ids.lua", "w")
file.write("return " .. textutils.serialize(ids))
file.close()
print("Successfully wrote new PIN.")
sleep(3)
elseif num == "3" then
term.clear()
term.setCursorPos(w / 2 - 5, h / 2 - 1)
print("Enter PIN:")
term.setCursorPos(w / 2 - 5, h / 2)
local id = read("\7")
local ids = dofile("ids.lua")
local found
for _, v in ipairs(ids) do if v.id == id then found = v end end
if found then
term.clear()
term.setCursorPos(1, 1)
print("To skip a value, just press enter without typing anything.")
write("Seconds valid? ")
local exp = read()
write("Maximum uses? ")
local uses = read()
write("Access level? ")
local level = tonumber(read())
found.expiration = tonumber(exp) and os.epoch("utc") + (tonumber(exp) * 1000) or found.expiration
found.uses = tonumber(uses) or found.uses
found.level = level or found.level
local file = fs.open("ids.lua", "w")
file.write("return " .. textutils.serialize(ids))
file.close()
print("Successfully updated PIN.")
sleep(3)
else
term.setCursorPos(w / 2 - 5, h / 2 + 1)
term.setTextColor(colors.red)
term.write("Unknown PIN.")
term.setTextColor(colors.white)
sleep(3)
end
elseif num == "4" then
local lines = {{"ID", "PIN", "Level", "Expires", "Uses Left"}}
local ids = dofile("ids.lua")
for i, v in ipairs(ids) do
lines[#lines+1] = {tostring(i), v.id, tostring(v.level), v.expiration == 0 and "Never" or os.date("!%c", v.expiration / 1000), v.uses == -1 and "Infinite" or tostring(v.uses)}
end
term.clear()
term.setCursorPos(1, 1)
textutils.pagedTabulate(table.unpack(lines))
print("Press enter to exit.")
read()
elseif num == "5" then
break
end
end
else
term.setCursorPos(w / 2 - 10, h / 2 + 2)
term.setTextColor(colors.red)
term.write("Incorrect password.")
term.setTextColor(colors.white)
sleep(3)
end
end
end)
end)
if not ok then
printError(err)
sleep(5)
end
os.reboot()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment