+ self:add_item("feature", xmlns);
+end
+function api:add_identity(category, type, name)
+ self:add_item("identity", {category = category, type = type, name = name});
+end
+
+local event_hook = function(host, mod_name, event_name, ...)
+ if type((...)) == "table" and (...).host and (...).host ~= host then return; end
+ for handler in pairs(event_hooks:get(host, mod_name, event_name) or NULL) do
+ handler(...);
+ end
+end;
+function api:add_event_hook(name, handler)
+ if not hooked:get(self.host, self.name, name) then
+ eventmanager.add_event_hook(name, function(...) event_hook(self.host, self.name, name, ...); end);
+ hooked:set(self.host, self.name, name, true);
+ end
+ event_hooks:set(self.host, self.name, name, handler, true);
+end
+
+function api:fire_event(...)
+ return (hosts[self.host] or prosody).events.fire_event(...);
+end
+
+function api:hook(event, handler, priority)
+ hooks:set(self.host, self.name, event, handler, true);
+ (hosts[self.host] or prosody).events.add_handler(event, handler, priority);
+end
+
+function api:hook_stanza(xmlns, name, handler, priority)
+ if not handler and type(name) == "function" then
+ -- If only 2 options then they specified no xmlns
+ xmlns, name, handler, priority = nil, xmlns, name, handler;
+ elseif not (handler and name) then
+ self:log("warn", "Error: Insufficient parameters to module:hook_stanza()");
+ return;
+ end
+ return api.hook(self, "stanza/"..(xmlns and (xmlns..":") or "")..name, function (data) return handler(data.origin, data.stanza, data); end, priority);
+end
+
+function api:require(lib)
+ local f, n = pluginloader.load_code(self.name, lib..".lib.lua");
+ if not f then
+ f, n = pluginloader.load_code(lib, lib..".lib.lua");
+ end
+ if not f then error("Failed to load plugin library '"..lib.."', error: "..n); end -- FIXME better error message
+ setfenv(f, setmetatable({ module = self }, { __index = _G }));
+ return f();
+end
+
+function api:get_option(name, default_value)
+ local value = config.get(self.host, self.name, name);
+ if value == nil then
+ value = config.get(self.host, "core", name);
+ if value == nil then
+ value = default_value;
+ end
+ end
+ return value;
+end
+
+function api:get_option_string(...)
+ local value = self:get_option(...);
+ if type(value) == "table" then
+ if #value > 1 then
+ self:log("error", "Config option '%s' does not take a list, using just the first item", name);
+ end
+ value = value[1];
+ end
+ if value == nil then
+ return nil;
+ end
+ return tostring(value);
+end
+
+function api:get_option_number(name, ...)
+ local value = self:get_option(name, ...);
+ if type(value) == "table" then
+ if #value > 1 then
+ self:log("error", "Config option '%s' does not take a list, using just the first item", name);
+ end
+ value = value[1];
+ end
+ local ret = tonumber(value);
+ if value ~= nil and ret == nil then
+ self:log("error", "Config option '%s' not understood, expecting a number", name);
+ end
+ return ret;
+end
+
+function api:get_option_boolean(name, ...)
+ local value = self:get_option(name, ...);
+ if type(value) == "table" then
+ if #value > 1 then
+ self:log("error", "Config option '%s' does not take a list, using just the first item", name);
+ end
+ value = value[1];
+ end
+ if value == nil then
+ return nil;
+ end
+ local ret = value == true or value == "true" or value == 1 or nil;
+ if ret == nil then
+ ret = (value == false or value == "false" or value == 0);
+ if ret then
+ ret = false;
+ else
+ ret = nil;
+ end
+ end
+ if ret == nil then
+ self:log("error", "Config option '%s' not understood, expecting true/false", name);
+ end
+ return ret;
+end
+
+function api:get_option_array(name, ...)
+ local value = self:get_option(name, ...);
+
+ if value == nil then
+ return nil;
+ end
+
+ if type(value) ~= "table" then
+ return array{ value }; -- Assume any non-list is a single-item list
+ end
+
+ return array():append(value); -- Clone
+end
+
+function api:get_option_set(name, ...)
+ local value = self:get_option_array(name, ...);
+
+ if value == nil then
+ return nil;
+ end
+
+ return set.new(value);
+end
+
+local t_remove = _G.table.remove;
+local module_items = multitable_new();
+function api:add_item(key, value)
+ self.items = self.items or {};
+ self.items[key] = self.items[key] or {};
+ t_insert(self.items[key], value);
+ self:fire_event("item-added/"..key, {source = self, item = value});
+end
+function api:remove_item(key, value)
+ local t = self.items and self.items[key] or NULL;
+ for i = #t,1,-1 do
+ if t[i] == value then
+ t_remove(self.items[key], i);
+ self:fire_event("item-removed/"..key, {source = self, item = value});
+ return value;