mod_mix: Various changes
- Refactor and clean code - Implement lots of MIX-ADMIN - Beginning of MIX-ANON
This commit is contained in:
parent
c7b9dc2432
commit
fd9735faba
14
mod_mix/anon.lib.lua
Normal file
14
mod_mix/anon.lib.lua
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
local dataforms = require("util.dataforms");
|
||||||
|
local namespaces = module:require("mix/namespaces");
|
||||||
|
|
||||||
|
local mix_anon_form = dataforms.new({
|
||||||
|
{ name = "FORM_TYPE", type = "hidden", value = namespaces.anon },
|
||||||
|
{ name = "JID Visibility", type = "text-single" },
|
||||||
|
{ name = "Private Messages", type = "text-single" },
|
||||||
|
{ name = "Presence", type = "text-single" },
|
||||||
|
{ name = "vCard", type = "text-single" },
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
form = mix_anon_form;
|
||||||
|
};
|
@ -13,11 +13,18 @@ end
|
|||||||
|
|
||||||
local function find_str(array, str)
|
local function find_str(array, str)
|
||||||
-- Returns the index of str in array. -1 if array does not contain str
|
-- Returns the index of str in array. -1 if array does not contain str
|
||||||
local i, _ = find(array, function(v) return v == str end);
|
local i, _ = find(array, function(v) return v == str; end);
|
||||||
return i;
|
return i;
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function in_array(array, element)
|
||||||
|
-- Returns true if element is in array. False otherwise.
|
||||||
|
local i, _ = find(array, function(v) return v == element; end);
|
||||||
|
return i ~= -1;
|
||||||
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
find_str = find_str,
|
find_str = find_str,
|
||||||
find = find,
|
find = find,
|
||||||
|
in_array = in_array,
|
||||||
};
|
};
|
||||||
|
@ -1,12 +1,20 @@
|
|||||||
local st = require("util.stanza");
|
local st = require("util.stanza");
|
||||||
|
local array = require("util.array");
|
||||||
|
local jid_lib = require("util.jid");
|
||||||
|
local uuid = require("util.uuid");
|
||||||
|
local time = require("util.time");
|
||||||
local helpers = module:require("mix/helpers");
|
local helpers = module:require("mix/helpers");
|
||||||
|
local namespaces = module:require("mix/namespaces");
|
||||||
|
local pep = module:depends("pep");
|
||||||
|
|
||||||
Participant = {};
|
|
||||||
|
local Participant = {};
|
||||||
Participant.__index = Participant;
|
Participant.__index = Participant;
|
||||||
function Participant:new(jid, nick)
|
function Participant:new(jid, nick, config)
|
||||||
return setmetatable({
|
return setmetatable({
|
||||||
jid = jid,
|
jid = jid,
|
||||||
nick = nick,
|
nick = nick,
|
||||||
|
config = config,
|
||||||
}, Participant);
|
}, Participant);
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -14,9 +22,9 @@ function Participant:from(config)
|
|||||||
return setmetatable(config, Participant);
|
return setmetatable(config, Participant);
|
||||||
end
|
end
|
||||||
|
|
||||||
Channel = {}
|
local Channel = {};
|
||||||
Channel.__index = Channel;
|
Channel.__index = Channel;
|
||||||
function Channel:new(jid, name, description, participants, subscriptions, spid, contacts, adhoc)
|
function Channel:new(jid, name, description, participants, administrators, owners, subscriptions, spid, contacts, adhoc, allowed, banned, config, nodes)
|
||||||
return setmetatable({
|
return setmetatable({
|
||||||
jid = jid,
|
jid = jid,
|
||||||
name = name,
|
name = name,
|
||||||
@ -26,6 +34,12 @@ function Channel:new(jid, name, description, participants, subscriptions, spid,
|
|||||||
spid = spid,
|
spid = spid,
|
||||||
contacts = contacts,
|
contacts = contacts,
|
||||||
adhoc = adhoc,
|
adhoc = adhoc,
|
||||||
|
administrators = administrators,
|
||||||
|
owners = owners,
|
||||||
|
config = config,
|
||||||
|
nodes = nodes,
|
||||||
|
allowed = allowed,
|
||||||
|
banned = banned,
|
||||||
}, Channel);
|
}, Channel);
|
||||||
end
|
end
|
||||||
function Channel:from(config)
|
function Channel:from(config)
|
||||||
@ -88,7 +102,322 @@ function Channel:debug_print()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Channel:allow_jid(jid)
|
||||||
|
local srv = pep.get_pep_service(jid_lib.node(self.jid));
|
||||||
|
array.push(self.allowed, jid);
|
||||||
|
srv:set_node_config(namespaces.allowed,
|
||||||
|
true,
|
||||||
|
{ ["max_items"] = #self.allowed + 1 });
|
||||||
|
srv:publish(namespaces.allowed, true, jid, nil);
|
||||||
|
end
|
||||||
|
|
||||||
|
function Channel:ban_jid(jid)
|
||||||
|
local srv = pep.get_pep_service(jid_lib.node(self.jid));
|
||||||
|
self.banned:push(jid);
|
||||||
|
srv:set_node_config(namespaces.banned,
|
||||||
|
true,
|
||||||
|
{ ["max_items"] = #self.banned + 1 });
|
||||||
|
srv:publish(namespaces.banned, true, jid, nil);
|
||||||
|
|
||||||
|
local i = helpers.find_str(jid, "@");
|
||||||
|
if i ~= nil then
|
||||||
|
-- "@" in JID => We're banning a real JID
|
||||||
|
if self:is_participant(jid) then
|
||||||
|
self:remove_participant(jid);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- No "@" in JID => We're banning a host
|
||||||
|
for _, participant in self.participants do
|
||||||
|
if jid_lib.host(participant.jid) == jid then
|
||||||
|
self:remove_participant(participant.jid);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Channel:remove_participant(jid)
|
||||||
|
-- Removes a user form the channel. May be a kick, may be a leave.
|
||||||
|
local srv = pep.get_pep_service(jid_lib.node(self.jid));
|
||||||
|
|
||||||
|
-- Step 1: Unsubscribe from all subscribed nodes
|
||||||
|
for _, node in ipairs(self.subscriptions[jid]) do
|
||||||
|
srv:remove_subscription(node, true, jid);
|
||||||
|
end
|
||||||
|
self.subscriptions[jid] = nil;
|
||||||
|
|
||||||
|
-- Step 2: Remove affiliations to all nodes
|
||||||
|
for _, node in ipairs(self.nodes) do
|
||||||
|
srv:set_affiliation(node, true, jid, "outcast");
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Step 3: Remove jid as participant
|
||||||
|
local i, _ = self:find_participant(jid);
|
||||||
|
table.remove(self.participants, i);
|
||||||
|
|
||||||
|
-- Step 4: Retract jid from participants node
|
||||||
|
local spid = self:get_spid(jid);
|
||||||
|
local notifier = st.stanza("retract", { id = spid });
|
||||||
|
srv:retract(namespaces.participants, true, jid, notifier);
|
||||||
|
self:save_state();
|
||||||
|
end
|
||||||
|
|
||||||
|
function Channel:is_allowed(jid)
|
||||||
|
local i, _ = helpers.find(self.allowed, jid);
|
||||||
|
local j, _ = helpers.find(self.allowed, jid_lib.host(jid));
|
||||||
|
|
||||||
|
return i ~= -1 and j ~= -1;
|
||||||
|
end
|
||||||
|
|
||||||
|
function Channel:is_admin(jid)
|
||||||
|
return helpers.in_array(self.administrators, jid);
|
||||||
|
end
|
||||||
|
|
||||||
|
function Channel:is_owner(jid)
|
||||||
|
return helpers.in_array(self.owners, jid);
|
||||||
|
end
|
||||||
|
|
||||||
|
function Channel:may_subscribe(jid, node, joining)
|
||||||
|
-- Returns true when a JID is allowed to subscribe to a node.
|
||||||
|
-- If joining is true and the node is set to "participants", then
|
||||||
|
-- true will be returned as the user will be a participant.
|
||||||
|
-- NOTE: When using this function directly, be careful to
|
||||||
|
-- check first whether the JID is allowed to join.
|
||||||
|
-- TODO: Presence
|
||||||
|
local group = "";
|
||||||
|
if node == namespaces.info then
|
||||||
|
group = self.config["Information Node Subscription"];
|
||||||
|
elseif node == namespaces.participants then
|
||||||
|
group = self.config["Participants Node Subscription"];
|
||||||
|
elseif node == namespaces.messages then
|
||||||
|
group = self.config["Messages Node Subscription"];
|
||||||
|
elseif node == namespaces.allowed then
|
||||||
|
group = self.config["Allowed Node Subscription"];
|
||||||
|
elseif node == namespaces.banned then
|
||||||
|
group = self.config["Banned Node Subscription"];
|
||||||
|
elseif node == namespaces.config then
|
||||||
|
group = "admins";
|
||||||
|
elseif node == namespaces.avatar or node == namespaces.avatar_metadata then
|
||||||
|
group = "participants";
|
||||||
|
end
|
||||||
|
|
||||||
|
module:log("debug", "may_subscribe: Group: %s", group);
|
||||||
|
module:log("debug", "Is Owner: %s", self:is_owner(jid));
|
||||||
|
module:log("debug", "Is Admin: %s", self:is_admin(jid));
|
||||||
|
module:log("debug", "Is Participant: %s", self:is_participant(jid));
|
||||||
|
module:log("debug", "Is allowed: %s", self:is_allowed(jid));
|
||||||
|
|
||||||
|
if group == "anyone" then
|
||||||
|
return true;
|
||||||
|
elseif group == "allowed" then
|
||||||
|
return self:is_allowed(jid);
|
||||||
|
elseif group == "nobody" then
|
||||||
|
return false;
|
||||||
|
elseif group == "admins" then
|
||||||
|
return self:is_admin(jid) or self:is_owner(jid);
|
||||||
|
elseif group == "owners" then
|
||||||
|
return self:is_owner(jid);
|
||||||
|
elseif group == "participants" then
|
||||||
|
return self:is_participant(jid) or self:is_admin(jid) or self:is_owner(jid) or joining;
|
||||||
|
end
|
||||||
|
|
||||||
|
return false;
|
||||||
|
end
|
||||||
|
|
||||||
|
function Channel:may_access(jid, node)
|
||||||
|
-- Access means read and subscribe
|
||||||
|
local group = "";
|
||||||
|
if node == namespaces.config then
|
||||||
|
-- TODO: For some reason, this is failing
|
||||||
|
--group = self.config["Configuration Node Access"];
|
||||||
|
group = "owners";
|
||||||
|
end
|
||||||
|
|
||||||
|
if group == "nobody" then
|
||||||
|
return false;
|
||||||
|
elseif group == "allowed" then
|
||||||
|
return self:is_allowed(jid);
|
||||||
|
elseif group == "admins" then
|
||||||
|
return self:is_admin(jid) or self:is_owner(jid);
|
||||||
|
elseif group == "owners" then
|
||||||
|
return self:is_owner(jid);
|
||||||
|
elseif group == "participants" then
|
||||||
|
return self:is_participant(jid) or self:is_admin(jid) or self:is_owner(jid);
|
||||||
|
end
|
||||||
|
|
||||||
|
return false;
|
||||||
|
end
|
||||||
|
|
||||||
|
function Channel:may_update(jid, node)
|
||||||
|
module:log("debug", "Update node %s", node);
|
||||||
|
local group = "";
|
||||||
|
-- TODO: Deal with avatars
|
||||||
|
if node == namespaces.info then
|
||||||
|
group = self.config["Information Node Update Rights"];
|
||||||
|
elseif node == namespaces.config then
|
||||||
|
-- TODO: Make this configurable
|
||||||
|
group = "admins";
|
||||||
|
elseif node == namespaces.avatar or node == namespaces.avatar_metadata then
|
||||||
|
group = self.config["Avatar Nodes Update Rights"];
|
||||||
|
end
|
||||||
|
|
||||||
|
if group == "admins" then
|
||||||
|
return self:is_admin(jid) or self:is_owner(jid);
|
||||||
|
elseif group == "owners" then
|
||||||
|
return self:is_owner(jid);
|
||||||
|
elseif group == "participants" then
|
||||||
|
return self:is_participant(jid) or self:is_admin(jid) or self:is_owner(jid);
|
||||||
|
elseif group == "allowed" then
|
||||||
|
return self:is_allowed(jid);
|
||||||
|
end
|
||||||
|
|
||||||
|
return false;
|
||||||
|
end
|
||||||
|
|
||||||
|
function Channel:may_join(jid)
|
||||||
|
-- Does the banned node exist?
|
||||||
|
if helpers.in_array(self.nodes, namespaces.banned) then
|
||||||
|
local host = jid_lib.host(jid);
|
||||||
|
if helpers.in_array(self.banned, host) or helpers.in_array(self.banned, jid) then
|
||||||
|
-- User or host is banned
|
||||||
|
return false;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Does the allowed node exist?
|
||||||
|
if helpers.in_array(self.nodes, namespaces.allowed) then
|
||||||
|
local host = jid_lib.host(jid);
|
||||||
|
if helpers.in_array(self.allowed, host) or helpers.in_array(self.allowed, jid) then
|
||||||
|
-- User or host is allowed
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
-- "Whitelist" is on but user or host is not on it.
|
||||||
|
return false;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
|
||||||
|
function Channel:may(jid, action, data)
|
||||||
|
if action == "subscribe" then
|
||||||
|
return self:may_subscribe(jid, data, false);
|
||||||
|
elseif action == "access" then
|
||||||
|
return self:may_access(jid, data);
|
||||||
|
elseif action == "update" then
|
||||||
|
return self:may_update(jid, data);
|
||||||
|
elseif action == "pm" then
|
||||||
|
return self.config["Private Messages"];
|
||||||
|
elseif action == "join" then
|
||||||
|
return self:may_join(jid);
|
||||||
|
elseif action == "invite" then
|
||||||
|
if not (self:is_admin(jid) or self:is_owner(jid)) then
|
||||||
|
return self.config["Participation Addition by Invitation from Participant"];
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
elseif action == "retract" then
|
||||||
|
-- Is this the same user
|
||||||
|
-- TODO: jid == data may not work when using SPIDs
|
||||||
|
if jid == data then
|
||||||
|
return self.config["User Message Retraction"];
|
||||||
|
else
|
||||||
|
local group = self.config["Administrator Message Retraction Rights"];
|
||||||
|
if group == "nobody" then
|
||||||
|
return false;
|
||||||
|
elseif group == "admins" then
|
||||||
|
return self:is_admin(jid) or self:is_owner(jid);
|
||||||
|
elseif group == "owners" then
|
||||||
|
return self:is_owner(jid);
|
||||||
|
end
|
||||||
|
|
||||||
|
return false;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Channel:broadcast_message(message, participant, archive)
|
||||||
|
-- Broadcast a message stanza according to rules layed out by
|
||||||
|
-- XEP-0369
|
||||||
|
local msg = st.clone(message);
|
||||||
|
msg:add_child(st.stanza("mix", { xmlns = namespaces.mix_core })
|
||||||
|
:tag("nick"):text(participant.nick):up()
|
||||||
|
:tag("jid"):text(participant.jid):up());
|
||||||
|
|
||||||
|
-- Put the message into the archive
|
||||||
|
local mam_id = uuid.generate();
|
||||||
|
msg.attr.id = mam_id;
|
||||||
|
-- NOTE: The spec says to do so
|
||||||
|
msg.attr.from = self.jid;
|
||||||
|
archive:append(message.attr.to, mam_id, msg, time.now());
|
||||||
|
msg.attr.from = self.jid.."/"..self:get_spid(participant.jid);
|
||||||
|
|
||||||
|
if module:fire_event("mix-broadcast-message", { message = msg, channel = self }) then
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, p in pairs(self.participants) do
|
||||||
|
-- Only users who subscribed to the messages node should receive
|
||||||
|
-- messages
|
||||||
|
if self:is_subscribed(p.jid, namespaces.messages) then
|
||||||
|
local tmp = st.clone(msg);
|
||||||
|
tmp.attr.to = p.jid;
|
||||||
|
module:send(tmp);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Channel:publish_participant(spid, participant)
|
||||||
|
-- Publish a new participant on the service
|
||||||
|
local srv = pep.get_pep_service(jid_lib.node(self.jid));
|
||||||
|
|
||||||
|
-- NOTE: This function has be to called *after* the new participant
|
||||||
|
-- has been added to the channel.participants array
|
||||||
|
srv:set_node_config(namespaces.participants,
|
||||||
|
true,
|
||||||
|
{ ["max_items"] = #self.participants });
|
||||||
|
srv:publish(namespaces.participants,
|
||||||
|
true,
|
||||||
|
spid,
|
||||||
|
st.stanza("item", { id = spid, xmlns = "http://jabber.org/protocol/pubsub" })
|
||||||
|
:tag("participant", { xmlns = namespaces.mix_core })
|
||||||
|
:tag("nick"):text(participant["nick"]):up()
|
||||||
|
:tag("jid"):text(participant["jid"]));
|
||||||
|
end
|
||||||
|
|
||||||
|
local default_channel_configuration = {
|
||||||
|
["Last Change Made By"] = "";
|
||||||
|
["Owner"] = {}; -- Filled in during creation
|
||||||
|
["Administrator"] = {};
|
||||||
|
["End of Life"] = "";
|
||||||
|
["Nodes Present"] = {}; -- Filled in during creation
|
||||||
|
["Messages Node Subscription"] = "participants";
|
||||||
|
["Presence Node Subscription"] = "participants";
|
||||||
|
["Participants Node Subscription"] = "participants";
|
||||||
|
["Information Node Subscription"] = "participants";
|
||||||
|
["Allowed Node Subscription"] = "admins";
|
||||||
|
["Banned Node Subscription"] = "admins";
|
||||||
|
["Configuration Node Access"] = "owners";
|
||||||
|
["Information Node Update Rights"] = "admins";
|
||||||
|
["Avatar Node Update Rights"] = "admins";
|
||||||
|
["Open Presence"] = false;
|
||||||
|
["Participants Must Provide Presence"] = false;
|
||||||
|
["User Message Retraction"] = false;
|
||||||
|
["Administrator Message Retraction Rights"] = "owners";
|
||||||
|
["Participation Addition by Invitation from Participant"] = false;
|
||||||
|
["Private Messages"] = true;
|
||||||
|
["Mandatory Nicks"] = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
local default_participant_configuration = {
|
||||||
|
["JID Visibility"] = "never";
|
||||||
|
["Private Messages"] = "allow";
|
||||||
|
["Presence"] = "share";
|
||||||
|
["vCard"] = "block";
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
Channel = Channel,
|
Channel = Channel,
|
||||||
Participant = Participant
|
Participant = Participant,
|
||||||
|
default_channel_configuration = default_channel_configuration,
|
||||||
|
default_participant_configuration = default_participant_configuration,
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
-- Big TODOlist
|
-- TODO: Channel info's Contacts is empty
|
||||||
-- TODO: Channel:is_subscribed could be replaced by get_pep_service(...):get_subscription
|
-- TODO: Maybe reset affiliations to none instead of outcast
|
||||||
|
-- TODO: Handle creation and deletion of avatar nodes when publishing to :config
|
||||||
|
|
||||||
local host = module:get_host();
|
local host = module:get_host();
|
||||||
if module:get_host_type() ~= "component" then
|
if module:get_host_type() ~= "component" then
|
||||||
@ -12,27 +13,18 @@ local uuid = require("util.uuid");
|
|||||||
local id = require("util.id");
|
local id = require("util.id");
|
||||||
local datetime = require("util.datetime");
|
local datetime = require("util.datetime");
|
||||||
local time = require("util.time");
|
local time = require("util.time");
|
||||||
local serialization = require("util.serialization");
|
|
||||||
local dataforms = require("util.dataforms");
|
local dataforms = require("util.dataforms");
|
||||||
|
local array = require("util.array");
|
||||||
|
local set = require("util.set");
|
||||||
local pep = module:depends("pep");
|
local pep = module:depends("pep");
|
||||||
|
|
||||||
local helpers = module:require("mix/helpers");
|
local helpers = module:require("mix/helpers");
|
||||||
|
local namespaces = module:require("mix/namespaces");
|
||||||
|
local anon = module:require("mix/anon");
|
||||||
|
|
||||||
local mixlib = module:require("mix/mix");
|
local mixlib = module:require("mix/mix");
|
||||||
Channel = mixlib.Channel;
|
local Channel = mixlib.Channel;
|
||||||
Participant = mixlib.Participant;
|
local Participant = mixlib.Participant;
|
||||||
|
|
||||||
-- XML namespaces
|
|
||||||
local mix_core_xmlns = "urn:xmpp:mix:core:1";
|
|
||||||
local mix_anon_xmlns = "urn:xmpp:mix:anon:0";
|
|
||||||
--local mix_admin_xmlns = "urn:xmpp:mix:admin:0";
|
|
||||||
local mix_node_messages = "urn:xmpp:mix:nodes:messages";
|
|
||||||
local mix_node_participants = "urn:xmpp:mix:nodes:participants";
|
|
||||||
local mix_node_info = "urn:xmpp:mix:nodes:info";
|
|
||||||
--local mix_node_allowed = "urn:xmpp:mix:nodes:allowed";
|
|
||||||
--local mix_node_banned = "urn:xmpp:mix:nodes:banned";
|
|
||||||
--local mix_node_config = "urn:xmpp:mix:nodes:config";
|
|
||||||
|
|
||||||
local mam_xmlns = "urn:xmpp:mam:2";
|
|
||||||
|
|
||||||
-- Persistent data
|
-- Persistent data
|
||||||
local persistent_channels = module:open_store("mix_channels", "keyval");
|
local persistent_channels = module:open_store("mix_channels", "keyval");
|
||||||
@ -43,29 +35,29 @@ local message_archive = module:open_store("mix_log", "archive");
|
|||||||
local default_channel_description = module:get_option("default_description", "A MIX channel for chatting");
|
local default_channel_description = module:get_option("default_description", "A MIX channel for chatting");
|
||||||
local default_channel_name = module:get_option("default_name", "MIX channel");
|
local default_channel_name = module:get_option("default_name", "MIX channel");
|
||||||
local restrict_channel_creation = module:get_option("restrict_local_channels", "local");
|
local restrict_channel_creation = module:get_option("restrict_local_channels", "local");
|
||||||
|
local service_name = module:get_option("service_name", "Prosody MIX service");
|
||||||
|
|
||||||
|
-- MIX configuration
|
||||||
|
local default_mix_nodes = array { namespaces.info, namespaces.participants, namespaces.messages, namespaces.config };
|
||||||
|
|
||||||
-- Dataforms
|
-- Dataforms
|
||||||
-- MAM
|
-- MAM
|
||||||
local mam_query_form = dataforms.new({
|
local mam_query_form = dataforms.new({
|
||||||
{ name = "FORM_TYPE", type = "hidden", value = mam_xmlns },
|
{ name = "FORM_TYPE", type = "hidden", value = namespaces.mam },
|
||||||
{ name = "with", type = "jid-single" },
|
{ name = "with", type = "jid-single" },
|
||||||
{ name = "start", type = "text-single" },
|
{ name = "start", type = "text-single" },
|
||||||
{ name = "end", type = "text-single" },
|
{ name = "end", type = "text-single" },
|
||||||
});
|
});
|
||||||
-- MIX Anon
|
-- MIX Core
|
||||||
local mix_anon_form = dataforms.new({
|
local mix_info_form = dataforms.new({
|
||||||
{ name = "FORM_TYPE", type = "hidden", value = mix_anon_xmlns },
|
{ name = "FORM_TYPE", type = "hidden", value = namespaces.mix_core },
|
||||||
{ name = "JID Visibility", type = "text-single" },
|
{ name = "Name", type = "text-single" },
|
||||||
{ name = "Private Messages", type = "text-single" },
|
{ name = "Description", type = "text-single" },
|
||||||
{ name = "Presence", type = "text-single" },
|
{ name = "Contact", type = "jid-multi" }
|
||||||
{ name = "vCard", type = "text-single" },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
-- MIX-ADMIN stuff
|
-- MIX-ADMIN stuff
|
||||||
--[[
|
|
||||||
local mix_config_form = dataforms.new({
|
local mix_config_form = dataforms.new({
|
||||||
{ name = "FORM_TYPE", type = "hidden", value = mix_admin_xmlns },
|
{ name = "FORM_TYPE", type = "hidden", value = namespaces.mix_admin },
|
||||||
{ name = "Last Change Made By", type = "jid-single" },
|
{ name = "Last Change Made By", type = "jid-single" },
|
||||||
{ name = "Owner", type = "jid-multi" },
|
{ name = "Owner", type = "jid-multi" },
|
||||||
{ name = "Administrator", type = "jid-multi" },
|
{ name = "Administrator", type = "jid-multi" },
|
||||||
@ -78,13 +70,45 @@ local mix_config_form = dataforms.new({
|
|||||||
{ name = "Allowed Node Subscription", type = "list-single" },
|
{ name = "Allowed Node Subscription", type = "list-single" },
|
||||||
{ name = "Banned Node Subscription", type = "list-single" },
|
{ name = "Banned Node Subscription", type = "list-single" },
|
||||||
{ name = "Configuration Node Access", type = "list-single" },
|
{ name = "Configuration Node Access", type = "list-single" },
|
||||||
|
{ name = "Information Node Update Rights", type = "list-single" },
|
||||||
|
{ name = "Avatar Node Update Rights", type = "list-single" },
|
||||||
|
{ name = "Open Presence", type = "boolean" },
|
||||||
|
{ name = "Participants Must Provide Presence", type = "boolean" },
|
||||||
|
{ name = "User Message Retraction", type = "boolean" },
|
||||||
|
{ name = "Administrator Message Retraction Rights", type = "list-single" },
|
||||||
|
{ name = "Participation Addition by Invitation from Participant", type = "boolean" },
|
||||||
|
{ name = "Private Messages", type = "boolean" },
|
||||||
|
{ name = "Mandatory Nicks", type = "boolean "},
|
||||||
});
|
});
|
||||||
]]--
|
|
||||||
|
local function mix_admin_node_to_value(node)
|
||||||
|
local map = {
|
||||||
|
[namespaces.messages] = "messages";
|
||||||
|
[namespaces.participants] = "participants";
|
||||||
|
[namespaces.info] = "info";
|
||||||
|
[namespaces.allowed] = "allowed";
|
||||||
|
[namespaces.banned] = "banned";
|
||||||
|
};
|
||||||
|
|
||||||
|
return "'"..map[node].."'";
|
||||||
|
end
|
||||||
|
|
||||||
|
local function mix_admin_value_to_node(value)
|
||||||
|
local map = {
|
||||||
|
["'messages'"] = namespaces.messages;
|
||||||
|
["'participants'"] = namespaces.participants;
|
||||||
|
["'info'"] = namespaces.info;
|
||||||
|
["'allowed'"] = namespaces.allowed;
|
||||||
|
["'banned'"] = namespaces.banned;
|
||||||
|
};
|
||||||
|
|
||||||
|
return map[value]
|
||||||
|
end
|
||||||
|
|
||||||
module:depends("disco");
|
module:depends("disco");
|
||||||
module:add_identity("conference", "mix", module:get_option("name", "Prosody MIX service"));
|
module:add_identity("conference", "mix", service_name);
|
||||||
module:add_feature("http://jabber.org/protocol/disco#info");
|
module:add_feature("http://jabber.org/protocol/disco#info");
|
||||||
module:add_feature(mix_core_xmlns);
|
module:add_feature(namespaces.mix_core);
|
||||||
|
|
||||||
local channels = {};
|
local channels = {};
|
||||||
|
|
||||||
@ -108,27 +132,10 @@ local function save_channels()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Channel:save_state()
|
function Channel:save_state()
|
||||||
-- Saving the entire state everything one channel seems stupid,
|
-- Store the channel in the persistent channel store
|
||||||
-- so we just save the changed channel
|
module:log("debug", "Saving channel %s...", self.jid);
|
||||||
module:log("debug", "Saving state of channel %s...", self.jid);
|
|
||||||
persistent_channel_data:set(self.jid, self);
|
persistent_channel_data:set(self.jid, self);
|
||||||
module:log("debug", "Saving state done.", self.jid);
|
module:log("debug", "Saving done.", self.jid);
|
||||||
end
|
|
||||||
|
|
||||||
local function publish_participant(service, channel, spid, participant)
|
|
||||||
-- Publish a new participant on the service
|
|
||||||
-- NOTE: This function has be to called *after* the new participant
|
|
||||||
-- has been added to the channel.participants attay
|
|
||||||
service:set_node_config(mix_node_participants,
|
|
||||||
true,
|
|
||||||
{ ["max_items"] = #channel.participants });
|
|
||||||
service:publish(mix_node_participants,
|
|
||||||
true,
|
|
||||||
spid,
|
|
||||||
st.stanza("item", { id = spid, xmlns = "http://jabber.org/protocol/pubsub" })
|
|
||||||
:tag("participant", { xmlns = mix_core_xmlns })
|
|
||||||
:tag("nick"):text(participant["nick"]):up()
|
|
||||||
:tag("jid"):text(participant["jid"]));
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function module.load()
|
function module.load()
|
||||||
@ -190,7 +197,7 @@ module:hook("iq/bare/http://jabber.org/protocol/disco#items:query", function(eve
|
|||||||
end
|
end
|
||||||
|
|
||||||
local reply = st.reply(stanza):tag("query", { xmlns = "http://jabber.org/protocol/disco#items", node = "mix" });
|
local reply = st.reply(stanza):tag("query", { xmlns = "http://jabber.org/protocol/disco#items", node = "mix" });
|
||||||
for _, node in pairs({mix_node_messages, mix_node_participants, mix_node_info}) do
|
for _, node in pairs(channel.nodes) do
|
||||||
reply:tag("item", { jid = channel.jid, node = node }):up();
|
reply:tag("item", { jid = channel.jid, node = node }):up();
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -233,14 +240,13 @@ module:hook("iq-get/host/http://jabber.org/protocol/disco#info:query", function(
|
|||||||
local origin, stanza = event.origin, event.stanza;
|
local origin, stanza = event.origin, event.stanza;
|
||||||
local reply = st.reply(stanza)
|
local reply = st.reply(stanza)
|
||||||
:tag("query", { xmlns = "http://jabber.org/protocol/disco#info" })
|
:tag("query", { xmlns = "http://jabber.org/protocol/disco#info" })
|
||||||
-- TODO: Name
|
:tag("identity", { category = "conference", type = "mix", name = service_name }):up()
|
||||||
:tag("identity", { category = "conference", type = "mix", name = "" }):up()
|
|
||||||
:tag("feature", { var = "http://jabber.org/protocol/disco#info" }):up()
|
:tag("feature", { var = "http://jabber.org/protocol/disco#info" }):up()
|
||||||
:tag("feature", { var = "http://jabber.org/protocol/disco#items" }):up()
|
:tag("feature", { var = "http://jabber.org/protocol/disco#items" }):up()
|
||||||
:tag("feature", { var = mix_core_xmlns }):up();
|
:tag("feature", { var = namespaces.mix_core }):up();
|
||||||
|
|
||||||
if can_create_channels(stanza.attr.from) then
|
if can_create_channels(stanza.attr.from) then
|
||||||
reply:tag("feature", { var = mix_core_xmlns.."#create-channel" }):up();
|
reply:tag("feature", { var = namespaces.mix_core.."#create-channel" }):up();
|
||||||
end
|
end
|
||||||
origin.send(reply);
|
origin.send(reply);
|
||||||
return true;
|
return true;
|
||||||
@ -259,14 +265,14 @@ module:hook("iq-get/bare/http://jabber.org/protocol/disco#info:query", function(
|
|||||||
reply:tag("feature", { var = "http://jabber.org/protocol/disco#info" }):up();
|
reply:tag("feature", { var = "http://jabber.org/protocol/disco#info" }):up();
|
||||||
reply:tag("identity", { category = "conference", name = channel.name, type = "mix" }):up();
|
reply:tag("identity", { category = "conference", name = channel.name, type = "mix" }):up();
|
||||||
|
|
||||||
reply:tag("feature", { var = mix_core_xmlns }):up();
|
reply:tag("feature", { var = namespaces.mix_core }):up();
|
||||||
reply:tag("feature", { var = "urn:xmpp:mam:2" }):up();
|
reply:tag("feature", { var = "urn:xmpp:mam:2" }):up();
|
||||||
|
|
||||||
origin.send(reply);
|
origin.send(reply);
|
||||||
return true;
|
return true;
|
||||||
end);
|
end);
|
||||||
|
|
||||||
module:hook("iq-set/bare/"..mam_xmlns..":query", function(event)
|
module:hook("iq-set/bare/"..namespaces.mam..":query", function(event)
|
||||||
local stanza, origin = event.stanza, event.origin;
|
local stanza, origin = event.stanza, event.origin;
|
||||||
local channel_jid = stanza.attr.to;
|
local channel_jid = stanza.attr.to;
|
||||||
local j, channel = get_channel(channel_jid);
|
local j, channel = get_channel(channel_jid);
|
||||||
@ -277,12 +283,12 @@ module:hook("iq-set/bare/"..mam_xmlns..":query", function(event)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Check if the user is subscribed to the messages node
|
-- Check if the user is subscribed to the messages node
|
||||||
if not channel:is_subscribed(stanza.attr.from, mix_node_messages) then
|
if not channel:is_subscribed(stanza.attr.from, namespaces.messages) then
|
||||||
origin.send(st.error_reply(stanza, "cancel", "forbidden"));
|
origin.send(st.error_reply(stanza, "cancel", "forbidden"));
|
||||||
return true;
|
return true;
|
||||||
end
|
end
|
||||||
|
|
||||||
local query = stanza:get_child("query", mam_xmlns);
|
local query = stanza:get_child("query", namespaces.mam);
|
||||||
local filter = {};
|
local filter = {};
|
||||||
local query_id = query.attr.queryid;
|
local query_id = query.attr.queryid;
|
||||||
local x = query:get_child("x", "jabber:x:data");
|
local x = query:get_child("x", "jabber:x:data");
|
||||||
@ -314,7 +320,7 @@ module:hook("iq-set/bare/"..mam_xmlns..":query", function(event)
|
|||||||
end
|
end
|
||||||
for message_id, item, when in data do
|
for message_id, item, when in data do
|
||||||
local msg = st.stanza("message", { from = channel_jid, to = stanza.attr.from, type = "groupchat" })
|
local msg = st.stanza("message", { from = channel_jid, to = stanza.attr.from, type = "groupchat" })
|
||||||
:tag("result", { xmlns = mam_xmlns, queryid = query_id, id = message_id })
|
:tag("result", { xmlns = namespaces.mam, queryid = query_id, id = message_id })
|
||||||
:tag("forwarded", { xmlns = "urn:xmpp:forward:0" })
|
:tag("forwarded", { xmlns = "urn:xmpp:forward:0" })
|
||||||
:tag("delay", { xmlns = "urn:xmpp:delay", stamp = datetime.datetime(when) }):up();
|
:tag("delay", { xmlns = "urn:xmpp:delay", stamp = datetime.datetime(when) }):up();
|
||||||
msg:add_child(item);
|
msg:add_child(item);
|
||||||
@ -323,16 +329,17 @@ module:hook("iq-set/bare/"..mam_xmlns..":query", function(event)
|
|||||||
return true;
|
return true;
|
||||||
end);
|
end);
|
||||||
|
|
||||||
module:hook("iq-get/bare/"..mam_xmlns..":query", function(event)
|
module:hook("iq-get/bare/"..namespaces.mam..":query", function(event)
|
||||||
if event.stanza.attr.id ~= "form1" then return; end
|
if event.stanza.attr.id ~= "form1" then return; end
|
||||||
|
|
||||||
module:log("debug", "Got a MAM query for supported fields");
|
module:log("debug", "Got a MAM query for supported fields");
|
||||||
|
|
||||||
|
-- TODO: Use dataforms:...
|
||||||
local ret = st.reply(event.stanza)
|
local ret = st.reply(event.stanza)
|
||||||
:tag("query", { xmlns = mam_xmlns })
|
:tag("query", { xmlns = namespaces.mam })
|
||||||
:tag("x", { xmlns = "jabber:x:data", type = "form"})
|
:tag("x", { xmlns = "jabber:x:data", type = "form"})
|
||||||
:tag("field", { type = "hidden", var = "FORM_TYPE" })
|
:tag("field", { type = "hidden", var = "FORM_TYPE" })
|
||||||
:tag("value"):text(mam_xmlns):up():up()
|
:tag("value"):text(namespaces.mam):up():up()
|
||||||
:tag("field", { type = "jid-single", var = "with" }):up()
|
:tag("field", { type = "jid-single", var = "with" }):up()
|
||||||
:tag("field", { type = "text-single", var = "start" }):up()
|
:tag("field", { type = "text-single", var = "start" }):up()
|
||||||
:tag("field", { type = "text-single", var = "end" });
|
:tag("field", { type = "text-single", var = "end" });
|
||||||
@ -340,7 +347,7 @@ module:hook("iq-get/bare/"..mam_xmlns..":query", function(event)
|
|||||||
return true;
|
return true;
|
||||||
end);
|
end);
|
||||||
|
|
||||||
module:hook("iq-set/bare/"..mix_core_xmlns..":leave", function(event)
|
module:hook("iq-set/bare/"..namespaces.mix_core..":leave", function(event)
|
||||||
module:log("debug", "MIX leave received");
|
module:log("debug", "MIX leave received");
|
||||||
local origin, stanza = event.origin, event.stanza;
|
local origin, stanza = event.origin, event.stanza;
|
||||||
local from = jid.bare(stanza.attr.from);
|
local from = jid.bare(stanza.attr.from);
|
||||||
@ -361,38 +368,22 @@ module:hook("iq-set/bare/"..mix_core_xmlns..":leave", function(event)
|
|||||||
return true;
|
return true;
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Remove the user as a participant by...
|
channel:remove_participant(from);
|
||||||
-- Unsubscribing
|
|
||||||
local srv = pep.get_pep_service(jid.node(channel.jid));
|
|
||||||
for _, node in pairs(channel.subscriptions[from]) do
|
|
||||||
srv:set_affiliation(node, true, from, "outcast");
|
|
||||||
srv:remove_subscription(node, true, from);
|
|
||||||
module:log("debug", "Unsubscribed %s from %s on %s", from, node, channel.jid);
|
|
||||||
end
|
|
||||||
channel.subscriptions[from] = nil;
|
|
||||||
-- Retracting the participation
|
|
||||||
local spid = channel:get_spid(from);
|
|
||||||
local notifier = st.stanza("retract", { id = spid });
|
|
||||||
-- TODO: Maybe error handling
|
|
||||||
srv:retract(mix_node_participants, true, spid, notifier);
|
|
||||||
-- Removing the user
|
|
||||||
table.remove(channel.participants, j);
|
|
||||||
channel:save_state();
|
|
||||||
|
|
||||||
module:fire_event("mix-channel-leave", { channel = channel, participant = participant });
|
module:fire_event("mix-channel-leave", { channel = channel, participant = participant });
|
||||||
|
|
||||||
origin.send(st.reply(stanza):tag("leave", { xmlns = mix_core_xmlns }));
|
origin.send(st.reply(stanza):tag("leave", { xmlns = namespaces.mix_core }));
|
||||||
return true;
|
return true;
|
||||||
end);
|
end);
|
||||||
|
|
||||||
module:hook("iq-set/bare/"..mix_core_xmlns..":join", function(event)
|
module:hook("iq-set/bare/"..namespaces.mix_core..":join", function(event)
|
||||||
module:log("debug", "MIX join received");
|
module:log("debug", "MIX join received");
|
||||||
|
|
||||||
local origin, stanza = event.origin, event.stanza;
|
local origin, stanza = event.origin, event.stanza;
|
||||||
local from = jid.bare(stanza.attr.from);
|
local from = jid.bare(stanza.attr.from);
|
||||||
local _, channel = get_channel(stanza.attr.to);
|
local _, channel = get_channel(stanza.attr.to);
|
||||||
if not channel then
|
if not channel then
|
||||||
origin.send(channel_not_found(stanza));
|
origin:send(channel_not_found(stanza));
|
||||||
return true;
|
return true;
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -403,12 +394,24 @@ module:hook("iq-set/bare/"..mix_core_xmlns..":join", function(event)
|
|||||||
return true;
|
return true;
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Is the user allowed to join?
|
||||||
|
if not channel:may(from, "join", nil) then
|
||||||
|
origin:send(st.error_reply(stanza, "cancel", "forbidden", "User or host is banned"));
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
|
||||||
local spid = channel:get_spid(from) or uuid.generate(); -- Stable Participant ID
|
local spid = channel:get_spid(from) or uuid.generate(); -- Stable Participant ID
|
||||||
local reply = st.reply(stanza)
|
local reply = st.reply(stanza)
|
||||||
:tag("join", { xmlns = mix_core_xmlns, id = spid });
|
:tag("join", { xmlns = namespaces.mix_core, id = spid });
|
||||||
local srv = pep.get_pep_service(jid.node(stanza.attr.to));
|
local join = stanza:get_child("join", namespaces.mix_core);
|
||||||
local join = stanza:get_child("join", mix_core_xmlns);
|
|
||||||
local nick_tag = join:get_child("nick");
|
local nick_tag = join:get_child("nick");
|
||||||
|
|
||||||
|
-- Check if the channel has mandatory nicks
|
||||||
|
if channel.config["Mandatory Nicks"] and not nick_tag then
|
||||||
|
origin:send(st.error_reply(stanza, "modify", "forbidden", "Nicks are mandatory"));
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
|
||||||
local nick;
|
local nick;
|
||||||
if not nick_tag then
|
if not nick_tag then
|
||||||
nick = jid.node(from);
|
nick = jid.node(from);
|
||||||
@ -417,86 +420,93 @@ module:hook("iq-set/bare/"..mix_core_xmlns..":join", function(event)
|
|||||||
end
|
end
|
||||||
module:log("debug", "User joining as nick %s", nick);
|
module:log("debug", "User joining as nick %s", nick);
|
||||||
|
|
||||||
|
local srv = pep.get_pep_service(jid.node(channel.jid));
|
||||||
local nodes = {};
|
local nodes = {};
|
||||||
local has_subscribed_once = false;
|
local has_subscribed_once = false;
|
||||||
local first_error = nil;
|
local first_error = nil;
|
||||||
|
local owner_or_admin = channel:is_admin(from) or channel:is_owner(from);
|
||||||
for subscribe in join:childtags("subscribe") do
|
for subscribe in join:childtags("subscribe") do
|
||||||
|
-- May the user subscribe to the node?
|
||||||
module:log("debug", "Subscribing user to node %s", subscribe.attr.node);
|
module:log("debug", "Subscribing user to node %s", subscribe.attr.node);
|
||||||
-- TODO: Once MIX-ADMIN is implemented, we should check here what
|
if channel:may_subscribe(from, subscribe.attr.node, true) then
|
||||||
-- affiliation we set, e.g. if the JID is the owner, then set owner.
|
-- Set the correct affiliation
|
||||||
srv:set_affiliation(subscribe.attr.node, true, from, "member");
|
local affiliation = "";
|
||||||
local ok, err = srv:add_subscription(subscribe.attr.node, true, from);
|
if owner_or_admin then
|
||||||
if not ok then
|
affiliation = "publisher";
|
||||||
module:log("debug", "Error during subscription: %s", err);
|
else
|
||||||
|
affiliation = "member";
|
||||||
|
end
|
||||||
|
srv:set_affiliation(subscribe.attr.node, true, from, affiliation);
|
||||||
|
|
||||||
-- MIX-CORE says that the first error should be returned when
|
local ok, err = srv:add_subscription(subscribe.attr.node, true, from);
|
||||||
-- no of the requested nodes could be subscribed to
|
if not ok then
|
||||||
if first_error ~= nil then
|
module:log("debug", "Error during subscription: %s", err);
|
||||||
first_error = err;
|
|
||||||
|
-- MIX-CORE says that the first error should be returned when
|
||||||
|
-- no of the requested nodes could be subscribed to
|
||||||
|
if first_error ~= nil then
|
||||||
|
first_error = err;
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Reset affiliation
|
||||||
|
srv:set_affiliation(subscribe.attr.node, true, from, "outcast");
|
||||||
|
|
||||||
|
else
|
||||||
|
table.insert(nodes, subscribe.attr.node);
|
||||||
|
reply:tag("subscribe", { node = subscribe.attr.node }):up();
|
||||||
|
has_subscribed_once = true;
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
table.insert(nodes, subscribe.attr.node);
|
module:log("debug", "Error during subscription: may_subscribe returned false");
|
||||||
reply:tag("subscribe", { node = subscribe.attr.node }):up();
|
if first_error ~= nil then
|
||||||
has_subscribed_once = true;
|
first_error = "Channel does not allow subscribing";
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if not has_subscribed_once then
|
if not has_subscribed_once then
|
||||||
origin.send(st.error_reply(stanza, "cancel", first_error));
|
-- TODO: This does not work
|
||||||
|
origin:send(st.error_reply(stanza, "cancel", first_error));
|
||||||
return true;
|
return true;
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO: Make the default configurable
|
-- TODO: Make the default configurable
|
||||||
local jid_visibility = "never"; -- default
|
local config = mixlib.default_participant_configuration;
|
||||||
local allow_pms = "allow";
|
local x = join:get_child("x");
|
||||||
local allow_vcards = "block";
|
|
||||||
local share_presence = "share";
|
|
||||||
local x = join:get_child("x", "jabber:x:data");
|
|
||||||
if x ~= nil then
|
if x ~= nil then
|
||||||
-- TODO: Rethink naming
|
|
||||||
-- TODO: Error handling?
|
-- TODO: Error handling?
|
||||||
local form, err = mix_anon_form:data(x);
|
local form, err = anon.form:data(x);
|
||||||
if form["JID Visibility"] then
|
if form["JID Visibility"] then
|
||||||
jid_visibility = form["JID Visibility"];
|
config["JID Visibility"] = form["JID Visibility"];
|
||||||
end
|
end
|
||||||
if form["Private Messages"] then
|
if form["Private Messages"] then
|
||||||
allow_pms = form["Private Messages"];
|
config["Private Messages"] = form["Private Messages"];
|
||||||
end
|
end
|
||||||
if form["Presence"] then
|
if form["Presence"] then
|
||||||
share_presence = form["Presence"];
|
config["Presence"] = form["Presence"];
|
||||||
end
|
end
|
||||||
if form["vCard"] then
|
if form["vCard"] then
|
||||||
allow_vcards = form["vCard"];
|
config["vCard"] = form["vCard"];
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local participant = Participant:new(jid.bare(from), nick, jid_visibility, allow_pms, share_presence, allow_vcards);
|
local participant = Participant:new(jid.bare(from), nick, config);
|
||||||
channel.subscriptions[from] = nodes;
|
channel.subscriptions[from] = nodes;
|
||||||
table.insert(channel.participants, participant)
|
table.insert(channel.participants, participant)
|
||||||
channel:set_spid(jid.bare(stanza.attr.from), spid);
|
channel:set_spid(jid.bare(stanza.attr.from), spid);
|
||||||
publish_participant(srv, channel, spid, participant);
|
channel:publish_participant(spid, participant);
|
||||||
channel:save_state();
|
channel:save_state();
|
||||||
|
|
||||||
module:fire_event("mix-channel-join", { channel = channel, participant = participant });
|
module:fire_event("mix-channel-join", { channel = channel, participant = participant });
|
||||||
|
|
||||||
reply:add_child(nick_tag);
|
-- We do not reuse nick_tag as it might be nil
|
||||||
reply:tag("x", { xmlns = "jabber:x:data", type = "result" })
|
reply:tag("nick"):text(nick):up();
|
||||||
:tag("field", { var = "FORM_TYPE", type = "hidden" })
|
reply:add_child(anon.form:form(config, "result"));
|
||||||
:tag("value"):text(mix_anon_xmlns):up():up()
|
|
||||||
:tag("field", { var = "JID Visibility"})
|
|
||||||
:tag("value"):text(jid_visibility):up():up()
|
|
||||||
:tag("field", { var = "Private Messages"})
|
|
||||||
:tag("value"):text(allow_pms):up():up()
|
|
||||||
:tag("field", { var = "Presence"})
|
|
||||||
:tag("value"):text(share_presence):up():up()
|
|
||||||
:tag("field", { var = "vCard"})
|
|
||||||
:tag("value"):text(allow_vcards):up():up();
|
|
||||||
|
|
||||||
origin.send(reply);
|
origin.send(reply);
|
||||||
return true
|
return true
|
||||||
end);
|
end);
|
||||||
|
|
||||||
module:hook("iq-set/bare/"..mix_core_xmlns..":setnick", function(event)
|
module:hook("iq-set/bare/"..namespaces.mix_core..":setnick", function(event)
|
||||||
module:log("debug", "MIX setnick received");
|
module:log("debug", "MIX setnick received");
|
||||||
local origin, stanza = event.origin, event.stanza;
|
local origin, stanza = event.origin, event.stanza;
|
||||||
local from = jid.bare(stanza.attr.from);
|
local from = jid.bare(stanza.attr.from);
|
||||||
@ -514,7 +524,7 @@ module:hook("iq-set/bare/"..mix_core_xmlns..":setnick", function(event)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- NOTE: Prosody should guarantee us that the setnick stanza exists
|
-- NOTE: Prosody should guarantee us that the setnick stanza exists
|
||||||
local setnick = stanza:get_child("setnick", mix_core_xmlns);
|
local setnick = stanza:get_child("setnick", namespaces.mix_core);
|
||||||
local nick = setnick:get_child("nick");
|
local nick = setnick:get_child("nick");
|
||||||
if nick == nil then
|
if nick == nil then
|
||||||
origin.send(st.error_reply(stanza, "cancel", "bad-request", "Missing <nick>"));
|
origin.send(st.error_reply(stanza, "cancel", "bad-request", "Missing <nick>"));
|
||||||
@ -524,14 +534,12 @@ module:hook("iq-set/bare/"..mix_core_xmlns..":setnick", function(event)
|
|||||||
-- Change the nick
|
-- Change the nick
|
||||||
channel.participants[j].nick = nick:get_text();
|
channel.participants[j].nick = nick:get_text();
|
||||||
-- Inform all other members
|
-- Inform all other members
|
||||||
local srv = pep.get_pep_service(jid.node(channel.jid));
|
channel:publish_participant(channel:get_spid(participant.jid), participant);
|
||||||
--local participant = channel.participants[participant_index];
|
|
||||||
publish_participant(srv, channel, channel:get_spid(participant.jid), participant);
|
|
||||||
|
|
||||||
module:fire_event("mix-change-nick", { channel = channel, participant = participant });
|
module:fire_event("mix-change-nick", { channel = channel, participant = participant });
|
||||||
|
|
||||||
origin.send(st.reply(stanza)
|
origin.send(st.reply(stanza)
|
||||||
:tag("setnick", { xmlns = mix_core_xmlns })
|
:tag("setnick", { xmlns = namespaces.mix_core })
|
||||||
:tag("nick"):text(nick:get_text()));
|
:tag("nick"):text(nick:get_text()));
|
||||||
channel:save_state();
|
channel:save_state();
|
||||||
return true;
|
return true;
|
||||||
@ -539,32 +547,38 @@ end);
|
|||||||
|
|
||||||
function Channel:publish_info(srv)
|
function Channel:publish_info(srv)
|
||||||
local timestamp = datetime.datetime(time.now());
|
local timestamp = datetime.datetime(time.now());
|
||||||
|
-- TODO: Check if this is correct
|
||||||
local info = st.stanza("item", { id = timestamp, xmlns = "http://jabber.org/protocol/pubsub" })
|
local info = st.stanza("item", { id = timestamp, xmlns = "http://jabber.org/protocol/pubsub" })
|
||||||
:tag("x", { xmlns = "jabber:x:data", type = "result" })
|
:add_child(mix_info_form:form({
|
||||||
:tag("field", { var = "FORM_TYPE", type = "hidden" })
|
["FORM_TYPE"] = "hidden",
|
||||||
:tag("value"):text(mix_core_xmlns):up():up()
|
["Name"] = self.name,
|
||||||
:tag("field", { var = "Name" })
|
["Description"] = self.description,
|
||||||
:tag("value"):text(self.name):up()
|
["Contacts"] = self.contacts
|
||||||
:tag("field", { var = "Contact" });
|
}, "result"));
|
||||||
for _, contact in pairs(self.contacts) do
|
srv:publish(namespaces.info, true, timestamp, info);
|
||||||
info:add_child(st.stanza("value"):text(contact));
|
|
||||||
end
|
|
||||||
srv:publish(mix_node_info, true, timestamp, info);
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function create_channel(node, creator, adhoc)
|
local function create_channel(node, creator, adhoc)
|
||||||
local channel = Channel:new(string.format("%s@%s", node, host),
|
-- TODO: Now all properties from the admin dataform are covered
|
||||||
|
local channel = Channel:new(string.format("%s@%s", node, host), -- Channel JID
|
||||||
default_channel_name,
|
default_channel_name,
|
||||||
default_channel_description,
|
default_channel_description,
|
||||||
{},
|
{}, -- Participants
|
||||||
{},
|
{}, -- Administrators
|
||||||
{},
|
{ creator }, -- Owners
|
||||||
{ creator },
|
{}, -- Subscriptions
|
||||||
adhoc);
|
{}, -- SPID mapping
|
||||||
|
{ creator }, -- Contacts
|
||||||
|
adhoc, -- Is channel an AdHoc channel
|
||||||
|
{}, -- Allowed
|
||||||
|
{}, -- Banned
|
||||||
|
mixlib.default_channel_configuration, -- Channel config
|
||||||
|
{}); -- Present nodes
|
||||||
|
|
||||||
-- Create the PEP nodes
|
-- Create the PEP nodes
|
||||||
local srv = pep.get_pep_service(node);
|
local srv = pep.get_pep_service(node);
|
||||||
-- MIX-CORE
|
-- MIX-CORE
|
||||||
for _, psnode in pairs({ mix_node_info, mix_node_participants, mix_node_messages }) do
|
for _, psnode in ipairs(default_mix_nodes) do
|
||||||
srv:create(psnode, true, {
|
srv:create(psnode, true, {
|
||||||
["access_model"] = "whitelist",
|
["access_model"] = "whitelist",
|
||||||
["persist_items"] = true,
|
["persist_items"] = true,
|
||||||
@ -572,28 +586,44 @@ local function create_channel(node, creator, adhoc)
|
|||||||
end
|
end
|
||||||
channel:publish_info(srv);
|
channel:publish_info(srv);
|
||||||
|
|
||||||
--[[
|
|
||||||
-- MIX-ADMIN
|
-- MIX-ADMIN
|
||||||
local admin_nodes = { mix_node_banned, mix_node_config };
|
local admin_nodes = array { namespaces.banned };
|
||||||
if adhoc then
|
if adhoc then
|
||||||
table.insert(admin_nodes, mix_node_allowed);
|
admin_nodes:push(namespaces.allowed);
|
||||||
end
|
end
|
||||||
for _, psnode in pairs(admin_nodes) do
|
for _, psnode in pairs(admin_nodes) do
|
||||||
srv:create(mix_node_allowed, true, { ["access_model"] = "whitelist" });
|
srv:create(psnode, true, {
|
||||||
srv:set_affiliation(mix_node_allowed, true, creator, "owner");
|
["access_model"] = "whitelist",
|
||||||
|
["persist_items"] = true,
|
||||||
|
});
|
||||||
|
srv:set_affiliation(namespaces.allowed, true, creator, "publish");
|
||||||
end
|
end
|
||||||
if adhoc then
|
if adhoc then
|
||||||
-- Allow the creator to join
|
-- Allow the creator to join
|
||||||
srv:publish(mix_node_allowed,
|
channel:allow_jid(creator);
|
||||||
true,
|
|
||||||
nil,
|
|
||||||
st.stanza("item", { id = creator }));
|
|
||||||
end
|
end
|
||||||
]]--
|
|
||||||
|
-- Some bookkeeping
|
||||||
|
local nodes = default_mix_nodes + admin_nodes;
|
||||||
|
local node_names = array(nodes):filter(function (element)
|
||||||
|
return element ~= namespaces.config;
|
||||||
|
end):map(mix_admin_node_to_value):push("'avatar'");
|
||||||
|
-- NOTE: This is to prevent cycles during serialization (apparently)
|
||||||
|
channel.nodes = {table.unpack(nodes)};
|
||||||
|
|
||||||
|
local config = mixlib.default_channel_configuration;
|
||||||
|
config["Owner"] = { creator };
|
||||||
|
config["Nodes Present"] = node_names;
|
||||||
|
channel.config = config;
|
||||||
|
srv:publish(namespaces.config,
|
||||||
|
true,
|
||||||
|
"",
|
||||||
|
st.stanza("item", { xmlns = "http://jabber.org/protocol/pubsub" })
|
||||||
|
:add_child(mix_config_form:form(config, "result")));
|
||||||
table.insert(channels, channel);
|
table.insert(channels, channel);
|
||||||
end
|
end
|
||||||
|
|
||||||
module:hook("iq-set/host/"..mix_core_xmlns..":create", function(event)
|
module:hook("iq-set/host/"..namespaces.mix_core..":create", function(event)
|
||||||
module:log("debug", "MIX create received");
|
module:log("debug", "MIX create received");
|
||||||
local origin, stanza = event.origin, event.stanza;
|
local origin, stanza = event.origin, event.stanza;
|
||||||
local from = jid.bare(stanza.attr.from);
|
local from = jid.bare(stanza.attr.from);
|
||||||
@ -604,7 +634,7 @@ module:hook("iq-set/host/"..mix_core_xmlns..":create", function(event)
|
|||||||
return true;
|
return true;
|
||||||
end
|
end
|
||||||
|
|
||||||
local create = stanza:get_child("create", mix_core_xmlns);
|
local create = stanza:get_child("create", namespaces.mix_core);
|
||||||
local node;
|
local node;
|
||||||
if create.attr.channel ~= nil then
|
if create.attr.channel ~= nil then
|
||||||
-- Create non-adhoc channel
|
-- Create non-adhoc channel
|
||||||
@ -637,17 +667,17 @@ module:hook("iq-set/host/"..mix_core_xmlns..":create", function(event)
|
|||||||
-- TODO: Add an event
|
-- TODO: Add an event
|
||||||
|
|
||||||
origin.send(st.reply(stanza)
|
origin.send(st.reply(stanza)
|
||||||
:tag("create", { xmlns = mix_core_xmlns, channel = node }));
|
:tag("create", { xmlns = namespaces.mix_core, channel = node }));
|
||||||
save_channels();
|
save_channels();
|
||||||
return true;
|
return true;
|
||||||
end);
|
end);
|
||||||
|
|
||||||
module:hook("iq-set/host/"..mix_core_xmlns..":destroy", function(event)
|
module:hook("iq-set/host/"..namespaces.mix_core..":destroy", function(event)
|
||||||
module:log("debug", "MIX destroy received");
|
module:log("debug", "MIX destroy received");
|
||||||
local origin, stanza = event.origin, event.stanza;
|
local origin, stanza = event.origin, event.stanza;
|
||||||
local from = jid.bare(stanza.attr.from);
|
local from = jid.bare(stanza.attr.from);
|
||||||
|
|
||||||
local destroy = stanza:get_child("destroy", mix_core_xmlns);
|
local destroy = stanza:get_child("destroy", namespaces.mix_core);
|
||||||
local node = destroy.attr.channel;
|
local node = destroy.attr.channel;
|
||||||
local node_jid = string.format("%s@%s", node, host);
|
local node_jid = string.format("%s@%s", node, host);
|
||||||
local i, channel = get_channel(node_jid);
|
local i, channel = get_channel(node_jid);
|
||||||
@ -656,15 +686,14 @@ module:hook("iq-set/host/"..mix_core_xmlns..":destroy", function(event)
|
|||||||
return true;
|
return true;
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO: Permission checks
|
if not channel:is_owner(from) then
|
||||||
-- TODO: Maybe make this configurable
|
|
||||||
if helpers.find_str(channel.contacts, from) == -1 then
|
|
||||||
origin.send(st.error_reply(stanza, "cancel", "forbidden"));
|
origin.send(st.error_reply(stanza, "cancel", "forbidden"));
|
||||||
return true;
|
return true;
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Remove all registered nodes
|
-- Remove all registered nodes
|
||||||
local srv = pep.get_pep_service(node);
|
local srv = pep.get_pep_service(node);
|
||||||
for _, pep_node in pairs({ mix_node_participants, mix_node_info, mix_node_messages }) do
|
for _, pep_node in pairs(channel.nodes) do
|
||||||
srv:delete(pep_node, true);
|
srv:delete(pep_node, true);
|
||||||
end
|
end
|
||||||
table.remove(channels, i);
|
table.remove(channels, i);
|
||||||
@ -698,31 +727,280 @@ module:hook("message/bare", function(event)
|
|||||||
return true;
|
return true;
|
||||||
end
|
end
|
||||||
|
|
||||||
local msg = st.clone(stanza);
|
-- Handles sending the message accordingly, firing an event and
|
||||||
msg:add_child(st.stanza("mix", { xmlns = mix_core_xmlns })
|
-- even doing nothing if an event handler for "mix-broadcast-message"
|
||||||
:tag("nick"):text(participant.nick):up()
|
-- returns true.
|
||||||
:tag("jid"):text(participant.jid):up());
|
channel:broadcast_message(stanza, participant, message_archive);
|
||||||
|
return true;
|
||||||
|
end);
|
||||||
|
|
||||||
-- Put the message into the archive
|
local function handle_new_node(srv, channel, node)
|
||||||
local mam_id = uuid.generate();
|
if node == namespaces.allowed then
|
||||||
msg.attr.id = mam_id;
|
-- If the allowed node is created, make sure to add the owners
|
||||||
-- NOTE: The spec says to do so
|
-- as allowed
|
||||||
msg.attr.from = channel.jid;
|
|
||||||
message_archive:append(stanza.attr.to, mam_id, msg, time.now());
|
|
||||||
msg.attr.from = channel.jid.."/"..channel:get_spid(from);
|
|
||||||
|
|
||||||
if module:fire_event("mix-broadcast-message", { message = msg, channel = channel }) then
|
channel.allowed = {};
|
||||||
|
for _, owner in pairs(channel.owners) do
|
||||||
|
array.push(channel.allowed, owner)
|
||||||
|
srv:publish(namespaces.allowed, true, owner, nil);
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Remove all non-allowed participants
|
||||||
|
for _, participant in channel.participants do
|
||||||
|
if not channel:is_allowed(participant.jid) then
|
||||||
|
channel:remove_participant(participant.jid);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- MIX Admin events
|
||||||
|
local function handle_pubsub_publish(event, publish, channel)
|
||||||
|
local node = publish.attr.node;
|
||||||
|
local from = jid.bare(event.stanza.attr.from);
|
||||||
|
local item = publish:get_child("item");
|
||||||
|
|
||||||
|
-- TODO: Deal with avatars
|
||||||
|
-- TODO: Should we trust the clients to behave?
|
||||||
|
-- local srv = pep.get_pep_service(jid.node(channel.jid));
|
||||||
|
if node == namespaces.config then
|
||||||
|
if not channel:may(from, "update", namespaces.config) then
|
||||||
|
-- TODO: This does not work
|
||||||
|
event.origin:send(st.error_reply(publish, "auth", "forbidden"));
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
|
||||||
|
local config, err = mix_config_form:data(item);
|
||||||
|
-- TODO: Error handling?
|
||||||
|
|
||||||
|
local srv = pep.get_pep_service(jid.node(channel.jid));
|
||||||
|
-- TODO: Check this over
|
||||||
|
local removed_admins = set.new(channel.administrators):difference(set.new(config["Administrator"]));
|
||||||
|
local removed_owners = set.new(channel.owners):difference(set.new(config["Owner"]));
|
||||||
|
local new_node_names = set.new(config["Nodes Present"]):difference(set.new(channel.nodes));
|
||||||
|
local removed_node_names = set.new(channel.nodes):difference(set.new(config["Nodes Present"]));
|
||||||
|
local new_nodes = array.map(new_node_names, mix_admin_value_to_node); -- TODO
|
||||||
|
local removed_nodes = array.map(removed_node_names, mix_admin_value_to_node); -- TODO
|
||||||
|
|
||||||
|
-- TODO: Verify that this is actually safe
|
||||||
|
for _, jid_ in pairs(array.append(removed_admins, removed_owners)) do
|
||||||
|
for _, psnode in pairs(channel.nodes) do
|
||||||
|
srv:set_affiliation(psnode, true, jid_, "member");
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TODO: Apply this to the PubSub publish
|
||||||
|
config["Last Change Made By"] = jid.bare(event.stanza.attr.from);
|
||||||
|
channel.owners = config["Owner"];
|
||||||
|
channel.administrators = config["Administrator"];
|
||||||
|
channel.config = config;
|
||||||
|
channel.nodes = config["Nodes Present"]
|
||||||
|
|
||||||
|
-- Remove all nodes that are not existant anymore...
|
||||||
|
for _, removed_node in pairs(removed_nodes) do
|
||||||
|
for participant, subscriptions in channel.participants do
|
||||||
|
local j, _ = helpers.find(subscriptions, removed_node);
|
||||||
|
if j ~= -1 then
|
||||||
|
srv:remove_subscription(removed_node, true, from);
|
||||||
|
table.remove(participant.subscriptions, j);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if removed_node == namespaces.allowed then
|
||||||
|
channel.allowed = nil;
|
||||||
|
elseif removed_node == namespaces.banned then
|
||||||
|
channel.banned = nil;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- ... and create all new ones.
|
||||||
|
for _, new_node in pairs(new_nodes) do
|
||||||
|
srv:create(new_node, true, {
|
||||||
|
["access_model"] = "whitelist",
|
||||||
|
["persist_items"] = true,
|
||||||
|
});
|
||||||
|
for participant, _ in channel.participants do
|
||||||
|
local affiliation = "";
|
||||||
|
if channel:is_admin(participant.jid) or channel:is_owner(participant.jid) then
|
||||||
|
affiliation = "publisher";
|
||||||
|
else
|
||||||
|
affiliation = "member";
|
||||||
|
end
|
||||||
|
|
||||||
|
srv:set_affiliation(new_node, true, affiliation);
|
||||||
|
end
|
||||||
|
|
||||||
|
handle_new_node(srv, channel, new_node);
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, jid_ in pairs(array.append(channel.owners, channel.administrators)) do
|
||||||
|
for _, psnode in pairs(channel.nodes) do
|
||||||
|
srv:set_affiliation(psnode, true, jid_, "publisher");
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif node == namespaces.info then
|
||||||
|
if not channel:may(from, "update", namespaces.info) then
|
||||||
|
-- TODO: This does not work
|
||||||
|
event.origin:send(st.error_reply(publish, "auth", "forbidden"));
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
|
||||||
|
local info, err = mix_info_form:data(item);
|
||||||
|
-- TODO: Error handling
|
||||||
|
if info["Name"] then
|
||||||
|
channel.name = info["Name"];
|
||||||
|
end
|
||||||
|
if info["Description"] then
|
||||||
|
channel.description = info["Description"];
|
||||||
|
end
|
||||||
|
if info["Contact"] then
|
||||||
|
channel.contacts = info["Contact"];
|
||||||
|
end
|
||||||
|
|
||||||
|
module:log("debug", "Channel info updated");
|
||||||
|
elseif node == namespaces.banned then
|
||||||
|
channel:ban_jid(publish:get_child("item").attr.id);
|
||||||
|
elseif node == namespaces.allowed then
|
||||||
|
channel:allow_jid(publish:get_child("item").attr.id);
|
||||||
|
elseif node == namespaces.participants or node == namespaces.messages then
|
||||||
|
-- We don't want admins or owners to publish to these nodes as they
|
||||||
|
-- are managed by the module
|
||||||
|
local reply = st.error_reply(event.stanza, "cancel", "feature-not-implemented");
|
||||||
|
reply:tag("unsupported", {
|
||||||
|
xmlns = "http://jabber.org/protocol/pubsub#errors",
|
||||||
|
feature = "publish",
|
||||||
|
});
|
||||||
|
event.origin:send(reply);
|
||||||
return true;
|
return true;
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, p in pairs(channel.participants) do
|
-- Let the actual PubSub implementation handle the rest
|
||||||
-- Only users who subscribed to the messages node should receive
|
channel:save_state();
|
||||||
-- messages
|
end
|
||||||
if channel:is_subscribed(p.jid, mix_node_messages) then
|
|
||||||
local tmp = st.clone(msg);
|
local function handle_pubsub_retract(event, retract, channel)
|
||||||
tmp.attr.to = p.jid;
|
local node = retract.attr.node;
|
||||||
module:send(tmp);
|
local item = retract:get_child("item");
|
||||||
|
if node == namespaces.banned then
|
||||||
|
channel.banned = array.filter(channel.banned, function (element)
|
||||||
|
return element ~= item.attr.id;
|
||||||
|
end);
|
||||||
|
elseif node == namespaces.allowed then
|
||||||
|
channel.allowed = array.filter(channel.allowed, function (element)
|
||||||
|
return element ~= item.attr.id;
|
||||||
|
end);
|
||||||
|
elseif node == namespaces.participants then
|
||||||
|
local reply = st.error_reply(event.stanza, "cancel", "feature-not-implemented");
|
||||||
|
reply:tag("unsupported", {
|
||||||
|
xmlns = "http://jabber.org/protocol/pubsub#errors",
|
||||||
|
feature = "delete-items"
|
||||||
|
});
|
||||||
|
event.origin:send(reply);
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
|
||||||
|
channel:save_state();
|
||||||
|
end
|
||||||
|
|
||||||
|
local function handle_pubsub_subscribe(event, subscribe, channel)
|
||||||
|
local from = jid.bare(event.stanza.attr.from);
|
||||||
|
local origin = event.origin;
|
||||||
|
local node = subscribe.attr.node;
|
||||||
|
|
||||||
|
-- Check for node existance
|
||||||
|
local i, _ = helpers.find(channel.nodes, node);
|
||||||
|
if i == -1 then
|
||||||
|
-- Let the actual PubSub implementation handle it
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
if not channel:may(from, "subscribe", node) then
|
||||||
|
origin:send(st.error_reply(event.stanza, "auth", "forbidden"));
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(channel.subscriptions[from], node);
|
||||||
|
channel:save_state();
|
||||||
|
-- The rest is handled by the actual PubSub implementation
|
||||||
|
end
|
||||||
|
|
||||||
|
local function handle_pubsub_unsubscribe(event, subscribe, channel)
|
||||||
|
local from = jid.bare(event.stanza.attr.from);
|
||||||
|
local origin = event.origin;
|
||||||
|
local node = subscribe.attr.node;
|
||||||
|
|
||||||
|
-- Check for node existance
|
||||||
|
local i, _ = helpers.find(channel.nodes, node);
|
||||||
|
if i == -1 then
|
||||||
|
-- Let the actual PubSub implementation handle it
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
if not channel:may(from, "subscribe", node) then
|
||||||
|
origin:send(st.error_reply(event.stanza, "auth", "forbidden"));
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
|
||||||
|
local j, _ = helpers.find(channel.subscriptions[from], node);
|
||||||
|
if j == -1 then
|
||||||
|
local errstanza = st.error_reply(event.stanza, "cancel", "unexpected-request");
|
||||||
|
errstanza:tag("not-subscribed", { xmlns = "http://jabber.org/protocol/pubsub#errors" });
|
||||||
|
origin:send(errstanza);
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
table.remove(channel.subscriptions[from], j);
|
||||||
|
channel:save_state();
|
||||||
|
-- The rest is handled by the actual PubSub implementation
|
||||||
|
end
|
||||||
|
|
||||||
|
local function handle_pubsub_items(event, items, channel)
|
||||||
|
local node = items.attr.node;
|
||||||
|
local from = jid.bare(event.stanza.attr.from);
|
||||||
|
if node == namespaces.config then
|
||||||
|
if not channel:may(from, "access", node) then
|
||||||
|
return true;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return true;
|
end
|
||||||
end);
|
|
||||||
|
module:hook("iq-get/bare/http://jabber.org/protocol/pubsub:pubsub", function(event)
|
||||||
|
local stanza = event.stanza;
|
||||||
|
local _, channel = get_channel(stanza.attr.to);
|
||||||
|
if not channel then
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
local pubsub = stanza:get_child("pubsub", "http://jabber.org/protocol/pubsub");
|
||||||
|
local items = pubsub:get_child("items");
|
||||||
|
if items then
|
||||||
|
return handle_pubsub_items(event, items, channel);
|
||||||
|
end
|
||||||
|
end, 1000);
|
||||||
|
|
||||||
|
module:hook("iq-set/bare/http://jabber.org/protocol/pubsub:pubsub", function(event)
|
||||||
|
local stanza = event.stanza;
|
||||||
|
local _, channel = get_channel(stanza.attr.to);
|
||||||
|
if not channel then
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
local pubsub = stanza:get_child("pubsub", "http://jabber.org/protocol/pubsub");
|
||||||
|
local publish = pubsub:get_child("publish");
|
||||||
|
if publish then
|
||||||
|
return handle_pubsub_publish(event, publish, channel);
|
||||||
|
end
|
||||||
|
|
||||||
|
local subscribe = pubsub:get_child("subscribe");
|
||||||
|
if subscribe then
|
||||||
|
return handle_pubsub_subscribe(event, subscribe, channel);
|
||||||
|
end
|
||||||
|
|
||||||
|
local unsubscribe = pubsub:get_child("unsubscribe");
|
||||||
|
if unsubscribe then
|
||||||
|
return handle_pubsub_unsubscribe(event, subscribe, channel);
|
||||||
|
end
|
||||||
|
|
||||||
|
local retract = pubsub:get_child("retract");
|
||||||
|
if retract then
|
||||||
|
return handle_pubsub_retract(event, retract, channel);
|
||||||
|
end
|
||||||
|
end, 1000);
|
||||||
|
19
mod_mix/namespaces.lib.lua
Normal file
19
mod_mix/namespaces.lib.lua
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
return {
|
||||||
|
-- XMLNS
|
||||||
|
-- MIX
|
||||||
|
mix_core = "urn:xmpp:mix:core:1";
|
||||||
|
mix_anon = "urn:xmpp:mix:anon:0";
|
||||||
|
mix_admin = "urn:xmpp:mix:admin:0";
|
||||||
|
-- MAM
|
||||||
|
mam = "urn:xmpp:mam:2";
|
||||||
|
-- User Avatar
|
||||||
|
avatar = "urn:xmpp:avatar:data";
|
||||||
|
avatar_metadata = "urn:xmpp:avatar:metadata";
|
||||||
|
-- MIX PubSub nodes
|
||||||
|
messages = "urn:xmpp:mix:nodes:messages";
|
||||||
|
participants = "urn:xmpp:mix:nodes:participants";
|
||||||
|
info = "urn:xmpp:mix:nodes:info";
|
||||||
|
allowed = "urn:xmpp:mix:nodes:allowed";
|
||||||
|
banned = "urn:xmpp:mix:nodes:banned";
|
||||||
|
config = "urn:xmpp:mix:nodes:config";
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user