local modpath = minetest.get_modpath(minetest.get_current_modname()) dofile(modpath.."/config.lua") dofile(modpath.."/debug.lua") local http = minetest.request_http_api() if http == nil then error("Please add matrix_bridge to secure.http_mods") end -- defines functions for matrix protocol local MatrixChat = { server = MATRIX_SERVER, username = MATRIX_USERNAME, password = MATRIX_PASSWORD, room = MATRIX_ROOM, proxy = MATRIX_HACK_PROXY, -- acquired on login userid = nil, token = nil, since = nil, eventid = nil } function MatrixChat:minechat(data) if data == nil then return end if self.since == data.next_batch then return end -- lets just get this working if data["rooms"] == nil then return end if data["rooms"]["join"] == nil then return end if data["rooms"]["join"][self.room] == nil then return end if data["rooms"]["join"][self.room]["timeline"] == nil then return else local events = data["rooms"]["join"][self.room]["timeline"]["events"] if events == nil then minetest.log("action", "matrix_bridge - found timeline but no events") return end 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 ~= self.userid then local message = event.sender .. ": " .. event.content.body minetest.log("action", message) minetest.chat_send_all(message) end end end end -- https://matrix.org/docs/api/client-server/#get-/sync -- GET /sync function MatrixChat:get_sync_table(timeout) local params = {} if self.since ~= nil then table.insert(params, "since=" .. self.since) end if timeout ~= nil then table.insert(params, "timeout=" .. timeout) end local u = self.server .."/_matrix/client/r0/sync" if #params > 0 then u = u .. "?" .. table.concat(params, "&") end local h = { "Authorization: Bearer " .. self.token, "Accept-Encoding: identity" } return {url=u, method="GET", extra_headers=h} end function MatrixChat:sync(timeout) if self.token == nil then return end 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.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", "Accept-Encoding: identity" } http.fetch({url=u, method="POST", extra_headers=h, post_data=d}, function(res) if res.code == 200 then minetest.log("action", res.data) local data = minetest.parse_json(res.data) if data.access_token ~= nil and data.user_id ~= nil then self.token = data.access_token self.userid = data.user_id MatrixChat:sync() minetest.log("action", "Matrix authenticated") MatrixChat:send("*** Server connected to the matrix") else minetest.log("error", "Matrix login failed") end else minetest.log("error", to_string(res)) end end ) end -- 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, "Accept-Encoding: identity" } local d = minetest.write_json({msgtype="m.text", body=msg}) local req if self.proxy then req = {url=u, method="POST", extra_headers=h, post_data=d} else req = {url=u, method="PUT", extra_headers=h, data=d} end http.fetch(req, function(res) if res.code == 200 then local data = minetest.parse_json(res.data) if data then minetest.log("action", "got " .. data["event_id"]) self.eventid = data["event_id"] else minetest.log("error", "matrix_bridge - cannot parse json") end elseif res.code == 401 then minetest.log("error", "matrix_bridge - not authorized to send messages") elseif res.code == 404 then minetest.log("error", "matrix_bridge - could not find endpoint for send") end end) 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, "Accept-Encoding: identity" } http.fetch({url=u, method="POST", extra_headers=h, function(res) end}) minetest.log("action", "matrix_bridge - signing out.") end -- print a sync url to console to test matrix connection in other applications function MatrixChat:get_access_url() local params = {} if self.since ~= nil then table.insert(params, "since=" .. self.since) end table.insert(params, "access_token=" .. self.token) local u = self.server .. "/_matrix/client/r0/sync?" .. table.concat(params, "&") print(u) end local INTERVAL = 60 local HANDLE = nil minetest.register_globalstep(function(dtime) if MatrixChat.token == nil or #minetest.get_connected_players() == 0 then return end if HANDLE == nil then local request = MatrixChat:get_sync_table(INTERVAL * 1000) request.timeout = INTERVAL HANDLE = http.fetch_async(request) else 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) MatrixChat.since = activity.next_batch end elseif result.code == 0 then elseif result.code == 404 then end HANDLE = nil end end end) minetest.register_privilege("matrix", { description = "Manage matrix bridge session", give_to_singleplayer = true, give_to_admin = true }) minetest.register_chatcommand("matrix", { privs = { matrix = true }, func = function(name, param) if param == "sync" then -- test sync as called from login MatrixChat:sync() return true, "[matrix_bridge] command: sync" elseif param == "logout" then MatrixChat:logout() return true, "[matrix_bridge] command: log out" elseif param == "login" then MatrixChat:login() return true, "[matrix_bridge] command: log in" elseif param == "print" then MatrixChat:get_access_url() return true, "[matrix_bridge] printed url to server console" end end}) minetest.register_on_shutdown(function() MatrixChat:logout() end) minetest.register_on_joinplayer(function(player, last_login) local name = player:get_player_name() MatrixChat:send("*** "..name.." joined the game"..(last_login == nil and " for the first time" or "")) end) minetest.register_on_leaveplayer(function(player, timed_out) local name = player:get_player_name() MatrixChat:send("*** "..name.." left the game"..(timed_out and " (Timed out)" or "")) end) minetest.register_on_chat_message(function(name, message) if message:sub(1, 1) == "/" or message:sub(1, 5) == "[off]" or (not minetest.check_player_privs(name, {shout=true})) then return end local nl = message:find("\n", 1, true) if nl then message = message:sub(1, nl - 1) end MatrixChat:send("<"..name.."> "..message) end) minetest.register_on_mods_loaded(function() MatrixChat:login() end)