From d0a8e78d335b0e2ed15eefc89d703d48594fdef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Rouits?= Date: Sat, 23 May 2020 15:17:53 +0200 Subject: [PATCH] Initial WIP commit, functionnal --- depends.txt | 1 + init.lua | 54 +++++++++++++ mod.conf | 3 + ssb.lua | 192 ++++++++++++++++++++++++++++++++++++++++++++++ textures/ssbmod_shell.png | Bin 0 -> 17401 bytes 5 files changed, 250 insertions(+) create mode 100644 depends.txt create mode 100644 init.lua create mode 100644 mod.conf create mode 100644 ssb.lua create mode 100644 textures/ssbmod_shell.png 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 0000000000000000000000000000000000000000..ded88ac2911159131f0364a9e69db09de755d6cb GIT binary patch literal 17401 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 kO8A97g>3t!J*(|UauArRWA z=W`e{?kKxgfHA6ZG;VnrUT+0VQgJ{?!K@jI-N`G`x5DJ;p5@6LI z&|sQ5G&lBP{Thb%-wR;=t4VBF(+$q^@OV`ON(M;=(SL)o5XAN*1p8GCvN(rFbE7vA z#=JH2p)qxb#F?-NUd4+=&C9U0tsU9ca`N%6QZKsS{1CCV+Y6xYiXVId=UjRY7 zfKnJk&di}Z-V04rp=X5!P{;x}PV%v-rEhj7Rw*U_JBVl>h=dh8?| zcfxGqo)C%!^Zy;uXcLvYEvvtWQ!u=3yMfo|W$@1Y3`Hxs42PoGfAdJ85WfrH*xhMh z_3|9%zmmoLSMpfBJcGWTEM1S+rxm8Kd_Jm?Fj?&LNZ`Il3ouX&FcyqmCEjqAxLL#8 zF<>f#Th-7J?IEolTRjqa%g$bTR1aFC?Ra73d?cGoU}QL40XI^OJ!pA!Ati-O-Ne+> zkH&2e-iEZ9vNLZ^J{+8^N+cb_lE#%#REBI+K|bw4Wce0k+CucX;BhsmK5j6qq=D+0 zgHd@*9by|o$b~bMEJ?)Mv3~6naC^%zwBaPev!yZb}*_N=3Ky}k;J zoiLZ0R>U>yx*b^z8>P{|#rb;?_csh5ix;1Lh%iYm8$;XnMHoB&Lil_YF!VH8D8L4$ zg2H_kTAK~DG@0mVGtu2)qO;9FbCZG18w_k*qhs?%9i8nag|`B`2*y+4$mx@yy$J05 z$ePs;p=h7xrvP;Cse^c5_Xn^mcvAd@CGfdCSg>gc)^~3qz-NI|rth`&3lZL?!OW;o z+>B8B_9xy2OuP3jSQEsp58Vo`a9E7*7&;1b2^HP(K4kSA6sADWsNjZ)d|$r>m^pn0 zf`Kw*j4X^;9unIeYeZyL3h_`Jn`rS)brpksU>FF zTh7|Y2^S#FDKx#O%fPlRIyP<4(X>UU-v!gf_fRs+)sH7Ei_7y^aJl^O2dWW|x5--| z*5*I1b$p-#U^tVPT*TFuBYyN2&EvhW`VETPtJMlDZCQb}t3%)^KfnwqUJp87`~cam zC={2y!`6-WeHTL;h9ax)ZkI26Qm8Z9L)JiGOf#o~>pJqWbm3Dc&YX#_O}iXp>P8}C z=3u0BN*^C}>+v}1mgAw7*se)YHEi7Q3?6-G4w|;UMw%{!qd2A}Q)=W;TQ`*=0Z^K` zkrq}}-E!Ow6vJ=bn8(h6kFbKVXoNP1xa5j&P~}f^xe@Byh{qm28!O(wm%>A@w_LWs z8FIpylZz1y76Re%2*D6m!me|O&scKaz`0&c^T6+~gv%X3Z_j$X_R@Fp#w$08yRhW` zOuaG*v_~4K3#bRfS1IDs6y9*bXr}r!+b)REN>PwE^JwaAgV*oED~nbl-l0KJET~=& z5}Vr5_RM=wHI-`m=YR78T=V^F5X;Btz1@B*fcF|#Af8Kr87fkJ9vF!%dKauCOCW^i zlAAB3E#)WGk0$MH3b%ot`H|zOm@3>uJtAvk$b}Om1ldd&>(@O=THP@GWVk)06skFZ zQ>>laQ-Y1_oLXn4KB!j|R8@~hP0b_<{RB1;>nD)=_LhZ+gtyw?6R5$MvF8yEum?VDD`4orh*7Rx z23f>C>Uth$ob`EX)cD~21#*{D7h=&CY*_acnzy})OeRE37S@H4kXYcBNemWvK?rF& z-G^ADiJE0LZ+ro(R{Rx97vG5`i|(YHg*cyZ3W}l%Z|rQxw*tU_N-L1sQ(b$Y)xbD7 z0gG}gZz3PhQwS!Ibs^RjX4_JcY4-x0(ciQZAG%*#g=|NJkhx`;_|84wL0M@z;(C0y z2_#C}Dk{RsC{npJ+#VHr#?}lymM$RqirPw)R+P%Z5JSpLW6I$1s0ak{+{#xG%|@X| zO+eM4`do!;6z)g(^q%ey$v;n>_7%)L;k(qKEJ8?O{)N^zZh3`{+1B_vWgEpY&h-gX z-PEXKaJ`1nM~YtTfov4X8~%!3n;+qm4Cw{ zf16Fci9-8@1`Z!_nyr~kvCZwq=P1l7j_HMkTedihj&`%)3LKK1yEO7XeAIZ{beAqz>-0@snurATlrLm|9iX6^7) zT*u_^9*Y^jK8*x{lklj>WW#vvTuh$K? z#}B{1hFq1no}mr1Yyq-c>zSMd_?nyzSRoZPENcKCnPVKN3lMj`3Y?zB$D`{)q*;X) z5n2pZXvt!lFxM%Bmu;2}9vqk6cqxVr9cDL&><;)9n8?V7_a-9HR262P!PG_U%V8w? zEkMd|DaoLs6!tbx5R4)NE6@&raej|&i$G9=!C>K=44(lLu z{Z53Fj7LdnjydjB%$zlwwz>(2dZ-gQk%$lm(KH1vw+o8mLpG>3`MO<}lKoDXQPYiQC;4RE-lb zRNXElw{${}%SxmR7<17?0kuA|5KZP^QIN=`5KG5NQ1mQK#gXLevHr?tO+)Q2$J+ye&oR46|NSra} zSETU*iT8A`!IO{8MW}BRA$fsuXy8M&t-*@-b9nRR3_98jXf7KkI8(*;h9>sbfSGgx zi9{Go(J^R9EhbGp8poY*CZ-;94u&?I1qCOet9>fknn%O$9|o_tjD#%`X~McSkHb#I zSWHf1Z!2o}7|>J924VqZutyf4lPPSlnK7*J>KcvbMiYZ|{@B z!BIP_9`C2tVN7f!>dI@7HFLXn1H!jLMK~2jCZC1Jt3l6*@XR*l!ksu&IjE8fnh+J9^zYBM(G5N=KDr=I>`?{U27M^&eYE2%?c~ zc=^R|>)k*x{TXNe5>t-;G6|(X>%uR4JPLZdP0I7v4bd=ij7A{Q z=aYgU7Sa?Ef`U}iLLz1$63!tKF_DN_^j_n42ZeCh86bl#pj4Tmjs=_&e zk+&tvPwz_y>%KfUunoW>0GEm{?0L6MgoA}lfpAOifQf9xp5Hf%%&w+sl+k-?`Ahin ziIHbroT}#I4Eehm=zzZ2!EcU5E7OEp0*2s zX|IpR?@>N~(%J3^OW=i7uc0>`hBxDZ;^G*0{Vc*Tp~V|WTSd6Gt?}zP`@FlMsctM= zayKzLpNr#_dH;&d8=uFCr`$^I>jF6D^C@yug!0=BO6iuADX6Ve;R`Sd9a9O8WYR)5 z!=dLn^)1?Nf>Gq-_1dXj8z?e(Jmqx0_ZIyQi{JT`_}S!~9#FBE6taIbf50@*Odyy3 z!br+TTPxa${j z^dv$yb}&H}MVM|YuNT0aa1G^^-hUjuDmh z_}a;rW7^P3$Xj-jQ&^Y!3y*`ht{nDbM=h*b{Ro9(r_KJkEeM)dj;Ff_A3XZd9K87a z*U{0w94=P?USB0y3gH4YmxXlFz}C$}3Th?#<1UN?MY{v9j;2?i7-GAS6A4#o76PzvAfq^^Yk zc-$VOc@{2B!)4RXg~#Q=@|IPU8diL6RG&N%ZBNr|PZA@20T2f*LtW?Sj~*(zqB0^rP?Xoy7^cu8->V+gsX~ zh7hOacp-r8OkvE*n^x;{Tj45kQ_f#7%UxnY^KjDElF@aAgJYW_RHs*jrdgcKy+?Cn z!KTG{;{6wq&Sel_0fbXALh@9d%N7vk4n2S#kEihbfPS(QNbzinq5;<&aH4LE)Cv6<%MG+k*)G4PHw_9>}LsTE*EVcasQ(W@W;QrOnVp&89HLO z3&-h+r#ihpj`;#ZjGZtEr=N8luDWIcPCWVh_VwAl1j&y}@$N0{>u8u*y>?-SxW&K9 z6lQLJev1+qQHGIMjHljxfvDYO7BW#2zJ@BOJ~yHFTquKNQyi7kYpHDOxDCJCk4@c; z=!*B^l4<9V1)%Jn0A^XG(9~q<{R^kLH5v&g5Lqb1fUHO=m$Dm8r2xPz$`opfEWGf7 zzz87}j4ez%GDcRwp;WS`Q1hU%D1$>&UC0_1J+4+&H5`B99GrCOIh5GhvS|(0uUSr8 z%(QK9L_8ivCY7K8Pq}X}Z9w7m`e=`!^72a5)(yp=A;U3Z^aMeT?P`a%nvJ{&VoVeFV%*gHsmJw$!YEVJK~VQu(ikn>=*6N zc(@_=qWZ*k(F6WTKb`$`L02o6~&)fI`434YiMHXO(0+z52+(f|RL%{7rPS4o| z9uBD9{++;b*(g=zXle)+%^mKkmAijmAEoHNEPE6Tc2}@48tEiQSEt?o*VkhqnKWSN z7BrU%f4~pTn`VhqrKMZ>=J3+3$UW_jiAQyzD%EO+NVGPu${@n98g~ zW0WoV&U{uscQPtwO(!6z`8_bwIp~Qz;+w;$JEeh|CiXadDa*??GPmC<{;aSVJG7zm zehXkqx4_L^AI4%AW>_I)v@A{ytQCPEdXphaIPts%cQ8Q0BEV`Q-Y!r$f|VnoX<77k z+d|+Au-y)d6fVhW?N%AwF8jL#vzxZ+R4+)z?c9LNt-|LkfmY^$p3hJ#y3qO$S3O8| z3C0wIbIT>Sgj;r?wv#^)JA~wznqdb1~*%uU90vj?ZD8>W+5=L7O{1$ zbWmtz4+fn+>?6Gx)!RKBs@H;*u;C9Y+DO{Wn=p48_^@Mpo&37WHOc}~xB3o+`W?NG z(|a(etOlF9w@_inGEBIuN(sN@BT3T!_O_)I)0-@VOvb{Br8%5>mX8`*%{`2HX&Pwy zD3{@AYSFQ6vySdAoiyDgLgdmCsH%rr&pO(d(@;zEw)yDlTnW=8KV!+q>A2*h=YA(K zz<$O5h3nhj6G+O-eQR3)+yG!PxA-jp>X^c;Oh*`7?w+S;0dJw>CA_MKq5!6TWLc0b zfCwq`aT%r+2|zZLg~}Ch)!sL#5Yt;0SqH*$POsk1vx-GOlNEr4`d}R;p_t0S4FO)g z2#l^nez`r1*SPf+Or3TOxd*qK(Uj2Qg;|>P_4=4C1Tqq_H;?9sMJ#M@&ZBvoj#$*9 z$ie3?gIeN3HY=F^0UEcyN}Wf&J!?^f>=i|!hi|Gf0rU!hyX3l_`EU0fWH}3<}nw++TtulN+FOj`GRF5Lwhufo&Mu+=MjGd^VqQd^ZY}O)X_jGYYLJADSMO)mImvf|3y=ZP)iso%^ zp}FZzXZFBhx@yn#ap$o#ozk+-lZBti{^tI7rUSi8m?ceYlRbBK z5vlsb@dz#2K+!-p7r`@6et|lL-0o5u*34$31mf)3g58soBe3`Hv@FieO_Je@a18QUgAzNtkZcHb`z^Rj zIP|Q6vI+H2y)J6;%7wD%`=AR$=8S-*TX4JWZB;4JqH*d_vP@29+@Ww=2>IdA+e-s_;Cs{Ko=?XY51q07^rNZawMO%s9PHK;gtG%BWzMA@VUluvG;-)l~th}zSqpyss6 z)JRe>V>IE0z=%3@%v+9hQ;(1&3-;8`A7%D5T2#?Hs7x=WgDn6~Ly3UF=K>fE20gYh ztaQBA%#4(dlHp~$7X$(1-JGU_^#tIl3Lv_&iQ2=&=iEUrYGw|$BMN25?O@zxCbA(0 z|Hyh|yF$oz+QX@dZBazl^rHH>LGV=h=vX0G!d+(~TA_~MoQQ~sUTScHsB zVa&`LW_Wck>v*Gu8EGAXVNwt*P5_a~tECO0`V_dzywIa*q)59!wS^&{8(~i>6_XGg z7vL^6DVbC{X%uqZ;erqd5Zb-46(u9eQ8umuR@S76CL^mLpHg=SPYcfi5^h9USf$su|4>yoqdgjMLnPMa*9Y;1+q$A3LsfFzM3}02DQsHcNl_9=Qfv5IjN< zjI?1!KI~;3ueY##EKlub-r9gY8I%!N0Pxn7Al1}M`wR)n$8xCu+z434PW$gkArKLP zFQ^kR^p6=s79c6ZNv0~4p6*-TjgsNzC?8h}RkM)pwF^hP9bd3)7ez&`WCrceE=9*b zmcz;!mauBvqNk)f56x=c+i|UfNuQPgplE!_l#BD4Oc*h6i~@s~EJDUGBWrq@zzDfe z2L2%>C}{{F9TM895c(!CC|pB#3RTApLSSgA9o`j7fwRK_WAFtHD5?%`!!US;)FR#7 z1v8x|FqrL0qy5z;N9|=O%G^6W7to@Z{CVZ^BsuYT+1inE* z5&$Dowd|fnw=WBKO(g3q4ze!%Ic)`u%C@A?&B9y0QwF!vtLY~wg5O( zEC{C5nEA5xT$h3n5Q1RkO*6J3%=+Hl%5r^i_y$+NTNgxhbu%$M*&0XXwA$TU0f%Ib zL2)s7N^^DbBU;=Z&??*-f97i6#Uf|^5H1D-q?)xXO|(gWHZ;} z8Ey+2HSOziHvcDbVmBtky-y^Kdmv~adj%g%Qe;6%mH$o#@I?SufZ0zR6#ff7kZEM;dXWx5!QiSJN~ zG+rS5Kga~n>Y^##ToKqkWa&^90EdbN!P)g{VIh;oQ4gSk^=}3&=dHlQt9qHh0a}TR zl02?bH-S8w_Y=b4gv~t6C5lkr?SBa`)>ri=gElNk0 z!&B*{5U{vzKn)nWg?uze8N%Mht>~P;9r>7Eyz7>r|CamjbQKkHigWp&Df+AlfJ3YZ zBPjA&c(oF7Ur3>s2qP6{*9HKmp`W>f9wy@a!0<9k_`u>+L^2Y2WO~y`w;YPeJIdpT>u;^z7Mzj-NzUk3WjOi;wQ={YmWm%$(~BiaOM&? zm1wuf#MB$IVIw7(pZ+YG`aRJBM;q@S=16%r>*05G0*J?*y`x-{v%G6&Hp zJDfw(%;$@`bw0D^es}cGDgZ=GRVXtj3o>d^q~Ma4LhG*-6b3DRV|Fy8l{;IY5@&Et;07*qoM6N<$g1rf3djJ3c literal 0 HcmV?d00001