mod_pubsub, util.pubsub: Support for unsubscribing
[prosody.git] / plugins / mod_pubsub.lua
1 local pubsub = require "util.pubsub";
2 local st = require "util.stanza";
3 local jid_bare = require "util.jid".bare;
4 local uuid_generate = require "util.uuid".generate;
5
6 require "core.modulemanager".load(module.host, "iq");
7
8 local xmlns_pubsub = "http://jabber.org/protocol/pubsub";
9 local xmlns_pubsub_errors = "http://jabber.org/protocol/pubsub#errors";
10 local xmlns_pubsub_event = "http://jabber.org/protocol/pubsub#event";
11
12 local service;
13
14 local handlers = {};
15
16 function handle_pubsub_iq(event)
17         local origin, stanza = event.origin, event.stanza;
18         local pubsub = stanza.tags[1];
19         local action = pubsub.tags[1];
20         local handler = handlers[stanza.attr.type.."_"..action.name];
21         if handler then
22                 handler(origin, stanza, action);
23                 return true;
24         end
25 end
26
27 local pubsub_errors = {
28         ["conflict"] = { "cancel", "conflict" };
29         ["invalid-jid"] = { "modify", "bad-request", nil, "invalid-jid" };
30         ["item-not-found"] = { "cancel", "item-not-found" };
31         ["not-subscribed"] = { "modify", "unexpected-request", nil, "not-subscribed" };
32 };
33 function pubsub_error_reply(stanza, error)
34         local e = pubsub_errors[error];
35         local reply = st.error_reply(stanza, unpack(e, 1, 3));
36         if e[4] then
37                 reply:tag(e[4], { xmlns = xmlns_pubsub_errors }):up();
38         end
39         return reply;
40 end
41
42 function handlers.get_items(origin, stanza, items)
43         local node = items.attr.node;
44         local item = items:get_child("item");
45         local id = item and item.attr.id;
46         local data = st.stanza("items", { node = node });
47         for _, entry in pairs(service:get(node, stanza.attr.from, id)) do
48                 data:add_child(entry);
49         end
50         if data then
51                 reply = st.reply(stanza)
52                         :tag("pubsub", { xmlns = xmlns_pubsub })
53                                 :add_child(data);
54         else
55                 reply = pubsub_error_reply(stanza, "item-not-found");
56         end
57         return origin.send(reply);
58 end
59
60 function handlers.set_create(origin, stanza, create)
61         local node = create.attr.node;
62         local ok, ret, reply;
63         if node then
64                 ok, ret = service:create(node, stanza.attr.from);
65                 if ok then
66                         reply = st.reply(stanza);
67                 else
68                         reply = pubsub_error_reply(stanza, ret);
69                 end
70         else
71                 repeat
72                         node = uuid_generate();
73                         ok, ret = service:create(node, stanza.attr.from);
74                 until ok;
75                 reply = st.reply(stanza)
76                         :tag("pubsub", { xmlns = xmlns_pubsub })
77                                 :tag("create", { node = node });
78         end
79         origin.send(reply);
80 end
81
82 function handlers.set_subscribe(origin, stanza, subscribe)
83         local node, jid = subscribe.attr.node, subscribe.attr.jid;
84         if jid_bare(jid) ~= jid_bare(stanza.attr.from) then
85                 return origin.send(pubsub_error_reply(stanza, "invalid-jid"));
86         end
87         local ok, ret = service:add_subscription(node, stanza.attr.from, jid);
88         local reply;
89         if ok then
90                 reply = st.reply(stanza)
91                         :tag("pubsub", { xmlns = xmlns_pubsub })
92                                 :tag("subscription", {
93                                         node = node,
94                                         jid = jid,
95                                         subscription = "subscribed"
96                                 });
97         else
98                 reply = pubsub_error_reply(stanza, ret);
99         end
100         return origin.send(reply);
101 end
102
103 function handlers.set_unsubscribe(origin, stanza, unsubscribe)
104         local node, jid = unsubscribe.attr.node, unsubscribe.attr.jid;
105         if jid_bare(jid) ~= jid_bare(stanza.attr.from) then
106                 return origin.send(pubsub_error_reply(stanza, "invalid-jid"));
107         end
108         local ok, ret = service:remove_subscription(node, stanza.attr.from, jid);
109         local reply;
110         if ok then
111                 reply = st.reply(stanza);
112         else
113                 reply = pubsub_error_reply(stanza, ret);
114         end
115         return origin.send(reply);
116 end
117
118 function handlers.set_publish(origin, stanza, publish)
119         local node = publish.attr.node;
120         local item = publish:get_child("item");
121         local id = (item and item.attr.id) or uuid_generate();
122         local ok, ret = service:publish(node, stanza.attr.from, id, item);
123         local reply;
124         if ok then
125                 reply = st.reply(stanza)
126                         :tag("pubsub", { xmlns = xmlns_pubsub })
127                                 :tag("publish", { node = node })
128                                         :tag("item", { id = id });
129         else
130                 reply = pubsub_error_reply(stanza, ret);
131         end
132         return origin.send(reply);
133 end
134
135 function simple_broadcast(node, jids, item)
136         local message = st.message({ from = module.host, type = "headline" })
137                 :tag("event", { xmlns = xmlns_pubsub_event })
138                         :tag("items", { node = node })
139                                 :add_child(item);
140         for jid in pairs(jids) do
141                 module:log("debug", "Sending notification to %s", jid);
142                 message.attr.to = jid;
143                 core_post_stanza(hosts[module.host], message);
144         end
145 end
146
147 module:hook("iq/host/http://jabber.org/protocol/pubsub:pubsub", handle_pubsub_iq);
148
149 service = pubsub.new({
150         broadcaster = simple_broadcast
151 });
152 module.environment.service = service;
153