| 1 | --[[ pubsub2room |
| 2 | relays pubsub broadcasts to rooms |
| 3 | |
| 4 | example conf: |
| 5 | pubsub2room = { |
| 6 | ["pubsub.prosody.im#commits"] = { |
| 7 | room = "prosody@conference.prosody.im"; |
| 8 | template = "${author.name} committed: ${title}"; |
| 9 | }; |
| 10 | }; |
| 11 | --]] |
| 12 | |
| 13 | local verse = require "verse"; |
| 14 | local st = require "prosody.util.stanza"; |
| 15 | local json = require "prosody.util.json"; |
| 16 | |
| 17 | -- FIXME Should this really be here? |
| 18 | local extractor_mt = { |
| 19 | __index = function (t, k) |
| 20 | local n; |
| 21 | if k:match("^data%.") then |
| 22 | k = k:gsub("^data.", ""); |
| 23 | n = t.data; |
| 24 | else |
| 25 | n = t.stanza; |
| 26 | end |
| 27 | for x in k:gmatch("[^.]+") do |
| 28 | local m, a = x:match("^([^@]*)@(.*)$"); |
| 29 | if m and a then |
| 30 | m = n:get_child(m); |
| 31 | return m and m.attr[a] or nil; |
| 32 | end |
| 33 | n = n:get_child(x); |
| 34 | if not n then return end |
| 35 | end |
| 36 | return n[1]; |
| 37 | end |
| 38 | }; |
| 39 | |
| 40 | local json_extractor_mt = { |
| 41 | __index = function (t, k) |
| 42 | if not k:find"%." then |
| 43 | return nil; |
| 44 | end |
| 45 | for x in k:gmatch("[^.]+") do |
| 46 | t = rawget(t, x) or rawget(t, tonumber(x)); |
| 47 | if t == nil then return end |
| 48 | end |
| 49 | if type(t) == "table" then |
| 50 | return json.encode(t); |
| 51 | end |
| 52 | return tostring(t); |
| 53 | end |
| 54 | }; |
| 55 | |
| 56 | local function new_extractor(stanza, data) |
| 57 | if not stanza then return nil end |
| 58 | if stanza.attr.xmlns == "urn:xmpp:json:0" then |
| 59 | local payload = json.decode(stanza:get_text()); |
| 60 | if type(payload) == "table" then |
| 61 | return setmetatable(payload, json_extractor_mt); |
| 62 | end |
| 63 | end |
| 64 | return setmetatable({ stanza = stanza, data = data }, extractor_mt); |
| 65 | end |
| 66 | |
| 67 | local last_message_time = 0; |
| 68 | |
| 69 | function riddim.plugins.pubsub2room(bot) |
| 70 | local bare_jid = require "prosody.util.jid".bare; |
| 71 | bot.stream:add_plugin("pubsub"); |
| 72 | |
| 73 | local config = bot.config.pubsub2room; |
| 74 | bot.stream:hook("pubsub/event", function(event) |
| 75 | local conf = config[event.from .. "#" .. event.node]; |
| 76 | if not conf then return end |
| 77 | local room = bot.rooms[conf.room]; |
| 78 | local data = st.stanza("data"):tag("id"):text(event.item.attr.id); |
| 79 | local entry = event.item and new_extractor(event.item.tags[1], data) |
| 80 | |
| 81 | if not conf or not entry or not room then return end |
| 82 | local message = conf.template:gsub("%${([^}]+)}", entry); |
| 83 | |
| 84 | -- Throttle to 1 message/second so we don't flood the room |
| 85 | if os.time() - last_message_time > 0 then |
| 86 | room:send_message(message); |
| 87 | last_message_time = os.time(); |
| 88 | else |
| 89 | last_message_time = last_message_time + 1; |
| 90 | verse.add_task(last_message_time - os.time(), function () |
| 91 | room:send_message(message); |
| 92 | end); |
| 93 | end |
| 94 | end); |
| 95 | |
| 96 | -- FIXME When to unsubscribe? |
| 97 | bot:hook("started", function() |
| 98 | local jid = bare_jid(bot.stream.jid); |
| 99 | for hostnode in pairs(config) do |
| 100 | local host, node = hostnode:match("^([^#]+)#(.*)"); |
| 101 | bot.stream.pubsub(host, node):subscribe(jid); |
| 102 | end |
| 103 | end); |
| 104 | end |