mcdiscordbot/MinecraftDiscordBot/ClientScript.lua
Michael Chen bbdd5ce586
Bump version to 1.1.4
Added build script to solution files
Added address to configure client connection destination (if proxied)
Added automatic semantic versioning build
2022-11-23 00:28:01 +01:00

269 lines
8.2 KiB
Lua

local secretToken = "$TOKEN"
local connectionUri = "$HOST"
local waitSeconds = 5
-- https://github.com/cc-tweaked/CC-Tweaked/blob/9cf70b10effeeed23e0e9c537bbbe0b2ff0d1a0f/src/main/java/dan200/computercraft/core/apis/http/options/AddressRule.java#L29
-- Chunk size must be less than packet size (type reply, success, chunkids, content: chunk) 16 kb for buffer
local maxMessageSize = (128 - 16) * 1024
local function chunkString(value, chunkSize)
if not chunkSize then chunkSize = maxMessageSize end
local length = value:len()
local total = math.ceil(length / chunkSize)
local chunks = {}
if length == 0 then
total = 1
chunks[1] = ""
else
local i = 1
for i=1,total do
local pos = 1 + ((i - 1) * chunkSize)
chunks[i] = value:sub(pos, pos + chunkSize - 1)
end
end
return total, chunks
end
local function sendJson(socket, message)
return socket.send(textutils.serializeJSON(message))
end
local function sendResponse(socket, id, result, success)
if success == nil then success = 0 end
local total, chunks = chunkString(result)
for i, chunk in pairs(chunks) do
sendJson(socket, { type = "reply", id = id, result = chunk, chunk = i, total = total, success = success })
end
end
-- error: no rs system
-- return rssystem rs
local function getPeripheral(name)
local dev = peripheral.find(name)
if not dev then error({message = "No peripheral '"..name.."' attached to the computer!"}) end
return dev
end
local function runRsCommand(params)
local script, reason = loadstring("local rs = peripheral.find(\"rsBridge\") if not rs then error({message = \"RS Bridge is not attached!\"}) end return rs."..params.command)
if not script then error({message = "Invalid command: "..reason.."!"}) end
local result = table.pack(pcall(script))
local success = result[1]
if not success then error({message = "Command execution failed: "..result[2].."!"}) end
local retvals = {}
retvals.n = result.n - 1
for i=1,retvals.n do retvals[tostring(i)] = result[i + 1] end
return textutils.serializeJSON(retvals)
end
local function getPeripheralInfo(side)
return {type = peripheral.getType(side), methods = peripheral.getMethods(side), side = side}
end
local function getPeripheralList()
local pers = {}
for i,side in pairs(peripheral.getNames()) do pers[side] = getPeripheralInfo(side) end
return pers
end
-- error: any error during execution
-- return string result
local function getResponse(parsed)
if parsed.method == "energyusage" then
return tostring(getPeripheral("rsBridge").getEnergyUsage())
elseif parsed.method == "energystorage" then
return tostring(getPeripheral("rsBridge").getEnergyStorage())
elseif parsed.method == "listitems" then
return textutils.serializeJSON(getPeripheral("rsBridge").listItems())
elseif parsed.method == "listfluids" then
return textutils.serializeJSON(getPeripheral("rsBridge").listFluids())
elseif parsed.method == "craft" then
return tostring(getPeripheral("rsBridge").craftItem(parsed.params))
elseif parsed.method == "getitem" then
local item = getPeripheral("rsBridge").getItem(parsed.params)
if not item then error({message = "Requested item not found!"}) end
return textutils.serializeJSON(item)
elseif parsed.method == "command" then
return runRsCommand(parsed.params)
elseif parsed.method == "peripherals" then
return textutils.serializeJSON(getPeripheralList())
elseif parsed.method == "getonline" then
return textutils.serializeJSON(getPeripheral("playerDetector").getOnlinePlayers())
elseif parsed.method == "whereis" then
local pos = getPeripheral("playerDetector").getPlayerPos(parsed.params.username)
if not pos then return "null" end
return textutils.serializeJSON(pos)
elseif parsed.method == "send" then
if not parsed.params.username then
getPeripheral("chatBox").sendMessage(parsed.params.message, parsed.params.prefix)
else
getPeripheral("chatBox").sendMessageToPlayer(parsed.params.message, parsed.params.username, parsed.params.prefix)
end
return "true"
end
error({message = "No message handler for method: "..parsed.method.."!"})
end
local function logJSON(json, prefix)
if not prefix then prefix = "" end
for k,v in pairs(json) do
local key = prefix..k
if type(v) == "table" then
logJSON(v, key..".")
else
print(key, "=", textutils.serializeJSON(v))
end
end
end
-- return bool success
local function handleMessage(socket, message)
local parsed, reason = textutils.unserializeJSON(message)
if not parsed then
print("Received message:", message)
printError("Message could not be parsed:", reason)
return false
end
pcall(function() print("Received JSON:") logJSON(parsed) end)
if parsed.type == "request" then
local success, result = pcall(function() return getResponse(parsed) end)
if not success then
if not result.message then
sendResponse(socket, parsed.id, result, 2)
else
sendResponse(socket, parsed.id, result.message, 1)
end
else
sendResponse(socket, parsed.id, result, 0)
end
return true
end
printError("Invalid message type:", parsed.type)
return false
end
local function responder(socket)
while true do
local message, binary = socket.receive()
if not not message and not binary then
if message == "outdated" then
printError("Current script is outdated! Please update from the host!")
return
end
handleMessage(socket, message)
end
end
end
local function termWaiter()
os.pullEvent("terminate")
end
local function chatEventListener(socket)
while true do
event, username, message, uuid, hidden = os.pullEvent("chat")
sendJson(socket, {type = "chat", username = username, message = message, uuid = uuid, hidden = hidden})
print("Chat event relayed!")
end
end
local function peripheralDetachEventListener(socket)
while true do
event, side = os.pullEvent("peripheral_detach")
sendJson(socket, {type = "peripheral_detach", side = side})
print("Peripheral was detached!")
end
end
local function peripheralAttachEventListener(socket)
while true do
event, side = os.pullEvent("peripheral")
sendJson(socket, {type = "peripheral", peripheral = getPeripheralInfo(side) })
print("Peripheral was attached!")
end
end
local function listAsSet(list)
local asSet = {}
for i,elem in pairs(list) do asSet[elem] = true end
return asSet
end
local function joinSets(a, b)
local joined = {}
for elem,exists in pairs(a) do if exists then joined[elem] = true end end
for elem,exists in pairs(b) do if exists then joined[elem] = true end end
return joined
end
local function playerStatusEventListener(socket)
local players = {}
while true do
local pd = peripheral.find("playerDetector")
if not not pd then
players = listAsSet(pd.getOnlinePlayers())
break
end
printError("playerDetector not connected!")
sleep(5)
end
while true do
local pd = peripheral.find("playerDetector")
if not not pd then
local newPlayers = listAsSet(pd.getOnlinePlayers())
for player,_ in pairs(joinSets(players, newPlayers)) do
if players[player] and (not newPlayers[player]) then
sendJson(socket, {type = "playerstatus", player = player, status = false})
elseif (not players[player]) and newPlayers[player] then
sendJson(socket, {type = "playerstatus", player = player, status = true})
end
end
players = newPlayers
end
sleep(1)
end
end
local function eventListeners(socket)
parallel.waitForAny(
termWaiter,
function() chatEventListener(socket) end,
function() playerStatusEventListener(socket) end,
function() peripheralDetachEventListener(socket) end,
function() peripheralAttachEventListener(socket) end
)
end
local function socketClient()
print("Connecting to the socket server at "..connectionUri.."...")
local socket, reason = http.websocket(connectionUri)
if not socket then error("Socket server could not be reached: "..reason) end
print("Connection successful!")
socket.send("login="..secretToken)
parallel.waitForAny(
function() responder(socket) end,
function() eventListeners(socket) end
)
socket.close()
end
local function main()
while true do
local status, error = pcall(socketClient)
if status then break end
printError("An uncaught exception was raised:", error)
printError("Restarting in", waitSeconds, "seconds...")
sleep(waitSeconds)
end
end
local oldPullEvent = os.pullEvent
os.pullEvent = os.pullEventRaw
pcall(main)
os.pullEvent = oldPullEvent