plugins/pubsub2room.lua

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