diff --git a/init.lua b/init.lua index 650b31f..578dfb1 100644 --- a/init.lua +++ b/init.lua @@ -11,17 +11,25 @@ if http == nil then error("Please add matrix_bridge to secure.http_mods") end -local token = nil -local txid = 0 --- used for msync() -local since = nil -local eventid = nil +-- defines functions for matrix protocol +local MatrixChat = { + server = MATRIX_SERVER, + username = MATRIX_USERNAME, + password = MATRIX_PASSWORD, + room = MATRIX_ROOM, + -- acquired on login + userid = nil, + token = nil, + since = nil, + lastok_since = nil, + eventid = nil +} -local function mchat(data) +function MatrixChat:minechat(data) if data == nil then return end - if since == data.next_batch then + if self.since == data.next_batch then return end -- lets just get this working @@ -31,13 +39,13 @@ local function mchat(data) if data["rooms"]["join"] == nil then return end - if data["rooms"]["join"][MATRIX_ROOM] == nil then + if data["rooms"]["join"][self.room] == nil then return end - if data["rooms"]["join"][MATRIX_ROOM]["timeline"] == nil then + if data["rooms"]["join"][self.room]["timeline"] == nil then return else - local events = data["rooms"]["join"][MATRIX_ROOM]["timeline"]["events"] + local events = data["rooms"]["join"][self.room]["timeline"]["events"] if events == nil then minetest.log("action", "matrix_bridge - found timeline but no events") return @@ -45,7 +53,7 @@ local function mchat(data) minetest.log("action", "matrix_bridge - sync'd and found new messages") for i, event in ipairs(events) do if event.type == "m.room.message" - and event.sender ~= MATRIX_USERNAME_LONG then + and event.sender ~= self.userid then local message = event.sender .. ": " .. event.content.body minetest.log("action", message) minetest.chat_send_all(message) @@ -54,48 +62,60 @@ local function mchat(data) end end -local function msync(s) --- optimization note: request more recent instead of unfiltered req --- local param1 = '&filter={\"room\":{\"timeline\":{\"limit\":1}}}' --- local param1 = "?access_token=" .. token - local param2 = "?full_state=false" - local param3 = "&timeout=5000" - local u = MATRIX_SERVER.."/_matrix/client/r0/sync" .. param2 .. param3 - if s == nil then -- first time sync - -- do nothing for now - else -- second time sync -> append since parameter - u = u .. "&since="..s +-- https://matrix.org/docs/api/client-server/#get-/sync +-- GET /sync +function MatrixChat:get_sync_table(timeout) + local params = {} + -- no filter param + if self.since ~= nil then + params[1] = "since=" .. self.since end - local h = {"Authorization: Bearer " .. token} - http.fetch({url=u, method="GET", extra_headers=h}, - function (res) - if res == nil then -- received nothing from server - minetest.log("error", "matrix_bridge - sync response is nil") - elseif res.code == 0 then - minetest.log("info", "matrix_bridge - not found / timeout") - elseif res.code == 404 then - else - local response = minetest.parse_json(res.data) - if response ~= nil then - mchat(response) - since = response.next_batch - end - end - end - ) + if timeout ~= nil then + -- if timeout is defined and since took the starting '?' + params[2] = "timeout=" .. timeout + end + local u = self.server .."/_matrix/client/r0/sync" + if params[1] ~= nil then + u = u .. "?" .. table.concat(params, "&") + end + local h = {"Authorization: Bearer " .. self.token} + return {url=u, method="GET", extra_headers=h} end -local function mlogin() - local u = MATRIX_SERVER.."/_matrix/client/r0/login" - local d = '{"type":"m.login.password","password":"'..MATRIX_PASSWORD..'","identifier":{"type":"m.id.user","user":"'..MATRIX_USERNAME..'"}}' +function MatrixChat:sync(timeout) + http.fetch(MatrixChat:get_sync_table(timeout), + function (res) + if res == nil then -- received nothing from server + minetest.log("error", "matrix_bridge - sync response is nil") + elseif res.code == 0 then + minetest.log("info", "matrix_bridge - not found / timeout") + elseif res.code == 404 then + else + local response = minetest.parse_json(res.data) + if response ~= nil then + MatrixChat:minechat(response) + self.lastok_since = self.since + self.since = response.next_batch + end + end + end +) +end + +-- https://matrix.org/docs/api/client-server/#post-/login +-- POST /login +function MatrixChat:login() + local u = self.server .."/_matrix/client/r0/login" + local d = '{"type":"m.login.password","password":"'.. self.password ..'","identifier":{"type":"m.id.user","user":"'.. self.username ..'"}}' local h = {"Content-Type: application/json"} http.fetch({url=u, method="POST", extra_headers=h, data=d}, function(res) if res.code == 200 then minetest.log("action", res.data) local data = minetest.parse_json(res.data) - token = data.access_token - msync() + self.token = data.access_token + self.userid = data.user_id + MatrixChat:sync() minetest.log("action", "Matrix authenticated") else minetest.log("error", to_string(res)) @@ -103,19 +123,24 @@ local function mlogin() end ) end -mlogin() +MatrixChat:login() -local function send_message(msg) - txid = txid + 1 - local u = MATRIX_SERVER.."/_matrix/client/r0/rooms/"..MATRIX_ROOM.."/send/m.room.message/" .. txid -- ?access_token=..token - local h = {"Content-Type: application/json", "Authorization: Bearer " .. token} +-- https://matrix.org/docs/api/client-server/#put-/rooms/-roomId-/send/-eventType-/-txnId- +-- PUT /rooms/{roomId}/send/{eventType}/{txnId} +function MatrixChat:send(msg) + if self.token == nil then + return + end + local txid = os.time() + local u = self.server .."/_matrix/client/r0/rooms/".. self.room .."/send/m.room.message/" .. txid -- ?access_token=..token + local h = {"Content-Type: application/json", "Authorization: Bearer " .. self.token} local d = minetest.write_json({msgtype="m.text", body=msg}) http.fetch({url=u, method="PUT", extra_headers=h, data=d}, function (res) if res.code == 200 then local data = minetest.parse_json(res.data) minetest.log("action", "got " .. data["event_id"]) - eventid = data["event_id"] + self.eventid = data["event_id"] elseif res.code == 401 then minetest.log("error", "matrix_bridge - not authorized to send messages") elseif res.code == 404 then @@ -124,34 +149,52 @@ local function send_message(msg) end) end --- http.fetch({ --- url = MATRIX_SERVER.."/_matrix/client/r0/login", --- method = "POST", --- extra_headers = {"Content-Type: application/json"}, --- data = '{"type":"m.login.password","password":"'..MATRIX_PASSWORD..'","identifier":{"type":"m.id.user","user":"'..MATRIX_USERNAME..'"}}' --- }, function(res) --- if res.code == 200 then --- minetest.log("action", res.data) --- local data = minetest.parse_json(res.data) --- token = data.access_token --- minetest.log("action", "Matrix authenticated") --- else --- minetest.log("error", to_string(res)) --- end --- end) - -function mlogout () - local u = MATRIX_SERVER.."/logout/all" - http.fetch({url=u, method="POST", function (res) end}) +-- https://matrix.org/docs/api/client-server/#post-/logout/all +-- POST /logout/all +function MatrixChat:logout () + local u = self.server .."/logout/all" + local h = {"Authorization: Bearer " .. self.token} + http.fetch({url=u, method="POST", extra_headers=h, function (res) end}) minetest.log("action", "matrix_bridge - signing out.") end -local SECONDS = 0 +-- print a sync url to console to test matrix connection in other applications +function MatrixChat:get_access_url() + local params = {} + -- no filter param + if self.since ~= nil then + params[1] = "since=" .. self.since + end + params[2] = "access_token=" .. self.token + local u = self.server .. "/_matrix/client/r0/sync?" .. table.concat(params, "&") + print(u) +end + +-- local SECONDS = 0 +local INTERVAL = 60 +local HANDLE = nil minetest.register_globalstep(function(dtime) - SECONDS = SECONDS + dtime - if SECONDS > 5 then - msync(since) - SECONDS = 0 -- reset +-- SECONDS = SECONDS + dtime + if HANDLE == nil then + local request = MatrixChat:get_sync_table(INTERVAL * 1000) + request.timeout = INTERVAL + HANDLE = http.fetch_async(request) +-- SECONDS = 0 -- reset + elseif HANDLE ~= nil then + local result = http.fetch_async_get(HANDLE) + if result.completed then + if result.code == 200 then + local activity = minetest.parse_json(result.data) + if activity ~= nil then + MatrixChat:minechat(activity) + end + MatrixChat.since = activity.next_batch + elseif result.code == 0 then + elseif result.code == 404 then + end + HANDLE = nil +-- SECONDS = 0 -- reset + end end end) @@ -160,47 +203,38 @@ minetest.register_chatcommand("matrix", { interact = true }, func = function(name, param) - if param == "nil" then -- test sync as called from login - msync() - return true, "[matrix_bridge] command: sync(nil)" + if param == "sync" then -- test sync as called from login + MatrixChat:sync() + return true, "[matrix_bridge] command: sync" elseif param == "logout" then - mlogout() + MatrixChat:logout() return true, "[matrix_bridge] command: log out" elseif param == "login" then - mlogin() + MatrixChat:login() return true, "[matrix_bridge] command: log in" - elseif #param > 1 then -- test sync with specific 'since' parameter - msync(param) - return true, "[matrix_bridge] command: sync("..since..")" - else -- test sync with current since parameter - msync(since) - return true, "[matrix_bridge] command: sync()" + elseif param == "print" then + MatrixChat:get_access_url() + return true, "[matrix_bridge] printed url to server console" end - end}) minetest.register_on_shutdown(function() - mlogout() + MatrixChat:logout() end) minetest.register_on_joinplayer(function(player) local name = player:get_player_name() - if token then - send_message("*** "..name.." joined the game") - end + MatrixChat:send("*** "..name.." joined the game") end) minetest.register_on_leaveplayer(function(player, timed_out) local name = player:get_player_name() - if token then - send_message("*** "..name.." left the game".. + MatrixChat:send("*** "..name.." left the game".. (timed_out and " (Timed out)" or "")) - end end) minetest.register_on_chat_message(function(name, message) - if token == nil - or message:sub(1, 1) == "/" + if message:sub(1, 1) == "/" or message:sub(1, 5) == "[off]" or (not minetest.check_player_privs(name, {shout=true})) then return @@ -209,7 +243,7 @@ minetest.register_on_chat_message(function(name, message) if nl then message = message:sub(1, nl - 1) end - send_message("<"..name.."> "..message) + MatrixChat:send("<"..name.."> "..message) end)