Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --
- -- JSON API by Elvis Jerricco: http://pastebin.com/4nRg9CHU
- --
- local encode, encodePretty, parseBoolean, parseNull, parseString, parseArray
- local parseObject, parseMember, parseValue, decode, decodeFromFile
- local controls = {["\n"]="\\n", ["\r"]="\\r", ["\t"]="\\t", ["\b"]="\\b", ["\f"]="\\f", ["\""]="\\\"", ["\\"]="\\\\"}
- local function isArray(t)
- local max = 0
- for k,v in pairs(t) do
- if type(k) ~= "number" then
- return false
- elseif k > max then
- max = k
- end
- end
- return max == #t
- end
- local whites = {['\n']=true; ['r']=true; ['\t']=true; [' ']=true; [',']=true; [':']=true}
- function removeWhite(str)
- while whites[str:sub(1, 1)] do
- str = str:sub(2)
- end
- return str
- end
- local function encodeCommon(val, pretty, tabLevel, tTracking)
- local str = ""
- -- Tabbing util
- local function tab(s)
- str = str .. ("\t"):rep(tabLevel) .. s
- end
- local function arrEncoding(val, bracket, closeBracket, iterator, loopFunc)
- str = str .. bracket
- if pretty then
- str = str .. "\n"
- tabLevel = tabLevel + 1
- end
- for k,v in iterator(val) do
- tab("")
- loopFunc(k,v)
- str = str .. ","
- if pretty then str = str .. "\n" end
- end
- if pretty then
- tabLevel = tabLevel - 1
- end
- if str:sub(-2) == ",\n" then
- str = str:sub(1, -3) .. "\n"
- elseif str:sub(-1) == "," then
- str = str:sub(1, -2)
- end
- tab(closeBracket)
- end
- -- Table encoding
- if type(val) == "table" then
- assert(not tTracking[val], "Cannot encode a table holding itself recursively")
- tTracking[val] = true
- if isArray(val) then
- arrEncoding(val, "[", "]", ipairs, function(k,v)
- str = str .. encodeCommon(v, pretty, tabLevel, tTracking)
- end)
- else
- arrEncoding(val, "{", "}", pairs, function(k,v)
- assert(type(k) == "string", "JSON object keys must be strings", 2)
- str = str .. encodeCommon(k, pretty, tabLevel, tTracking)
- str = str .. (pretty and ": " or ":") .. encodeCommon(v, pretty, tabLevel, tTracking)
- end)
- end
- -- String encoding
- elseif type(val) == "string" then
- str = '"' .. val:gsub("[%c\"\\]", controls) .. '"'
- -- Number encoding
- elseif type(val) == "number" or type(val) == "boolean" then
- str = tostring(val)
- else
- error("JSON only supports arrays, objects, numbers, booleans, and strings", 2)
- end
- return str
- end
- function encode(val)
- return encodeCommon(val, false, 0, {})
- end
- function encodePretty(val)
- return encodeCommon(val, true, 0, {})
- end
- function parseBoolean(str)
- if str:sub(1, 4) == "true" then
- return true, removeWhite(str:sub(5))
- else
- return false, removeWhite(str:sub(6))
- end
- end
- function parseNull(str)
- return nil, removeWhite(str:sub(5))
- end
- local numChars = {['e']=true; ['E']=true; ['+']=true; ['-']=true; ['.']=true}
- function parseNumber(str)
- local i = 1
- while numChars[str:sub(i, i)] or tonumber(str:sub(i, i)) do
- i = i + 1
- end
- local val = tonumber(str:sub(1, i - 1))
- str = removeWhite(str:sub(i))
- return val, str
- end
- function parseString(str)
- local i,j = str:find('[^\\]"')
- local s = str:sub(2, j - 1)
- for k,v in pairs(controls) do
- s = s:gsub(v, k)
- end
- str = removeWhite(str:sub(j + 1))
- return s, str
- end
- function parseArray(str)
- str = removeWhite(str:sub(2))
- local val = {}
- local i = 1
- while str:sub(1, 1) ~= "]" do
- local v = nil
- v, str = parseValue(str)
- val[i] = v
- i = i + 1
- str = removeWhite(str)
- end
- str = removeWhite(str:sub(2))
- return val, str
- end
- function parseObject(str)
- str = removeWhite(str:sub(2))
- local val = {}
- while str:sub(1, 1) ~= "}" do
- local k, v = nil, nil
- k, v, str = parseMember(str)
- val[k] = v
- str = removeWhite(str)
- end
- str = removeWhite(str:sub(2))
- return val, str
- end
- function parseMember(str)
- local k = nil
- k, str = parseValue(str)
- local val = nil
- val, str = parseValue(str)
- return k, val, str
- end
- function parseValue(str)
- local fchar = str:sub(1, 1)
- if fchar == "{" then
- return parseObject(str)
- elseif fchar == "[" then
- return parseArray(str)
- elseif tonumber(fchar) ~= nil or numChars[fchar] then
- return parseNumber(str)
- elseif str:sub(1, 4) == "true" or str:sub(1, 5) == "false" then
- return parseBoolean(str)
- elseif fchar == "\"" then
- return parseString(str)
- elseif str:sub(1, 4) == "null" then
- return parseNull(str)
- end
- return nil
- end
- function decode(str)
- str = removeWhite(str)
- t = parseValue(str)
- return t
- end
- function decodeFromFile(path)
- local file = assert(fs.open(path, "r"))
- return decode(file.readAll())
- end
- --
- -- Socket Library. Fast and asynchronous.
- --
- local Socket = {}
- Socket.__index = Socket
- Socket.timeout = 30 -- Should be server timeout + 10. DO NOT CHANGE UNLESS
- -- YOU OWN THE SERVER FOR THE SOCKET CONNECTION, AND
- -- HAVE CONFIGURED THE SERVER CORRECTLY.
- Socket.connections = {}
- Socket.messages = {}
- Socket.timers = {}
- Socket.emptyResponse = "empty response"
- Socket.unexpectedError = "unexpected error"
- Socket.httpFailure = "http failure"
- Socket.parseError = "parse error"
- Socket.verifyFailure = "verify failure"
- Socket.serverError = "server error"
- Socket.identityTaken = "identifier already in use"
- Socket.timeoutError = "timeout"
- function Socket.randomId()
- return tostring({}):sub(8, -1)
- end
- function Socket.encodePost(data)
- local ret = ""
- for k, v in pairs(data) do
- ret = ret .. textutils.urlEncode(k) .. "=" .. textutils.urlEncode(v) .. "&"
- end
- return ret:sub(1, -2)
- end
- function Socket.new(address, identifier)
- local self = setmetatable({}, Socket)
- self:setup(address, identifier)
- table.insert(Socket.connections, self)
- return self
- end
- local function queueSocketFailure(fullAddress, message)
- local requestObject = Socket.messages[fullAddress]
- if requestObject then
- if requestObject.listen == "true" then
- os.queueEvent("socket_failure", requestObject.address, "listen",
- "", requestObject.identifier, message)
- else
- os.queueEvent("socket_failure", requestObject.address, "send",
- requestObject.channel, requestObject.identifier, message)
- end
- end
- Socket.messages[fullAddress] = nil
- end
- local function refreshConnection(fullAddress)
- local requestObject = Socket.messages[fullAddress]
- local request = {
- ["identifier"] = requestObject.identifier,
- ["listen"] = "true",
- ["messageId"] = requestObject.messageId
- }
- if not http.request(fullAddress, Socket.encodePost(request)) then
- error("Socket: HTTP request failed")
- end
- local timer = os.startTimer(Socket.timeout)
- Socket.timers[fullAddress] = timer
- end
- local function handleReceivedMessage(fullAddress, message)
- local requestObject = Socket.messages[fullAddress]
- resp, result = pcall(function() return decode(message) end)
- if resp then
- if type(result.MessageId) == "string" and
- result.MessageId == requestObject.messageId then
- if result.Error then
- if type(result.Message) == "string" then
- if result.Message == Socket.identityTaken then
- queueSocketFailure(fullAddress, Socket.identityTaken)
- else
- queueSocketFailure(fullAddress, Socket.serverError)
- end
- else
- queueSocketFailure(fullAddress, Socket.serverError)
- end
- elseif result.SendSuccess then
- os.queueEvent("socket_write_success", requestObject.address,
- requestObject.channel, requestObject.identifier,
- requestObject.message)
- Socket.messages[fullAddress] = nil
- elseif result.RefreshConnection then
- refreshConnection(fullAddress)
- elseif type(result.Queue) == "table" then
- local queue = result.Queue
- for k, v in pairs(queue) do
- if type(v.Channel) == "string" and
- type(v.Message) == "string" then
- os.queueEvent("socket_message", requestObject.address,
- v.Channel, requestObject.identifier, v.Message)
- end
- end
- refreshConnection(fullAddress)
- elseif type(result.Message) == "string" and
- type(result.Channel) == "string" and
- result.Message:len() > 0 and result.Channel:len() > 0 then
- os.queueEvent("socket_message", requestObject.address,
- result.Channel, requestObject.identifier, result.Message)
- refreshConnection(fullAddress)
- else
- queueSocketFailure(fullAddress, Socket.verifyFailure)
- end
- else
- queueSocketFailure(fullAddress, Socket.verifyFailure)
- end
- else
- queueSocketFailure(fullAddress, Socket.parseError)
- end
- end
- local function getSocketFromTimer(id)
- for k, v in pairs(Socket.timers) do
- if v == id then
- return k
- end
- end
- end
- local function getSocketFromDetails(address, identifier)
- for k, v in pairs(Socket.connections) do
- if v.address == address and v.identifier == identifier then
- return v
- end
- end
- end
- function Socket.run()
- while true do
- local event, url, handle, identifier, message = os.pullEvent()
- if event == "timer" then
- local timerAddress = getSocketFromTimer(url)
- if timerAddress then
- queueSocketFailure(timerAddress, Socket.timeoutError)
- end
- elseif event == "socket_message" then
- local channel = handle
- local socket = getSocketFromDetails(url, identifier)
- if socket then
- if type(socket.listeners[channel]) == "function" then
- socket.listeners[channel](message)
- end
- end
- elseif event == "http_success" then
- if type(url) == "string" and
- type(handle) == "table" and
- type(handle.readAll) == "function" then
- if Socket.messages[url] then
- if not handle then
- queueSocketFailure(url, Socket.emptyResponse)
- else
- local resp, contents = pcall(handle.readAll)
- if resp then
- handleReceivedMessage(url, contents)
- else
- queueSocketFailure(url, Socket.unexpectedError)
- end
- end
- end
- end
- elseif event == "http_failure" then
- queueSocketFailure(url, Socket.httpFailure)
- end
- end
- end
- function Socket:setup(address, identifier)
- if self.closed then
- error("u wot m8, use :reconnect plz")
- end
- if not identifier then
- identifier = Socket.randomId()
- end
- if address:sub(1, 4) ~= "http" then
- error("http prefix required for socket address")
- end
- self.address = address
- self.identifier = identifier
- self.listeners = {}
- local messageId = Socket.randomId()
- local request = {
- ["identifier"] = identifier,
- ["listen"] = "true",
- ["messageId"] = messageId
- }
- fullAddress = address .. "#" .. messageId
- self.fullAddress = fullAddress
- self.listenId = messageId
- if not http.request(fullAddress, Socket.encodePost(request)) then
- error("Socket: HTTP request failed")
- end
- request["address"] = address
- Socket.messages[fullAddress] = request
- local timer = os.startTimer(Socket.timeout)
- Socket.timers[fullAddress] = timer
- end
- function Socket:write(channel, contents)
- if self.closed then
- error("Cannot write to closed socket")
- end
- local messageId = Socket.randomId()
- local request = {
- ["channel"] = channel,
- ["identifier"] = self.identifier,
- ["listen"] = "false",
- ["messageId"] = messageId,
- ["message"] = contents
- }
- fullAddress = self.address .. "#" .. messageId
- if not http.request(fullAddress, Socket.encodePost(request)) then
- error("Socket: HTTP request failed")
- end
- request["address"] = self.address
- Socket.messages[fullAddress] = request
- end
- function Socket:on(channel, func)
- if self.closed then
- error("Really? You want to attach a listener on a closed socket?")
- end
- self.listeners[channel] = func
- end
- function Socket:reconnect()
- self.closed = false
- table.insert(Socket.connections, self)
- Socket.messages[self.fullAddress] = {
- ["identifier"] = self.identifier,
- ["messageId"] = self.listenId,
- ["listen"] = "true",
- ["address"] = self.address
- }
- refreshConnection(self.fullAddress)
- end
- function Socket:close()
- self.closed = true
- for k, v in pairs(Socket.connections) do
- if v == self then
- table.remove(Socket.connections, k)
- break
- end
- end
- end
- local messages = {}
- local function clearArea()
- local oldX, oldY = term.getCursorPos()
- for i = 1, 18 do
- term.setCursorPos(1, i)
- term.clearLine()
- end
- term.setCursorPos(oldX, oldY)
- end
- local function renderMessages()
- local oldX, oldY = term.getCursorPos()
- local position = 18
- for i = #messages, math.max(1, #messages - 28), -1 do
- term.setCursorPos(1, position)
- term.write(messages[i])
- position = position - 1
- end
- term.setCursorPos(oldX, oldY)
- end
- local function receivedMessage(msg)
- msg = msg:gsub("\\u003c", "<"):gsub("\\u003e", ">"):gsub("\\u0026", "&")
- table.insert(messages, msg)
- clearArea()
- renderMessages()
- end
- local function main()
- write("Enter a username: ")
- username = read()
- clearArea()
- s = Socket.new("http://onelann.tk:9000/", Socket.randomId())
- s:on("message", receivedMessage)
- while true do
- term.setCursorPos(1, 19)
- term.clearLine()
- term.setCursorPos(1, 19)
- term.write("> ")
- message = read()
- clearArea()
- renderMessages()
- s:write("broadcast", "<" .. username .. "> " .. message)
- end
- end
- local function monitor()
- while true do
- local event, p1, p2, p3 = os.pullEvent()
- if false and event:find("socket") then
- table.insert(messages, "[EVENT] " .. event .. " | " .. p1 .. "|" .. p2 .. "|" .. p3)
- clearArea()
- renderMessages()
- end
- end
- end
- parallel.waitForAny(Socket.run, main, monitor)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement