diff mod_muc_log_http/muc_log_http/mod_muc_log_http.lua @ 105:cef943f0a551

mod_muc_log_http: themified ...
author Thilo Cestonaro <thilo@cestona.ro>
date Tue, 08 Dec 2009 21:12:40 +0100
parents mod_muc_log_http/mod_muc_log_http.lua@dc0fe152cadc
children 0e4e000e3de9
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_muc_log_http/muc_log_http/mod_muc_log_http.lua	Tue Dec 08 21:12:40 2009 +0100
@@ -0,0 +1,734 @@
+-- Copyright (C) 2009 Thilo Cestonaro
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local prosody = prosody;
+local tabSort = table.sort;
+local tonumber = _G.tonumber;
+local tostring = _G.tostring;
+local strformat = string.format;
+local splitJid = require "util.jid".split;
+local config_get = require "core.configmanager".get;
+local httpserver = require "net.httpserver";
+local datamanager = require "util.datamanager";
+local data_load, data_getpath = datamanager.load, datamanager.getpath;
+local datastore = "muc_log";
+local urlBase = "muc_log";
+local muc_hosts = {};
+local config = nil;
+local tostring = _G.tostring;
+local tonumber = _G.tonumber;
+local os_date, os_time = os.date, os.time;
+local str_format = string.format;
+local io_open = io.open;
+local themesParent = (CFG_PLUGINDIR or "./plugins/") .. "muc_log_http/themes";
+
+local lom = require "lxp.lom";
+
+--[[ LuaFileSystem 
+* URL: http://www.keplerproject.org/luafilesystem/index.html
+* Install: luarocks install luafilesystem
+* ]]
+local lfs = require "lfs";
+
+
+--[[
+* Default templates for the html output.
+]]--
+local html = {};
+local theme = "default";
+
+local function checkDatastorePathExists(node, host, today, create)
+	create = create or false;
+	local path = data_getpath(node, host, datastore, "dat", true);
+	path = path:gsub("/[^/]*$", "");
+
+	-- check existance
+	local attributes, err = lfs.attributes(path);
+	if attributes == nil or attributes.mode ~= "directory" then
+		module:log("warn", "muc_log folder isn't a folder: %s", path);
+		return false;
+	end
+	
+	attributes, err = lfs.attributes(path .. "/" .. today);
+	if attributes == nil then
+		if create then
+			return lfs.mkdir(path .. "/" .. today);
+		else
+			return false;
+		end
+	elseif attributes.mode == "directory" then
+		return true;
+	end
+	return false;
+end
+
+function createDoc(body)
+	if body then
+		return html.doc:gsub("###BODY_STUFF###", body);
+	end
+end
+
+local function htmlEscape(t)
+	t = t:gsub("<", "&lt;");
+	t = t:gsub(">", "&gt;");
+	t = t:gsub("(http://[%a%d@%.:/&%?=%-_#]+)", [[<a href="%1">%1</a>]]);
+	t = t:gsub("\n", "<br />");
+	return t;
+end
+
+function splitUrl(url)
+	local tmp = url:sub(string.len("/muc_log/") + 1);
+	local day = nil;
+	local room = nil;
+	local component = nil;
+	local at = nil;
+	local slash = nil;
+	local slash2 = nil;
+	
+	slash = tmp:find("/");
+	if slash then
+	 	component = tmp:sub(1, slash - 1);
+		if tmp:len() > slash then
+			room = tmp:sub(slash + 1);
+			slash = room:find("/");
+			if slash then
+				tmp = room;
+				room = tmp:sub(1, slash - 1);
+				if tmp:len() > slash then
+					day = tmp:sub(slash + 1);
+					slash = day:find("/");
+					if slash then
+						day = day:sub(1, slash - 1);
+					end
+				end
+			end
+		end
+	end
+	
+	return room, component, day;
+end
+
+local function generateComponentListSiteContent()
+	local components = "";
+	for component,muc_host in pairs(muc_hosts or {}) do
+		components = components .. html.components.bit:gsub("###COMPONENT###", component);
+	end
+	if components ~= "" then
+		return html.components.body:gsub("###COMPONENTS_STUFF###", components);
+	end
+end
+
+local function generateRoomListSiteContent(component)
+	local rooms = "";
+	if prosody.hosts[component] and prosody.hosts[component].muc ~= nil then
+		for jid, room in pairs(prosody.hosts[component].muc.rooms) do
+			local node = splitJid(jid);
+			if not room._data.hidden and node then
+				rooms = rooms .. html.rooms.bit:gsub("###ROOM###", node):gsub("###COMPONENT###", component);
+			end
+		end
+		if rooms ~= "" then
+			return html.rooms.body:gsub("###ROOMS_STUFF###", rooms):gsub("###COMPONENT###", component);
+		end
+	end
+end
+
+-- Calendar stuff
+local function getDaysForMonth(month, year)
+    local daysCount = 30;
+    local leapyear = false;
+
+    if year%4 == 0 and year%100 == 0 then
+        if year%400 == 0 then
+            leapyear = true;
+        else
+            leapyear = false; -- turn of the century but not a leapyear
+        end
+    elseif year%4 == 0 then
+        leapyear = true;
+    end
+
+    if month == 2 and leapyear then
+        daysCount = 29;
+    elseif month == 2 and not leapyear then
+        daysCount = 28;
+    elseif  month < 8 and month%2 == 1 or
+            month >= 8 and month%2 == 0
+    then
+        daysCount = 31;
+    end
+    return daysCount;
+end
+
+local function createMonth(month, year, dayCallback)
+    local htmlStr = html.month.header;
+    local days = getDaysForMonth(month, year);
+    local time = os_time{year=year, month=month, day=1};
+    local dow = tostring(os_date("%a", time))
+    local title = tostring(os_date("%B", time));
+    local weekDays = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
+    local weekDay = 0;
+    local weeks = 1;
+	local logAvailableForMinimumOneDay = false;
+
+    local weekDaysHtml = "";
+    for _, tmp in ipairs(weekDays) do
+        weekDaysHtml = weekDaysHtml .. html.month.weekDay:gsub("###DAY###", tmp) .. "\n";
+    end
+
+    htmlStr = htmlStr:gsub("###TITLE###", title):gsub("###WEEKDAYS###", weekDaysHtml);
+
+    for i = 1, 31 do
+        weekDay = weekDay + 1;
+        if weekDay == 1 then htmlStr = htmlStr .. "<tr>\n"; end
+        if i == 1 then
+            for _, tmp in ipairs(weekDays) do
+                if dow ~= tmp then
+                    htmlStr = htmlStr .. html.month.emptyDay .. "\n";
+                    weekDay = weekDay + 1;
+                else
+                    break;
+                end
+            end
+        end
+        if i < days + 1 then
+            local tmp = tostring("<font color='#DDDDDD'>"..tostring(i).."</font>");
+            if dayCallback ~= nil and dayCallback.callback ~= nil then
+                tmp = dayCallback.callback(dayCallback.path, i, month, year);
+            end
+			if tmp == nil then
+            	tmp = tostring("<font color='#DDDDDD'>"..tostring(i).."</font>");
+			else
+				logAvailableForMinimumOneDay = true;
+			end
+            htmlStr = htmlStr .. html.month.day:gsub("###DAY###", tmp) .. "\n";
+        end
+
+        if i >= days then
+            break;
+        end
+
+        if weekDay == 7 then
+            weekDay = 0;
+            weeks = weeks + 1;
+            htmlStr = htmlStr .. "</tr>\n";
+        end
+    end
+
+    if weekDay + 1 < 8 or weeks < 6 then
+        weekDay = weekDay + 1;
+        if weekDay > 7 then
+            weekDay = 1;
+        end
+        if weekDay == 1 then
+            weeks = weeks + 1;
+        end
+        for y = weeks, 6 do
+            if weekDay == 1 then
+                htmlStr = htmlStr .. "<tr>\n";
+            end
+            for i = weekDay, 7 do
+                htmlStr = htmlStr .. html.month.emptyDay .. "\n";
+            end
+            weekDay = 1
+            htmlStr = htmlStr .. "</tr>\n";
+        end
+    end
+    htmlStr = htmlStr .. html.month.footer;
+	if logAvailableForMinimumOneDay then
+    	return htmlStr;
+	end
+end
+
+local function createYear(year, dayCallback)
+	local year = year;
+	local tmp;
+	if tonumber(year) <= 99 then
+		year = year + 2000;
+	end
+	local htmlStr = "<div name='yearDiv' style='padding: 40px; text-align: center;'>" .. html.year.title:gsub("###YEAR###", tostring(year));
+    for i=1, 12 do
+		tmp = createMonth(i, year, dayCallback);
+		if tmp then
+        	htmlStr = htmlStr .. "<div style='float: left; padding: 5px;'>\n" .. tmp .. "</div>\n";
+		end
+    end
+	return htmlStr .. "</div><br style='clear:both;'/> \n";
+end
+
+local function perDayCallback(path, day, month, year)
+	local year = year;
+	if year > 2000 then
+		year = year - 2000;
+	end
+	local bareDay = str_format("%.02d%.02d%.02d", year, month, day);
+	local attributes, err = lfs.attributes(path.."/"..bareDay)
+	if attributes ~= nil and attributes.mode == "directory" then
+		local s = html.days.bit;
+		s = s:gsub("###BARE_DAY###", bareDay);
+		s = s:gsub("###DAY###", day);
+		return s;
+	end
+	return;
+end
+
+local function generateDayListSiteContentByRoom(bareRoomJid)
+	local days = "";
+	local arrDays = {};
+	local tmp;
+	local node, host, resource = splitJid(bareRoomJid);
+	local path = data_getpath(node, host, datastore);
+	local room = nil;
+	local attributes = nil;
+	
+	path = path:gsub("/[^/]*$", "");
+	attributes = lfs.attributes(path);
+	if muc_hosts ~= nil and muc_hosts[host] and prosody.hosts[host] ~= nil and prosody.hosts[host].muc ~= nil and prosody.hosts[host].muc.rooms[bareRoomJid] ~= nil then
+		room = prosody.hosts[host].muc.rooms[bareRoomJid];
+		if room._data.hidden then
+			room = nil
+		end
+	end
+	if attributes ~= nil and room ~= nil then
+		local alreadyDoneYears = {};
+		for file in lfs.dir(path) do
+			local year, month, day = file:match("^(%d%d)(%d%d)(%d%d)");
+			if year ~= nil and alreadyDoneYears[year] == nil then
+				days = days .. createYear(year, {callback=perDayCallback, path=path});
+				alreadyDoneYears[year] = true;
+			end
+		end
+	end
+	
+	if days ~= "" then
+		tmp = html.days.body:gsub("###DAYS_STUFF###", days);
+		return tmp:gsub("###JID###", bareRoomJid);
+	end
+end
+
+local function parseIqStanza(stanza, timeStuff, nick)
+	local text = nil;
+	local victim = nil;
+	if(stanza.attr.type == "set") then
+		for _,tag in ipairs(stanza) do
+			if tag.tag == "query" then
+				for _,item in ipairs(tag) do
+					if item.tag == "item" and item.attr.nick ~= nil and tostring(item.attr.role) == 'none' then
+						victim = item.attr.nick;
+						for _,reason in ipairs(item) do
+							if reason.tag == "reason" then
+								text = reason[1];
+								break;
+							end
+						end
+						break;
+					end 
+				end
+				break;
+			end
+		end
+		if victim ~= nil then
+			if text ~= nil then	
+				text = html.day.reason:gsub("###REASON###", htmlEscape(text));
+			else
+				text = "";
+			end	
+			return html.day.kick:gsub("###TIME_STUFF###", timeStuff):gsub("###VICTIM###", victim):gsub("###REASON_STUFF###", text);
+		end
+	end
+	return;
+end
+
+local function parsePresenceStanza(stanza, timeStuff, nick)
+	local ret = "";
+	local showJoin = "block"
+	
+	if config and not config.showJoin then
+		showJoin = "none";
+	end
+
+	if stanza.attr.type == nil then
+		local showStatus = "block"
+		if config and not config.showStatus then
+			showStatus = "none";
+		end
+		local show, status = nil, "";
+		local alreadyJoined = false;
+		for _, tag in ipairs(stanza) do
+			if tag.tag == "alreadyJoined" then
+				alreadyJoined = true;
+			elseif tag.tag == "show" then
+				show = tag[1];
+			elseif tag.tag == "status" then
+				status = tag[1];
+			end
+		end
+		if alreadyJoined == true then
+			if show == nil then
+				show = "online";
+			end
+			ret = html.day.presence.statusChange:gsub("###TIME_STUFF###", timeStuff);
+			if status ~= "" then
+				status = html.day.presence.statusText:gsub("###STATUS###", htmlEscape(status));
+			end
+			ret = ret:gsub("###SHOW###", show):gsub("###NICK###", nick):gsub("###SHOWHIDE###", showStatus):gsub("###STATUS_STUFF###", status);
+		else
+			ret = html.day.presence.join:gsub("###TIME_STUFF###", timeStuff):gsub("###SHOWHIDE###", showJoin):gsub("###NICK###", nick);
+		end
+	elseif stanza.attr.type ~= nil and stanza.attr.type == "unavailable" then
+
+		ret = html.day.presence.leave:gsub("###TIME_STUFF###", timeStuff):gsub("###SHOWHIDE###", showJoin):gsub("###NICK###", nick);
+	end
+	return ret;
+end
+
+local function parseMessageStanza(stanza, timeStuff, nick)
+	local body, title, ret = nil, nil, "";
+	
+	for _,tag in ipairs(stanza) do
+		if tag.tag == "body" then
+			body = tag[1];
+			if nick ~= nil then
+				break;
+			end
+		elseif tag.tag == "nick" and nick == nil then
+			nick = htmlEscape(tag[1]);
+			if body ~= nil or title ~= nil then
+				break;
+			end
+		elseif tag.tag == "subject" then
+			title = tag[1];
+			if nick ~= nil then
+				break;
+			end
+		end
+	end
+	if nick ~= nil and body ~= nil then
+		body = htmlEscape(body);
+		local me = body:find("^/me");
+		local template = "";
+		if not me then			
+			template = html.day.message;
+		else
+			template = html.day.messageMe;
+			body = body:gsub("^/me ", "");
+		end
+		ret = template:gsub("###TIME_STUFF###", timeStuff):gsub("###NICK###", nick):gsub("###MSG###", body);
+	elseif nick ~= nil and title ~= nil then
+		title = htmlEscape(title);
+		ret = html.day.titleChange:gsub("###TIME_STUFF###", timeStuff):gsub("###NICK###", nick):gsub("###TITLE###", title);	
+	end
+	return ret;
+end
+
+local function incrementDay(bare_day)
+	local year, month, day = bare_day:match("^(%d%d)(%d%d)(%d%d)");
+	local leapyear = false;
+	module:log("debug", tostring(day).."/"..tostring(month).."/"..tostring(year))
+	
+	day = tonumber(day);
+	month = tonumber(month);
+	year = tonumber(year);
+	
+	if year%4 == 0 and year%100 == 0 then
+		if year%400 == 0 then
+			leapyear = true;
+		else
+			leapyear = false; -- turn of the century but not a leapyear
+		end
+	elseif year%4 == 0 then
+		leapyear = true;
+	end	
+	
+	if (month == 2 and leapyear and day + 1 > 29) or
+	   (month == 2 and not leapyear and day + 1 > 28) or
+	   (month < 8 and month%2 == 1 and day + 1 > 31) or
+	   (month < 8 and month%2 == 0 and day + 1 > 30) or
+	   (month >= 8 and month%2 == 0 and day + 1 > 31) or
+	   (month >= 8 and month%2 == 1 and day + 1 > 30)
+	then
+		if month + 1 > 12 then
+			year = year + 1;
+		else
+			month = month + 1;
+		end
+	else
+		day = day + 1;
+	end
+	return strformat("%.02d%.02d%.02d", year, month, day);
+end
+
+local function findNextDay(bareRoomJid, bare_day)
+	local node, host, resource = splitJid(bareRoomJid);
+	local day = incrementDay(bare_day);
+	local max_trys = 7;
+	
+	module:log("debug", day);	
+	while(not checkDatastorePathExists(node, host, day, false)) do
+		max_trys = max_trys - 1;
+		if max_trys == 0 then
+			break;
+		end
+		day = incrementDay(day);
+	end
+	if max_trys == 0 then
+		return nil;
+	else
+		return day;
+	end
+end
+
+local function decrementDay(bare_day)
+	local year, month, day = bare_day:match("^(%d%d)(%d%d)(%d%d)");
+	module:log("debug", tostring(day).."/"..tostring(month).."/"..tostring(year))
+	day = tonumber(day);
+	month = tonumber(month);
+	year = tonumber(year);
+	
+	if day - 1 == 0 then
+		if month - 1 == 0 then
+			year = year - 1;
+		else
+			month = month - 1;
+		end
+	else
+		day = day - 1;
+	end
+	return strformat("%.02d%.02d%.02d", year, month, day);
+end
+
+local function findPreviousDay(bareRoomJid, bare_day)
+	local node, host, resource = splitJid(bareRoomJid);
+	local day = decrementDay(bare_day);
+	local max_trys = 7;
+	module:log("debug", day);
+	while(not checkDatastorePathExists(node, host, day, false)) do
+		max_trys = max_trys - 1;
+		if max_trys == 0 then
+			break;
+		end
+		day = decrementDay(day);
+	end
+	if max_trys == 0 then
+		return nil;
+	else
+		return day;
+	end
+end
+
+local function parseDay(bareRoomJid, roomSubject, bare_day)
+	local ret = "";
+	local year;
+	local month;
+	local day;
+	local tmp;
+	local node, host, resource = splitJid(bareRoomJid);
+	local year, month, day = bare_day:match("^(%d%d)(%d%d)(%d%d)");
+	local previousDay = findPreviousDay(bareRoomJid, bare_day);
+	local nextDay = findNextDay(bareRoomJid, bare_day);
+	
+	if bare_day ~= nil then
+		local data = data_load(node, host, datastore .. "/" .. bare_day);
+		if data ~= nil then
+			for i=1, #data, 1 do
+				local stanza = lom.parse(data[i]);
+				if stanza ~= nil and stanza.attr ~= nil and stanza.attr.time ~= nil then
+					local timeStuff = html.day.time:gsub("###TIME###", stanza.attr.time);
+					if stanza[1] ~= nil then
+						local nick;
+						local tmp;
+						
+						-- grep nick from "from" resource
+						if stanza[1].attr.from ~= nil then -- presence and messages
+							nick = htmlEscape(stanza[1].attr.from:match("/(.+)$"));
+						elseif stanza[1].attr.to ~= nil then -- iq
+							nick = htmlEscape(stanza[1].attr.to:match("/(.+)$"));
+						end
+						
+						if stanza[1].tag == "presence" and nick ~= nil then
+							tmp = parsePresenceStanza(stanza[1], timeStuff, nick);
+						elseif stanza[1].tag == "message" then
+							tmp = parseMessageStanza(stanza[1], timeStuff, nick);
+						elseif stanza[1].tag == "iq" then
+							tmp = parseIqStanza(stanza[1], timeStuff, nick);
+						else
+							module:log("info", "unknown stanza subtag in log found. room: %s; day: %s", bareRoomJid, year .. "/" .. month .. "/" .. day);
+						end
+						if tmp ~= nil then
+							ret = ret .. tmp
+							tmp = nil;
+						end
+					end
+				end
+			end
+		end
+		if ret ~= "" then
+			if nextDay then
+				nextDay = html.day.dayLink:gsub("###DAY###", nextDay):gsub("###TEXT###", "next day &gt;&gt;")
+			end
+			if previousDay then
+				previousDay = html.day.dayLink:gsub("###DAY###", previousDay):gsub("###TEXT###", "&lt;&lt; previous day");
+			end
+			tmp = html.day.body:gsub("###DAY_STUFF###", ret):gsub("###JID###", bareRoomJid);
+			tmp = tmp:gsub("###YEAR###", year):gsub("###MONTH###", month):gsub("###DAY###", day);
+			tmp = tmp:gsub("###TITLE_STUFF###", html.day.title:gsub("###TITLE###", roomSubject));
+			tmp = tmp:gsub("###STATUS_CHECKED###", config.showStatus and "checked='checked'" or "");
+			tmp = tmp:gsub("###JOIN_CHECKED###", config.showJoin and "checked='checked'" or "");
+			tmp = tmp:gsub("###NEXT_LINK###", nextDay or "");
+			tmp = tmp:gsub("###PREVIOUS_LINK###", previousDay or "");
+			
+			return tmp;
+		end
+	end
+end
+
+function handle_request(method, body, request)
+	local node, host, day = splitUrl(request.url.path);
+	
+	if muc_hosts ~= nil then
+	 	if node ~= nil and host ~= nil then
+			local bare = node .. "@" .. host;
+			if prosody.hosts[host] ~= nil and prosody.hosts[host].muc ~= nil then
+				if prosody.hosts[host].muc.rooms[bare] ~= nil then
+					local room = prosody.hosts[host].muc.rooms[bare];
+					if day == nil then
+						return createDoc(generateDayListSiteContentByRoom(bare));
+					else
+						local subject = ""
+						if room._data ~= nil and room._data.subject ~= nil then
+							subject = room._data.subject;
+						end
+						return createDoc(parseDay(bare, subject, day));
+					end
+				else
+					return createDoc(generateRoomListSiteContent(host));
+				end
+			else
+				return createDoc(generateComponentListSiteContent());
+			end
+		elseif host ~= nil then
+			return createDoc(generateRoomListSiteContent(host));
+		else
+			return createDoc(generateComponentListSiteContent());
+		end
+	end
+	return;
+end
+
+-- Compatibility: Lua-5.1
+function split(str, pat)
+   local t = {}  -- NOTE: use {n = 0} in Lua-5.0
+   local fpat = "(.-)" .. pat
+   local last_end = 1
+   local s, e, cap = str:find(fpat, 1)
+   while s do
+      if s ~= 1 or cap ~= "" then
+	 table.insert(t,cap)
+      end
+      last_end = e+1
+      s, e, cap = str:find(fpat, last_end)
+   end
+   if last_end <= #str then
+      cap = str:sub(last_end)
+      table.insert(t, cap)
+   end
+   return t
+end
+
+local function assign(arr, content)
+	local tmp = html;
+	local idx = nil;
+	for _,i in ipairs(arr) do
+		if idx ~= nil then
+			if tmp[idx] == nil then
+				tmp[idx] = {};
+			end
+			tmp = tmp[idx];
+		end
+		idx = i;
+	end
+	tmp[idx] = content;
+end
+
+local function readFile(filepath)
+	local f = assert(io_open(filepath, "r"));
+	local t = f:read("*all");
+	f:close()
+	return t;
+end
+
+local function loadTheme(path)
+	local iter = lfs.dir(path);
+    for file in iter do
+        if file ~= "." and file ~= ".." then
+			module:log("debug", "opening theme file: " .. file);
+			local tmp = split(file:gsub("\.html$", ""), "_");
+			local content = readFile(path .. "/" .. file);
+			assign(tmp, content);
+		end
+	end
+	return true;
+end
+
+function module.load()
+	config = config_get("*", "core", "muc_log_http") or {};
+	if config.showStatus == nil then
+		config.showStatus = true;
+	end
+	if config.showJoin == nil then
+		config.showJoin = true;
+	end
+
+	theme = config.theme or "default";
+	local themePath = themesParent .. "/" .. tostring(theme);
+	local attributes, err = lfs.attributes(themePath);
+	if attributes == nil or attributes.mode ~= "directory" then
+		module:log("error", "Theme folder of theme \"".. tostring(theme) .. "\" isn't existing. expected Path: " .. themePath);
+		return false;
+	end
+	
+	-- module:log("debug", (require "util.serialization").serialize(html));
+	if(not loadTheme(themePath)) then
+		module:log("error", "Theme \"".. tostring(theme) .. "\" is missing something.");
+		return false;
+	end
+	-- module:log("debug", (require "util.serialization").serialize(html));
+
+	httpserver.new_from_config({ config.http_port or true }, handle_request, { base = urlBase, ssl = false, port = 5290 });
+	
+	for jid, host in pairs(prosody.hosts) do
+		if host.muc then
+			local enabledModules = config_get(jid, "core", "modules_enabled");
+			if enabledModules then
+				for _,mod in ipairs(enabledModules) do
+					if(mod == "muc_log") then
+						module:log("debug", "component: %s", tostring(jid));
+						muc_hosts[jid] = true;
+						break;
+					end
+				end
+			end
+		end
+	end
+	module:log("debug", "loaded mod_muc_log_http");
+end
+
+function module.unload()
+	muc_hosts = nil;
+	module:log("debug", "unloaded mod_muc_log_http");
+end
+
+module:add_event_hook("component-activated", function(component, config)
+	if config.core and config.core.modules_enabled then
+		for _,mod in ipairs(config.core.modules_enabled) do
+			if(mod == "muc_log") then
+				module:log("debug", "component: %s", tostring(component));
+				muc_hosts[component] = true;
+				break;
+			end
+		end
+	end
+end);