1local http = require "net.http"; 2local json = require "util.json"; 3local st = require "util.stanza"; 4local xml = require "util.xml"; 5local unpack = rawget(_G, "unpack") or table.unpack; 6 7local url = module:get_option_string("component_post_url"); 8assert(url, "Missing required config option 'component_post_url'"); 9 10local stanza_kinds = module:get_option_set("post_stanza_types", { "message" }); 11 12local http_error_map = { 13 [0] = { "cancel", "remote-server-timeout", "Connection failure" }; 14 -- 4xx 15 [400] = { "modify", "bad-request" }; 16 [401] = { "auth", "not-authorized" }; 17 [402] = { "auth", "forbidden", "Payment required" }; 18 [403] = { "auth", "forbidden" }; 19 [404] = { "cancel", "item-not-found" }; 20 [410] = { "cancel", "gone" }; 21 -- 5xx 22 [500] = { "cancel", "internal-server-error" }; 23 [501] = { "cancel", "feature-not-implemented" }; 24 [502] = { "cancel", "remote-server-timeout", "Bad gateway" }; 25 [503] = { "wait", "remote-server-timeout", "Service temporarily unavailable" }; 26 [504] = { "wait", "remote-server-timeout", "Gateway timeout" }; 27} 28 29local function error_reply(stanza, code) 30 local error = http_error_map[code] or { "cancel", "service-unavailable" }; 31 return st.error_reply(stanza, unpack(error, 1, 3)); 32end 33 34function handle_stanza(event) 35 local stanza = event.stanza; 36 local request_body = json.encode({ 37 to = stanza.attr.to; 38 from = stanza.attr.from; 39 kind = stanza.name; 40 body = stanza.name == "message" and stanza:get_child_text("body") or nil; 41 stanza = tostring(stanza); 42 }); 43 http.request(url, { 44 body = request_body; 45 }, function (response_text, code, response) 46 if stanza.attr.type == "error" then return; end -- Avoid error loops, don't reply to error stanzas 47 if code == 200 and response_text and response.headers["content-type"] == "application/json" then 48 local response_data = json.decode(response_text); 49 if response_data.stanza then 50 local reply_stanza = xml.parse(response_data.stanza); 51 if reply_stanza then 52 reply_stanza.attr.from, reply_stanza.attr.to = stanza.attr.to, stanza.attr.from; 53 module:send(reply_stanza); 54 else 55 module:log("warn", "Unable to parse reply stanza"); 56 end 57 else 58 local stanza_kind = response_data.kind or "message"; 59 local to = response_data.to or stanza.attr.from; 60 local from = response_data.from or stanza.attr.to; 61 local reply_stanza = st.stanza(stanza_kind, { 62 to = to, from = from; 63 type = response_data.type or (stanza_kind == "message" and "chat") or nil; 64 }); 65 if stanza_kind == "message" and response_data.body then 66 reply_stanza:tag("body"):text(tostring(response_data.body)):up(); 67 end 68 module:log("debug", "Sending %s", tostring(reply_stanza)); 69 module:send(reply_stanza); 70 end 71 elseif code >= 200 and code <= 299 then 72 return; 73 else 74 module:send(error_reply(stanza, code)); 75 end 76 return true; 77 end); 78 return true; 79end 80 81for stanza_kind in stanza_kinds do 82 for _, jid_type in ipairs({ "host", "bare", "full" }) do 83 module:hook(stanza_kind.."/"..jid_type, handle_stanza); 84 end 85end 86 87-- Simple handler for an always-online JID that allows everyone to subscribe to presence 88local function default_presence_handler(event) 89 local stanza = event.stanza; 90 module:log("debug", "Handling %s", tostring(stanza)); 91 if stanza.attr.type == "probe" then 92 module:send(st.presence({ to = stanza.attr.from, from = stanza.attr.to.."/default" })); 93 elseif stanza.attr.type == "subscribe" then 94 module:send(st.presence({ type = "subscribed", to = stanza.attr.from, from = stanza.attr.to.."/default" })); 95 module:send(st.presence({ to = stanza.attr.from, from = stanza.attr.to.."/default" })); 96 elseif stanza.attr.type == "unsubscribe" then 97 module:send(st.presence({ type = "unavailable", to = stanza.attr.from, from = stanza.attr.to.."/default" })); 98 end 99 return true; 100end 101 102module:hook("presence/bare", default_presence_handler, -1); 103