skynet/index.lua

308 lines
7.9 KiB
Lua

-- Skynet v1.3 // server
-- orig. written by minish - 2023-10
-- revived 2025-01
--#region Imports
local cfg = require("cfg")
local ch = require("chat")
local tk = require("tracking")
local cmds = require("cmds")
--#endregion
--#region Machine Preparation
settings.set("shell.allow_disk_startup", false)
--#endregion
--#region Listener States
local wsHandle
local termStreak = 0
local termStreakAt = 0
local lastPlrList
--#endregion
--#region Init/event Helpers
local function connectToLink()
http.websocketAsync(cfg.linkToWS)
end
local function scheduleScan()
os.startTimer(2)
end
--#endregion
--#region Helpers
local function dispatchLink(mtag, cont, data)
if not wsHandle then
return
end
local msg = {
Tag = mtag,
[cont] = data,
}
local jmsg = textutils.serialiseJSON(msg)
wsHandle.send(jmsg)
end
--#endregion
--#region Command Handling
local function nextWord(str)
local first, last
if str:sub(1, 1) == "[" then
str = str:sub(2)
first, last = str:find("[^]]+")
else
first, last = str:find("%S+")
end
if not last then
return str, nil
end
return str:sub(last + 2), str:sub(first, last)
end
local function processMessage(unm, msg, hidden)
if not hidden and msg:sub(1, #cfg.prefix) ~= cfg.prefix then
-- it isn't smth we care about so let's broadcast?
if not cfg.sendToLink then
-- no let's not
return
end
dispatchLink(
"on_minecraft_chat", "OnMinecraftChat",
{ Sender = unm, Content = msg }
)
return
end
if not hidden then
msg = msg:sub(#cfg.prefix + 1)
end
local cmd
msg, cmd = nextWord(msg)
local xcmd
for k, v in next, cmds do
if k == cmd then
xcmd = v
break
end
for _, a in next, v.alias do
if a == cmd then
xcmd = v
break
end
end
end
local user = cfg.users[unm]
if user == nil then
if xcmd.allowAnybody then
-- prevent nil index w/ dummy user
user = {}
else
ch.reportLog("foreign user " .. unm .. " tries: " .. cmd)
return
end
end
if not xcmd then
ch.reportLog("user " .. unm .. " tries invalid cmd: " .. cmd)
ch.send(unm, "not a valid command", "cmd")
return
end
if user.limited and not xcmd.allowLimitedUser then
ch.reportLog("limited user " .. unm .. " tries: " .. cmd)
return
end
if xcmd.requireAdminPrivilege and not user.admin then
ch.reportLog("non-admin user " .. unm .. " attempted to " .. cmd)
ch.send(unm, "no permission to do that..", "cmd")
return
end
local args = {}
for _, x in next, xcmd.args do
local arg
if x.match == "block" then
msg, arg = nextWord(msg)
if not arg and not x.optional then
ch.send(unm, "missing arg: " .. x.desc, "cmd")
return
end
elseif x.match == "player" then
msg, arg = nextWord(msg)
if not arg and not x.optional then
ch.send(unm, "missing player arg: " .. x.desc, "cmd")
return
else
arg = tk:resTg(arg)
if not arg then
ch.send(unm, "target not found for: " .. x.desc, "cmd")
return
end
end
elseif x.match == "rest" then
arg = msg
msg = ""
elseif x.match == "number" then
msg, arg = nextWord(msg)
if not arg and x.optional then
arg = nil
else
arg = tonumber(arg)
if not arg then
ch.send(unm, "invalid number for: " .. x.desc, "cmd")
return
end
end
elseif x.match == "boolean" then
msg, arg = nextWord(msg)
if not arg and x.optional then
arg = "f"
else
arg = (arg or ""):lower()
if arg == "true" or arg == "t" or
arg == "yes" or arg == "y" or
arg == "on" or arg == "1" then
arg = true
elseif arg == "false" or arg == "f" or
arg == "no" or arg == "n" or
arg == "off" or arg == "0" then
arg = false
else
ch.send(unm, "invalid boolean for: " .. x.desc, "cmd")
return
end
end
else
-- what??
return true, error("invalid match type")
end
table.insert(args, arg)
end
ch.reportLog("user " .. unm .. " runs: " .. cmd)
local res = xcmd.proc(unm, table.unpack(args))
if res then
-- command requested shutdown
return true
end
end
--#endregion
local listeners = {
["chat"] = function(username, message, uuid, isHidden)
if not ch.checkDebounce(username, message, uuid, isHidden) then
return
end
return processMessage(username, message, isHidden)
end,
["playerChangedDimension"] = function(username, fromDim, toDim)
ch.reportLog(username .. " goes from " .. fromDim .. " to " .. toDim, "dims")
end,
["playerJoin"] = function(username, dimension)
tk.plrs[username] = { dimension = dimension }
end,
["playerLeave"] = function(username, dimension)
tk.plrs[username] = nil
end,
["websocket_success"] = function(url, handle)
wsHandle = handle
ch.reportLog("connected!", "link")
end,
["websocket_failure"] = function(url, err)
ch.reportLog("connection failed", "link")
end,
["websocket_message"] = function(url, data, isBinary)
local msg = textutils.unserialiseJSON(data)
if not msg then return end
if msg.Tag == "on_discord_chat" then
local p = msg.OnDiscordChat
if not p then return end
local sender, content = p.SenderNickname, p.Content
if not sender or not content then return end
ch.saysquare("@" .. sender, content)
end
end,
["websocket_closed"] = function(url, reason, code)
ch.reportLog("disconnected", "link")
wsHandle = nil
connectToLink()
end,
["timer"] = function()
scheduleScan()
local plrs = tk:scanRange(16)
table.sort(plrs)
local plrList = table.concat(plrs, ", ")
if plrList ~= lastPlrList then
if #plrs > 0 then
ch.reportLog("players in range: " .. plrList, "scan")
elseif lastPlrList ~= nil then
ch.reportLog("players left range", "scan")
end
lastPlrList = plrList
end
end,
["peripheral_detach"] = function()
end,
["terminate"] = function()
-- should we reset term streak
if os.clock() - termStreakAt > 1 then
-- yes
termStreak = 1
termStreakAt = os.clock()
else
-- no so increase streak
termStreak = termStreak + 1
end
-- check if streak is too much
if termStreak >= 2 then
ch.reportLog("restarting to prevent sys access..")
os.reboot()
end
print("?")
ch.reportLog("ALERT!! somebody tried to terminate skynet runner")
end,
}
local function evDispatcher()
while true do
local params = { os.pullEventRaw() }
local event = table.remove(params, 1)
local listener = listeners[event]
if listener and listener(table.unpack(params)) then
break
end
end
end
tk:refresh()
ch.userBc("Loaded!")
ch.reportLog("you are auto subbed to log, welcome!")
if cfg.linkToWS ~= nil then
connectToLink()
end
scheduleScan()
evDispatcher()