mix: Implement a subset of MAM and refactor
This commit is contained in:
parent
e09f4ebcd3
commit
0352f94c42
23
mod_mix/helpers.lib.lua
Normal file
23
mod_mix/helpers.lib.lua
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
-- Helper functions for mod_mix
|
||||||
|
function find(array, f)
|
||||||
|
-- Searches for an element for which f returns true. The first element
|
||||||
|
-- and its index are returned. If none are found, then -1, nil is returned.
|
||||||
|
--
|
||||||
|
-- f is a function that takes the value and returns either true or false.
|
||||||
|
for i, v in pairs(array) do
|
||||||
|
if f(v) then return i, v; end
|
||||||
|
end
|
||||||
|
|
||||||
|
return -1, nil;
|
||||||
|
end
|
||||||
|
|
||||||
|
function find_str(array, str)
|
||||||
|
-- Returns the index of str in array. -1 if array does not contain str
|
||||||
|
local i, v = find(array, function(v) return v == str end);
|
||||||
|
return i;
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
find_str = find_str,
|
||||||
|
find = find,
|
||||||
|
};
|
94
mod_mix/mix.lib.lua
Normal file
94
mod_mix/mix.lib.lua
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
local st = require("util.stanza");
|
||||||
|
local helpers = module:require("mix/helpers");
|
||||||
|
|
||||||
|
Participant = {};
|
||||||
|
Participant.__index = Participant;
|
||||||
|
function Participant:new(jid, nick)
|
||||||
|
return setmetatable({
|
||||||
|
jid = jid,
|
||||||
|
nick = nick,
|
||||||
|
}, Participant);
|
||||||
|
end
|
||||||
|
|
||||||
|
function Participant:from(config)
|
||||||
|
return setmetatable(config, Participant);
|
||||||
|
end
|
||||||
|
|
||||||
|
Channel = {}
|
||||||
|
Channel.__index = Channel;
|
||||||
|
function Channel:new(jid, name, description, participants, subscriptions, spid, contacts, adhoc)
|
||||||
|
return setmetatable({
|
||||||
|
jid = jid,
|
||||||
|
name = name,
|
||||||
|
description = description,
|
||||||
|
participants = participants,
|
||||||
|
subscriptions = subscriptions,
|
||||||
|
spid = spid,
|
||||||
|
contacts = contacts,
|
||||||
|
adhoc = adhoc,
|
||||||
|
}, Channel);
|
||||||
|
end
|
||||||
|
function Channel:from(config)
|
||||||
|
-- Turn a channel into a Channel object
|
||||||
|
local o = setmetatable(config, Channel);
|
||||||
|
for i, _ in pairs(o.participants) do
|
||||||
|
o.participants[i] = Participant:from(o.participants[i]);
|
||||||
|
end
|
||||||
|
return o;
|
||||||
|
end
|
||||||
|
|
||||||
|
function Channel:get_spid(jid)
|
||||||
|
-- Returns the Stable Participant ID for the *BARE* jid
|
||||||
|
return self.spid[jid];
|
||||||
|
end
|
||||||
|
|
||||||
|
function Channel:set_spid(jid, spid)
|
||||||
|
-- Sets the Stable Participant ID for the *BARE* jid
|
||||||
|
self.spid[jid] = spid;
|
||||||
|
end
|
||||||
|
|
||||||
|
function Channel:find_participant(jid)
|
||||||
|
-- Returns the index of a participant in a channel. Returns -1
|
||||||
|
-- if the participant is not found
|
||||||
|
return helpers.find(self.participants, function(p) return p.jid == jid end);
|
||||||
|
end
|
||||||
|
|
||||||
|
function Channel:is_participant(jid)
|
||||||
|
-- Returns true if jid is a participant of the channel. False otherwise.
|
||||||
|
local i, _ = self:find_participant(jid);
|
||||||
|
return i ~= -1;
|
||||||
|
end
|
||||||
|
|
||||||
|
function Channel:is_subscribed(jid, node)
|
||||||
|
-- Returns true of JID is subscribed to node on this channel. Returns false
|
||||||
|
-- otherwise.
|
||||||
|
return helpers.find_str(self.subscriptions[jid], node) ~= -1;
|
||||||
|
end
|
||||||
|
|
||||||
|
function Channel:debug_print()
|
||||||
|
module:log("debug", "Channel %s (%s)", self.jid, self.name);
|
||||||
|
module:log("debug", "'%s'", self.description);
|
||||||
|
for _, p in pairs(self.participants) do
|
||||||
|
module:log("debug", "=> %s (%s)", p.jid, p.nick);
|
||||||
|
end
|
||||||
|
|
||||||
|
module:log("debug", "Contacts:");
|
||||||
|
for _, c in pairs(self.contacts) do
|
||||||
|
module:log("debug", "=> %s", c);
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.subscriptions then
|
||||||
|
module:log("debug", "Subscriptions:");
|
||||||
|
for user, subs in pairs(self.subscriptions) do
|
||||||
|
module:log("debug", "[%s]", user);
|
||||||
|
for _, sub in pairs(self.subscriptions[user]) do
|
||||||
|
module:log("debug", "=> %s", sub);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
Channel = Channel,
|
||||||
|
Participant = Participant
|
||||||
|
};
|
@ -1,10 +1,6 @@
|
|||||||
local host = module:get_host();
|
local host = module:get_host();
|
||||||
local mm = require("core.modulemanger");
|
|
||||||
if module:get_host_type() ~= "component" then
|
if module:get_host_type() ~= "component" then
|
||||||
error("mix should be loaded as a component", 0);
|
error("MIX should be loaded as a component", 0);
|
||||||
end
|
|
||||||
if mm.is_loaded(host, "mix_pam") then
|
|
||||||
error("mix and mix_pam shouldn't be loaded together on the same host");
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local st = require("util.stanza");
|
local st = require("util.stanza");
|
||||||
@ -14,108 +10,51 @@ 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 serialization = require("util.serialization");
|
||||||
|
local dataforms = require("util.dataforms");
|
||||||
local pep = module:depends("pep");
|
local pep = module:depends("pep");
|
||||||
|
local helpers = module:require("mix/helpers");
|
||||||
|
|
||||||
|
local mixlib = module:require("mix/mix");
|
||||||
|
Channel = mixlib.Channel;
|
||||||
|
Participant = mixlib.Participant;
|
||||||
|
|
||||||
-- XML namespaces
|
-- XML namespaces
|
||||||
local mix_core_xmlns = "urn:xmpp:mix:core:1";
|
local mix_core_xmlns = "urn:xmpp:mix:core:1";
|
||||||
|
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 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");
|
||||||
local persistent_channel_data = module:open_store("mix_data", "keyval");
|
local persistent_channel_data = module:open_store("mix_data", "keyval");
|
||||||
|
local message_archive = module:open_store("mix_log", "archive");
|
||||||
|
|
||||||
-- Configuration
|
-- Configuration
|
||||||
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");
|
||||||
|
|
||||||
|
-- MAM stuff
|
||||||
|
local mam_query_form = dataforms.new({
|
||||||
|
{ name = "FORM_TYPE", type = "hidden", value = mam_xmlns },
|
||||||
|
{ name = "with", type = "jid-single" },
|
||||||
|
{ name = "start", type = "text-single" },
|
||||||
|
{ name = "end", type = "text-single" },
|
||||||
|
});
|
||||||
|
|
||||||
module:depends("disco");
|
module:depends("disco");
|
||||||
-- module:depends("mam"); TODO: Once message sending works
|
-- module:depends("mam"); TODO: Once message sending works
|
||||||
module:add_identity("conference", "mix", module:get_option("name", "Prosody MIX service"));
|
module:add_identity("conference", "mix", module:get_option("name", "Prosody MIX service"));
|
||||||
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(mix_core_xmlns);
|
||||||
|
|
||||||
Participant = {};
|
|
||||||
Participant.__index = Participant;
|
|
||||||
function Participant:new(jid, nick)
|
|
||||||
return setmetatable({
|
|
||||||
jid = jid,
|
|
||||||
nick = nick,
|
|
||||||
}, Participant);
|
|
||||||
end
|
|
||||||
|
|
||||||
Channel = {}
|
|
||||||
Channel.__index = Channel;
|
|
||||||
function Channel:new(jid, name, description, participants, subscriptions, spid, contacts, adhoc)
|
|
||||||
return setmetatable({
|
|
||||||
jid = jid,
|
|
||||||
name = name,
|
|
||||||
description = description,
|
|
||||||
participants = participants,
|
|
||||||
subscriptions = subscriptions,
|
|
||||||
spid = spid,
|
|
||||||
contacts = contacts,
|
|
||||||
adhoc = adhoc,
|
|
||||||
}, Channel);
|
|
||||||
end
|
|
||||||
function Channel:from(config)
|
|
||||||
return setmetatable(config, Channel);
|
|
||||||
end
|
|
||||||
|
|
||||||
function Channel:get_spid(jid)
|
|
||||||
-- Returns the Stable Participant ID for the *BARE* jid
|
|
||||||
return self.spid[jid];
|
|
||||||
end
|
|
||||||
|
|
||||||
function Channel:set_spid(jid, spid)
|
|
||||||
-- Sets the Stable Participant ID for the *BARE* jid
|
|
||||||
self.spid[jid] = spid;
|
|
||||||
end
|
|
||||||
|
|
||||||
function Channel:debug_print()
|
|
||||||
module:log("debug", "Channel %s (%s)", self.jid, self.name);
|
|
||||||
module:log("debug", "'%s'", self.description);
|
|
||||||
for _, p in pairs(self.participants) do
|
|
||||||
module:log("debug", "=> %s (%s)", p.jid, p.nick);
|
|
||||||
end
|
|
||||||
|
|
||||||
module:log("debug", "Contacts:");
|
|
||||||
for _, c in pairs(self.contacts) do
|
|
||||||
module:log("debug", "=> %s", c);
|
|
||||||
end
|
|
||||||
|
|
||||||
if self.subscriptions then
|
|
||||||
module:log("debug", "Subscriptions:");
|
|
||||||
for user, subs in pairs(self.subscriptions) do
|
|
||||||
module:log("debug", "[%s]", user);
|
|
||||||
for _, sub in pairs(self.subscriptions[user]) do
|
|
||||||
module:log("debug", "=> %s", sub);
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local channels = {};
|
local channels = {};
|
||||||
|
|
||||||
function get_channel(jid)
|
function get_channel(jid)
|
||||||
-- Return the channel object from the channels array for which the
|
-- Return the channel object from the channels array for which the
|
||||||
-- JID matches. If none is found, returns -1, nil
|
-- JID matches. If none is found, returns -1, nil
|
||||||
for i, channel in pairs(channels) do
|
return helpers.find(channels, function(c) return c.jid == jid; end);
|
||||||
if channel.jid == jid then
|
|
||||||
return i, channel;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return -1, nil;
|
|
||||||
end
|
|
||||||
|
|
||||||
function publish_participant(service, spid, participant)
|
|
||||||
service:publish("urn:xmpp:mix:nodes: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 save_channels()
|
function save_channels()
|
||||||
@ -139,19 +78,31 @@ function Channel:save_state()
|
|||||||
module:log("debug", "Saving state done.", self.jid);
|
module:log("debug", "Saving state done.", self.jid);
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function publish_participant(service, spid, participant)
|
||||||
|
-- Publish a new participant on the service
|
||||||
|
service:publish("urn:xmpp:mix:nodes: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
|
||||||
|
|
||||||
function module.load()
|
function module.load()
|
||||||
module:log("info", "Loading MIX channels...");
|
module:log("info", "Loading MIX channels...");
|
||||||
|
|
||||||
local channel_list = persistent_channels:get("channels");
|
local channel_list = persistent_channels:get("channels");
|
||||||
if channel_list then
|
if channel_list then
|
||||||
for _, channel in pairs(channel_list) do
|
for _, channel in pairs(channel_list) do
|
||||||
table.insert(channels, Channel:from(persistent_channel_data:get(channel)));
|
local channel = Channel:from(persistent_channel_data:get(channel));
|
||||||
module:log("debug", "MIX channel %s loaded", channel);
|
table.insert(channels, channel);
|
||||||
|
module:log("debug", "MIX channel %s loaded", channel.jid);
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
module:log("debug", "No MIX channels found.");
|
module:log("debug", "No MIX channels found.");
|
||||||
end
|
end
|
||||||
module:log("debug", "Loading MIX channels done.");
|
module:log("info", "Loading MIX channels done.");
|
||||||
end
|
end
|
||||||
|
|
||||||
function channel_not_found(stanza)
|
function channel_not_found(stanza)
|
||||||
@ -177,16 +128,27 @@ module:hook("iq/bare/http://jabber.org/protocol/disco#items:query", function(eve
|
|||||||
module:log("debug", "IQ-GET disco#items");
|
module:log("debug", "IQ-GET disco#items");
|
||||||
|
|
||||||
local origin, stanza = event.origin, event.stanza;
|
local origin, stanza = event.origin, event.stanza;
|
||||||
|
if stanza:get_child("query", "http://jabber.org/protocol/disco#items").attr.node ~= "mix" then
|
||||||
|
origin.send(st.error_reply(stanza, "modify", "bad-request"));
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TODO: Maybe here we should check if the user has permissions to get infos
|
||||||
|
-- about the channel before saying that it doesn't exist to prevent creating
|
||||||
|
-- an oracle.
|
||||||
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
|
||||||
|
|
||||||
-- TODO: Maybe check permissions
|
if not channel:is_participant(jid.bare(stanza.attr.from)) then
|
||||||
|
origin.send(st.error_reply(stanza, "cancel", "forbidden"));
|
||||||
|
return true;
|
||||||
|
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({"urn:xmpp:mix:nodes:messages", "urn:xmpp:mix:nodes:participants", "urn:xmpp:mix:nodes:info"}) do
|
for _, node in pairs({mix_node_messages, mix_node_participants, mix_node_info}) do
|
||||||
reply:tag("item", { jid = channel.jid, node = node }):up();
|
reply:tag("item", { jid = channel.jid, node = node }):up();
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -196,9 +158,10 @@ end);
|
|||||||
|
|
||||||
function can_create_channels(user)
|
function can_create_channels(user)
|
||||||
-- Returns true when the jid is allowed to create MIX channels. False otherwise.
|
-- Returns true when the jid is allowed to create MIX channels. False otherwise.
|
||||||
|
-- NOTE: Taken from plugins/muc/mod_muc.lua
|
||||||
|
local host_suffix = host:gsub("^[^%.]+%.", "");
|
||||||
|
|
||||||
if restrict_channel_creation == "local" then
|
if restrict_channel_creation == "local" then
|
||||||
-- NOTE: Taken from plugins/muc/mod_muc.lua
|
|
||||||
local host_suffix = host:gsub("^[^%.]+%.", "");
|
|
||||||
module:log("debug", "Comparing %s (Sender) to %s (Host)", jid.host(user), host_suffix);
|
module:log("debug", "Comparing %s (Sender) to %s (Host)", jid.host(user), host_suffix);
|
||||||
|
|
||||||
if jid.host(user) == host_suffix then
|
if jid.host(user) == host_suffix then
|
||||||
@ -206,6 +169,16 @@ function can_create_channels(user)
|
|||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
end
|
end
|
||||||
|
elseif type(restrict_channel_creation) == "table" then
|
||||||
|
if helpers.find_str(restrict_channel_creation, user) ~= -1 then
|
||||||
|
-- User was specifically listed
|
||||||
|
return true;
|
||||||
|
elseif helpers.find_str(restrict_channel_creation, jid.host(user)) then
|
||||||
|
-- User's host was allowed
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
|
||||||
|
return false;
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO: Handle also true/"admin" (See mod_muc)
|
-- TODO: Handle also true/"admin" (See mod_muc)
|
||||||
@ -221,11 +194,14 @@ module:hook("iq-get/host/http://jabber.org/protocol/disco#info:query", function(
|
|||||||
-- TODO: Name
|
-- TODO: Name
|
||||||
:tag("identity", { category = "conference", type = "mix", 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 = mix_core_xmlns }):up();
|
:tag("feature", { var = mix_core_xmlns }):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 = mix_core_xmlns.."#create-channel" }):up();
|
||||||
end
|
end
|
||||||
|
origin.send(reply);
|
||||||
|
return true;
|
||||||
end);
|
end);
|
||||||
|
|
||||||
module:hook("iq-get/bare/http://jabber.org/protocol/disco#info:query", function(event)
|
module:hook("iq-get/bare/http://jabber.org/protocol/disco#info:query", function(event)
|
||||||
@ -242,23 +218,84 @@ module:hook("iq-get/bare/http://jabber.org/protocol/disco#info:query", function(
|
|||||||
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 = mix_core_xmlns }):up();
|
||||||
-- TODO: Once message sending works, uncomment this
|
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);
|
||||||
|
|
||||||
-- TODO: Make this a function of Channel:
|
module:hook("iq-set/bare/"..mam_xmlns..":query", function(event)
|
||||||
function find_participant(table, jid)
|
local stanza, origin = event.stanza, event.origin;
|
||||||
for i, v in pairs(table) do
|
local channel_jid = stanza.attr.to;
|
||||||
if v.jid == jid then
|
local j, channel = get_channel(channel_jid);
|
||||||
return i;
|
if j == -1 then
|
||||||
end
|
-- TODO: Is this correct?
|
||||||
|
origin.send(channel_not_found(stanza));
|
||||||
|
return true;
|
||||||
end
|
end
|
||||||
|
|
||||||
return -1;
|
-- Check if the user is subscribed to the messages node
|
||||||
end
|
if not channel:is_subscribed(stanza.attr.from, mix_node_messages) then
|
||||||
|
origin.send(st.error_reply(stanza, "cancel", "forbidden"));
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
|
||||||
|
local query = stanza:get_child("query", mam_xmlns);
|
||||||
|
local filter = {};
|
||||||
|
local query_id = query.attr.queryid;
|
||||||
|
local x = query:get_child("x", "jabber:x:data");
|
||||||
|
if x ~= nil then
|
||||||
|
local form, err = mam_query_form:data(x);
|
||||||
|
|
||||||
|
-- Validate
|
||||||
|
if (form["start"] and not form["end"]) or (not form["start"] and form["end"]) then
|
||||||
|
origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid timestamp"));
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
|
||||||
|
module:log("debug", "Got a MAM query between %s and %s", form["start"], form["end"]);
|
||||||
|
filter = {
|
||||||
|
start = datetime.parse(form["start"]); ["end"] = datetime.parse(form["end"])
|
||||||
|
};
|
||||||
|
end
|
||||||
|
local data, err = message_archive:find(channel_jid, filter);
|
||||||
|
if not data then
|
||||||
|
module:log("debug", "MAM error: %s", err);
|
||||||
|
|
||||||
|
if err == "item-not-found" then
|
||||||
|
origin.send(st.error_reply(stanza, "modify", "item-not-found"));
|
||||||
|
else
|
||||||
|
origin.send(st.error_reply(stanza, "cancel", "internal-server-error"));
|
||||||
|
end
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
for id, item, when in data do
|
||||||
|
local msg = st.stanza("message", { from = channel_jid, to = stanza.attr.from, type = "groupchat" })
|
||||||
|
:tag("result", { xmlns = mam_xmlns, queryid = query_id, id = id })
|
||||||
|
:tag("forwarded", { xmlns = "urn:xmpp:forward:0" })
|
||||||
|
:tag("delay", { xmlns = "urn:xmpp:delay", stamp = datetime.datetime(when) }):up();
|
||||||
|
msg:add_child(item);
|
||||||
|
origin.send(msg);
|
||||||
|
end
|
||||||
|
return true;
|
||||||
|
end);
|
||||||
|
|
||||||
|
module:hook("iq-get/bare/"..mam_xmlns..":query", function(event)
|
||||||
|
if event.stanza.attr.id ~= "form1" then return; end
|
||||||
|
|
||||||
|
module:log("debug", "Got a MAM query for supported fields");
|
||||||
|
|
||||||
|
local ret = st.reply(event.stanza)
|
||||||
|
:tag("query", { xmlns = mam_xmlns })
|
||||||
|
:tag("x", { xmlns = "jabber:x:data", type = "form"})
|
||||||
|
:tag("field", { type = "hidden", var = "FORM_TYPE" })
|
||||||
|
:tag("value"):text(mam_xmlns):up():up()
|
||||||
|
:tag("field", { type = "jid-single", var = "with" }):up()
|
||||||
|
:tag("field", { type = "text-single", var = "start" }):up()
|
||||||
|
:tag("field", { type = "text-single", var = "end" });
|
||||||
|
module:send(ret);
|
||||||
|
return true;
|
||||||
|
end);
|
||||||
|
|
||||||
module:hook("iq-set/bare/"..mix_core_xmlns..":leave", function(event)
|
module:hook("iq-set/bare/"..mix_core_xmlns..":leave", function(event)
|
||||||
module:log("debug", "MIX leave received");
|
module:log("debug", "MIX leave received");
|
||||||
@ -270,8 +307,8 @@ module:hook("iq-set/bare/"..mix_core_xmlns..":leave", function(event)
|
|||||||
return true;
|
return true;
|
||||||
end
|
end
|
||||||
|
|
||||||
local participant_index = find_participant(channel.participants, from);
|
local j, _ = channel:find_participant(from);
|
||||||
if participant_index == -1 then
|
if j == -1 then
|
||||||
origin.send(st.error_reply(stanza,
|
origin.send(st.error_reply(stanza,
|
||||||
"cancel",
|
"cancel",
|
||||||
"forbidden",
|
"forbidden",
|
||||||
@ -289,10 +326,10 @@ module:hook("iq-set/bare/"..mix_core_xmlns..":leave", function(event)
|
|||||||
module:log("debug", "Unsubscribed %s from %s on %s", from, node, channel.jid);
|
module:log("debug", "Unsubscribed %s from %s on %s", from, node, channel.jid);
|
||||||
end
|
end
|
||||||
channels[i].subscriptions[from] = nil;
|
channels[i].subscriptions[from] = nil;
|
||||||
-- Retracting the participatio)
|
-- Retracting the participation
|
||||||
srv:retract("urn:xmpp:mix:nodes:participants", true, channel:get_spid(from), true);
|
srv:retract("urn:xmpp:mix:nodes:participants", true, channel:get_spid(from), true);
|
||||||
-- Removing the user
|
-- Removing the user
|
||||||
table.remove(channels[i].participants, participant_index);
|
table.remove(channels[i].participants, j);
|
||||||
channel:save_state();
|
channel:save_state();
|
||||||
|
|
||||||
origin.send(st.reply(stanza):tag("leave", { xmlns = mix_core_xmlns }));
|
origin.send(st.reply(stanza):tag("leave", { xmlns = mix_core_xmlns }));
|
||||||
@ -311,8 +348,8 @@ module:hook("iq-set/bare/"..mix_core_xmlns..":join", function(event)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Prevent the user from joining multiple times
|
-- Prevent the user from joining multiple times
|
||||||
local participant_index = find_participant(channel.participants, from);
|
local j, _ = channel:find_participant(from);
|
||||||
if participant_index ~= -1 then
|
if j ~= -1 then
|
||||||
module:send(st.error_reply(stanza, "cancel", "bad-request", "User already joined"));
|
module:send(st.error_reply(stanza, "cancel", "bad-request", "User already joined"));
|
||||||
return true;
|
return true;
|
||||||
end
|
end
|
||||||
@ -373,8 +410,8 @@ module:hook("iq-set/bare/"..mix_core_xmlns..":setnick", function(event)
|
|||||||
return true;
|
return true;
|
||||||
end
|
end
|
||||||
|
|
||||||
local participant_index = find_participant(channel.participants, from);
|
local j, participant = channel:find_participant(from);
|
||||||
if participant_index == -1 then
|
if j == -1 then
|
||||||
channel:debug_print();
|
channel:debug_print();
|
||||||
module:log("debug", "%s is not a participant in %s", from, channel.jid);
|
module:log("debug", "%s is not a participant in %s", from, channel.jid);
|
||||||
return true;
|
return true;
|
||||||
@ -389,10 +426,10 @@ module:hook("iq-set/bare/"..mix_core_xmlns..":setnick", function(event)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Change the nick
|
-- Change the nick
|
||||||
channels[i].participants[participant_index].nick = nick:get_text();
|
channels[i].participants[j].nick = nick:get_text();
|
||||||
-- Inform all other members
|
-- Inform all other members
|
||||||
local srv = pep.get_pep_service(channel.jid);
|
local srv = pep.get_pep_service(channel.jid);
|
||||||
local participant = channel.participants[participant_index];
|
--local participant = channel.participants[participant_index];
|
||||||
publish_participant(srv, channel:get_spid(participant.jid), participant);
|
publish_participant(srv, channel:get_spid(participant.jid), participant);
|
||||||
|
|
||||||
origin.send(st.reply(stanza)
|
origin.send(st.reply(stanza)
|
||||||
@ -403,15 +440,18 @@ module:hook("iq-set/bare/"..mix_core_xmlns..":setnick", function(event)
|
|||||||
end);
|
end);
|
||||||
|
|
||||||
function Channel:publish_info(srv)
|
function Channel:publish_info(srv)
|
||||||
srv:publish("urn:xmpp:mix:nodes:info", true, timestamp,
|
local timestamp = datetime.datetime(time.now());
|
||||||
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" })
|
:tag("x", { xmlns = "jabber:x:data", type = "result" })
|
||||||
:tag("field", { var = "FORM_TYPE", type = "hidden" })
|
:tag("field", { var = "FORM_TYPE", type = "hidden" })
|
||||||
:tag("value"):text(mix_core_xmlns):up():up()
|
:tag("value"):text(mix_core_xmlns):up():up()
|
||||||
:tag("field", { var = "Name" })
|
:tag("field", { var = "Name" })
|
||||||
:tag("value"):text(self.name):up()
|
:tag("value"):text(self.name):up()
|
||||||
:tag("field", { var = "Contact" })
|
:tag("field", { var = "Contact" });
|
||||||
:tag("value"):text(self.contacts));
|
for _, contact in pairs(self.contacts) do
|
||||||
|
info:add_child(st.stanza("value"):text(contact));
|
||||||
|
end
|
||||||
|
srv:publish(mix_node_info, true, timestamp, info);
|
||||||
end
|
end
|
||||||
|
|
||||||
function create_channel(node, creator, adhoc)
|
function create_channel(node, creator, adhoc)
|
||||||
@ -427,7 +467,7 @@ function create_channel(node, creator, adhoc)
|
|||||||
local srv = pep.get_pep_service(node);
|
local srv = pep.get_pep_service(node);
|
||||||
local timestamp = datetime.datetime(time.now());
|
local timestamp = datetime.datetime(time.now());
|
||||||
local access_model = adhoc and "whitelist" or "open";
|
local access_model = adhoc and "whitelist" or "open";
|
||||||
for _, psnode in pairs({"urn:xmpp:mix:nodes:info", "urn:xmpp:mix:nodes:participants", "urn:xmpp:mix:nodes:messages" }) do
|
for _, psnode in pairs({ mix_node_info, mix_node_participants, mix_node_messages }) do
|
||||||
srv:create(psnode, true, { ["access_model"] = access_model });
|
srv:create(psnode, true, { ["access_model"] = access_model });
|
||||||
|
|
||||||
-- If the channel is an adhoc channel, then we need to make sure that
|
-- If the channel is an adhoc channel, then we need to make sure that
|
||||||
@ -455,9 +495,11 @@ module:hook("iq-set/host/"..mix_core_xmlns..":create", function(event)
|
|||||||
local node;
|
local node;
|
||||||
if create.attr.channel ~= nil then
|
if create.attr.channel ~= nil then
|
||||||
-- Create non-adhoc channel
|
-- Create non-adhoc channel
|
||||||
|
module:log("debug", "Attempting to create channel %s", create.attr.channel);
|
||||||
node = create.attr.channel;
|
node = create.attr.channel;
|
||||||
local _, channel = get_channel(stanza.attr.to);
|
local i, _ = get_channel(create.attr.channel.."@"..stanza.attr.to);
|
||||||
if channel then
|
module:log("debug", "Channel index %s", i);
|
||||||
|
if i ~= -1 then
|
||||||
origin.send(st.error_reply(stanza,
|
origin.send(st.error_reply(stanza,
|
||||||
"cancel",
|
"cancel",
|
||||||
"conflict",
|
"conflict",
|
||||||
@ -470,8 +512,8 @@ module:hook("iq-set/host/"..mix_core_xmlns..":create", function(event)
|
|||||||
-- Create adhoc channel
|
-- Create adhoc channel
|
||||||
while (true) do
|
while (true) do
|
||||||
node = id.short();
|
node = id.short();
|
||||||
local _, channel = get_channel(string.format("%s@%s", node, host));
|
local i, _ = get_channel(string.format("%s@%s", node, host));
|
||||||
if channel == nil then
|
if i == -1 then
|
||||||
break;
|
break;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -499,11 +541,15 @@ module:hook("iq-set/host/"..mix_core_xmlns..":destroy", function(event)
|
|||||||
origin.send(channel_not_found(stanza));
|
origin.send(channel_not_found(stanza));
|
||||||
return true;
|
return true;
|
||||||
end
|
end
|
||||||
-- TODO: Check permissions: can_create_channels and maybe compare to the contact JIDs
|
|
||||||
|
-- TODO: Maybe make this configurable
|
||||||
|
if helpers.find_str(channel.contacts, from) == -1 then
|
||||||
|
origin.send(st.error_reply(stanza, "cancel", "forbidden"));
|
||||||
|
return true;
|
||||||
|
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({ "urn:xmpp:mix:nodes:participants", "urn:xmpp:mix:nodes:info" }) do
|
for _, pep_node in pairs({ mix_node_participants, mix_node_info, mix_node_messages }) do
|
||||||
srv:delete(pep_node, true);
|
srv:delete(pep_node, true);
|
||||||
end
|
end
|
||||||
table.remove(channels, i);
|
table.remove(channels, i);
|
||||||
@ -516,12 +562,10 @@ module:hook("iq-set/host/"..mix_core_xmlns..":destroy", function(event)
|
|||||||
end);
|
end);
|
||||||
|
|
||||||
module:hook("message/bare", function(event)
|
module:hook("message/bare", function(event)
|
||||||
module:log("debug", "MIX message detected");
|
module:log("debug", "MIX message received");
|
||||||
local stanza, origin = event.stanza, event.origin;
|
local stanza, origin = event.stanza, event.origin;
|
||||||
|
|
||||||
if stanza.attr.type ~= "groupchat" then
|
if stanza.attr.type ~= "groupchat" then
|
||||||
-- TODO: Is this correct?
|
origin.send(st.error_reply(stanza, "modify", "bad-request", "Non-groupchat message"));
|
||||||
origin.send(st.error_reply(stanza, "cancel", "bad-request", "Non-groupchat message"));
|
|
||||||
return true;
|
return true;
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -531,25 +575,35 @@ module:hook("message/bare", function(event)
|
|||||||
origin.send(channel_not_found(stanza));
|
origin.send(channel_not_found(stanza));
|
||||||
return true;
|
return true;
|
||||||
end
|
end
|
||||||
|
|
||||||
local participant_index = find_participant(channel.participants, from);
|
local j, participant = channel:find_participant(from);
|
||||||
if participant_index == -1 then
|
if j == -1 then
|
||||||
origin.send(st.error_reply(stanza, "cancel", "forbidden", "Not a participant"));
|
origin.send(st.error_reply(stanza, "cancel", "forbidden", "Not a participant"));
|
||||||
return true;
|
return true;
|
||||||
end
|
end
|
||||||
local participant = channel.participants[participant_index];
|
|
||||||
|
|
||||||
local msg = st.clone(stanza);
|
local msg = st.clone(stanza);
|
||||||
msg:add_child(st.stanza("mix", { xmlns = mix_core_xmlns })
|
msg:add_child(st.stanza("mix", { xmlns = mix_core_xmlns })
|
||||||
:tag("nick"):text(participant.nick):up()
|
:tag("nick"):text(participant.nick):up()
|
||||||
:tag("jid"):text(participant.jid):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 = channel.jid;
|
||||||
|
message_archive:append(stanza.attr.to, mam_id, msg, time.now());
|
||||||
|
module:log("debug", "Message put into MAM archive!");
|
||||||
msg.attr.from = channel.jid.."/"..channel:get_spid(from);
|
msg.attr.from = channel.jid.."/"..channel:get_spid(from);
|
||||||
|
|
||||||
for _, p in pairs(channel.participants) do
|
for _, p in pairs(channel.participants) do
|
||||||
-- TODO: Add message to the MAM and return its ID
|
-- Only users who subscribed to the messages node should receive
|
||||||
local tmp = st.clone(msg);
|
-- messages
|
||||||
tmp.attr.to = p.jid;
|
if channel:is_subscribed(p.jid, mix_node_messages) then
|
||||||
module:send(tmp);
|
local tmp = st.clone(msg);
|
||||||
module:log("debug", "Message from %s sent to %s", participant.jid, p.jid);
|
tmp.attr.to = p.jid;
|
||||||
|
module:send(tmp);
|
||||||
|
end
|
||||||
end
|
end
|
||||||
return true;
|
return true;
|
||||||
end);
|
end);
|
||||||
|
Loading…
Reference in New Issue
Block a user