Software / code / prosody-modules
Comparison
mod_ircd/mod_ircd.in.lua.old_annotate @ 487:8bdab5489653
mod_ircd: code cleanup, added full logic for changing nicks (locally it works no traces), removed many comment lines (there was an over abundance of 'em they're in the .old_annotate file)
| author | Marco Cirillo <maranda@lightwitch.org> |
|---|---|
| date | Fri, 02 Dec 2011 01:03:06 +0000 |
| parent | 485:mod_ircd/mod_ircd.in.lua@f8cc2be7e16a |
comparison
equal
deleted
inserted
replaced
| 486:b84493ef1d1d | 487:8bdab5489653 |
|---|---|
| 1 -- README | |
| 2 -- Squish verse into this dir, then squish them into one, which you move | |
| 3 -- and rename to mod_ircd.lua in your prosody modules/plugins dir. | |
| 4 -- | |
| 5 -- IRC spec: | |
| 6 -- http://tools.ietf.org/html/rfc2812 | |
| 7 local _module = module | |
| 8 module = _G.module | |
| 9 local module = _module | |
| 10 -- | |
| 11 local component_jid, component_secret, muc_server, port_number = | |
| 12 module.host, nil, module:get_option_string("conference_server"), module:get_option_number("listener_port", 7000); | |
| 13 | |
| 14 if not muc_server then | |
| 15 module:log ("error", "You need to set the MUC server! halting.") | |
| 16 return false; | |
| 17 end | |
| 18 | |
| 19 package.loaded["util.sha1"] = require "util.encodings"; | |
| 20 local verse = require "verse" | |
| 21 require "verse.component" | |
| 22 require "socket" | |
| 23 c = verse.new();--verse.logger()) | |
| 24 c:add_plugin("groupchat"); | |
| 25 | |
| 26 local function verse2prosody(e) | |
| 27 return c:event("stanza", e.stanza) or true; | |
| 28 end | |
| 29 module:hook("message/bare", verse2prosody); | |
| 30 module:hook("message/full", verse2prosody); | |
| 31 module:hook("presence/bare", verse2prosody); | |
| 32 module:hook("presence/full", verse2prosody); | |
| 33 c.type = "component"; | |
| 34 c.send = core_post_stanza; | |
| 35 | |
| 36 -- This plugin is actually a verse based component, but that mode is currently commented out | |
| 37 | |
| 38 -- Add some hooks for debugging | |
| 39 --c:hook("opened", function () print("Stream opened!") end); | |
| 40 --c:hook("closed", function () print("Stream closed!") end); | |
| 41 --c:hook("stanza", function (stanza) print("Stanza:", stanza) end); | |
| 42 | |
| 43 -- This one prints all received data | |
| 44 --c:hook("incoming-raw", print, 1000); | |
| 45 --c:hook("stanza", print, 1000); | |
| 46 --c:hook("outgoing-raw", print, 1000); | |
| 47 | |
| 48 -- Print a message after authentication | |
| 49 --c:hook("authentication-success", function () print("Logged in!"); end); | |
| 50 --c:hook("authentication-failure", function (err) print("Failed to log in! Error: "..tostring(err.condition)); end); | |
| 51 | |
| 52 -- Print a message and exit when disconnected | |
| 53 --c:hook("disconnected", function () print("Disconnected!"); os.exit(); end); | |
| 54 | |
| 55 -- Now, actually start the connection: | |
| 56 --c.connect_host = "127.0.0.1" | |
| 57 --c:connect_component(component_jid, component_secret); | |
| 58 | |
| 59 local jid = require "util.jid"; | |
| 60 local nodeprep = require "util.encodings".stringprep.nodeprep; | |
| 61 | |
| 62 local function utf8_clean (s) | |
| 63 local push, join = table.insert, table.concat; | |
| 64 local r, i = {}, 1; | |
| 65 if not(s and #s > 0) then | |
| 66 return "" | |
| 67 end | |
| 68 while true do | |
| 69 local c = s:sub(i,i) | |
| 70 local b = c:byte(); | |
| 71 local w = ( | |
| 72 (b >= 9 and b <= 10 and 0) or | |
| 73 (b >= 32 and b <= 126 and 0) or | |
| 74 (b >= 192 and b <= 223 and 1) or | |
| 75 (b >= 224 and b <= 239 and 2) or | |
| 76 (b >= 240 and b <= 247 and 3) or | |
| 77 (b >= 248 and b <= 251 and 4) or | |
| 78 (b >= 251 and b <= 252 and 5) or nil | |
| 79 ) | |
| 80 if not w then | |
| 81 push(r, "?") | |
| 82 else | |
| 83 local n = i + w; | |
| 84 if w == 0 then | |
| 85 push(r, c); | |
| 86 elseif n > #s then | |
| 87 push(r, ("?"):format(b)); | |
| 88 else | |
| 89 local e = s:sub(i+1,n); | |
| 90 if e:match('^[\128-\191]*$') then | |
| 91 push(r, c); | |
| 92 push(r, e); | |
| 93 i = n; | |
| 94 else | |
| 95 push(r, ("?"):format(b)); | |
| 96 end | |
| 97 end | |
| 98 end | |
| 99 i = i + 1; | |
| 100 if i > #s then | |
| 101 break | |
| 102 end | |
| 103 end | |
| 104 return join(r); | |
| 105 end | |
| 106 | |
| 107 local function parse_line(line) | |
| 108 local ret = {}; | |
| 109 if line:sub(1,1) == ":" then | |
| 110 ret.from, line = line:match("^:(%w+)%s+(.*)$"); | |
| 111 end | |
| 112 for part in line:gmatch("%S+") do | |
| 113 if part:sub(1,1) == ":" then | |
| 114 ret[#ret+1] = line:match(":(.*)$"); | |
| 115 break | |
| 116 end | |
| 117 ret[#ret+1]=part; | |
| 118 end | |
| 119 return ret; | |
| 120 end | |
| 121 | |
| 122 local function build_line(parts) | |
| 123 if #parts > 1 then | |
| 124 parts[#parts] = ":" .. parts[#parts]; | |
| 125 end | |
| 126 return (parts.from and ":"..parts.from.." " or "")..table.concat(parts, " "); | |
| 127 end | |
| 128 | |
| 129 local function irc2muc(channel, nick) | |
| 130 local room = channel and nodeprep(channel:match("^#(%w+)")) or nil; | |
| 131 return jid.join(room, muc_server, nick) | |
| 132 end | |
| 133 local function muc2irc(room) | |
| 134 local channel, _, nick = jid.split(room); | |
| 135 return "#"..channel, nick; | |
| 136 end | |
| 137 local role_map = { | |
| 138 moderator = "@", | |
| 139 participant = "", | |
| 140 visitor = "", | |
| 141 none = "" | |
| 142 } | |
| 143 local aff_map = { | |
| 144 owner = "~", | |
| 145 administrator = "&", | |
| 146 member = "+", | |
| 147 none = "" | |
| 148 } | |
| 149 local role_modemap = { | |
| 150 moderator = "o", | |
| 151 participant = "", | |
| 152 visitor = "", | |
| 153 none = "" | |
| 154 } | |
| 155 local aff_modemap = { | |
| 156 owner = "q", | |
| 157 administrator = "a", | |
| 158 member = "v", | |
| 159 none = "" | |
| 160 } | |
| 161 | |
| 162 local irc_listener = { default_port = port_number, default_mode = "*l" }; | |
| 163 | |
| 164 local sessions = {}; | |
| 165 local jids = {}; | |
| 166 local commands = {}; | |
| 167 | |
| 168 local nicks = {}; | |
| 169 | |
| 170 local st = require "util.stanza"; | |
| 171 | |
| 172 local conference_server = muc_server; | |
| 173 | |
| 174 local function irc_close_session(session) | |
| 175 session.conn:close(); | |
| 176 end | |
| 177 | |
| 178 function irc_listener.onincoming(conn, data) | |
| 179 local session = sessions[conn]; | |
| 180 if not session then | |
| 181 session = { conn = conn, host = component_jid, reset_stream = function () end, | |
| 182 close = irc_close_session, log = logger.init("irc"..(conn.id or "1")), | |
| 183 rooms = {}, | |
| 184 roster = {} }; | |
| 185 sessions[conn] = session; | |
| 186 function session.data(data) | |
| 187 local parts = parse_line(data); | |
| 188 module:log("debug", require"util.serialization".serialize(parts)); | |
| 189 local command = table.remove(parts, 1); | |
| 190 if not command then | |
| 191 return; | |
| 192 end | |
| 193 command = command:upper(); | |
| 194 if not session.nick then | |
| 195 if not (command == "USER" or command == "NICK") then | |
| 196 module:log("debug", "Client tried to send command %s before registering", command); | |
| 197 return session.send{from=muc_server, "451", command, "You have not registered"} | |
| 198 end | |
| 199 end | |
| 200 if commands[command] then | |
| 201 local ret = commands[command](session, parts); | |
| 202 if ret then | |
| 203 return session.send(ret); | |
| 204 end | |
| 205 else | |
| 206 session.send{from=muc_server, "421", session.nick, command, "Unknown command"}; | |
| 207 return module:log("debug", "Unknown command: %s", command); | |
| 208 end | |
| 209 end | |
| 210 function session.send(data) | |
| 211 if type(data) == "string" then | |
| 212 return conn:write(data.."\r\n"); | |
| 213 elseif type(data) == "table" then | |
| 214 local line = build_line(data); | |
| 215 module:log("debug", line); | |
| 216 conn:write(line.."\r\n"); | |
| 217 end | |
| 218 end | |
| 219 end | |
| 220 if data then | |
| 221 session.data(data); | |
| 222 end | |
| 223 end | |
| 224 | |
| 225 function irc_listener.ondisconnect(conn, error) | |
| 226 local session = sessions[conn]; | |
| 227 if session then | |
| 228 for _, room in pairs(session.rooms) do | |
| 229 room:leave("Disconnected"); | |
| 230 end | |
| 231 if session.nick then | |
| 232 nicks[session.nick] = nil; | |
| 233 end | |
| 234 if session.full_jid then | |
| 235 jids[session.full_jid] = nil; | |
| 236 end | |
| 237 end | |
| 238 sessions[conn] = nil; | |
| 239 end | |
| 240 | |
| 241 function commands.NICK(session, args) | |
| 242 if session.nick then | |
| 243 session.send{from = muc_server, "484", "*", nick, "I'm afraid I can't let you do that"}; | |
| 244 --TODO Loop throug all rooms and change nick, with help from Verse. | |
| 245 return; | |
| 246 end | |
| 247 local nick = args[1]; | |
| 248 nick = nick:gsub("[^%w_]",""); | |
| 249 if nicks[nick] then | |
| 250 session.send{from=muc_server, "433", nick, "The nickname "..nick.." is already in use"}; | |
| 251 return; | |
| 252 end | |
| 253 local full_jid = jid.join(nick, component_jid, "ircd"); | |
| 254 jids[full_jid] = session; | |
| 255 jids[full_jid]["ar_last"] = {}; | |
| 256 nicks[nick] = session; | |
| 257 session.nick = nick; | |
| 258 session.full_jid = full_jid; | |
| 259 session.type = "c2s"; | |
| 260 | |
| 261 session.send{from = muc_server, "001", nick, "Welcome in the IRC to MUC XMPP Gateway, "..nick}; | |
| 262 session.send{from = muc_server, "002", nick, "Your host is "..muc_server.." running Prosody "..prosody.version}; | |
| 263 session.send{from = muc_server, "003", nick, "This server was created the "..os.date(nil, prosody.start_time)} | |
| 264 session.send{from = muc_server, "004", nick, table.concat({muc_server, "mod_ircd(alpha-0.8)", "i", "aoqv"}, " ")}; | |
| 265 session.send{from = muc_server, "375", nick, "- "..muc_server.." Message of the day -"}; | |
| 266 session.send{from = muc_server, "372", nick, "-"}; | |
| 267 session.send{from = muc_server, "372", nick, "- Please be warned that this is only a partial irc implementation,"}; | |
| 268 session.send{from = muc_server, "372", nick, "- it's made to facilitate users transiting away from irc to XMPP."}; | |
| 269 session.send{from = muc_server, "372", nick, "-"}; | |
| 270 session.send{from = muc_server, "372", nick, "- Prosody is _NOT_ an IRC Server and it never will."}; | |
| 271 session.send{from = muc_server, "372", nick, "- We also would like to remind you that this plugin is provided as is,"}; | |
| 272 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"}; | |
| 273 session.send{from = muc_server, "372", nick, "- risk as there's a not so little chance something will break."}; | |
| 274 | |
| 275 session.send{from = nick, "MODE", nick, "+i"}; -- why -> Invisible mode setting, | |
| 276 -- enforce by default on most servers (since the source host doesn't show it's sensible to have it "set") | |
| 277 end | |
| 278 | |
| 279 function commands.USER(session, params) | |
| 280 -- FIXME | |
| 281 -- Empty command for now | |
| 282 end | |
| 283 | |
| 284 local function mode_map(am, rm, nicks) | |
| 285 local rnick; | |
| 286 local c_modes; | |
| 287 c_modes = aff_modemap[am]..role_modemap[rm] | |
| 288 rnick = string.rep(nicks.." ", c_modes:len()) | |
| 289 if c_modes == "" then return nil, nil end | |
| 290 return c_modes, rnick | |
| 291 end | |
| 292 | |
| 293 function commands.JOIN(session, args) | |
| 294 local channel = args[1]; | |
| 295 if not channel then return end | |
| 296 local room_jid = irc2muc(channel); | |
| 297 print(session.full_jid); | |
| 298 if not jids[session.full_jid].ar_last[room_jid] then jids[session.full_jid].ar_last[room_jid] = {}; end | |
| 299 local room, err = c:join_room(room_jid, session.nick, { source = session.full_jid } ); | |
| 300 if not room then | |
| 301 return ":"..muc_server.." ERR :Could not join room: "..err | |
| 302 end | |
| 303 session.rooms[channel] = room; | |
| 304 room.channel = channel; | |
| 305 room.session = session; | |
| 306 session.send{from=session.nick, "JOIN", channel}; | |
| 307 if room.subject then | |
| 308 session.send{from=muc_server, 332, session.nick, channel ,room.subject}; | |
| 309 end | |
| 310 commands.NAMES(session, channel); | |
| 311 | |
| 312 room:hook("subject-changed", function(changed) | |
| 313 session.send((":%s TOPIC %s :%s"):format(changed.by.nick, channel, changed.to or "")); | |
| 314 end); | |
| 315 | |
| 316 room:hook("message", function(event) | |
| 317 if not event.body then return end | |
| 318 local nick, body = event.nick, event.body; | |
| 319 if nick ~= session.nick then | |
| 320 if body:sub(1,4) == "/me " then | |
| 321 body = "\1ACTION ".. body:sub(5) .. "\1" | |
| 322 end | |
| 323 local type = event.stanza.attr.type; | |
| 324 session.send{from=nick, "PRIVMSG", type == "groupchat" and channel or nick, body}; | |
| 325 --FIXME PM's probably won't work | |
| 326 end | |
| 327 end); | |
| 328 | |
| 329 room:hook("presence", function(ar) | |
| 330 local c_modes; | |
| 331 local rnick; | |
| 332 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 | |
| 333 local x_ar = ar.stanza:get_child("x", "http://jabber.org/protocol/muc#user") | |
| 334 if x_ar then | |
| 335 local xar_item = x_ar:get_child("item") | |
| 336 if xar_item and xar_item.attr and ar.stanza.attr.type ~= "unavailable" then | |
| 337 if xar_item.attr.affiliation and xar_item.attr.role then | |
| 338 if not jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] and | |
| 339 not jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] then | |
| 340 jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] = xar_item.attr.affiliation | |
| 341 jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] = xar_item.attr.role | |
| 342 c_modes, rnick = mode_map(xar_item.attr.affiliation, xar_item.attr.role, ar.nick); | |
| 343 if c_modes and rnick then session.send((":%s MODE %s +%s"):format(muc_server, channel, c_modes.." "..rnick)); end | |
| 344 else | |
| 345 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); | |
| 346 if c_modes and rnick then session.send((":%s MODE %s -%s"):format(muc_server, channel, c_modes.." "..rnick)); end | |
| 347 jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] = xar_item.attr.affiliation | |
| 348 jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] = xar_item.attr.role | |
| 349 c_modes, rnick = mode_map(xar_item.attr.affiliation, xar_item.attr.role, ar.nick); | |
| 350 if c_modes and rnick then session.send((":%s MODE %s +%s"):format(muc_server, channel, c_modes.." "..rnick)); end | |
| 351 end | |
| 352 end | |
| 353 end | |
| 354 end | |
| 355 end, -1); | |
| 356 end | |
| 357 | |
| 358 c:hook("groupchat/joined", function(room) | |
| 359 local session = room.session or jids[room.opts.source]; | |
| 360 local channel = "#"..room.jid:match("^(.*)@"); | |
| 361 session.send{from=session.nick.."!"..session.nick, "JOIN", channel}; | |
| 362 if room.topic then | |
| 363 session.send{from=muc_server, 332, room.topic}; | |
| 364 end | |
| 365 commands.NAMES(session, channel) | |
| 366 room:hook("occupant-joined", function(nick) | |
| 367 session.send{from=nick.nick.."!"..nick.nick, "JOIN", channel}; | |
| 368 end); | |
| 369 room:hook("occupant-left", function(nick) | |
| 370 jids[session.full_jid].ar_last[nick.jid:match("^(.*)/")][nick.nick] = nil; -- ugly | |
| 371 session.send{from=nick.nick.."!"..nick.nick, "PART", channel}; | |
| 372 end); | |
| 373 end); | |
| 374 | |
| 375 function commands.NAMES(session, channel) | |
| 376 local nicks = { }; | |
| 377 local room = session.rooms[channel]; | |
| 378 local symbols_map = { | |
| 379 owner = "~", | |
| 380 administrator = "&", | |
| 381 moderator = "@", | |
| 382 member = "+" | |
| 383 } | |
| 384 | |
| 385 if not room then return end | |
| 386 -- TODO Break this out into commands.NAMES | |
| 387 for nick, n in pairs(room.occupants) do | |
| 388 if n.affiliation == "owner" and n.role == "moderator" then | |
| 389 nick = symbols_map[n.affiliation]..nick; | |
| 390 elseif n.affiliation == "administrator" and n.role == "moderator" then | |
| 391 nick = symbols_map[n.affiliation]..nick; | |
| 392 elseif n.affiliation == "member" and n.role == "moderator" then | |
| 393 nick = symbols_map[n.role]..nick; | |
| 394 elseif n.affiliation == "member" and n.role == "partecipant" then | |
| 395 nick = symbols_map[n.affiliation]..nick; | |
| 396 elseif n.affiliation == "none" and n.role == "moderator" then | |
| 397 nick = symbols_map[n.role]..nick; | |
| 398 end | |
| 399 table.insert(nicks, nick); | |
| 400 end | |
| 401 nicks = table.concat(nicks, " "); | |
| 402 session.send((":%s 353 %s = %s :%s"):format(muc_server, session.nick, channel, nicks)); | |
| 403 session.send((":%s 366 %s %s :End of /NAMES list."):format(muc_server, session.nick, channel)); | |
| 404 session.send(":"..muc_server.." 353 "..session.nick.." = "..channel.." :"..nicks); | |
| 405 end | |
| 406 | |
| 407 function commands.PART(session, args) | |
| 408 local channel, part_message = unpack(args); | |
| 409 local room = channel and nodeprep(channel:match("^#(%w+)")) or nil; | |
| 410 if not room then return end | |
| 411 channel = channel:match("^([%S]*)"); | |
| 412 session.rooms[channel]:leave(part_message); | |
| 413 jids[session.full_jid].ar_last[room.."@"..muc_server] = nil; | |
| 414 session.send(":"..session.nick.." PART :"..channel); | |
| 415 end | |
| 416 | |
| 417 function commands.PRIVMSG(session, args) | |
| 418 local channel, message = unpack(args); | |
| 419 if message and #message > 0 then | |
| 420 if message:sub(1,8) == "\1ACTION " then | |
| 421 message = "/me ".. message:sub(9,-2) | |
| 422 end | |
| 423 message = utf8_clean(message); | |
| 424 if channel:sub(1,1) == "#" then | |
| 425 if session.rooms[channel] then | |
| 426 module:log("debug", "%s sending PRIVMSG \"%s\" to %s", session.nick, message, channel); | |
| 427 session.rooms[channel]:send_message(message); | |
| 428 end | |
| 429 else -- private message | |
| 430 local nick = channel; | |
| 431 module:log("debug", "PM to %s", nick); | |
| 432 for channel, room in pairs(session.rooms) do | |
| 433 module:log("debug", "looking for %s in %s", nick, channel); | |
| 434 if room.occupants[nick] then | |
| 435 module:log("debug", "found %s in %s", nick, channel); | |
| 436 local who = room.occupants[nick]; | |
| 437 -- FIXME PMs in verse | |
| 438 --room:send_private_message(nick, message); | |
| 439 local pm = st.message({type="chat",to=who.jid}, message); | |
| 440 module:log("debug", "sending PM to %s: %s", nick, tostring(pm)); | |
| 441 room:send(pm) | |
| 442 break | |
| 443 end | |
| 444 end | |
| 445 end | |
| 446 end | |
| 447 end | |
| 448 | |
| 449 function commands.PING(session, args) | |
| 450 session.send{from=muc_server, "PONG", args[1]}; | |
| 451 end | |
| 452 | |
| 453 function commands.TOPIC(session, message) | |
| 454 if not message then return end | |
| 455 local channel, topic = message[1], message[2]; | |
| 456 channel = utf8_clean(channel); | |
| 457 topic = utf8_clean(topic); | |
| 458 if not channel then return end | |
| 459 local room = session.rooms[channel]; | |
| 460 | |
| 461 if topic then room:set_subject(topic); end | |
| 462 end | |
| 463 | |
| 464 function commands.WHO(session, args) | |
| 465 local channel = args[1]; | |
| 466 if session.rooms[channel] then | |
| 467 local room = session.rooms[channel] | |
| 468 for nick in pairs(room.occupants) do | |
| 469 --n=MattJ 91.85.191.50 irc.freenode.net MattJ H :0 Matthew Wild | |
| 470 session.send{from=muc_server, 352, session.nick, channel, nick, nick, muc_server, nick, "H", "0 "..nick} | |
| 471 end | |
| 472 session.send{from=muc_server, 315, session.nick, channel, "End of /WHO list"}; | |
| 473 end | |
| 474 end | |
| 475 | |
| 476 function commands.MODE(session, args) -- FIXME | |
| 477 -- emptied for the time being, until something sane which works is available. | |
| 478 end | |
| 479 | |
| 480 function commands.QUIT(session, args) | |
| 481 session.send{"ERROR", "Closing Link: "..session.nick}; | |
| 482 for _, room in pairs(session.rooms) do | |
| 483 room:leave(args[1]); | |
| 484 end | |
| 485 jids[session.full_jid] = nil; | |
| 486 nicks[session.nick] = nil; | |
| 487 sessions[session.conn] = nil; | |
| 488 session:close(); | |
| 489 end | |
| 490 | |
| 491 function commands.RAW(session, data) | |
| 492 --c:send(data) | |
| 493 end | |
| 494 | |
| 495 local function desetup() | |
| 496 require "net.connlisteners".deregister("irc"); | |
| 497 end | |
| 498 | |
| 499 --c:hook("ready", function () | |
| 500 require "net.connlisteners".register("irc", irc_listener); | |
| 501 require "net.connlisteners".start("irc"); | |
| 502 --end); | |
| 503 | |
| 504 module:hook("module-unloaded", desetup) | |
| 505 | |
| 506 | |
| 507 --print("Starting loop...") | |
| 508 --verse.loop() |