# HG changeset patch # User Marco Cirillo # Date 1335703640 0 # Node ID 6b3606e0a6e7d9377d72806282d9149d47134629 # Parent aef5355a2ca6b0266b8bbf80157adff3e43c2766 mod_ircd: net.connlisteners is gone and won't be back, *disintegrated* diff -r aef5355a2ca6 -r 6b3606e0a6e7 mod_ircd/dev/mod_ircd.old_comments --- a/mod_ircd/dev/mod_ircd.old_comments Sun Apr 29 12:46:36 2012 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,508 +0,0 @@ --- README --- Squish verse into this dir, then squish them into one, which you move --- and rename to mod_ircd.lua in your prosody modules/plugins dir. --- --- IRC spec: --- http://tools.ietf.org/html/rfc2812 -local _module = module -module = _G.module -local module = _module --- -local component_jid, component_secret, muc_server, port_number = - module.host, nil, module:get_option_string("conference_server"), module:get_option_number("listener_port", 7000); - -if not muc_server then - module:log ("error", "You need to set the MUC server! halting.") - return false; -end - -package.loaded["util.sha1"] = require "util.encodings"; -local verse = require "verse" -require "verse.component" -require "socket" -c = verse.new();--verse.logger()) -c:add_plugin("groupchat"); - -local function verse2prosody(e) - return c:event("stanza", e.stanza) or true; -end -module:hook("message/bare", verse2prosody); -module:hook("message/full", verse2prosody); -module:hook("presence/bare", verse2prosody); -module:hook("presence/full", verse2prosody); -c.type = "component"; -c.send = core_post_stanza; - --- This plugin is actually a verse based component, but that mode is currently commented out - --- Add some hooks for debugging ---c:hook("opened", function () print("Stream opened!") end); ---c:hook("closed", function () print("Stream closed!") end); ---c:hook("stanza", function (stanza) print("Stanza:", stanza) end); - --- This one prints all received data ---c:hook("incoming-raw", print, 1000); ---c:hook("stanza", print, 1000); ---c:hook("outgoing-raw", print, 1000); - --- Print a message after authentication ---c:hook("authentication-success", function () print("Logged in!"); end); ---c:hook("authentication-failure", function (err) print("Failed to log in! Error: "..tostring(err.condition)); end); - --- Print a message and exit when disconnected ---c:hook("disconnected", function () print("Disconnected!"); os.exit(); end); - --- Now, actually start the connection: ---c.connect_host = "127.0.0.1" ---c:connect_component(component_jid, component_secret); - -local jid = require "util.jid"; -local nodeprep = require "util.encodings".stringprep.nodeprep; - -local function utf8_clean (s) - local push, join = table.insert, table.concat; - local r, i = {}, 1; - if not(s and #s > 0) then - return "" - end - while true do - local c = s:sub(i,i) - local b = c:byte(); - local w = ( - (b >= 9 and b <= 10 and 0) or - (b >= 32 and b <= 126 and 0) or - (b >= 192 and b <= 223 and 1) or - (b >= 224 and b <= 239 and 2) or - (b >= 240 and b <= 247 and 3) or - (b >= 248 and b <= 251 and 4) or - (b >= 251 and b <= 252 and 5) or nil - ) - if not w then - push(r, "?") - else - local n = i + w; - if w == 0 then - push(r, c); - elseif n > #s then - push(r, ("?"):format(b)); - else - local e = s:sub(i+1,n); - if e:match('^[\128-\191]*$') then - push(r, c); - push(r, e); - i = n; - else - push(r, ("?"):format(b)); - end - end - end - i = i + 1; - if i > #s then - break - end - end - return join(r); -end - -local function parse_line(line) - local ret = {}; - if line:sub(1,1) == ":" then - ret.from, line = line:match("^:(%w+)%s+(.*)$"); - end - for part in line:gmatch("%S+") do - if part:sub(1,1) == ":" then - ret[#ret+1] = line:match(":(.*)$"); - break - end - ret[#ret+1]=part; - end - return ret; -end - -local function build_line(parts) - if #parts > 1 then - parts[#parts] = ":" .. parts[#parts]; - end - return (parts.from and ":"..parts.from.." " or "")..table.concat(parts, " "); -end - -local function irc2muc(channel, nick) - local room = channel and nodeprep(channel:match("^#(%w+)")) or nil; - return jid.join(room, muc_server, nick) -end -local function muc2irc(room) - local channel, _, nick = jid.split(room); - return "#"..channel, nick; -end -local role_map = { - moderator = "@", - participant = "", - visitor = "", - none = "" -} -local aff_map = { - owner = "~", - administrator = "&", - member = "+", - none = "" -} -local role_modemap = { - moderator = "o", - participant = "", - visitor = "", - none = "" -} -local aff_modemap = { - owner = "q", - administrator = "a", - member = "v", - none = "" -} - -local irc_listener = { default_port = port_number, default_mode = "*l" }; - -local sessions = {}; -local jids = {}; -local commands = {}; - -local nicks = {}; - -local st = require "util.stanza"; - -local conference_server = muc_server; - -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 = component_jid, reset_stream = function () end, - close = irc_close_session, log = logger.init("irc"..(conn.id or "1")), - rooms = {}, - roster = {} }; - sessions[conn] = session; - function session.data(data) - local parts = parse_line(data); - module:log("debug", require"util.serialization".serialize(parts)); - local command = table.remove(parts, 1); - if not command then - return; - end - command = command:upper(); - if not session.nick then - if not (command == "USER" or command == "NICK") then - module:log("debug", "Client tried to send command %s before registering", command); - return session.send{from=muc_server, "451", command, "You have not registered"} - end - end - if commands[command] then - local ret = commands[command](session, parts); - if ret then - return session.send(ret); - end - else - session.send{from=muc_server, "421", session.nick, command, "Unknown command"}; - return module:log("debug", "Unknown command: %s", command); - end - end - function session.send(data) - if type(data) == "string" then - return conn:write(data.."\r\n"); - elseif type(data) == "table" then - local line = build_line(data); - module:log("debug", line); - conn:write(line.."\r\n"); - end - end - end - if data then - session.data(data); - end -end - -function irc_listener.ondisconnect(conn, error) - local session = sessions[conn]; - if session then - for _, room in pairs(session.rooms) do - room:leave("Disconnected"); - end - if session.nick then - nicks[session.nick] = nil; - end - if session.full_jid then - jids[session.full_jid] = nil; - end - end - sessions[conn] = nil; -end - -function commands.NICK(session, args) - if session.nick then - session.send{from = muc_server, "484", "*", nick, "I'm afraid I can't let you do that"}; - --TODO Loop throug all rooms and change nick, with help from Verse. - return; - end - local nick = args[1]; - nick = nick:gsub("[^%w_]",""); - if nicks[nick] then - session.send{from=muc_server, "433", nick, "The nickname "..nick.." is already in use"}; - return; - end - local full_jid = jid.join(nick, component_jid, "ircd"); - jids[full_jid] = session; - jids[full_jid]["ar_last"] = {}; - nicks[nick] = session; - session.nick = nick; - session.full_jid = full_jid; - session.type = "c2s"; - - session.send{from = muc_server, "001", nick, "Welcome in the IRC to MUC XMPP Gateway, "..nick}; - session.send{from = muc_server, "002", nick, "Your host is "..muc_server.." running Prosody "..prosody.version}; - session.send{from = muc_server, "003", nick, "This server was created the "..os.date(nil, prosody.start_time)} - session.send{from = muc_server, "004", nick, table.concat({muc_server, "mod_ircd(alpha-0.8)", "i", "aoqv"}, " ")}; - session.send{from = muc_server, "375", nick, "- "..muc_server.." Message of the day -"}; - session.send{from = muc_server, "372", nick, "-"}; - session.send{from = muc_server, "372", nick, "- Please be warned that this is only a partial irc implementation,"}; - session.send{from = muc_server, "372", nick, "- it's made to facilitate users transiting away from irc to XMPP."}; - session.send{from = muc_server, "372", nick, "-"}; - session.send{from = muc_server, "372", nick, "- Prosody is _NOT_ an IRC Server and it never will."}; - session.send{from = muc_server, "372", nick, "- We also would like to remind you that this plugin is provided as is,"}; - session.send{from = muc_server, "372", nick, "- it's still an Alpha and it's still a work in progress, use it at your sole"}; - session.send{from = muc_server, "372", nick, "- risk as there's a not so little chance something will break."}; - - session.send{from = nick, "MODE", nick, "+i"}; -- why -> Invisible mode setting, - -- enforce by default on most servers (since the source host doesn't show it's sensible to have it "set") -end - -function commands.USER(session, params) - -- FIXME - -- Empty command for now -end - -local function mode_map(am, rm, nicks) - local rnick; - local c_modes; - c_modes = aff_modemap[am]..role_modemap[rm] - rnick = string.rep(nicks.." ", c_modes:len()) - if c_modes == "" then return nil, nil end - return c_modes, rnick -end - -function commands.JOIN(session, args) - local channel = args[1]; - if not channel then return end - local room_jid = irc2muc(channel); - print(session.full_jid); - if not jids[session.full_jid].ar_last[room_jid] then jids[session.full_jid].ar_last[room_jid] = {}; end - local room, err = c:join_room(room_jid, session.nick, { source = session.full_jid } ); - if not room then - return ":"..muc_server.." ERR :Could not join room: "..err - end - session.rooms[channel] = room; - room.channel = channel; - room.session = session; - session.send{from=session.nick, "JOIN", channel}; - if room.subject then - session.send{from=muc_server, 332, session.nick, channel ,room.subject}; - end - commands.NAMES(session, channel); - - room:hook("subject-changed", function(changed) - session.send((":%s TOPIC %s :%s"):format(changed.by.nick, channel, changed.to or "")); - end); - - room:hook("message", function(event) - if not event.body then return end - local nick, body = event.nick, event.body; - if nick ~= session.nick then - if body:sub(1,4) == "/me " then - body = "\1ACTION ".. body:sub(5) .. "\1" - end - local type = event.stanza.attr.type; - session.send{from=nick, "PRIVMSG", type == "groupchat" and channel or nick, body}; - --FIXME PM's probably won't work - end - end); - - room:hook("presence", function(ar) - local c_modes; - local rnick; - if ar.nick and not jids[session.full_jid].ar_last[ar.room_jid][ar.nick] then jids[session.full_jid].ar_last[ar.room_jid][ar.nick] = {} end - local x_ar = ar.stanza:get_child("x", "http://jabber.org/protocol/muc#user") - if x_ar then - local xar_item = x_ar:get_child("item") - if xar_item and xar_item.attr and ar.stanza.attr.type ~= "unavailable" then - if xar_item.attr.affiliation and xar_item.attr.role then - if not jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] and - not jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] then - jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] = xar_item.attr.affiliation - jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] = xar_item.attr.role - c_modes, rnick = mode_map(xar_item.attr.affiliation, xar_item.attr.role, ar.nick); - if c_modes and rnick then session.send((":%s MODE %s +%s"):format(muc_server, channel, c_modes.." "..rnick)); end - else - c_modes, rnick = mode_map(jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"], jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"], ar.nick); - if c_modes and rnick then session.send((":%s MODE %s -%s"):format(muc_server, channel, c_modes.." "..rnick)); end - jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] = xar_item.attr.affiliation - jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] = xar_item.attr.role - c_modes, rnick = mode_map(xar_item.attr.affiliation, xar_item.attr.role, ar.nick); - if c_modes and rnick then session.send((":%s MODE %s +%s"):format(muc_server, channel, c_modes.." "..rnick)); end - end - end - end - end - end, -1); -end - -c:hook("groupchat/joined", function(room) - local session = room.session or jids[room.opts.source]; - local channel = "#"..room.jid:match("^(.*)@"); - session.send{from=session.nick.."!"..session.nick, "JOIN", channel}; - if room.topic then - session.send{from=muc_server, 332, room.topic}; - end - commands.NAMES(session, channel) - room:hook("occupant-joined", function(nick) - session.send{from=nick.nick.."!"..nick.nick, "JOIN", channel}; - end); - room:hook("occupant-left", function(nick) - jids[session.full_jid].ar_last[nick.jid:match("^(.*)/")][nick.nick] = nil; -- ugly - session.send{from=nick.nick.."!"..nick.nick, "PART", channel}; - end); -end); - -function commands.NAMES(session, channel) - local nicks = { }; - local room = session.rooms[channel]; - local symbols_map = { - owner = "~", - administrator = "&", - moderator = "@", - member = "+" - } - - if not room then return end - -- TODO Break this out into commands.NAMES - for nick, n in pairs(room.occupants) do - if n.affiliation == "owner" and n.role == "moderator" then - nick = symbols_map[n.affiliation]..nick; - elseif n.affiliation == "administrator" and n.role == "moderator" then - nick = symbols_map[n.affiliation]..nick; - elseif n.affiliation == "member" and n.role == "moderator" then - nick = symbols_map[n.role]..nick; - elseif n.affiliation == "member" and n.role == "partecipant" then - nick = symbols_map[n.affiliation]..nick; - elseif n.affiliation == "none" and n.role == "moderator" then - nick = symbols_map[n.role]..nick; - end - table.insert(nicks, nick); - end - nicks = table.concat(nicks, " "); - session.send((":%s 353 %s = %s :%s"):format(muc_server, session.nick, channel, nicks)); - session.send((":%s 366 %s %s :End of /NAMES list."):format(muc_server, session.nick, channel)); - session.send(":"..muc_server.." 353 "..session.nick.." = "..channel.." :"..nicks); -end - -function commands.PART(session, args) - local channel, part_message = unpack(args); - local room = channel and nodeprep(channel:match("^#(%w+)")) or nil; - if not room then return end - channel = channel:match("^([%S]*)"); - session.rooms[channel]:leave(part_message); - jids[session.full_jid].ar_last[room.."@"..muc_server] = nil; - session.send(":"..session.nick.." PART :"..channel); -end - -function commands.PRIVMSG(session, args) - local channel, message = unpack(args); - if message and #message > 0 then - if message:sub(1,8) == "\1ACTION " then - message = "/me ".. message:sub(9,-2) - end - message = utf8_clean(message); - if channel:sub(1,1) == "#" then - if session.rooms[channel] then - module:log("debug", "%s sending PRIVMSG \"%s\" to %s", session.nick, message, channel); - session.rooms[channel]:send_message(message); - end - else -- private message - local nick = channel; - module:log("debug", "PM to %s", nick); - for channel, room in pairs(session.rooms) do - module:log("debug", "looking for %s in %s", nick, channel); - if room.occupants[nick] then - module:log("debug", "found %s in %s", nick, channel); - local who = room.occupants[nick]; - -- FIXME PMs in verse - --room:send_private_message(nick, message); - local pm = st.message({type="chat",to=who.jid}, message); - module:log("debug", "sending PM to %s: %s", nick, tostring(pm)); - room:send(pm) - break - end - end - end - end -end - -function commands.PING(session, args) - session.send{from=muc_server, "PONG", args[1]}; -end - -function commands.TOPIC(session, message) - if not message then return end - local channel, topic = message[1], message[2]; - channel = utf8_clean(channel); - topic = utf8_clean(topic); - if not channel then return end - local room = session.rooms[channel]; - - if topic then room:set_subject(topic); end -end - -function commands.WHO(session, args) - local channel = args[1]; - if session.rooms[channel] then - local room = session.rooms[channel] - for nick in pairs(room.occupants) do - --n=MattJ 91.85.191.50 irc.freenode.net MattJ H :0 Matthew Wild - session.send{from=muc_server, 352, session.nick, channel, nick, nick, muc_server, nick, "H", "0 "..nick} - end - session.send{from=muc_server, 315, session.nick, channel, "End of /WHO list"}; - end -end - -function commands.MODE(session, args) -- FIXME - -- emptied for the time being, until something sane which works is available. -end - -function commands.QUIT(session, args) - session.send{"ERROR", "Closing Link: "..session.nick}; - for _, room in pairs(session.rooms) do - room:leave(args[1]); - end - jids[session.full_jid] = nil; - nicks[session.nick] = nil; - sessions[session.conn] = nil; - session:close(); -end - -function commands.RAW(session, data) - --c:send(data) -end - -local function desetup() - require "net.connlisteners".deregister("irc"); -end - ---c:hook("ready", function () - require "net.connlisteners".register("irc", irc_listener); - require "net.connlisteners".start("irc"); ---end); - -module:hook("module-unloaded", desetup) - - ---print("Starting loop...") ---verse.loop() diff -r aef5355a2ca6 -r 6b3606e0a6e7 mod_ircd/mod_ircd.in.lua --- a/mod_ircd/mod_ircd.in.lua Sun Apr 29 12:46:36 2012 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,597 +0,0 @@ --- README --- Squish verse into this dir, then squish them into one, which you move --- and rename to mod_ircd.lua in your prosody modules/plugins dir. --- --- IRC spec: --- http://tools.ietf.org/html/rfc2812 - -local _module = module -module = _G.module -local module = _module -local client_xmlns = "jabber:client" - -local component_jid, component_secret, muc_server, port_number = - module.host, nil, module:get_option_string("conference_server"), module:get_option_number("listener_port", 7000); - -if not muc_server then - module:log ("error", "You need to set the MUC server in the configuration (conference_server)!") - module:log ("error", "Be a good boy or girl and go read the wiki at: http://code.google.com/p/prosody-modules/wiki/mod_ircd") - return false; -end - -package.loaded["util.sha1"] = require "util.encodings"; -local verse = require "verse" -require "verse.component" -require "socket" -c = verse.new(); -- something interferes with prosody's console logging -c:add_plugin("groupchat"); - -local function verse2prosody(e) - return c:event("stanza", e.stanza) or true; -end -module:hook("message/bare", verse2prosody); -module:hook("message/full", verse2prosody); -module:hook("presence/bare", verse2prosody); -module:hook("presence/full", verse2prosody); -c.type = "component"; -c.send = core_post_stanza; - -local jid = require "util.jid"; -local nodeprep = require "util.encodings".stringprep.nodeprep; - -local function utf8_clean (s) - local push, join = table.insert, table.concat; - local r, i = {}, 1; - if not(s and #s > 0) then - return "" - end - while true do - local c = s:sub(i,i) - local b = c:byte(); - local w = ( - (b >= 9 and b <= 10 and 0) or - (b >= 32 and b <= 126 and 0) or - (b >= 192 and b <= 223 and 1) or - (b >= 224 and b <= 239 and 2) or - (b >= 240 and b <= 247 and 3) or - (b >= 248 and b <= 251 and 4) or - (b >= 251 and b <= 252 and 5) or nil - ) - if not w then - push(r, "?") - else - local n = i + w; - if w == 0 then - push(r, c); - elseif n > #s then - push(r, ("?"):format(b)); - else - local e = s:sub(i+1,n); - if e:match('^[\128-\191]*$') then - push(r, c); - push(r, e); - i = n; - else - push(r, ("?"):format(b)); - end - end - end - i = i + 1; - if i > #s then - break - end - end - return join(r); -end - -local function parse_line(line) - local ret = {}; - if line:sub(1,1) == ":" then - ret.from, line = line:match("^:(%w+)%s+(.*)$"); - end - for part in line:gmatch("%S+") do - if part:sub(1,1) == ":" then - ret[#ret+1] = line:match(":(.*)$"); - break - end - ret[#ret+1]=part; - end - return ret; -end - -local function build_line(parts) - if #parts > 1 then - parts[#parts] = ":" .. parts[#parts]; - end - return (parts.from and ":"..parts.from.." " or "")..table.concat(parts, " "); -end - -local function irc2muc(channel, nick) - local room = channel and nodeprep(channel:match("^#(%w+)")) or nil; - if not nick then - return jid.join(room, muc_server); - else - return jid.join(room, muc_server, nick); - end -end -local function muc2irc(room) - local channel, _, nick = jid.split(room); - return "#"..channel, nick; -end -local role_map = { - moderator = "@", - participant = "", - visitor = "", - none = "" -} -local aff_map = { - owner = "~", - administrator = "&", - member = "+", - none = "" -} -local role_modemap = { - moderator = "o", - participant = "", - visitor = "", - none = "" -} -local aff_modemap = { - owner = "q", - administrator = "a", - member = "v", - none = "" -} - -local irc_listener = { default_port = port_number, default_mode = "*l" }; - -local sessions = {}; -local jids = {}; -local commands = {}; - -local nicks = {}; -local usernames = {}; - -local st = require "util.stanza"; - -local conference_server = muc_server; - -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 = component_jid, reset_stream = function () end, - close = irc_close_session, log = logger.init("irc"..(conn.id or "1")), - rooms = {}, roster = {}, has_un = false }; - sessions[conn] = session; - - function session.data(data) - local parts = parse_line(data); - module:log("debug", require"util.serialization".serialize(parts)); - local command = table.remove(parts, 1); - if not command then - return; - end - command = command:upper(); - if not session.username and not session.nick then - if not (command == "USER" or command == "NICK") then - module:log("debug", "Client tried to send command %s before registering", command); - return session.send{from=muc_server, "451", command, "You have not completed the registration."} - end - end - if commands[command] then - local ret = commands[command](session, parts); - if ret then - return session.send(ret); - end - else - session.send{from=muc_server, "421", session.nick, command, "Unknown command"}; - return module:log("debug", "Unknown command: %s", command); - end - end - - function session.send(data) - if type(data) == "string" then - return conn:write(data.."\r\n"); - elseif type(data) == "table" then - local line = build_line(data); - module:log("debug", line); - conn:write(line.."\r\n"); - end - end - end - - if data then - session.data(data); - end -end - -function irc_listener.ondisconnect(conn, error) - local session = sessions[conn]; - - if session then - for _, room in pairs(session.rooms) do - room:leave("Disconnected"); - end - if session.nick then - nicks[session.nick] = nil; - end - if session.full_jid then - jids[session.full_jid] = nil; - end - if session.username then - usernames[session.username] = nil; - end - end - sessions[conn] = nil; -end - -local function nick_inuse(nick) - if nicks[nick] then return true else return false end -end -local function check_username(un) - local count = 0; - local result; - - for name, given in pairs(usernames) do - if un == given then count = count + 1; end - end - - result = count + 1; - - if count > 0 then return tostring(un)..tostring(result); else return tostring(un); end -end -local function set_t_data(session, full_jid) - session.full_jid = full_jid; - jids[full_jid] = session; - jids[full_jid]["ar_last"] = {}; - jids[full_jid]["nicks_changing"] = {}; - - if session.nick then nicks[session.nick] = session; end -end -local function send_motd(session) - local nick = session.nick; - - if session.username and session.nick then -- send MOTD only if username and nick are set - session.send{from = muc_server, "001", nick, "Welcome in the IRC to MUC XMPP Gateway, "..nick}; - session.send{from = muc_server, "002", nick, "Your host is "..muc_server.." running Prosody "..prosody.version}; - session.send{from = muc_server, "003", nick, "This server was created the "..os.date(nil, prosody.start_time)} - session.send{from = muc_server, "004", nick, table.concat({muc_server, "mod_ircd(alpha-0.8)", "i", "aoqv"}, " ")}; - session.send((":%s %s %s %s :%s"):format(muc_server, "005", nick, "CHANTYPES=# PREFIX=(qaov)~&@+", "are supported by this server")); - session.send((":%s %s %s %s :%s"):format(muc_server, "005", nick, "STATUSMSG=~&@+", "are supported by this server")); - session.send{from = muc_server, "375", nick, "- "..muc_server.." Message of the day -"}; - session.send{from = muc_server, "372", nick, "-"}; - session.send{from = muc_server, "372", nick, "- Please be warned that this is only a partial irc implementation,"}; - session.send{from = muc_server, "372", nick, "- it's made to facilitate users transiting away from irc to XMPP."}; - session.send{from = muc_server, "372", nick, "-"}; - session.send{from = muc_server, "372", nick, "- Prosody is _NOT_ an IRC Server and it never will."}; - session.send{from = muc_server, "372", nick, "- We also would like to remind you that this plugin is provided as is,"}; - session.send{from = muc_server, "372", nick, "- it's still an Alpha and it's still a work in progress, use it at your sole"}; - session.send{from = muc_server, "372", nick, "- risk as there's a not so little chance something will break."}; - session.send{from = nick, "MODE", nick, "+i"}; -- why -> Invisible mode setting, - end -- enforce by default on most servers (since the source host doesn't show it's sensible to have it "set") -end - -function commands.NICK(session, args) - local nick = args[1]; - nick = nick:gsub("[^%w_]",""); - - if session.nick and not nick_inuse(nick) then -- changing nick - local oldnick = session.nick; - - -- update and replace session data - session.nick = nick; - nicks[oldnick] = nil; - nicks[nick] = session; - - session.send{from=oldnick.."!"..nicks[nick].username, "NICK", nick}; - - -- broadcast changes if required - if session.rooms then - session.nicks_changing[nick] = { oldnick, session.username }; - - for id, room in pairs(session.rooms) do room:change_nick(nick); end - - session.nicks_changing[nick] = nil; - end - - return; - elseif nick_inuse(nick) then - session.send{from=muc_server, "433", nick, "The nickname "..nick.." is already in use"}; return; - end - - session.nick = nick; - session.type = "c2s"; - nicks[nick] = session; - - -- Some choppy clients send in NICK before USER, that needs to be handled - if session.username then - set_t_data(session, jid.join(session.username, component_jid, "ircd")); - end - - send_motd(session); -end - -function commands.USER(session, params) - local username = params[1]; - - if not session.has_un then - local un_checked = check_username(username); - - usernames[un_checked] = username; - session.username = un_checked; - session.has_un = true; - - if not session.full_jid then - set_t_data(session, jid.join(session.username, component_jid, "ircd")); - end - else - return session.send{from=muc_server, "462", "USER", "You may not re-register."} - end - - send_motd(session); -end - -function commands.USERHOST(session, params) -- can show only users on the gateway. Needed for some clients to determinate self hostmask. - local nick = params[1]; - - if not nick then session.send{from=muc_server, "461", "USERHOST", "Not enough parameters"}; return; end - - if nicks[nick] and nicks[nick].nick and nicks[nick].username then - session.send{from=muc_server, "302", session.nick, nick.."=+"..nicks[nick].username}; return; - else - return; - end -end - -local function mode_map(am, rm, nicks) - local rnick; - local c_modes; - c_modes = aff_modemap[am]..role_modemap[rm] - rnick = string.rep(nicks.." ", c_modes:len()) - if c_modes == "" then return nil, nil end - return c_modes, rnick -end - -function commands.JOIN(session, args) - local channel = args[1]; - if not channel then return end - local room_jid = irc2muc(channel); - - if not jids[session.full_jid].ar_last[room_jid] then jids[session.full_jid].ar_last[room_jid] = {}; end - local room, err = c:join_room(room_jid, session.nick, { source = session.full_jid } ); - if not room then - return ":"..muc_server.." ERR :Could not join room: "..err - end - - session.rooms[channel] = room; - room.session = session; - - if session.nicks_changing[session.nick] then -- my own nick is changing - commands.NAMES(session, channel); - else - session.send{from=session.nick.."!"..session.username, "JOIN", channel}; - if room.subject then - session.send{from=muc_server, 332, session.nick, channel, room.subject}; - end - commands.NAMES(session, channel); - end - - room:hook("subject-changed", function(changed) - session.send{from=changed.by.nick, "TOPIC", channel, changed.to or ""} - end); - - room:hook("message", function(event) - if not event.body then return end - local nick, body = event.nick, event.body; - if nick ~= session.nick then - if body:sub(1,4) == "/me " then - body = "\1ACTION ".. body:sub(5) .. "\1" - end - local type = event.stanza.attr.type; - session.send{from=nick, "PRIVMSG", type == "groupchat" and channel or nick, body}; - --FIXME PM's probably won't work - end - end); - - room:hook("presence", function(ar) - local c_modes; - local rnick; - if ar.nick and not jids[session.full_jid].ar_last[ar.room_jid][ar.nick] then jids[session.full_jid].ar_last[ar.room_jid][ar.nick] = {} end - local x_ar = ar.stanza:get_child("x", "http://jabber.org/protocol/muc#user") - if x_ar then - local xar_item = x_ar:get_child("item") - if xar_item and xar_item.attr and ar.stanza.attr.type ~= "unavailable" then - if xar_item.attr.affiliation and xar_item.attr.role then - if not jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] and - not jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] then - jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] = xar_item.attr.affiliation - jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] = xar_item.attr.role - n_self_changing = nicks[ar.nick] and nicks[ar.nick].nicks_changing and nicks[ar.nick].nicks_changing[ar.nick] - if n_self_changing then return; end - c_modes, rnick = mode_map(xar_item.attr.affiliation, xar_item.attr.role, ar.nick); - if c_modes and rnick then session.send((":%s MODE %s +%s"):format(muc_server, channel, c_modes.." "..rnick)); end - else - c_modes, rnick = mode_map(jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"], jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"], ar.nick); - if c_modes and rnick then session.send((":%s MODE %s -%s"):format(muc_server, channel, c_modes.." "..rnick)); end - jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] = xar_item.attr.affiliation - jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] = xar_item.attr.role - n_self_changing = nicks[ar.nick] and nicks[ar.nick].nicks_changing and nicks[ar.nick].nicks_changing[ar.nick] - if n_self_changing then return; end - c_modes, rnick = mode_map(xar_item.attr.affiliation, xar_item.attr.role, ar.nick); - if c_modes and rnick then session.send((":%s MODE %s +%s"):format(muc_server, channel, c_modes.." "..rnick)); end - end - end - end - end - end, -1); -end - -c:hook("groupchat/joined", function(room) - local session = room.session or jids[room.opts.source]; - local channel = "#"..room.jid:match("^(.*)@"); - - room:hook("occupant-joined", function(nick) - if session.nicks_changing[nick.nick] then - session.send{from=session.nicks_changing[nick.nick][1].."!"..(session.nicks_changing[nick.nick][2] or "xmpp"), "NICK", nick.nick}; - session.nicks_changing[nick.nick] = nil; - else - session.send{from=nick.nick.."!"..(nicks[nick.nick] and nicks[nick.nick].username or "xmpp"), "JOIN", channel}; - end - end); - room:hook("occupant-left", function(nick) - if jids[session.full_jid] then jids[session.full_jid].ar_last[nick.jid:match("^(.*)/")][nick.nick] = nil; end - local status_code = - nick.presence:get_child("x","http://jabber.org/protocol/muc#user") and - nick.presence:get_child("x","http://jabber.org/protocol/muc#user"):get_child("status") and - nick.presence:get_child("x","http://jabber.org/protocol/muc#user"):get_child("status").attr.code; - - - if status_code == "303" then - local newnick = - nick.presence:get_child("x","http://jabber.org/protocol/muc#user") and - nick.presence:get_child("x","http://jabber.org/protocol/muc#user"):get_child("item") and - nick.presence:get_child("x","http://jabber.org/protocol/muc#user"):get_child("item").attr.nick; - - session.nicks_changing[newnick] = { nick.nick, (nicks[nick.nick] and nicks[nick.nick].username or "xmpp") }; return; - end - - for id, data in pairs(session.nicks_changing) do - if data[1] == nick.nick then return; end - end - session.send{from=nick.nick.."!"..(nicks[nick.nick] and nicks[nick.nick].username or "xmpp"), "PART", channel}; - end); -end); - -function commands.NAMES(session, channel) - local nicks = { }; - if type(channel) == "table" then channel = channel[1] end - - local room = session.rooms[channel]; - - local symbols_map = { - owner = "~", - administrator = "&", - moderator = "@", - member = "+" - } - - if not room then return end - -- TODO Break this out into commands.NAMES - for nick, n in pairs(room.occupants) do - if n.affiliation == "owner" and n.role == "moderator" then - nick = symbols_map[n.affiliation]..nick; - elseif n.affiliation == "administrator" and n.role == "moderator" then - nick = symbols_map[n.affiliation]..nick; - elseif n.affiliation == "member" and n.role == "moderator" then - nick = symbols_map[n.role]..nick; - elseif n.affiliation == "member" and n.role == "partecipant" then - nick = symbols_map[n.affiliation]..nick; - elseif n.affiliation == "none" and n.role == "moderator" then - nick = symbols_map[n.role]..nick; - end - table.insert(nicks, nick); - end - nicks = table.concat(nicks, " "); - session.send((":%s 353 %s = %s :%s"):format(muc_server, session.nick, channel, nicks)); - session.send((":%s 366 %s %s :End of /NAMES list."):format(muc_server, session.nick, channel)); - session.send(":"..muc_server.." 353 "..session.nick.." = "..channel.." :"..nicks); -end - -function commands.PART(session, args) - local channel, part_message = unpack(args); - local room = channel and nodeprep(channel:match("^#(%w+)")) or nil; - if not room then return end - channel = channel:match("^([%S]*)"); - session.rooms[channel]:leave(part_message); - jids[session.full_jid].ar_last[room.."@"..muc_server] = nil; - session.send{from=session.nick.."!"..session.username, "PART", channel}; -end - -function commands.PRIVMSG(session, args) - local channel, message = unpack(args); - if message and #message > 0 then - if message:sub(1,8) == "\1ACTION " then - message = "/me ".. message:sub(9,-2) - end - message = utf8_clean(message); - if channel:sub(1,1) == "#" then - if session.rooms[channel] then - module:log("debug", "%s sending PRIVMSG \"%s\" to %s", session.nick, message, channel); - session.rooms[channel]:send_message(message); - end - else -- private message - local nick = channel; - module:log("debug", "PM to %s", nick); - for channel, room in pairs(session.rooms) do - module:log("debug", "looking for %s in %s", nick, channel); - if room.occupants[nick] then - module:log("debug", "found %s in %s", nick, channel); - local who = room.occupants[nick]; - -- FIXME PMs in verse - --room:send_private_message(nick, message); - local pm = st.message({type="chat",to=who.jid}, message); - module:log("debug", "sending PM to %s: %s", nick, tostring(pm)); - room:send(pm) - break - end - end - end - end -end - -function commands.PING(session, args) - session.send{from=muc_server, "PONG", args[1]}; -end - -function commands.TOPIC(session, message) - if not message then return end - local channel, topic = message[1], message[2]; - channel = utf8_clean(channel); - topic = utf8_clean(topic); - if not channel then return end - local room = session.rooms[channel]; - - if topic then room:set_subject(topic); end -end - -function commands.WHO(session, args) - local channel = args[1]; - if session.rooms[channel] then - local room = session.rooms[channel] - for nick in pairs(room.occupants) do - session.send{from=muc_server, 352, session.nick, channel, nick, nick, muc_server, nick, "H", "0 "..nick} - end - session.send{from=muc_server, 315, session.nick, channel, "End of /WHO list"}; - end -end - -function commands.MODE(session, args) -- Empty command -end - -function commands.QUIT(session, args) - session.send{"ERROR", "Closing Link: "..session.nick}; - for _, room in pairs(session.rooms) do - room:leave(args[1]); - end - jids[session.full_jid] = nil; - nicks[session.nick] = nil; - usernames[session.username] = nil; - sessions[session.conn] = nil; - session:close(); -end - -function commands.RAW(session, data) -- Empty command -end - -local function desetup() - require "net.connlisteners".deregister("irc"); -end - -require "net.connlisteners".register("irc", irc_listener); -require "net.connlisteners".start("irc"); - -module:hook("module-unloaded", desetup) diff -r aef5355a2ca6 -r 6b3606e0a6e7 mod_ircd/squishy --- a/mod_ircd/squishy Sun Apr 29 12:46:36 2012 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -Output "mod_ircd.lua" - --- In theory, you should be able to leave all but verses groupchat and presence plugins -local verse_path = GetOption("verse"); -if not verse_path then - if GetOption("use-http") then - AutoFetchURL "http://code.matthewwild.co.uk/verse/raw-file/tip?" - verse_path = ""; - else - Module "verse" - end -end - -if verse_path then - if verse_path:match("verse%.lua$") then - Module "verse" (verse_path) - else - Module "verse" (verse_path.."/init.lua") - Module "verse.component" (verse_path.."/component.lua") - Module "verse.plugins.groupchat" (verse_path.."/plugins/groupchat.lua") - Module "verse.plugins.presence" (verse_path.."/plugins/presence.lua") - end -end - -Main "mod_ircd.in.lua" diff -r aef5355a2ca6 -r 6b3606e0a6e7 mod_ircd/verse/verse.lua --- a/mod_ircd/verse/verse.lua Sun Apr 29 12:46:36 2012 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,580 +0,0 @@ -package.preload['verse.plugins.presence'] = (function (...) -function verse.plugins.presence(stream) - stream.last_presence = nil; - - stream:hook("presence-out", function (presence) - if not presence.attr.to then - stream.last_presence = presence; -- Cache non-directed presence - end - end, 1); - - function stream:resend_presence() - if last_presence then - stream:send(last_presence); - end - end - - function stream:set_status(opts) - local p = verse.presence(); - if type(opts) == "table" then - if opts.show then - p:tag("show"):text(opts.show):up(); - end - if opts.prio then - p:tag("priority"):text(tostring(opts.prio)):up(); - end - if opts.msg then - p:tag("status"):text(opts.msg):up(); - end - end - -- TODO maybe use opts as prio if it's a int, - -- or as show or status if it's a string? - - stream:send(p); - end -end - end) -package.preload['verse.plugins.groupchat'] = (function (...) -local events = require "events"; - -local room_mt = {}; -room_mt.__index = room_mt; - -local xmlns_delay = "urn:xmpp:delay"; -local xmlns_muc = "http://jabber.org/protocol/muc"; - -function verse.plugins.groupchat(stream) - stream:add_plugin("presence") - stream.rooms = {}; - - stream:hook("stanza", function (stanza) - local room_jid = jid.bare(stanza.attr.from); - if not room_jid then return end - local room = stream.rooms[room_jid] - if not room and stanza.attr.to and room_jid then - room = stream.rooms[stanza.attr.to.." "..room_jid] - end - if room and room.opts.source and stanza.attr.to ~= room.opts.source then return end - if room then - local nick = select(3, jid.split(stanza.attr.from)); - local body = stanza:get_child_text("body"); - local delay = stanza:get_child("delay", xmlns_delay); - local event = { - room_jid = room_jid; - room = room; - sender = room.occupants[nick]; - nick = nick; - body = body; - stanza = stanza; - delay = (delay and delay.attr.stamp); - }; - local ret = room:event(stanza.name, event); - return ret or (stanza.name == "message") or nil; - end - end, 500); - - function stream:join_room(jid, nick, opts) - if not nick then - return false, "no nickname supplied" - end - opts = opts or {}; - local room = setmetatable({ - stream = stream, jid = jid, nick = nick, - subject = nil, - occupants = {}, - opts = opts, - events = events.new() - }, room_mt); - if opts.source then - self.rooms[opts.source.." "..jid] = room; - else - self.rooms[jid] = room; - end - local occupants = room.occupants; - room:hook("presence", function (presence) - local nick = presence.nick or nick; - if not occupants[nick] and presence.stanza.attr.type ~= "unavailable" then - occupants[nick] = { - nick = nick; - jid = presence.stanza.attr.from; - presence = presence.stanza; - }; - local x = presence.stanza:get_child("x", xmlns_muc .. "#user"); - if x then - local x_item = x:get_child("item"); - if x_item and x_item.attr then - occupants[nick].real_jid = x_item.attr.jid; - occupants[nick].affiliation = x_item.attr.affiliation; - occupants[nick].role = x_item.attr.role; - end - --TODO Check for status 100? - end - if nick == room.nick then - room.stream:event("groupchat/joined", room); - else - room:event("occupant-joined", occupants[nick]); - end - elseif occupants[nick] and presence.stanza.attr.type == "unavailable" then - if nick == room.nick then - room.stream:event("groupchat/left", room); - if room.opts.source then - self.rooms[room.opts.source.." "..jid] = nil; - else - self.rooms[jid] = nil; - end - else - occupants[nick].presence = presence.stanza; - room:event("occupant-left", occupants[nick]); - occupants[nick] = nil; - end - end - end); - room:hook("message", function(event) - local subject = event.stanza:get_child_text("subject"); - if not subject then return end - subject = #subject > 0 and subject or nil; - if subject ~= room.subject then - local old_subject = room.subject; - room.subject = subject; - return room:event("subject-changed", { from = old_subject, to = subject, by = event.sender, event = event }); - end - end, 2000); - local join_st = verse.presence():tag("x",{xmlns = xmlns_muc}):reset(); - self:event("pre-groupchat/joining", join_st); - room:send(join_st) - self:event("groupchat/joining", room); - return room; - end - - stream:hook("presence-out", function(presence) - if not presence.attr.to then - for _, room in pairs(stream.rooms) do - room:send(presence); - end - presence.attr.to = nil; - end - end); -end - -function room_mt:send(stanza) - if stanza.name == "message" and not stanza.attr.type then - stanza.attr.type = "groupchat"; - end - if stanza.name == "presence" then - stanza.attr.to = self.jid .."/"..self.nick; - end - if stanza.attr.type == "groupchat" or not stanza.attr.to then - stanza.attr.to = self.jid; - end - if self.opts.source then - stanza.attr.from = self.opts.source - end - self.stream:send(stanza); -end - -function room_mt:send_message(text) - self:send(verse.message():tag("body"):text(text)); -end - -function room_mt:set_subject(text) - self:send(verse.message():tag("subject"):text(text)); -end - -function room_mt:change_nick(new) - self.nick = new; - self:send(verse.presence()); -end - -function room_mt:leave(message) - self.stream:event("groupchat/leaving", self); - self:send(verse.presence({type="unavailable"})); -end - -function room_mt:admin_set(nick, what, value, reason) - self:send(verse.iq({type="set"}) - :query(xmlns_muc .. "#admin") - :tag("item", {nick = nick, [what] = value}) - :tag("reason"):text(reason or "")); -end - -function room_mt:set_role(nick, role, reason) - self:admin_set(nick, "role", role, reason); -end - -function room_mt:set_affiliation(nick, affiliation, reason) - self:admin_set(nick, "affiliation", affiliation, reason); -end - -function room_mt:kick(nick, reason) - self:set_role(nick, "none", reason); -end - -function room_mt:ban(nick, reason) - self:set_affiliation(nick, "outcast", reason); -end - -function room_mt:event(name, arg) - self.stream:debug("Firing room event: %s", name); - return self.events.fire_event(name, arg); -end - -function room_mt:hook(name, callback, priority) - return self.events.add_handler(name, callback, priority); -end - end) -package.preload['verse.component'] = (function (...) -local verse = require "verse"; -local stream = verse.stream_mt; - -local jid_split = require "util.jid".split; -local lxp = require "lxp"; -local st = require "util.stanza"; -local sha1 = require "util.sha1".sha1; - --- Shortcuts to save having to load util.stanza -verse.message, verse.presence, verse.iq, verse.stanza, verse.reply, verse.error_reply = - st.message, st.presence, st.iq, st.stanza, st.reply, st.error_reply; - -local new_xmpp_stream = require "util.xmppstream".new; - -local xmlns_stream = "http://etherx.jabber.org/streams"; -local xmlns_component = "jabber:component:accept"; - -local stream_callbacks = { - stream_ns = xmlns_stream, - stream_tag = "stream", - default_ns = xmlns_component }; - -function stream_callbacks.streamopened(stream, attr) - stream.stream_id = attr.id; - if not stream:event("opened", attr) then - stream.notopen = nil; - end - return true; -end - -function stream_callbacks.streamclosed(stream) - return stream:event("closed"); -end - -function stream_callbacks.handlestanza(stream, stanza) - if stanza.attr.xmlns == xmlns_stream then - return stream:event("stream-"..stanza.name, stanza); - elseif stanza.attr.xmlns or stanza.name == "handshake" then - return stream:event("stream/"..(stanza.attr.xmlns or xmlns_component), stanza); - end - - return stream:event("stanza", stanza); -end - -function stream:reset() - if self.stream then - self.stream:reset(); - else - self.stream = new_xmpp_stream(self, stream_callbacks); - end - self.notopen = true; - return true; -end - -function stream:connect_component(jid, pass) - self.jid, self.password = jid, pass; - self.username, self.host, self.resource = jid_split(jid); - - function self.data(conn, data) - local ok, err = self.stream:feed(data); - if ok then return; end - stream:debug("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " ")); - stream:close("xml-not-well-formed"); - end - - self:hook("incoming-raw", function (data) return self.data(self.conn, data); end); - - self.curr_id = 0; - - self.tracked_iqs = {}; - self:hook("stanza", function (stanza) - local id, type = stanza.attr.id, stanza.attr.type; - if id and stanza.name == "iq" and (type == "result" or type == "error") and self.tracked_iqs[id] then - self.tracked_iqs[id](stanza); - self.tracked_iqs[id] = nil; - return true; - end - end); - - self:hook("stanza", function (stanza) - if stanza.attr.xmlns == nil or stanza.attr.xmlns == "jabber:client" then - if stanza.name == "iq" and (stanza.attr.type == "get" or stanza.attr.type == "set") then - local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns; - if xmlns then - ret = self:event("iq/"..xmlns, stanza); - if not ret then - ret = self:event("iq", stanza); - end - end - if ret == nil then - self:send(verse.error_reply(stanza, "cancel", "service-unavailable")); - return true; - end - else - ret = self:event(stanza.name, stanza); - end - end - return ret; - end, -1); - - self:hook("opened", function (attr) - print(self.jid, self.stream_id, attr.id); - local token = sha1(self.stream_id..pass, true); - - self:send(st.stanza("handshake", { xmlns = xmlns_component }):text(token)); - self:hook("stream/"..xmlns_component, function (stanza) - if stanza.name == "handshake" then - self:event("authentication-success"); - end - end); - end); - - local function stream_ready() - self:event("ready"); - end - self:hook("authentication-success", stream_ready, -1); - - -- Initialise connection - self:connect(self.connect_host or self.host, self.connect_port or 5347); - self:reopen(); -end - -function stream:reopen() - self:reset(); - self:send(st.stanza("stream:stream", { to = self.host, ["xmlns:stream"]='http://etherx.jabber.org/streams', - xmlns = xmlns_component, version = "1.0" }):top_tag()); -end - -function stream:close(reason) - if not self.notopen then - self:send(""); - end - local on_disconnect = self.conn.disconnect(); - self.conn:close(); - on_disconnect(conn, reason); -end - -function stream:send_iq(iq, callback) - local id = self:new_id(); - self.tracked_iqs[id] = callback; - iq.attr.id = id; - self:send(iq); -end - -function stream:new_id() - self.curr_id = self.curr_id + 1; - return tostring(self.curr_id); -end - end) - --- Use LuaRocks if available -pcall(require, "luarocks.require"); - --- Load LuaSec if available -pcall(require, "ssl"); - -local server = require "net.server"; -local events = require "util.events"; -local logger = require "util.logger"; - -module("verse", package.seeall); -local verse = _M; -_M.server = server; - -local stream = {}; -stream.__index = stream; -stream_mt = stream; - -verse.plugins = {}; - -local max_id = 0; - -function verse.new(logger, base) - local t = setmetatable(base or {}, stream); - max_id = max_id + 1; - t.id = tostring(max_id); - t.logger = logger or verse.new_logger("stream"..t.id); - t.events = events.new(); - t.plugins = {}; - t.verse = verse; - return t; -end - -verse.add_task = require "util.timer".add_task; - -verse.logger = logger.init; -- COMPAT: Deprecated -verse.new_logger = logger.init; -verse.log = verse.logger("verse"); - -local function format(format, ...) - local n, arg, maxn = 0, { ... }, select('#', ...); - return (format:gsub("%%(.)", function (c) if n <= maxn then n = n + 1; return tostring(arg[n]); end end)); -end - -function verse.set_log_handler(log_handler, levels) - levels = levels or { "debug", "info", "warn", "error" }; - logger.reset(); - local function _log_handler(name, level, message, ...) - return log_handler(name, level, format(message, ...)); - end - if log_handler then - for i, level in ipairs(levels) do - logger.add_level_sink(level, _log_handler); - end - end -end - -function _default_log_handler(name, level, message) - return io.stderr:write(name, "\t", level, "\t", message, "\n"); -end -verse.set_log_handler(_default_log_handler, { "error" }); - -local function error_handler(err) - verse.log("error", "Error: %s", err); - verse.log("error", "Traceback: %s", debug.traceback()); -end - -function verse.set_error_handler(new_error_handler) - error_handler = new_error_handler; -end - -function verse.loop() - return xpcall(server.loop, error_handler); -end - -function verse.step() - return xpcall(server.step, error_handler); -end - -function verse.quit() - return server.setquitting(true); -end - -function stream:connect(connect_host, connect_port) - connect_host = connect_host or "localhost"; - connect_port = tonumber(connect_port) or 5222; - - -- Create and initiate connection - local conn = socket.tcp() - conn:settimeout(0); - local success, err = conn:connect(connect_host, connect_port); - - if not success and err ~= "timeout" then - self:warn("connect() to %s:%d failed: %s", connect_host, connect_port, err); - return self:event("disconnected", { reason = err }) or false, err; - end - - local conn = server.wrapclient(conn, connect_host, connect_port, new_listener(self), "*a"); - if not conn then - self:warn("connection initialisation failed: %s", err); - return self:event("disconnected", { reason = err }) or false, err; - end - - self.conn = conn; - self.send = function (stream, data) - self:event("outgoing", data); - data = tostring(data); - self:event("outgoing-raw", data); - return conn:write(data); - end; - return true; -end - -function stream:close() - if not self.conn then - verse.log("error", "Attempt to close disconnected connection - possibly a bug"); - return; - end - local on_disconnect = self.conn.disconnect(); - self.conn:close(); - on_disconnect(conn, reason); -end - --- Logging functions -function stream:debug(...) - return self.logger("debug", ...); -end - -function stream:warn(...) - return self.logger("warn", ...); -end - -function stream:error(...) - return self.logger("error", ...); -end - --- Event handling -function stream:event(name, ...) - self:debug("Firing event: "..tostring(name)); - return self.events.fire_event(name, ...); -end - -function stream:hook(name, ...) - return self.events.add_handler(name, ...); -end - -function stream:unhook(name, handler) - return self.events.remove_handler(name, handler); -end - -function verse.eventable(object) - object.events = events.new(); - object.hook, object.unhook = stream.hook, stream.unhook; - local fire_event = object.events.fire_event; - function object:event(name, ...) - return fire_event(name, ...); - end - return object; -end - -function stream:add_plugin(name) - if self.plugins[name] then return true; end - if require("verse.plugins."..name) then - local ok, err = verse.plugins[name](self); - if ok ~= false then - self:debug("Loaded %s plugin", name); - self.plugins[name] = true; - else - self:warn("Failed to load %s plugin: %s", name, err); - end - end - return self; -end - --- Listener factory -function new_listener(stream) - local conn_listener = {}; - - function conn_listener.onconnect(conn) - stream.connected = true; - stream:event("connected"); - end - - function conn_listener.onincoming(conn, data) - stream:event("incoming-raw", data); - end - - function conn_listener.ondisconnect(conn, err) - stream.connected = false; - stream:event("disconnected", { reason = err }); - end - - function conn_listener.ondrain(conn) - stream:event("drained"); - end - - function conn_listener.onstatus(conn, new_status) - stream:event("status", new_status); - end - - return conn_listener; -end - -return verse; -