view mod_ircd/mod_ircd.lua @ 234:abcb59ab355c

Add new motd_sequential module. This module lets you define numbered messages shown to each user in order, but only once per user, and persistent across server restarts. Useful for notifying users of added features and changes in an incremental fashion.
author Jeff Mitchell <jeffrey.mitchell@gmail.com>
date Wed, 04 Aug 2010 22:29:51 +0000
parents 16b76c7b6316
children 24582ea48471
line wrap: on
line source

local irc_listener = { default_port = 6667, default_mode = "*l" };

local sessions = {};
local commands = {};

local nicks = {};

local st = require "util.stanza";

local conference_server = module:get_option("conference_server") or "conference.jabber.org";

local function irc_close_session(session)
	session.conn:close();
end

function irc_listener.onincoming(conn, data)
	local session = sessions[conn];
	if not session then
		session = { conn = conn, host = module.host, reset_stream = function () end,
			close = irc_close_session, log = logger.init("irc"..(conn.id or "1")),
			roster = {} };
		sessions[conn] = session;
		function session.data(data)
			module:log("debug", "Received: %s", data);
			local command, args = data:match("^%s*([^ ]+) *(.*)%s*$");
			if not command then
				module:log("warn", "Invalid command: %s", data);
				return;
			end
			command = command:upper();
			module:log("debug", "Received command: %s", command);
			if commands[command] then
				local ret = commands[command](session, args);
				if ret then
					session.send(ret.."\r\n");
				end
			end
		end
		function session.send(data)
			module:log("debug", "sending: %s", data);
			return conn:write(data.."\r\n");
		end
	end
	if data then
		session.data(data);
	end
end

function irc_listener.ondisconnect(conn, error)
	module:log("debug", "Client disconnected");
	sessions[conn] = nil;
end

function commands.NICK(session, nick)
	nick = nick:match("^[%w_]+");
	if nicks[nick] then
		session.send(":"..session.host.." 433 * The nickname "..nick.." is already in use");
		return;
	end
	nicks[nick] = session;
	session.nick = nick;
	session.full_jid = nick.."@"..module.host.."/ircd";
	session.type = "c2s";
	module:log("debug", "Client bound to %s", session.full_jid);
	session.send(":"..session.host.." 001 "..session.nick.." :Welcome to XMPP via the "..session.host.." gateway "..session.nick);
end

local joined_mucs = {};
function commands.JOIN(session, channel)
	if not joined_mucs[channel] then
		joined_mucs[channel] = { occupants = {}, sessions = {} };
	end
	joined_mucs[channel].sessions[session] = true;
	local join_stanza = st.presence({ from = session.full_jid, to = channel:gsub("^#", "").."@"..conference_server.."/"..session.nick });
	core_process_stanza(session, join_stanza);
	session.send(":"..session.nick.." JOIN :"..channel);
	session.send(":"..session.host.." 332 "..session.nick.." "..channel.." :Connection in progress...");
        local nicks = session.nick;
        for nick in pairs(joined_mucs[channel].occupants) do
            nicks = nicks.." "..nick;
        end
        session.send(":"..session.host.." 353 "..session.nick.." = "..channel.." :"..nicks);
	session.send(":"..session.host.." 366 "..session.nick.." "..channel.." :End of /NAMES list.");
end

function commands.PART(session, channel)
	local channel, part_message = channel:match("^([^:]+):?(.*)$");
	channel = channel:match("^([%S]*)");
	core_process_stanza(session, st.presence{ type = "unavailable", from = session.full_jid,
		to = channel:gsub("^#", "").."@"..conference_server.."/"..session.nick }:tag("status"):text(part_message));
	session.send(":"..session.nick.." PART :"..channel);
end

function commands.PRIVMSG(session, message)
	local who, message = message:match("^(%S+) :(.+)$");
	if joined_mucs[who] then
		core_process_stanza(session, st.message{to=who:gsub("^#", "").."@"..conference_server, type="groupchat"}:tag("body"):text(message));
	end
end

function commands.PING(session, server)
	session.send(":"..session.host..": PONG "..server);
end

function commands.WHO(session, channel)
	if joined_mucs[channel] then
		for nick in pairs(joined_mucs[channel].occupants) do
			--n=MattJ 91.85.191.50 irc.freenode.net MattJ H :0 Matthew Wild
			session.send(":"..session.host.." 352 "..session.nick.." "..channel.." "..nick.." "..nick.." "..session.host.." "..nick.." H :0 "..nick);
		end
		session.send(":"..session.host.." 315 "..session.nick.." "..channel.. " :End of /WHO list");
	end
end

function commands.MODE(session, channel)
	session.send(":"..session.host.." 324 "..session.nick.." "..channel.." +J"); 
end

--- Component (handle stanzas from the server for IRC clients)
function irc_component(origin, stanza)
	local from, from_bare = stanza.attr.from, jid.bare(stanza.attr.from);
	local from_node = "#"..jid.split(stanza.attr.from);
	
	if joined_mucs[from_node] and from_bare == from then
		-- From room itself
		local joined_muc = joined_mucs[from_node];
		if stanza.name == "message" then
			local subject = stanza:get_child("subject");
			subject = subject and (subject:get_text() or "");
			if subject then
				for session in pairs(joined_muc.sessions) do
					session.send(":"..session.host.." 332 "..session.nick.." "..from_node.." :"..subject);
				end
			end
		end
	elseif joined_mucs[from_node] then
		-- From room occupant
		local joined_muc = joined_mucs[from_node];
		local nick = select(3, jid.split(from)):gsub(" ", "_");
		if stanza.name == "presence" then
			local what;
			if not stanza.attr.type then
				if joined_muc.occupants[nick] then
					return;
				end
				joined_muc.occupants[nick] = true;
				what = "JOIN";
			else
				joined_muc.occupants[nick] = nil;
				what = "PART";
			end
			for session in pairs(joined_muc.sessions) do
				if nick ~= session.nick then
					session.send(":"..nick.."!"..nick.." "..what.." :"..from_node);
				end
			end
		elseif stanza.name == "message" then
			local body = stanza:get_child("body");
			body = body and body:get_text() or "";
			local hasdelay = stanza:get_child("delay", "urn:xmpp:delay");
			if body ~= "" and nick then
				local to_nick = jid.split(stanza.attr.to);
				local session = nicks[to_nick];
				if nick ~= session.nick or hasdelay then
				    session.send(":"..nick.." PRIVMSG "..from_node.." :"..body);
				end
			end
			if not nick then
				module:log("error", "Invalid nick from JID: %s", from);
			end
		end
	end
end

require "core.componentmanager".register_component(module.host, irc_component);

prosody.events.add_handler("server-stopping", function (shutdown)
	module:log("debug", "Closing IRC connections prior to shutdown");
	for channel, joined_muc in pairs(joined_mucs) do
		for session in pairs(joined_muc.sessions) do
			core_process_stanza(session,
				st.presence{ type = "unavailable", from = session.full_jid,
					to = channel:gsub("^#", "").."@"..conference_server.."/"..session.nick }
					:tag("status")
						:text("Connection closed: Server is shutting down"..(shutdown.reason and (": "..shutdown.reason) or "")));
			session:close();
		end
	end
end);

require "net.connlisteners".register("irc", irc_listener);
require "net.connlisteners".start("irc", { port = module:get_option("port") });