280 lines
7.6 KiB
Lua
280 lines
7.6 KiB
Lua
-- 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
|