-- 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()