diff --git a/depends.txt b/depends.txt new file mode 100644 index 0000000..4ad96d5 --- /dev/null +++ b/depends.txt @@ -0,0 +1 @@ +default diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..b6cd736 --- /dev/null +++ b/init.lua @@ -0,0 +1,54 @@ +local json = require("json") +ssb = dofile(minetest.get_modpath("scuttlebutt") .. "/ssb.lua") + +function ssb_receive(key, json) + relation = "FROM JSON" + message = key .. "said: " .. "FROM JSON" + name = "FROM RELATION" + minetest.chat_send_player(name, message) +end + +function ssb_send(player, key, message) + if key == "" or message == "" then + return "error: empty key or message!" + end + + local pname = player:get_player_name() + message = string.format("Minetest bot here. %s says: %s", pname, message) + + local content = { + type = "post", + text = message, + recps = {key}, + } + + local ret, err = ssb:publish(content) + if (not err) then + return "ok: message sent." + -- return (string.format("ret: %s", json.encode(ret))) + else + return (string.format("error happened: %s", err)) + end +end + +minetest.register_chatcommand("ssb", { + params = "<@key> [message]", + description = "Scuttlebutt Chat", + privs = { + ssb = true, + }, + func = function(name, param) + local player = minetest.get_player_by_name(name) + local key, message = string.match(param, "^(@[^ ]+) *(.*)$") + if not key or key == "" then + return true, "error: could not parse key" + elseif not message or message == "" then + local settings = minetest.settings:to_table() + local ip = settings.server_address or "127.0.0.1" + local port = settings.port or 30000 + message = "Please join me on minetest: " .. ip .. ":" .. port + -- return true, "error: could not parse message" + end + return true, ssb_send(player, key, message) + end +}) diff --git a/mod.conf b/mod.conf new file mode 100644 index 0000000..b0e8b1d --- /dev/null +++ b/mod.conf @@ -0,0 +1,3 @@ +name = scuttlebutt +description = Add Scuttlebutt chat. +depends = default diff --git a/ssb.lua b/ssb.lua new file mode 100644 index 0000000..ecccf97 --- /dev/null +++ b/ssb.lua @@ -0,0 +1,192 @@ +local json = require("json") +local socket = require("socket") +local unix = require("socket.unix") +local bit32 = require("bit32") +local ssb = { VERSION = '0.1' } + +local function get_ssb_dir() + local home = os.getenv("HOME") + return string.format("%s/.ssb", home) +end + +local function read_manifest() + local manifest = get_ssb_dir() .. "/manifest.json" + local f = io.open(manifest, "rb") + if not f then + return nil, "cannot find manifest: " .. manifest + end + local buf = f:read("*all") + f:close() + local js = json.decode(buf) + return js, nil +end + +function do_read(conn, id, final) + local buf, err = conn:receive(9) + if string.len(buf) < 9 then + minetest.log("do_read: header len should be 9 but is: " .. string.len(buf)) + return nil, string.len(buf) + end + + local first = string.byte(buf, 1) + local len = bit32.bor(bit32.lshift(string.byte(buf, 2), 24), + bit32.lshift(string.byte(buf, 3), 16), + bit32.lshift(string.byte(buf, 4), 8), + string.byte(buf, 5)) + -- remove minus sign + local id = bit32.bxor(bit32.bor(bit32.lshift(string.byte(buf, 6), 24), + bit32.lshift(string.byte(buf, 7), 16), + bit32.lshift(string.byte(buf, 8), 8), + string.byte(buf, 9)), 0xFFFFFFFF) + 1 + -- minetest.log("do_read: len: " .. len) + -- minetest.log("do_read: id : " .. id) + if id ~= 1 then + minetest.log("do_read: id should be 1 but is: " .. id) + -- minetest.log("do_read: " .. string.byte(buf, 6) .. string.byte(buf, 7) .. string.byte(buf, 8) .. string.byte(buf, 9)) + end + return conn:receive(len) +end + +function do_write(conn, req, mtype, ptype, id, final) + -- first byte construction + local pkt = { + buffer = 0, + string = 1, + json = 2, + } + + local pktype = pkt[ptype] + local stream + if mtype == "async" then stream = 0 else stream = 1 end + local fin + if final then fin = 1 else fin = 0 end + local first = string.char(bit32.bor(bit32.lshift(stream, 3), bit32.lshift(fin, 2), bit32.band(pktype, 3))) + + -- big-endian header construction + local b0 = bit32.rshift(bit32.band(string.len(req), 0xFF000000), 24) + local b1 = bit32.rshift(bit32.band(string.len(req), 0x00FF0000), 16) + local b2 = bit32.rshift(bit32.band(string.len(req), 0x0000FF00), 8) + local b3 = bit32.band(string.len(req), 0x000000FF) + -- limitation to 255 ids + local head = string.char(b0, b1, b2, b3, 0, 0, 0, id) + + -- concat + local buf = first .. head .. req + return conn:send(buf) +end + +function do_goodbye(conn) + return do_write(conn, "", "async", "buffer", 0, true) +end + +function rpc_async(conn, meth, mtype, param) + -- wrap + local req = json.encode({ + name = meth, + args = {param}, + }) + + local ret, err = do_write(conn, req, mtype, "json", 1, false) + -- minetest.log("do_write: len: " .. ret) + if err then + minetest.log("do_write: " .. err) + return ret, err + end + + ret, err = do_read(conn, 1, false) + -- minetest.log("do_read: len: " .. string.len(ret)) + if err then + minetest.log("do_read: " .. err) + return ret, err + end + + do_goodbye(conn) + +-- local ret = json.encode({ +-- name = meth, +-- type = mtype, +-- arg = param, +-- }) +-- local err = nil + return ret, err +end + +function muxrpc(conn, meth, mtype, param) + local switch = { + async = rpc_async, + -- TODO implement all types + } + + local rpc = switch[mtype] + if not rpc then + return nil, "method type unimplemented yet." + end + + return rpc(conn, meth, mtype, param) +end + +local function parse_msgs(arr) + local r = {} + for i, msg in ipairs(arr) do + r[i] = json.decode(msg) + end + return r +end + +local mt = { + __index = function(self, k) + local manifest, err = read_manifest() + if err then + return function(s, ...) + return nil, "cannot initialize: " .. err + end + end + + return function(s, ...) + local res, err = nil + + if not manifest[k] then + return err, "method not found: " .. k + end + + local mtype = manifest[k] + + local params = {...} + local js + -- for key, val in ipairs(params) do + -- minetest.log(key .. "=" .. json.encode(val)) + -- end + -- take only first param + js = params[1] + + local c = unix() + local ret, err = c:connect(get_ssb_dir() .. "/socket") + if err then + return c, err + end + + local ret, err = muxrpc(c, k, mtype, js) + if err then + c:close() + return ret, err + end + + -- minetest.log(ret) + if not err then + if (type(ret) ~= "table") then + res = json.decode(ret) + else + res = parse_msgs(ret) + end + else + res = nil + err = ret + end + + return res, err + end + end +} + +setmetatable(ssb, mt) +return ssb diff --git a/textures/ssbmod_shell.png b/textures/ssbmod_shell.png new file mode 100644 index 0000000..ded88ac Binary files /dev/null and b/textures/ssbmod_shell.png differ