mod_xxxx/verse/plugins/groupchat.lua

178 lines
5.0 KiB
Lua

local verse = require "verse";
local events = require "events";
local jid = require "util.jid";
local room_mt = {};
room_mt.__index = room_mt;
local xmlns_delay = "urn:xmpp:delay";
local xmlns_muc = "http://jabber.org/protocol/muc";
function verse.plugins.groupchat(stream)
stream:add_plugin("presence")
stream.rooms = {};
stream:hook("stanza", function (stanza)
local room_jid = jid.bare(stanza.attr.from);
if not room_jid then return end
local room = stream.rooms[room_jid]
if not room and stanza.attr.to and room_jid then
room = stream.rooms[stanza.attr.to.." "..room_jid]
end
if room and room.opts.source and stanza.attr.to ~= room.opts.source then return end
if room then
local nick = select(3, jid.split(stanza.attr.from));
local body = stanza:get_child_text("body");
local delay = stanza:get_child("delay", xmlns_delay);
local event = {
room_jid = room_jid;
room = room;
sender = room.occupants[nick];
nick = nick;
body = body;
stanza = stanza;
delay = (delay and delay.attr.stamp);
};
local ret = room:event(stanza.name, event);
return ret or (stanza.name == "message") or nil;
end
end, 500);
function stream:join_room(jid, nick, opts)
if not nick then
return false, "no nickname supplied"
end
opts = opts or {};
local room = setmetatable(verse.eventable{
stream = stream, jid = jid, nick = nick,
subject = nil,
occupants = {},
opts = opts,
}, room_mt);
if opts.source then
self.rooms[opts.source.." "..jid] = room;
else
self.rooms[jid] = room;
end
local occupants = room.occupants;
room:hook("presence", function (presence)
local nick = presence.nick or nick;
if not occupants[nick] and presence.stanza.attr.type ~= "unavailable" then
occupants[nick] = {
nick = nick;
jid = presence.stanza.attr.from;
presence = presence.stanza;
};
local x = presence.stanza:get_child("x", xmlns_muc .. "#user");
if x then
local x_item = x:get_child("item");
if x_item and x_item.attr then
occupants[nick].real_jid = x_item.attr.jid;
occupants[nick].affiliation = x_item.attr.affiliation;
occupants[nick].role = x_item.attr.role;
end
--TODO Check for status 100?
end
if nick == room.nick then
room.stream:event("groupchat/joined", room);
else
room:event("occupant-joined", occupants[nick]);
end
elseif occupants[nick] and presence.stanza.attr.type == "unavailable" then
if nick == room.nick then
room.stream:event("groupchat/left", room);
if room.opts.source then
self.rooms[room.opts.source.." "..jid] = nil;
else
self.rooms[jid] = nil;
end
else
occupants[nick].presence = presence.stanza;
room:event("occupant-left", occupants[nick]);
occupants[nick] = nil;
end
end
end);
room:hook("message", function(event)
local subject = event.stanza:get_child_text("subject");
if not subject then return end
subject = #subject > 0 and subject or nil;
if subject ~= room.subject then
local old_subject = room.subject;
room.subject = subject;
return room:event("subject-changed", { from = old_subject, to = subject, by = event.sender, event = event });
end
end, 2000);
local join_st = verse.presence():tag("x",{xmlns = xmlns_muc}):reset();
self:event("pre-groupchat/joining", join_st);
room:send(join_st)
self:event("groupchat/joining", room);
return room;
end
stream:hook("presence-out", function(presence)
if not presence.attr.to then
for _, room in pairs(stream.rooms) do
room:send(presence);
end
presence.attr.to = nil;
end
end);
end
function room_mt:send(stanza)
if stanza.name == "message" and not stanza.attr.type then
stanza.attr.type = "groupchat";
end
if stanza.name == "presence" then
stanza.attr.to = self.jid .."/"..self.nick;
end
if stanza.attr.type == "groupchat" or not stanza.attr.to then
stanza.attr.to = self.jid;
end
if self.opts.source then
stanza.attr.from = self.opts.source
end
self.stream:send(stanza);
end
function room_mt:send_message(text)
self:send(verse.message():tag("body"):text(text));
end
function room_mt:set_subject(text)
self:send(verse.message():tag("subject"):text(text));
end
function room_mt:leave(message)
self.stream:event("groupchat/leaving", self);
local presence = verse.presence({type="unavailable"});
if message then
presence:tag("status"):text(message);
end
self:send(presence);
end
function room_mt:admin_set(nick, what, value, reason)
self:send(verse.iq({type="set"})
:query(xmlns_muc .. "#admin")
:tag("item", {nick = nick, [what] = value})
:tag("reason"):text(reason or ""));
end
function room_mt:set_role(nick, role, reason)
self:admin_set(nick, "role", role, reason);
end
function room_mt:set_affiliation(nick, affiliation, reason)
self:admin_set(nick, "affiliation", affiliation, reason);
end
function room_mt:kick(nick, reason)
self:set_role(nick, "none", reason);
end
function room_mt:ban(nick, reason)
self:set_affiliation(nick, "outcast", reason);
end