comparison mod_ircd/mod_ircd.in.lua @ 491:5b3db688213d

mod_ircd: Fixed nick change logic (thanks mva), so that the self nick-change "flag" is removed properly, improved the logic to use verse's room_mt:change_nick (thanks Zash) yet to be pushed into main, added squished verse with the meta method included.
author Marco Cirillo <maranda@lightwitch.org>
date Fri, 02 Dec 2011 20:53:09 +0000
parents 00b77a9f2d5f
children 1a71e0e21a29
comparison
equal deleted inserted replaced
490:00b77a9f2d5f 491:5b3db688213d
12 12
13 local component_jid, component_secret, muc_server, port_number = 13 local component_jid, component_secret, muc_server, port_number =
14 module.host, nil, module:get_option_string("conference_server"), module:get_option_number("listener_port", 7000); 14 module.host, nil, module:get_option_string("conference_server"), module:get_option_number("listener_port", 7000);
15 15
16 if not muc_server then 16 if not muc_server then
17 module:log ("error", "You need to set the MUC server! halting.") 17 module:log ("error", "You need to set the MUC server! halting.")
18 return false; 18 return false;
19 end 19 end
20 20
21 package.loaded["util.sha1"] = require "util.encodings"; 21 package.loaded["util.sha1"] = require "util.encodings";
22 local verse = require "verse" 22 local verse = require "verse"
23 require "verse.component" 23 require "verse.component"
24 require "socket" 24 require "socket"
25 c = verse.new(); 25 c = verse.new();
26 c:add_plugin("groupchat"); 26 c:add_plugin("groupchat");
27 27
28 local function verse2prosody(e) 28 local function verse2prosody(e)
29 return c:event("stanza", e.stanza) or true; 29 return c:event("stanza", e.stanza) or true;
30 end 30 end
31 module:hook("message/bare", verse2prosody); 31 module:hook("message/bare", verse2prosody);
32 module:hook("message/full", verse2prosody); 32 module:hook("message/full", verse2prosody);
33 module:hook("presence/bare", verse2prosody); 33 module:hook("presence/bare", verse2prosody);
34 module:hook("presence/full", verse2prosody); 34 module:hook("presence/full", verse2prosody);
37 37
38 local jid = require "util.jid"; 38 local jid = require "util.jid";
39 local nodeprep = require "util.encodings".stringprep.nodeprep; 39 local nodeprep = require "util.encodings".stringprep.nodeprep;
40 40
41 local function utf8_clean (s) 41 local function utf8_clean (s)
42 local push, join = table.insert, table.concat; 42 local push, join = table.insert, table.concat;
43 local r, i = {}, 1; 43 local r, i = {}, 1;
44 if not(s and #s > 0) then 44 if not(s and #s > 0) then
45 return "" 45 return ""
46 end 46 end
47 while true do 47 while true do
48 local c = s:sub(i,i) 48 local c = s:sub(i,i)
49 local b = c:byte(); 49 local b = c:byte();
50 local w = ( 50 local w = (
51 (b >= 9 and b <= 10 and 0) or 51 (b >= 9 and b <= 10 and 0) or
52 (b >= 32 and b <= 126 and 0) or 52 (b >= 32 and b <= 126 and 0) or
53 (b >= 192 and b <= 223 and 1) or 53 (b >= 192 and b <= 223 and 1) or
54 (b >= 224 and b <= 239 and 2) or 54 (b >= 224 and b <= 239 and 2) or
55 (b >= 240 and b <= 247 and 3) or 55 (b >= 240 and b <= 247 and 3) or
56 (b >= 248 and b <= 251 and 4) or 56 (b >= 248 and b <= 251 and 4) or
57 (b >= 251 and b <= 252 and 5) or nil 57 (b >= 251 and b <= 252 and 5) or nil
58 ) 58 )
59 if not w then 59 if not w then
60 push(r, "?") 60 push(r, "?")
61 else 61 else
62 local n = i + w; 62 local n = i + w;
63 if w == 0 then 63 if w == 0 then
64 push(r, c); 64 push(r, c);
65 elseif n > #s then 65 elseif n > #s then
66 push(r, ("?"):format(b)); 66 push(r, ("?"):format(b));
67 else 67 else
68 local e = s:sub(i+1,n); 68 local e = s:sub(i+1,n);
69 if e:match('^[\128-\191]*$') then 69 if e:match('^[\128-\191]*$') then
70 push(r, c); 70 push(r, c);
71 push(r, e); 71 push(r, e);
72 i = n; 72 i = n;
73 else 73 else
74 push(r, ("?"):format(b)); 74 push(r, ("?"):format(b));
75 end 75 end
76 end 76 end
77 end 77 end
78 i = i + 1; 78 i = i + 1;
79 if i > #s then 79 if i > #s then
80 break 80 break
81 end 81 end
82 end 82 end
83 return join(r); 83 return join(r);
84 end 84 end
85 85
86 local function parse_line(line) 86 local function parse_line(line)
87 local ret = {}; 87 local ret = {};
88 if line:sub(1,1) == ":" then 88 if line:sub(1,1) == ":" then
89 ret.from, line = line:match("^:(%w+)%s+(.*)$"); 89 ret.from, line = line:match("^:(%w+)%s+(.*)$");
90 end 90 end
91 for part in line:gmatch("%S+") do 91 for part in line:gmatch("%S+") do
92 if part:sub(1,1) == ":" then 92 if part:sub(1,1) == ":" then
93 ret[#ret+1] = line:match(":(.*)$"); 93 ret[#ret+1] = line:match(":(.*)$");
94 break 94 break
95 end 95 end
96 ret[#ret+1]=part; 96 ret[#ret+1]=part;
97 end 97 end
98 return ret; 98 return ret;
99 end 99 end
100 100
101 local function build_line(parts) 101 local function build_line(parts)
102 if #parts > 1 then 102 if #parts > 1 then
103 parts[#parts] = ":" .. parts[#parts]; 103 parts[#parts] = ":" .. parts[#parts];
104 end 104 end
105 return (parts.from and ":"..parts.from.." " or "")..table.concat(parts, " "); 105 return (parts.from and ":"..parts.from.." " or "")..table.concat(parts, " ");
106 end 106 end
107 107
108 local function irc2muc(channel, nick) 108 local function irc2muc(channel, nick)
109 local room = channel and nodeprep(channel:match("^#(%w+)")) or nil; 109 local room = channel and nodeprep(channel:match("^#(%w+)")) or nil;
110 if not nick then 110 if not nick then
111 return jid.join(room, muc_server); 111 return jid.join(room, muc_server);
112 else 112 else
113 return jid.join(room, muc_server, nick); 113 return jid.join(room, muc_server, nick);
114 end 114 end
115 end 115 end
116 local function muc2irc(room) 116 local function muc2irc(room)
117 local channel, _, nick = jid.split(room); 117 local channel, _, nick = jid.split(room);
118 return "#"..channel, nick; 118 return "#"..channel, nick;
119 end 119 end
120 local role_map = { 120 local role_map = {
121 moderator = "@", 121 moderator = "@",
122 participant = "", 122 participant = "",
123 visitor = "", 123 visitor = "",
124 none = "" 124 none = ""
125 } 125 }
126 local aff_map = { 126 local aff_map = {
127 owner = "~", 127 owner = "~",
128 administrator = "&", 128 administrator = "&",
129 member = "+", 129 member = "+",
130 none = "" 130 none = ""
131 } 131 }
132 local role_modemap = { 132 local role_modemap = {
133 moderator = "o", 133 moderator = "o",
134 participant = "", 134 participant = "",
135 visitor = "", 135 visitor = "",
136 none = "" 136 none = ""
137 } 137 }
138 local aff_modemap = { 138 local aff_modemap = {
139 owner = "q", 139 owner = "q",
140 administrator = "a", 140 administrator = "a",
141 member = "v", 141 member = "v",
142 none = "" 142 none = ""
143 } 143 }
144 144
145 local irc_listener = { default_port = port_number, default_mode = "*l" }; 145 local irc_listener = { default_port = port_number, default_mode = "*l" };
146 146
147 local sessions = {}; 147 local sessions = {};
154 local st = require "util.stanza"; 154 local st = require "util.stanza";
155 155
156 local conference_server = muc_server; 156 local conference_server = muc_server;
157 157
158 local function irc_close_session(session) 158 local function irc_close_session(session)
159 session.conn:close(); 159 session.conn:close();
160 end 160 end
161 161
162 function irc_listener.onincoming(conn, data) 162 function irc_listener.onincoming(conn, data)
163 local session = sessions[conn]; 163 local session = sessions[conn];
164 if not session then 164 if not session then
165 session = { conn = conn, host = component_jid, reset_stream = function () end, 165 session = { conn = conn, host = component_jid, reset_stream = function () end,
166 close = irc_close_session, log = logger.init("irc"..(conn.id or "1")), 166 close = irc_close_session, log = logger.init("irc"..(conn.id or "1")),
167 rooms = {}, roster = {}, has_un = false }; 167 rooms = {}, roster = {}, has_un = false };
168 sessions[conn] = session; 168 sessions[conn] = session;
169 169
170 function session.data(data) 170 function session.data(data)
171 local parts = parse_line(data); 171 local parts = parse_line(data);
172 module:log("debug", require"util.serialization".serialize(parts)); 172 module:log("debug", require"util.serialization".serialize(parts));
173 local command = table.remove(parts, 1); 173 local command = table.remove(parts, 1);
174 if not command then 174 if not command then
175 return; 175 return;
176 end 176 end
177 command = command:upper(); 177 command = command:upper();
178 if not session.username and not session.nick then 178 if not session.username and not session.nick then
179 if not (command == "USER" or command == "NICK") then 179 if not (command == "USER" or command == "NICK") then
180 module:log("debug", "Client tried to send command %s before registering", command); 180 module:log("debug", "Client tried to send command %s before registering", command);
181 return session.send{from=muc_server, "451", command, "You have not completed the registration."} 181 return session.send{from=muc_server, "451", command, "You have not completed the registration."}
182 end 182 end
183 end 183 end
184 if commands[command] then 184 if commands[command] then
185 local ret = commands[command](session, parts); 185 local ret = commands[command](session, parts);
186 if ret then 186 if ret then
187 return session.send(ret); 187 return session.send(ret);
188 end 188 end
189 else 189 else
190 session.send{from=muc_server, "421", session.nick, command, "Unknown command"}; 190 session.send{from=muc_server, "421", session.nick, command, "Unknown command"};
191 return module:log("debug", "Unknown command: %s", command); 191 return module:log("debug", "Unknown command: %s", command);
192 end 192 end
193 end 193 end
194 194
195 function session.send(data) 195 function session.send(data)
196 if type(data) == "string" then 196 if type(data) == "string" then
197 return conn:write(data.."\r\n"); 197 return conn:write(data.."\r\n");
198 elseif type(data) == "table" then 198 elseif type(data) == "table" then
199 local line = build_line(data); 199 local line = build_line(data);
200 module:log("debug", line); 200 module:log("debug", line);
201 conn:write(line.."\r\n"); 201 conn:write(line.."\r\n");
202 end 202 end
203 end 203 end
204 end 204 end
205 205
206 if data then 206 if data then
207 session.data(data); 207 session.data(data);
208 end 208 end
209 end 209 end
210 210
211 function irc_listener.ondisconnect(conn, error) 211 function irc_listener.ondisconnect(conn, error)
212 local session = sessions[conn]; 212 local session = sessions[conn];
213 213
214 if session then 214 if session then
215 for _, room in pairs(session.rooms) do 215 for _, room in pairs(session.rooms) do
216 room:leave("Disconnected"); 216 room:leave("Disconnected");
217 end 217 end
218 if session.nick then 218 if session.nick then
219 nicks[session.nick] = nil; 219 nicks[session.nick] = nil;
220 end 220 end
221 if session.full_jid then 221 if session.full_jid then
222 jids[session.full_jid] = nil; 222 jids[session.full_jid] = nil;
223 end 223 end
224 if session.username then 224 if session.username then
225 usernames[session.username] = nil; 225 usernames[session.username] = nil;
226 end 226 end
227 end 227 end
228 sessions[conn] = nil; 228 sessions[conn] = nil;
229 end 229 end
230 230
231 local function nick_inuse(nick) 231 local function nick_inuse(nick)
232 if nicks[nick] then return true else return false end 232 if nicks[nick] then return true else return false end
233 end 233 end
234 local function check_username(un) 234 local function check_username(un)
235 local count = 0; 235 local count = 0;
236 local result; 236 local result;
237 237
238 for name, given in pairs(usernames) do 238 for name, given in pairs(usernames) do
239 if un == given then count = count + 1; end 239 if un == given then count = count + 1; end
240 end 240 end
241 241
242 result = count + 1; 242 result = count + 1;
243 243
244 if count > 0 then return tostring(un)..tostring(result); else return tostring(un); end 244 if count > 0 then return tostring(un)..tostring(result); else return tostring(un); end
245 end
246 local function change_nick_st(fulljid, roomjid, tonick)
247 return st.presence({ from = fulljid, to = roomjid, type = "unavailable" }):tag("status"):text("Changing nickname to: "..tonick):up();
248 end 245 end
249 local function set_t_data(session, full_jid) 246 local function set_t_data(session, full_jid)
250 session.full_jid = full_jid; 247 session.full_jid = full_jid;
251 jids[full_jid] = session; 248 jids[full_jid] = session;
252 jids[full_jid]["ar_last"] = {}; 249 jids[full_jid]["ar_last"] = {};
253 jids[full_jid]["nicks_changing"] = {}; 250 jids[full_jid]["nicks_changing"] = {};
254 251
255 if session.nick then nicks[session.nick] = session; end 252 if session.nick then nicks[session.nick] = session; end
256 end 253 end
257 local function send_motd(session) 254 local function send_motd(session)
258 local nick = session.nick; 255 local nick = session.nick;
259 session.send{from = muc_server, "001", nick, "Welcome in the IRC to MUC XMPP Gateway, "..nick}; 256 session.send{from = muc_server, "001", nick, "Welcome in the IRC to MUC XMPP Gateway, "..nick};
260 session.send{from = muc_server, "002", nick, "Your host is "..muc_server.." running Prosody "..prosody.version}; 257 session.send{from = muc_server, "002", nick, "Your host is "..muc_server.." running Prosody "..prosody.version};
261 session.send{from = muc_server, "003", nick, "This server was created the "..os.date(nil, prosody.start_time)} 258 session.send{from = muc_server, "003", nick, "This server was created the "..os.date(nil, prosody.start_time)}
262 session.send{from = muc_server, "004", nick, table.concat({muc_server, "mod_ircd(alpha-0.8)", "i", "aoqv"}, " ")}; 259 session.send{from = muc_server, "004", nick, table.concat({muc_server, "mod_ircd(alpha-0.8)", "i", "aoqv"}, " ")};
263 session.send{from = muc_server, "375", nick, "- "..muc_server.." Message of the day -"}; 260 session.send{from = muc_server, "375", nick, "- "..muc_server.." Message of the day -"};
264 session.send{from = muc_server, "372", nick, "-"}; 261 session.send{from = muc_server, "372", nick, "-"};
265 session.send{from = muc_server, "372", nick, "- Please be warned that this is only a partial irc implementation,"}; 262 session.send{from = muc_server, "372", nick, "- Please be warned that this is only a partial irc implementation,"};
266 session.send{from = muc_server, "372", nick, "- it's made to facilitate users transiting away from irc to XMPP."}; 263 session.send{from = muc_server, "372", nick, "- it's made to facilitate users transiting away from irc to XMPP."};
267 session.send{from = muc_server, "372", nick, "-"}; 264 session.send{from = muc_server, "372", nick, "-"};
268 session.send{from = muc_server, "372", nick, "- Prosody is _NOT_ an IRC Server and it never will."}; 265 session.send{from = muc_server, "372", nick, "- Prosody is _NOT_ an IRC Server and it never will."};
269 session.send{from = muc_server, "372", nick, "- We also would like to remind you that this plugin is provided as is,"}; 266 session.send{from = muc_server, "372", nick, "- We also would like to remind you that this plugin is provided as is,"};
270 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"}; 267 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"};
271 session.send{from = muc_server, "372", nick, "- risk as there's a not so little chance something will break."}; 268 session.send{from = muc_server, "372", nick, "- risk as there's a not so little chance something will break."};
272 269
273 session.send{from = nick, "MODE", nick, "+i"}; -- why -> Invisible mode setting, 270 session.send{from = nick, "MODE", nick, "+i"}; -- why -> Invisible mode setting,
274 -- enforce by default on most servers (since the source host doesn't show it's sensible to have it "set") 271 -- enforce by default on most servers (since the source host doesn't show it's sensible to have it "set")
275 end 272 end
276 273
277 function commands.NICK(session, args) 274 function commands.NICK(session, args)
278 local nick = args[1]; 275 local nick = args[1];
279 nick = nick:gsub("[^%w_]",""); 276 nick = nick:gsub("[^%w_]","");
280 277
281 if session.nick and not nick_inuse(nick) then -- changing nick 278 if session.nick and not nick_inuse(nick) then -- changing nick
282 local oldnick = session.nick; 279 local oldnick = session.nick;
283 280
284 -- update and replace session data 281 -- update and replace session data
285 session.nick = nick; 282 session.nick = nick;
286 nicks[oldnick] = nil; 283 nicks[oldnick] = nil;
287 nicks[nick] = session; 284 nicks[nick] = session;
288 285
289 session.send{from=oldnick.."!"..nicks[nick].username, "NICK", nick}; 286 session.send{from=oldnick.."!"..nicks[nick].username, "NICK", nick};
290 287
291 -- broadcast changes if required 288 -- broadcast changes if required
292 if session.rooms then 289 if session.rooms then
293 for id, room in pairs(session.rooms) do 290 session.nicks_changing[nick] = { oldnick, session.username };
294 session.nicks_changing[session.nick] = { oldnick, session.username }; 291
295 292 for id, room in pairs(session.rooms) do room:change_nick(nick); end
296 local node = jid.split(room.jid); 293
297 local oldjid = jid.join(node, muc_server, session.nick); 294 session.nicks_changing[nick] = nil;
298 local room_name = room.jid 295 end
299 296
300 room:send(change_nick_st(session.full_jid, jid.join(node, muc_server, oldnick), session.nick)); 297 return;
301 local room, err = c:join_room(room_name, session.nick, { source = session.full_jid } ); 298 elseif nick_inuse(nick) then
302 if not room then 299 session.send{from=muc_server, "433", nick, "The nickname "..nick.." is already in use"}; return;
303 session.send{from=nick.nick.."!"..session.username, "PART", id}; 300 end
304 return ":"..muc_server.." ERR :Failed to change nick and rejoin: "..err 301
305 end 302 session.nick = nick;
306 end 303 session.type = "c2s";
307 end 304 nicks[nick] = session;
308 305
309 return; 306 -- Some choppy clients send in NICK before USER, that needs to be handled
310 elseif nick_inuse(nick) then 307 if session.username then
311 session.send{from=muc_server, "433", nick, "The nickname "..nick.." is already in use"}; return; 308 set_t_data(session, jid.join(session.username, component_jid, "ircd"));
312 end 309 end
313 310
314 session.nick = nick; 311 if session.username and session.nick then -- send MOTD
315 session.type = "c2s"; 312 send_motd(session);
316 nicks[nick] = session; 313 end
317
318 -- Some choppy clients send in NICK before USER, that needs to be handled
319 if session.username then
320 set_t_data(session, jid.join(session.username, component_jid, "ircd"));
321 end
322
323 if session.username and session.nick then -- send MOTD
324 send_motd(session);
325 end
326 end 314 end
327 315
328 function commands.USER(session, params) 316 function commands.USER(session, params)
329 local username = params[1]; 317 local username = params[1];
330 318
331 if not session.has_un then 319 if not session.has_un then
332 local un_checked = check_username(username); 320 local un_checked = check_username(username);
333 321
334 usernames[un_checked] = username; 322 usernames[un_checked] = username;
335 session.username = un_checked; 323 session.username = un_checked;
336 session.has_un = true; 324 session.has_un = true;
337 325
338 if not session.full_jid then 326 if not session.full_jid then
339 set_t_data(session, jid.join(session.username, component_jid, "ircd")); 327 set_t_data(session, jid.join(session.username, component_jid, "ircd"));
340 end 328 end
341 else 329 else
342 return session.send{from=muc_server, "462", "USER", "You may not re-register."} 330 return session.send{from=muc_server, "462", "USER", "You may not re-register."}
343 end 331 end
344 332
345 if session.username and session.nick then -- send MOTD 333 if session.username and session.nick then -- send MOTD
346 send_motd(session); 334 send_motd(session);
347 end 335 end
348 end 336 end
349 337
350 local function mode_map(am, rm, nicks) 338 local function mode_map(am, rm, nicks)
351 local rnick; 339 local rnick;
352 local c_modes; 340 local c_modes;
353 c_modes = aff_modemap[am]..role_modemap[rm] 341 c_modes = aff_modemap[am]..role_modemap[rm]
354 rnick = string.rep(nicks.." ", c_modes:len()) 342 rnick = string.rep(nicks.." ", c_modes:len())
355 if c_modes == "" then return nil, nil end 343 if c_modes == "" then return nil, nil end
356 return c_modes, rnick 344 return c_modes, rnick
357 end 345 end
358 346
359 function commands.JOIN(session, args) 347 function commands.JOIN(session, args)
360 local channel = args[1]; 348 local channel = args[1];
361 if not channel then return end 349 if not channel then return end
362 local room_jid = irc2muc(channel); 350 local room_jid = irc2muc(channel);
363 351
364 if not jids[session.full_jid].ar_last[room_jid] then jids[session.full_jid].ar_last[room_jid] = {}; end 352 if not jids[session.full_jid].ar_last[room_jid] then jids[session.full_jid].ar_last[room_jid] = {}; end
365 local room, err = c:join_room(room_jid, session.nick, { source = session.full_jid } ); 353 local room, err = c:join_room(room_jid, session.nick, { source = session.full_jid } );
366 if not room then 354 if not room then
367 return ":"..muc_server.." ERR :Could not join room: "..err 355 return ":"..muc_server.." ERR :Could not join room: "..err
368 end 356 end
369 357
370 session.rooms[channel] = room; 358 session.rooms[channel] = room;
371 room.session = session; 359 room.session = session;
372 360
373 if session.nicks_changing[session.nick] then -- my own nick is changing 361 if session.nicks_changing[session.nick] then -- my own nick is changing
374 commands.NAMES(session, channel); 362 commands.NAMES(session, channel);
375 else 363 else
376 session.send{from=session.nick.."!"..session.username, "JOIN", channel}; 364 session.send{from=session.nick.."!"..session.username, "JOIN", channel};
377 if room.subject then 365 if room.subject then
378 session.send{from=muc_server, 332, session.nick, channel, room.subject}; 366 session.send{from=muc_server, 332, session.nick, channel, room.subject};
379 end 367 end
380 commands.NAMES(session, channel); 368 commands.NAMES(session, channel);
381 end 369 end
382 370
383 room:hook("subject-changed", function(changed) 371 room:hook("subject-changed", function(changed)
384 session.send((":%s TOPIC %s :%s"):format(changed.by.nick, channel, changed.to or "")); 372 session.send((":%s TOPIC %s :%s"):format(changed.by.nick, channel, changed.to or ""));
385 end); 373 end);
386 374
387 room:hook("message", function(event) 375 room:hook("message", function(event)
388 if not event.body then return end 376 if not event.body then return end
389 local nick, body = event.nick, event.body; 377 local nick, body = event.nick, event.body;
390 if nick ~= session.nick then 378 if nick ~= session.nick then
391 if body:sub(1,4) == "/me " then 379 if body:sub(1,4) == "/me " then
392 body = "\1ACTION ".. body:sub(5) .. "\1" 380 body = "\1ACTION ".. body:sub(5) .. "\1"
393 end 381 end
394 local type = event.stanza.attr.type; 382 local type = event.stanza.attr.type;
395 session.send{from=nick, "PRIVMSG", type == "groupchat" and channel or nick, body}; 383 session.send{from=nick, "PRIVMSG", type == "groupchat" and channel or nick, body};
396 --FIXME PM's probably won't work 384 --FIXME PM's probably won't work
397 end 385 end
398 end); 386 end);
399 387
400 room:hook("presence", function(ar) 388 room:hook("presence", function(ar)
401 local c_modes; 389 local c_modes;
402 local rnick; 390 local rnick;
403 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 391 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
404 local x_ar = ar.stanza:get_child("x", "http://jabber.org/protocol/muc#user") 392 local x_ar = ar.stanza:get_child("x", "http://jabber.org/protocol/muc#user")
405 if x_ar then 393 if x_ar then
406 local xar_item = x_ar:get_child("item") 394 local xar_item = x_ar:get_child("item")
407 if xar_item and xar_item.attr and ar.stanza.attr.type ~= "unavailable" then 395 if xar_item and xar_item.attr and ar.stanza.attr.type ~= "unavailable" then
408 if xar_item.attr.affiliation and xar_item.attr.role then 396 if xar_item.attr.affiliation and xar_item.attr.role then
409 if not jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] and 397 if not jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] and
410 not jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] then 398 not jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] then
411 jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] = xar_item.attr.affiliation 399 jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] = xar_item.attr.affiliation
412 jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] = xar_item.attr.role 400 jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] = xar_item.attr.role
413 c_modes, rnick = mode_map(xar_item.attr.affiliation, xar_item.attr.role, ar.nick); 401 n_self_changing = nicks[ar.nick] and nicks[ar.nick].nicks_changing and nicks[ar.nick].nicks_changing[ar.nick]
414 if c_modes and rnick then session.send((":%s MODE %s +%s"):format(muc_server, channel, c_modes.." "..rnick)); end 402 if n_self_changing then return; end
415 else 403 c_modes, rnick = mode_map(xar_item.attr.affiliation, xar_item.attr.role, ar.nick);
416 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); 404 if c_modes and rnick then session.send((":%s MODE %s +%s"):format(muc_server, channel, c_modes.." "..rnick)); end
417 if c_modes and rnick then session.send((":%s MODE %s -%s"):format(muc_server, channel, c_modes.." "..rnick)); end 405 else
418 jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] = xar_item.attr.affiliation 406 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);
419 jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] = xar_item.attr.role 407 if c_modes and rnick then session.send((":%s MODE %s -%s"):format(muc_server, channel, c_modes.." "..rnick)); end
420 c_modes, rnick = mode_map(xar_item.attr.affiliation, xar_item.attr.role, ar.nick); 408 jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["affiliation"] = xar_item.attr.affiliation
421 if c_modes and rnick then session.send((":%s MODE %s +%s"):format(muc_server, channel, c_modes.." "..rnick)); end 409 jids[session.full_jid].ar_last[ar.room_jid][ar.nick]["role"] = xar_item.attr.role
422 end 410 n_self_changing = nicks[ar.nick] and nicks[ar.nick].nicks_changing and nicks[ar.nick].nicks_changing[ar.nick]
423 end 411 if n_self_changing then return; end
424 end 412 c_modes, rnick = mode_map(xar_item.attr.affiliation, xar_item.attr.role, ar.nick);
425 end 413 if c_modes and rnick then session.send((":%s MODE %s +%s"):format(muc_server, channel, c_modes.." "..rnick)); end
426 end, -1); 414 end
415 end
416 end
417 end
418 end, -1);
427 end 419 end
428 420
429 c:hook("groupchat/joined", function(room) 421 c:hook("groupchat/joined", function(room)
430 local session = room.session or jids[room.opts.source]; 422 local session = room.session or jids[room.opts.source];
431 local channel = "#"..room.jid:match("^(.*)@"); 423 local channel = "#"..room.jid:match("^(.*)@");
432 424
433 room:hook("occupant-joined", function(nick) 425 room:hook("occupant-joined", function(nick)
434 if session.nicks_changing[nick.nick] then 426 if session.nicks_changing[nick.nick] then
435 session.send{from=session.nicks_changing[nick.nick][1].."!"..(session.nicks_changing[nick.nick][2] or "xmpp"), "NICK", nick.nick}; 427 session.send{from=session.nicks_changing[nick.nick][1].."!"..(session.nicks_changing[nick.nick][2] or "xmpp"), "NICK", nick.nick};
436 session.nicks_changing[nick.nick] = nil; 428 session.nicks_changing[nick.nick] = nil;
437 else 429 else
438 session.send{from=nick.nick.."!"..(nicks[nick.nick] and nicks[nick.nick].username or "xmpp"), "JOIN", channel}; 430 session.send{from=nick.nick.."!"..(nicks[nick.nick] and nicks[nick.nick].username or "xmpp"), "JOIN", channel};
439 end 431 end
440 end); 432 end);
441 room:hook("occupant-left", function(nick) 433 room:hook("occupant-left", function(nick)
442 if jids[session.full_jid] then jids[session.full_jid].ar_last[nick.jid:match("^(.*)/")][nick.nick] = nil; end 434 if jids[session.full_jid] then jids[session.full_jid].ar_last[nick.jid:match("^(.*)/")][nick.nick] = nil; end
443 local status_code = 435 local status_code =
444 nick.presence:get_child("x","http://jabber.org/protocol/muc#user") and 436 nick.presence:get_child("x","http://jabber.org/protocol/muc#user") and
445 nick.presence:get_child("x","http://jabber.org/protocol/muc#user"):get_child("status") and 437 nick.presence:get_child("x","http://jabber.org/protocol/muc#user"):get_child("status") and
446 nick.presence:get_child("x","http://jabber.org/protocol/muc#user"):get_child("status").attr.code; 438 nick.presence:get_child("x","http://jabber.org/protocol/muc#user"):get_child("status").attr.code;
447 439
448 440
449 if status_code == "303" then 441 if status_code == "303" then
450 local newnick = 442 local newnick =
451 nick.presence:get_child("x","http://jabber.org/protocol/muc#user") and 443 nick.presence:get_child("x","http://jabber.org/protocol/muc#user") and
452 nick.presence:get_child("x","http://jabber.org/protocol/muc#user"):get_child("item") and 444 nick.presence:get_child("x","http://jabber.org/protocol/muc#user"):get_child("item") and
453 nick.presence:get_child("x","http://jabber.org/protocol/muc#user"):get_child("item").attr.nick; 445 nick.presence:get_child("x","http://jabber.org/protocol/muc#user"):get_child("item").attr.nick;
454 446
455 session.nicks_changing[newnick] = { nick.nick, (nicks[nick.nick] and nicks[nick.nick].username or "xmpp") }; return; 447 session.nicks_changing[newnick] = { nick.nick, (nicks[nick.nick] and nicks[nick.nick].username or "xmpp") }; return;
456 end 448 end
457 449
458 local self_change = false; 450 for id, data in pairs(session.nicks_changing) do
459 for _, data in pairs(session.nicks_changing) do 451 if data[1] == nick.nick then return; end
460 if data[1] == nick.nick then self_change = true; break; end 452 end
461 end 453 session.send{from=nick.nick.."!"..(nicks[nick.nick] and nicks[nick.nick].username or "xmpp"), "PART", channel};
462 if self_change then return; end 454 end);
463 session.send{from=nick.nick.."!"..(nicks[nick.nick] and nicks[nick.nick].username or "xmpp"), "PART", channel};
464 end);
465 end); 455 end);
466 456
467 function commands.NAMES(session, channel) 457 function commands.NAMES(session, channel)
468 local nicks = { }; 458 local nicks = { };
469 if type(channel) == "table" then channel = channel[1] end 459 if type(channel) == "table" then channel = channel[1] end
470 460
471 local room = session.rooms[channel]; 461 local room = session.rooms[channel];
472 462
473 local symbols_map = { 463 local symbols_map = {
474 owner = "~", 464 owner = "~",
475 administrator = "&", 465 administrator = "&",
476 moderator = "@", 466 moderator = "@",
477 member = "+" 467 member = "+"
478 } 468 }
479 469
480 if not room then return end 470 if not room then return end
481 -- TODO Break this out into commands.NAMES 471 -- TODO Break this out into commands.NAMES
482 for nick, n in pairs(room.occupants) do 472 for nick, n in pairs(room.occupants) do
483 if n.affiliation == "owner" and n.role == "moderator" then 473 if n.affiliation == "owner" and n.role == "moderator" then
484 nick = symbols_map[n.affiliation]..nick; 474 nick = symbols_map[n.affiliation]..nick;
485 elseif n.affiliation == "administrator" and n.role == "moderator" then 475 elseif n.affiliation == "administrator" and n.role == "moderator" then
486 nick = symbols_map[n.affiliation]..nick; 476 nick = symbols_map[n.affiliation]..nick;
487 elseif n.affiliation == "member" and n.role == "moderator" then 477 elseif n.affiliation == "member" and n.role == "moderator" then
488 nick = symbols_map[n.role]..nick; 478 nick = symbols_map[n.role]..nick;
489 elseif n.affiliation == "member" and n.role == "partecipant" then 479 elseif n.affiliation == "member" and n.role == "partecipant" then
490 nick = symbols_map[n.affiliation]..nick; 480 nick = symbols_map[n.affiliation]..nick;
491 elseif n.affiliation == "none" and n.role == "moderator" then 481 elseif n.affiliation == "none" and n.role == "moderator" then
492 nick = symbols_map[n.role]..nick; 482 nick = symbols_map[n.role]..nick;
493 end 483 end
494 table.insert(nicks, nick); 484 table.insert(nicks, nick);
495 end 485 end
496 nicks = table.concat(nicks, " "); 486 nicks = table.concat(nicks, " ");
497 session.send((":%s 353 %s = %s :%s"):format(muc_server, session.nick, channel, nicks)); 487 session.send((":%s 353 %s = %s :%s"):format(muc_server, session.nick, channel, nicks));
498 session.send((":%s 366 %s %s :End of /NAMES list."):format(muc_server, session.nick, channel)); 488 session.send((":%s 366 %s %s :End of /NAMES list."):format(muc_server, session.nick, channel));
499 session.send(":"..muc_server.." 353 "..session.nick.." = "..channel.." :"..nicks); 489 session.send(":"..muc_server.." 353 "..session.nick.." = "..channel.." :"..nicks);
500 end 490 end
501 491
502 function commands.PART(session, args) 492 function commands.PART(session, args)
503 local channel, part_message = unpack(args); 493 local channel, part_message = unpack(args);
504 local room = channel and nodeprep(channel:match("^#(%w+)")) or nil; 494 local room = channel and nodeprep(channel:match("^#(%w+)")) or nil;
505 if not room then return end 495 if not room then return end
506 channel = channel:match("^([%S]*)"); 496 channel = channel:match("^([%S]*)");
507 session.rooms[channel]:leave(part_message); 497 session.rooms[channel]:leave(part_message);
508 jids[session.full_jid].ar_last[room.."@"..muc_server] = nil; 498 jids[session.full_jid].ar_last[room.."@"..muc_server] = nil;
509 session.send(":"..session.nick.." PART :"..channel); 499 session.send(":"..session.nick.." PART :"..channel);
510 end 500 end
511 501
512 function commands.PRIVMSG(session, args) 502 function commands.PRIVMSG(session, args)
513 local channel, message = unpack(args); 503 local channel, message = unpack(args);
514 if message and #message > 0 then 504 if message and #message > 0 then
515 if message:sub(1,8) == "\1ACTION " then 505 if message:sub(1,8) == "\1ACTION " then
516 message = "/me ".. message:sub(9,-2) 506 message = "/me ".. message:sub(9,-2)
517 end 507 end
518 message = utf8_clean(message); 508 message = utf8_clean(message);
519 if channel:sub(1,1) == "#" then 509 if channel:sub(1,1) == "#" then
520 if session.rooms[channel] then 510 if session.rooms[channel] then
521 module:log("debug", "%s sending PRIVMSG \"%s\" to %s", session.nick, message, channel); 511 module:log("debug", "%s sending PRIVMSG \"%s\" to %s", session.nick, message, channel);
522 session.rooms[channel]:send_message(message); 512 session.rooms[channel]:send_message(message);
523 end 513 end
524 else -- private message 514 else -- private message
525 local nick = channel; 515 local nick = channel;
526 module:log("debug", "PM to %s", nick); 516 module:log("debug", "PM to %s", nick);
527 for channel, room in pairs(session.rooms) do 517 for channel, room in pairs(session.rooms) do
528 module:log("debug", "looking for %s in %s", nick, channel); 518 module:log("debug", "looking for %s in %s", nick, channel);
529 if room.occupants[nick] then 519 if room.occupants[nick] then
530 module:log("debug", "found %s in %s", nick, channel); 520 module:log("debug", "found %s in %s", nick, channel);
531 local who = room.occupants[nick]; 521 local who = room.occupants[nick];
532 -- FIXME PMs in verse 522 -- FIXME PMs in verse
533 --room:send_private_message(nick, message); 523 --room:send_private_message(nick, message);
534 local pm = st.message({type="chat",to=who.jid}, message); 524 local pm = st.message({type="chat",to=who.jid}, message);
535 module:log("debug", "sending PM to %s: %s", nick, tostring(pm)); 525 module:log("debug", "sending PM to %s: %s", nick, tostring(pm));
536 room:send(pm) 526 room:send(pm)
537 break 527 break
538 end 528 end
539 end 529 end
540 end 530 end
541 end 531 end
542 end 532 end
543 533
544 function commands.PING(session, args) 534 function commands.PING(session, args)
545 session.send{from=muc_server, "PONG", args[1]}; 535 session.send{from=muc_server, "PONG", args[1]};
546 end 536 end
547 537
548 function commands.TOPIC(session, message) 538 function commands.TOPIC(session, message)
549 if not message then return end 539 if not message then return end
550 local channel, topic = message[1], message[2]; 540 local channel, topic = message[1], message[2];
551 channel = utf8_clean(channel); 541 channel = utf8_clean(channel);
552 topic = utf8_clean(topic); 542 topic = utf8_clean(topic);
553 if not channel then return end 543 if not channel then return end
554 local room = session.rooms[channel]; 544 local room = session.rooms[channel];
555 545
556 if topic then room:set_subject(topic); end 546 if topic then room:set_subject(topic); end
557 end 547 end
558 548
559 function commands.WHO(session, args) 549 function commands.WHO(session, args)
560 local channel = args[1]; 550 local channel = args[1];
561 if session.rooms[channel] then 551 if session.rooms[channel] then
562 local room = session.rooms[channel] 552 local room = session.rooms[channel]
563 for nick in pairs(room.occupants) do 553 for nick in pairs(room.occupants) do
564 session.send{from=muc_server, 352, session.nick, channel, nick, nick, muc_server, nick, "H", "0 "..nick} 554 session.send{from=muc_server, 352, session.nick, channel, nick, nick, muc_server, nick, "H", "0 "..nick}
565 end 555 end
566 session.send{from=muc_server, 315, session.nick, channel, "End of /WHO list"}; 556 session.send{from=muc_server, 315, session.nick, channel, "End of /WHO list"};
567 end 557 end
568 end 558 end
569 559
570 function commands.MODE(session, args) -- Empty command 560 function commands.MODE(session, args) -- Empty command
571 end 561 end
572 562
573 function commands.QUIT(session, args) 563 function commands.QUIT(session, args)
574 session.send{"ERROR", "Closing Link: "..session.nick}; 564 session.send{"ERROR", "Closing Link: "..session.nick};
575 for _, room in pairs(session.rooms) do 565 for _, room in pairs(session.rooms) do
576 room:leave(args[1]); 566 room:leave(args[1]);
577 end 567 end
578 jids[session.full_jid] = nil; 568 jids[session.full_jid] = nil;
579 nicks[session.nick] = nil; 569 nicks[session.nick] = nil;
580 usernames[session.username] = nil; 570 usernames[session.username] = nil;
581 sessions[session.conn] = nil; 571 sessions[session.conn] = nil;
582 session:close(); 572 session:close();
583 end 573 end
584 574
585 function commands.RAW(session, data) -- Empty command 575 function commands.RAW(session, data) -- Empty command
586 end 576 end
587 577
588 local function desetup() 578 local function desetup()
589 require "net.connlisteners".deregister("irc"); 579 require "net.connlisteners".deregister("irc");
590 end 580 end
591 581
592 require "net.connlisteners".register("irc", irc_listener); 582 require "net.connlisteners".register("irc", irc_listener);
593 require "net.connlisteners".start("irc"); 583 require "net.connlisteners".start("irc");
594 584
595 module:hook("module-unloaded", desetup) 585 module:hook("module-unloaded", desetup)
596