sc3-rom-dump/rom/apis/chatbox.lua

280 lines
7.6 KiB
Lua
Raw Normal View History

2024-01-15 10:36:50 +01:00
-- SwitchCraft chatbox legacy handler by Lemmmy
-- Contact Lemmmy#4600 regarding any issues
SERVER_URL = "wss://chat.sc3.io"
closeReasons = {
["SERVER_STOPPING"] = 4000,
["EXTERNAL_GUESTS_NOT_ALLOWED"] = 4001,
["UNKNOWN_LICENSE_KEY"] = 4002,
["INVALID_LICENSE_KEY"] = 4003,
["DISABLED_LICENSE"] = 4004,
["CHANGED_LICENSE_KEY"] = 4005,
["FATAL_ERROR"] = 4100,
["UNSUPPORTED_ENDPOINT"] = 4101,
}
-- connection params
local running = false
local connected = false
local ws, wsURL, licenseKey
local licenseOwner, capabilities, players
local connectionAttempts = 0
local chatboxError, chatboxErrorCode
function _shouldStart()
return settings.get("chatbox.license_key") ~= nil
end
local function getLicenseKey()
return settings.get("chatbox.license_key"):gsub("%s+", "")
end
local function isVerbose()
return settings.get("chatbox.verbose", false)
end
local function log(message)
if not isVerbose() then return end
local oldColour = term.getTextColour()
term.setTextColour(colours.lightGrey)
print("\n" .. message)
term.setTextColour(oldColour)
end
local function handleChatMessage(data)
local event = data.event
local source
if data.event == "chat_discord" then
source = {data.discordUser.name and (data.discordUser.name .. "#" .. data.discordUser.discriminator) or data.discordUser.id}
elseif data.event == "chat_chatbox" then
source = {data.rawName, data.user.name or data.user.uuid}
else
source = {data.user.name or data.user.uuid}
end
os.queueEvent(
event,
unpack(source),
data.rawText or data.text,
data
)
end
local function updatePlayer(player)
for i, p in ipairs(players) do
if p.uuid == player.uuid then
players[i] = player
return
end
end
table.insert(players, player)
end
local function handleEventMessage(data)
if not data.event then return end
if data.event == "join" then
os.queueEvent("join", data.user.name or data.user.uuid, data)
elseif data.event == "leave" then
os.queueEvent("leave", data.user.name or data.user.uuid, data)
elseif data.event == "death" then
os.queueEvent(
"death",
data.user.name or data.user.uuid,
data.source and (data.source.name or data.source.uuid) or nil,
data.text,
data
)
elseif data.event == "world_change" then
updatePlayer(data.user)
os.queueEvent(
"world_change",
data.user.name or data.user.uuid,
data.origin,
data.destination,
data
)
elseif data.event == "afk" then
updatePlayer(data.user)
os.queueEvent("afk", data.user.name or data.user.uuid, data)
elseif data.event == "afk_return" then
updatePlayer(data.user)
os.queueEvent("afk_return", data.user.name or data.user.uuid, data)
elseif data.event == "chat_ingame" or data.event == "chat_discord" or data.event == "chat_chatbox" then
handleChatMessage(data)
elseif data.event == "command" then
os.queueEvent(
"command",
data.user.name or data.user.uuid,
data.command,
data.args,
data
)
elseif data.event == "server_restart_scheduled" then
os.queueEvent("server_restart_scheduled", data.restartType, data.restartSeconds, data)
elseif data.event == "server_restart_cancelled" then
os.queueEvent("server_restart_cancelled", data.restartType, data)
end
end
local function handleMessage(eventData)
local rawMessage = eventData[3]
local data = textutils.unserializeJSON(rawMessage)
if not data or not data.type then return end
if data.type == "hello" then
connected = true
licenseOwner = data.licenseOwner
capabilities = data.capabilities
elseif data.type == "players" then
players = data.players
elseif data.type == "event" then
handleEventMessage(data)
end
end
-- return true if we should retry
local function handleClose(eventData)
chatboxError = eventData[3]; local err = chatboxError
chatboxErrorCode = eventData[4]; local code = chatboxErrorCode
if code == closeReasons.SERVER_STOPPING then
return false
elseif code == closeReasons.UNKNOWN_LICENSE_KEY
or code == closeReasons.INVALID_LICENSE_KEY
or code == closeReasons.DISABLED_LICENSE
or code == closeReasons.CHANGED_LICENSE_KEY then
printError("Chatbox error: ")
printError(err)
return false
else
connectionAttempts = connectionAttempts + 1
if connectionAttempts >= 3 then
printError("Couldn't connect to chatbox server after 3 attempts: ")
printError((chatboxError or "unknown") .. " (" .. (chatboxErrorCode or "unknown") .. ")")
return false
else
return true
end
end
end
-- prefix arg is no longer used
function say(text, name, prefix, mode)
if not isConnected() or not ws then error("Chatbox is not connected.", 2) end
if not hasCapability("say") then error("You do not have the 'say' capability.", 2) end
if type(text) ~= "string" then error("Invalid argument #1. Expected string.", 2) end
if name and type(name) ~= "string" then error("Invalid argument #2. Expected string.", 2) end
if mode and mode ~= "markdown" and mode ~= "format" then error("Invalid mode argument #4. Must be 'markdown' or 'format'.", 2) end
ws.send(textutils.serializeJSON({
type = "say",
text = text,
name = name,
mode = mode or "markdown"
}))
return true -- compat
end
function tell(user, text, name, prefix, mode)
if not isConnected() or not ws then error("Chatbox is not connected.", 2) end
if not hasCapability("tell") then error("You do not have the 'tell' capability.", 2) end
if type(user) ~= "string" then error("Invalid argument #1. Expected string.", 2) end
if type(text) ~= "string" then error("Invalid argument #2. Expected string.", 2) end
if name and type(name) ~= "string" then error("Invalid argument #3. Expected string.", 2) end
if mode and mode ~= "markdown" and mode ~= "format" then error("Invalid mode argument #5. Must be 'markdown' or 'format'.", 2) end
ws.send(textutils.serializeJSON({
type = "tell",
user = user,
text = text,
name = name,
mode = mode or "markdown"
}))
return true -- compat
end
function stop()
running = false
if ws then ws.close() end
end
function run()
if running then
error("Chatbox is already running.", 2)
end
running = true
log("Connecting to chatbox server at " .. SERVER_URL)
licenseKey = getLicenseKey()
wsURL = SERVER_URL .. "/v2/" .. textutils.urlEncode(licenseKey)
http.websocketAsync(wsURL)
while running do
local eventData = {os.pullEventRaw()}
local event = eventData[1]
if event == "websocket_success" and eventData[2] == wsURL then
ws = eventData[3]
elseif event == "websocket_message" and eventData[2] == wsURL then
local ok, err = pcall(handleMessage, eventData)
if not ok then
log("Chatbox error: " .. err)
end
elseif event == "websocket_closed" and eventData[2] == wsURL then
if handleClose(eventData) then
running = false
run()
end
end
end
return chatboxError ~= nil and chatboxErrorCode ~= nil, chatboxError, chatboxErrorCode
end
function getError()
return chatboxError, chatboxErrorCode
end
function isConnected()
return running and connected
end
function getLicenseOwner()
return licenseOwner
end
function getCapabilities()
return capabilities or {}
end
function getPlayers()
return players or {}
end
-- legacy compat
function getPlayerList()
if not players then return {} end
local out = {}
for i, player in pairs(players) do
out[i] = player.name or player.uuid
end
return out
end
function hasCapability(capability)
if not capabilities then return false end
for _, cap in pairs(capabilities) do
if cap:lower() == capability:lower() then return true end
end
return false
end