plugins/groupchat.lua

1
local verse = require "verse";
2
local events = require "prosody.util.events";
3
local jid = require "prosody.util.jid";
4
 
5
local room_mt = {};
6
room_mt.__index = room_mt;
7
 
8
local xmlns_delay = "urn:xmpp:delay";
9
local xmlns_muc = "http://jabber.org/protocol/muc";
10
 
11
function verse.plugins.groupchat(stream)
12
	stream:add_plugin("presence")
13
	stream.rooms = {};
14
 
15
	stream:hook("stanza", function (stanza)
16
		local room_jid = jid.bare(stanza.attr.from);
17
		if not room_jid then return end
18
		local room = stream.rooms[room_jid]
19
		if not room and stanza.attr.to and room_jid then
20
			room = stream.rooms[stanza.attr.to.." "..room_jid]
21
		end
22
		if room and room.opts.source and stanza.attr.to ~= room.opts.source then return end
23
		if room then
24
			local nick = select(3, jid.split(stanza.attr.from));
25
			local body = stanza:get_child_text("body");
26
			local delay = stanza:get_child("delay", xmlns_delay);
27
			local event = {
28
				room_jid = room_jid;
29
				room = room;
30
				sender = room.occupants[nick];
31
				nick = nick;
32
				body = body;
33
				stanza = stanza;
34
				delay = (delay and delay.attr.stamp);
35
			};
36
			local ret = room:event(stanza.name, event);
37
			return ret or (stanza.name == "message") or nil;
38
		end
39
	end, 500);
40
 
41
	function stream:join_room(jid, nick, opts, password)
42
		if not nick then
43
			return false, "no nickname supplied"
44
		end
45
		opts = opts or {};
46
		local room = setmetatable(verse.eventable{
47
			stream = stream, jid = jid, nick = nick,
48
			subject = nil,
49
			occupants = {},
50
			opts = opts,
51
		}, room_mt);
52
		if opts.source then
53
			self.rooms[opts.source.." "..jid] = room;
54
		else
55
			self.rooms[jid] = room;
56
		end
57
		local occupants = room.occupants;
58
		room:hook("presence", function (presence)
59
			local nick = presence.nick or nick;
60
			if not occupants[nick] and presence.stanza.attr.type ~= "unavailable" then
61
				occupants[nick] = {
62
					nick = nick;
63
					jid = presence.stanza.attr.from;
64
					presence = presence.stanza;
65
				};
66
				local x = presence.stanza:get_child("x", xmlns_muc .. "#user");
67
				if x then
68
					local x_item = x:get_child("item");
69
					if x_item and x_item.attr then
70
						occupants[nick].real_jid    = x_item.attr.jid;
71
						occupants[nick].affiliation = x_item.attr.affiliation;
72
						occupants[nick].role        = x_item.attr.role;
73
					end
74
					--TODO Check for status 100?
75
				end
76
				if nick == room.nick then
77
					room.stream:event("groupchat/joined", room);
78
				else
79
					room:event("occupant-joined", occupants[nick]);
80
				end
81
			elseif occupants[nick] and presence.stanza.attr.type == "unavailable" then
82
				if nick == room.nick then
83
					room.stream:event("groupchat/left", room);
84
					if room.opts.source then
85
						self.rooms[room.opts.source.." "..jid] = nil;
86
					else
87
						self.rooms[jid] = nil;
88
					end
89
				else
90
					occupants[nick].presence = presence.stanza;
91
					room:event("occupant-left", occupants[nick]);
92
					occupants[nick] = nil;
93
				end
94
			end
95
		end);
96
		room:hook("message", function(event)
97
			local subject = event.stanza:get_child_text("subject");
98
			if not subject then return end
99
			subject = #subject > 0 and subject or nil;
100
			if subject ~= room.subject then
101
				local old_subject = room.subject;
102
				room.subject = subject;
103
				return room:event("subject-changed", { from = old_subject, to = subject, by = event.sender, event = event });
104
			end
105
		end, 2000);
106
		local join_st = verse.presence():tag("x",{xmlns = xmlns_muc}):reset();
107
		if password then
108
			join_st:get_child("x", xmlns_muc):tag("password"):text(password):reset();
109
		end
110
		self:event("pre-groupchat/joining", join_st);
111
		room:send(join_st)
112
		self:event("groupchat/joining", room);
113
		return room;
114
	end
115
 
116
	stream:hook("presence-out", function(presence)
117
		if not presence.attr.to then
118
			for _, room in pairs(stream.rooms) do
119
				room:send(presence);
120
			end
121
			presence.attr.to = nil;
122
		end
123
	end);
124
end
125
 
126
function room_mt:send(stanza)
127
	if stanza.name == "message" and not stanza.attr.type then
128
		stanza.attr.type = "groupchat";
129
	end
130
	if stanza.name == "presence" then
131
		stanza.attr.to = self.jid .."/"..self.nick;
132
	end
133
	if stanza.attr.type == "groupchat" or not stanza.attr.to then
134
		stanza.attr.to = self.jid;
135
	end
136
	if self.opts.source then
137
		stanza.attr.from = self.opts.source
138
	end
139
	self.stream:send(stanza);
140
end
141
 
142
function room_mt:send_message(text)
143
	self:send(verse.message():tag("body"):text(text));
144
end
145
 
146
function room_mt:set_subject(text)
147
	self:send(verse.message():tag("subject"):text(text));
148
end
149
 
150
function room_mt:leave(message)
151
	self.stream:event("groupchat/leaving", self);
152
	local presence = verse.presence({type="unavailable"});
153
	if message then
154
		presence:tag("status"):text(message);
155
	end
156
	self:send(presence);
157
end
158
 
159
function room_mt:admin_set(nick, what, value, reason)
160
	self:send(verse.iq({type="set"})
161
		:query(xmlns_muc .. "#admin")
162
			:tag("item", {nick = nick, [what] = value})
163
				:tag("reason"):text(reason or ""));
164
end
165
 
166
function room_mt:set_role(nick, role, reason)
167
	self:admin_set(nick, "role", role, reason);
168
end
169
 
170
function room_mt:set_affiliation(nick, affiliation, reason)
171
	self:admin_set(nick, "affiliation", affiliation, reason);
172
end
173
 
174
function room_mt:kick(nick, reason)
175
	self:set_role(nick, "none", reason);
176
end
177
 
178
function room_mt:ban(nick, reason)
179
	self:set_affiliation(nick, "outcast", reason);
180
end