Fri May 27 13:04:13 ART 2005  Alberto Bertogli <albertogli@telpin.com.ar>
  tagged 3.5
Fri May 27 13:04:02 ART 2005  Alberto Bertogli <albertogli@telpin.com.ar>
  * Version 3.5
Fri May 27 12:51:44 ART 2005  Alberto Bertogli <albertogli@telpin.com.ar>
  * Mark users that don't have you on their contact list.
  Patch by Sebastian Santisi.
Fri May 27 12:40:21 ART 2005  Alberto Bertogli <albertogli@telpin.com.ar>
  * Add a command to print the differences between fwd and reverse lists.
Sun May 15 12:01:44 ART 2005  Alberto Bertogli <albertogli@telpin.com.ar>
  * Fix how nick changes are shown.
Sun May 15 12:00:24 ART 2005  Alberto Bertogli <albertogli@telpin.com.ar>
  * Quote nicks and groups properly.
  The change implemented for renames needs to be extended to all user and group
  names, since the server likes it better that way. So this change allows users
  and groups to be named with spaces and extended characters.
Sat May 14 01:44:48 ART 2005  Alberto Bertogli <albertogli@telpin.com.ar>
  * Don't allow lines longer than 1500 chars.
  As the max length for a message is 1500, never allow lines longer than that.
  Yes, it wastes a couple of bytes for the "m friend " or "r " beginning, but
  if you're writing a message _that_ long, a couple of bytes won't make a
  difference.
Sat May 14 01:05:04 ART 2005  Alberto Bertogli <albertogli@telpin.com.ar>
  * Fix renames to allow extended characters.
  It seems to be a bug in the MSN servers because it won't store extended
  characters in the nicks we set to people if they're sent url-encoded, so
  send them directly in UTF-8.
Sat May 14 00:37:46 ART 2005  Alberto Bertogli <albertogli@telpin.com.ar>
  * Show your new nick when changing it.
Sat May 14 00:36:31 ART 2005  Alberto Bertogli <albertogli@telpin.com.ar>
  * Rename "show nick changes" to "show realnick changes".
Sat May 14 00:33:24 ART 2005  Alberto Bertogli <albertogli@telpin.com.ar>
  * Small updates to msnrc.sample.
Sat May 14 00:32:09 ART 2005  Alberto Bertogli <albertogli@telpin.com.ar>
  * Allow renames with spaces.
Thu May 12 15:40:09 ART 2005  Alberto Bertogli <albertogli@telpin.com.ar>
  * Change colors for self rename message.
Thu May 12 15:35:54 ART 2005  Alberto Bertogli <albertogli@telpin.com.ar>
  * Implement show on real nick changes.
  This patch implements detection, showing and logging of real nicks changes,
  enabled optionally with the "show nick changes" msnrc option. Based on
  Sebastián Santisi's work.
  
Thu May 12 15:31:35 ART 2005  Alberto Bertogli <albertogli@telpin.com.ar>
  * Update docs and installer to use "INSTALL.txt"
Fri Apr 22 23:39:36 ART 2005  Alberto Bertogli <albertogli@telpin.com.ar>
  * Fix input buffer lenght check.
Tue Apr 12 17:55:10 ART 2005  Alberto Bertogli <albertogli@telpin.com.ar>
  * Small tab completion fix.
Tue Apr 12 17:53:47 ART 2005  Alberto Bertogli <albertogli@telpin.com.ar>
  * Return nicks only if they don't contain spaces.
Tue Apr 12 15:32:37 ART 2005  Alberto Bertogli <albertogli@telpin.com.ar>
  * Fix user renaming.
Tue Apr 12 15:32:16 ART 2005  Alberto Bertogli <albertogli@telpin.com.ar>
  * Handle Ctrl+W.
Tue Apr 12 15:30:53 ART 2005  Alberto Bertogli <albertogli@telpin.com.ar>
  * Fix tab completion.
Sat Apr  9 15:51:51 ART 2005  Alberto Bertogli <albertogli@telpin.com.ar>
  * Add an exporter for darcs changes.
Sat Apr  9 15:50:42 ART 2005  Alberto Bertogli <albertogli@telpin.com.ar>
  * Remove trailing whitespaces.
Sat Apr  9 15:44:12 ART 2005  Alberto Bertogli <albertogli@telpin.com.ar>
  * Implement tab completion.
  This patch implements tab completion not only for nicks/emails but also for
  commands. It still doesn't handle command arguments (like "status o<TAB>"
  doesn't complete "online"), but it's not that important anyway.
  
  It was sent by Sebastian Santisi <s@ntisi.com.ar> (I made only small
  modifications).
diff -rN -u old-msnlib/install new-msnlib/install
--- old-msnlib/install	2005-05-27 13:16:48.000000000 -0300
+++ new-msnlib/install	2005-05-12 14:42:14.000000000 -0300
@@ -15,7 +15,7 @@
 echo "*** Installing the documentation"
 rm -r $DESTDIR/doc/msnlib 2>/dev/null
 mkdir $DESTDIR/doc/msnlib 2>/dev/null
-cp -v README INSTALL $DESTDIR/doc/msnlib/
+cp -v README INSTALL.txt $DESTDIR/doc/msnlib/
 cp -Rv doc/* $DESTDIR/doc/msnlib/
 echo
 
@@ -26,6 +26,6 @@
 echo
 
 echo "*** Done"
-echo "Please read the INSTALL file to see the next step"
+echo "Please read the INSTALL.txt file to see the next step"
 echo
 
diff -rN -u old-msnlib/msn new-msnlib/msn
--- old-msnlib/msn	2005-05-27 13:16:48.000000000 -0300
+++ new-msnlib/msn	2005-05-27 12:59:26.000000000 -0300
@@ -39,6 +39,7 @@
 q		Quits the program
 w		Prints your entire contact list
 ww		Prints your entire contact list, including email addresses
+wd		Prints the differences between your forward and reverse lists
 e		Prints your online contacts
 ee		Prints your online contacts, including email addresses
 eg		Prints your online contacts with the groups
@@ -59,7 +60,7 @@
 color [theme]	Shows or set the color theme to "theme"
 close nick	Closes the switchboard connection with "nick"
 config		Shows the configuration
-info [nick]	Shows the user information and pending messages (if any), 
+info [nick]	Shows the user information and pending messages (if any),
 		or our personal info
 nick newnick	Changes your nick to "newnick"
 privacy p a	Sets whether accept messages from people not on your list (p)
@@ -115,6 +116,13 @@
 c = color_classes['default']()
 
 
+# command list for tab-completion purposes
+command_list = [ 'a', 'add', 'block', 'close', 'color', 'config', 'del', 'e',
+	'ee', 'eg', 'g', 'gadd', 'gdel', 'green', 'h', 'help', 'info',
+	'invite', 'lignore', 'luignore', 'm', 'nick', 'privacy', 'q', 'r',
+	'ren', 'status', 'unblock', 'w', 'wd', 'wr', 'ww' ]
+
+
 #
 # different useful prints
 #
@@ -160,19 +168,42 @@
 	ul.sort()
 	for email in ul:
 		u = userlist[email]
-		if u.status != 'FLN': 
+		if u.status != 'FLN':
 			hl = 1
 		else:
 			if only_online:	continue
 			hl = 0
 		status = msnlib.reverse_status[u.status]
-		printl('%7.7s :: %s ' % (status, u.nick), bold = hl)
+		printl('%7.7s :: %s' % (status, u.nick), bold = hl)
 		if include_emails:
-			printl('(%s) ' % (email), bold = hl)
+			printl(' (%s)' % (email), bold = hl)
 		if 'B' in u.lists:
-			printl('[!]')
+			printl(' [!]')
+		if email not in md.reverse.keys():
+			printl(' [X]')
 		printl('\n')
 
+def print_diff(md):
+	"Prints the differences between forward and reverse lists"
+	fwdl = md.users.keys()
+	fwdl.sort()
+
+	revl = md.reverse.keys()
+	revl.sort()
+
+	printl("People you have that don't have you:\n", bold = 1)
+	for email in fwdl:
+		if email not in revl:
+			user = md.users[email]
+			printl("    %s (%s)\n" % (user.nick, email))
+	printl('\n')
+
+	printl("People you don't have that have you:\n", bold = 1)
+	for email in revl:
+		if email not in fwdl:
+			user = md.reverse[email]
+			printl("    %s (%s)\n" % (user.nick, email))
+
 def print_group_list(md):
 	"Prints the group list"
 	gids = md.groups.keys()
@@ -202,13 +233,15 @@
 				if only_online: continue
 				hl = 0
 			status = msnlib.reverse_status[u.status]
-			printl('\t%7.7s :: %s ' % (status, u.nick), bold = hl)
+			printl('\t%7.7s :: %s' % (status, u.nick), bold = hl)
 			if include_emails:
-				printl('(%s) ' % (email), bold = hl)
+				printl(' (%s)' % (email), bold = hl)
 			if 'B' in u.lists:
-				printl('[!]')
+				printl(' [!]')
+			if email not in md.reverse.keys():
+				printl(' [X]')
 			printl('\n')
-			
+
 def print_user_info(email):
 	"Prints the user information, and pending messages"
 	u = m.users[email]
@@ -231,14 +264,14 @@
 	if u.priv.has_key('typing') and u.priv['typing']:
 		out += c.bold + 'Last typing at:\t' + c.normal
 		out += time.asctime(time.localtime(u.priv['typing'])) + '\n'
-	if u.sbd: 
+	if u.sbd:
 		out += c.bold + 'Switchboard:\t' + c.normal + str(u.sbd) + '\n'
 		if u.sbd.msgqueue:
 			out += c.bold + 'Pending messages:' + '\n'
 			for msg in u.sbd.msgqueue:
 				out += c.bold + '\t>>> ' + c.normal + msg + '\n'
 	printl(out)
-	
+
 def print_prompt():
 	"Prints the user prompt"
 	safe_write('\r' + c.red + c.bold + '[msn] ' + c.normal)
@@ -281,7 +314,7 @@
 	"Beeps unless it's told to be quiet"
 	if not q:
 		printl('\a')
-	
+
 
 def safe_flush():
 	"""Safely flushes stdout. It fixes a strange issue with flush and
@@ -344,31 +377,73 @@
 	else:
 		return None
 
-def matchemail(begin):
+def findemailnick(email, begin):
+	"""Check if the email/nick of the given user begins with the given
+	beginning. Returns 1 if it matches the nick, or 2 if it matches the
+	email."""
+	if m.users[email].nick.find(begin) == 0:
+		return 1
+	elif email.find(begin) == 0:
+		return 2
+	return 0
+
+# global variable for matchemail()
+start_from = 0
+def matchemail(begin, only_online = 0, start = 0):
 	""""Returns a matching email/nick for the given beginning; it avoids
-	nicks with spaces"""
-	l = len(begin)
-	# first try the ones with sbd
-	for email in m.users.keys():
-		if not m.users[email].sbd:
-			continue
-		nick = m.users[email].nick
-		if ' ' in nick:
-			nick = email
-		if len(nick) >= l and nick[0:l] == begin:
-			return nick
-	# then the nicks
-	for email in m.users.keys():
-		nick = m.users[email].nick
-		if ' ' in nick:
+	beginnings with spaces. If only_online is equal to 1 searchs only for
+	not offline users or users with an sbd.  If start=1 it iterates the
+	last match and do a cyclical search."""
+
+	global start_from
+
+	if ' ' in begin:
+		return None
+
+	emails = m.users.keys()
+
+	if start_from >= len(emails):
+		# the list has changed while iterating, reset
+		start_from = 0
+
+	if start:
+		pos = (start_from + 1) % len(emails)
+	else:
+		pos = start_from
+
+	found = 0
+	while not found:
+		# we made a complete loop without matches
+		if start and pos == start_from:
+			break
+		elif pos == (start_from - 1) % len(emails):
+			break
+
+		#msnlib.debug("l: %d %s\n" % (pos, emails[pos]))
+		current = emails[pos]
+		if only_online and m.users[current].status == 'FLN':
+			pos = (pos + 1) % len(emails)
 			continue
-		if len(nick) >= l and nick[0:l] == begin:
+		if findemailnick(emails[pos], begin):
+			found = 1
+			break
+		pos = (pos + 1) % len(emails)
+
+	start_from = pos
+
+	if found:
+		if findemailnick(emails[pos], begin) == 1:
+			# return the nick
+			nick = email2nick(emails[pos])
+			if ' ' in nick:
+				return emails[pos]
 			return nick
-	# and finally the emails
-	for email in m.users.keys():
-		if len(email) >= l and email[0:l] == begin:
-			return email
-	return None
+		else:
+			return emails[pos]
+	else:
+		start_from = 0
+		return None
+
 
 def gname2gid(gname):
 	"Returns a group name according to the given group id"
@@ -400,16 +475,16 @@
 def null(s):
 	"Null function, useful to void debug ones"
 	pass
-		
+
 def log_msg(email, type, msg, mtime = 0, users = []):
 	"""Logs the message or event of the 'type', related to 'email',
 	with the content 'msg', to a file in the specified directory.  See
 	documentation for more specific details, specially about
 	formatting."""
-	
+
 	if not config['log history']:
 		return
-	
+
 	if config['profile']:
 		prepend = config['profile'] + '::'
 	else:
@@ -445,7 +520,9 @@
 		out += '*** ' + msg + '\n'
 	elif type == 'multi':
 		out += '+++ ' + msg + '\n'
-	
+	elif type == 'realnick':
+		out += '--- ' + msg + '\n'
+
 	fd = open(file, 'a')
 	fd.write(out)
 	fd.close()
@@ -506,7 +583,21 @@
 # input buffer, where all the characters written by the user are stored in
 inbuf = ''
 
-# input history buffer, to store previous commands. 
+# vars to control the tabs completions:
+# match of commands
+matchc_last = 0
+matchc_status = 0
+matchc_root = ''
+# match of last send/received
+matchl_status = 0
+# match of m command
+matchm_status = 0
+matchm_root = ''
+# match of others arguments
+matchp_status = 0
+matchp_root = ''
+
+# input history buffer, to store previous commands.
 # we use a list [buffer, pointer] to avoid namespace pollution
 inbuf_history = [[], -1]
 
@@ -523,10 +614,33 @@
 		printl(out + '\n', c.green, 1)
 		redraw_cli()
 		return
-	
+
 	in_esc = 0
 	input = sys.stdin.read()
+
+	global matchc_last
+	global matchc_status
+	global matchc_root
+	global matchl_status
+	global matchm_status
+	global matchm_root
+	global matchp_status
+	global matchp_root
+
 	for char in input:
+		# decrease the flag of the tab completion of commands
+		if matchc_status != 0:
+			matchc_status = matchc_status - 1
+		# decrease the flag of the last received/last send completion
+		elif matchl_status != 0:
+			matchl_status = matchl_status - 1
+		# decrease the flag of the m completion
+		elif matchm_status != 0:
+			matchm_status = matchm_status - 1
+		# decrease the flag of the other arguments completion
+		elif matchp_status != 0:
+			matchp_status = matchp_status - 1
+
 		if char == '\r':
 			# replace \r with \n, so we handle mac keyboard input
 			# properly (it breaks \r\n tho, but nobody uses it)
@@ -538,14 +652,14 @@
 				del(inbuf_history[0][0])
 			inbuf_history[0].append(inbuf[:-1])
 			inbuf_history[1] = len(inbuf_history[0]) - 1 # moves the pointer
-			
+
 			safe_write(char)
 			tmpbuf = inbuf
 			inbuf = ''
 			out = parse_cmd(tmpbuf)
 			printl(out + '\n', c.green, 1)
 			redraw_cli()
-			
+
 		elif char == '\b' or ord(char) == 127:		# ^H / DEL
 			inbuf = inbuf[:-2]
 			redraw_cli()
@@ -553,12 +667,26 @@
 		elif ord(char) == 21:				# ^U
 			inbuf = ''
 			redraw_cli()
-			
+
+		elif ord(char) == 23:				# ^W
+			inbuf = inbuf[:-1]
+			inbuf = inbuf.rstrip()
+			pos = inbuf.rfind(' ')
+			if pos > 0:
+				inbuf = inbuf[:pos].rstrip() + ' '
+			else:
+				inbuf = ''
+			redraw_cli()
+
 		elif char == '\t':				# tab
+			p = inbuf.split()
+
 			# we do a basic cycling between the last received and
 			# last sent; first we build the two strings and then
 			# we see which one applies according to some messy
 			# logic
+			# FIXME: it fails if we haven't in our contact list
+			# the person with we are talking
 			if email2nick(last_received):
 				nick = email2nick(last_received)
 				if ' ' in nick:
@@ -574,59 +702,110 @@
 				mtolsent = 'm ' + nick + ' '
 			else:
 				mtolsent = None
-			
-			if len(inbuf) == 1:
+
+			# in an empty buffer we fill with the last received or
+			# the last sent
+			if len(p) == 0:
 				if mtolsent:
 					inbuf = mtolsent
+					matchl_status = 2
 				elif mtolrecv:
 					inbuf = mtolrecv
+					matchl_status = 2
+				else:
+					inbuf = inbuf[:-1]
+					beep()
+
+			# if in the last cycle we have replaced with
+			# mtolsent or mtolrecv we try to fill ciclical
+			elif mtolsent and mtolrecv and matchl_status == 1:
+				if inbuf.strip() == mtolrecv.strip():
+					inbuf = mtolsent
+					matchl_status = 2
+				else:
+					inbuf = mtolrecv
+					matchl_status = 2
+
+			# temporarily if not mtolsent or mtolrecv we beep
+			# FIXME it do nothing if there's only mtolrecv and
+			# it changes between two tabs
+			elif matchl_status == 1:
+				# it avoids that in the next iteration the
+				# empty buffer completion will be taked for
+				# an m completion
+				matchl_status = 2
+				inbuf = inbuf[:-1]
+				beep()
+
+			# we have something that is neither mtolsent or
+			# mtolrecv, if is the m command we try to find a
+   			# matching email/nick
+			elif p[0] == 'm' and len(p) == 2:
+				begin = p[1]
+				if matchm_status == 1:
+					begin = matchm_root
+
+				# we try to match with onlines contacts
+				# and contacts with sbd
+				email = matchemail(begin, 1, matchm_status)
+				if not email:
+					inbuf = inbuf[:-1]
+					beep()
 				else:
+					matchm_root = begin
+					matchm_status = 2
+					inbuf = 'm ' + email + ' '
+
+			# if there's an only word buffer we try to match
+			# with one of the commands
+			elif len(p) == 1:
+				# if it's the 2nd tab we build a ciclical
+				# matching; if not, we remember the last match
+				if matchc_status == 1:
+					p[0] = matchc_root
+					matchc_last = matchc_last + 1
+
+				found = 1
+				while found or matchc_last != len(command_list):
+					if matchc_last == len(command_list):
+						matchc_last = 0
+						found = 0
+						continue
+					elif command_list[matchc_last].find(p[0]) == 0:
+						matchc_status = 2
+						matchc_root = p[0]
+						break
+					matchc_last = matchc_last + 1
+				if matchc_last == len(command_list):
 					inbuf = inbuf[:-1]
 					beep()
+				else:
+					inbuf = command_list[matchc_last] + ' '
+
 			else:
-				# if we have mtolsent, replace with mtolrecv
-				# (if possible, otherwise beep)
-				if mtolsent and inbuf.strip() == mtolsent.strip():
-					if mtolrecv:
-						inbuf = mtolrecv
-					else:
-						inbuf = inbuf[:-1]
-						beep()
-				# the opposite case
-				elif mtolrecv and inbuf.strip() == mtolrecv.strip():
-					if mtolsent:
-						inbuf = mtolsent
-					else:
-						inbuf = inbuf[:-1]
-						beep()
-				# we have something that is neither mtolsent or
-				# mtolrecv, we try to find a matching
-				# email/nick
+				pn = p[len(p) - 1]
+				if matchp_status == 1:
+					pn = matchp_root
+
+				# we try to match with all of contacts
+				email = matchemail(pn, 0, matchp_status)
+				if not email:
+					inbuf = inbuf[:-1]
+					beep()
 				else:
-					p = inbuf.split()
-					if len(p) < 2:
-						# space + TAB or equivalent,
-						# just beep and ignore it
-						inbuf = inbuf[:-1]
-						beep()
-					elif p[0] != 'm' or len(p) != 2:
-						inbuf = inbuf[:-1]
-						beep()
-					else:
-						begin = p[1]
-						email = matchemail(begin)
-						if not email:
-							inbuf = inbuf[:-1]
-							beep()
-						else:
-							inbuf = 'm ' + email + ' '
+					matchp_root = pn
+					matchp_status = 2
+					inbuf = inbuf.rstrip()
+					pos = inbuf.rfind(' ')
+					inbuf = inbuf[:pos] + ' ' + email + ' '
+
 			redraw_cli()
-			
+
 		elif ord(char) == 4:				# EOT
 			safe_write('\n')
 			out = parse_cmd('')
 			printl(out + '\n', c.green, 1)
-			
+
 		elif ord(char) == 27:				# ESC
 			# we use in_esc for escape secuenses (composed of
 			# ESC + '[' + LETTER). 1 means got ESC, 2 means got
@@ -634,14 +813,25 @@
 			# generic handling
 			in_esc = 1
 			inbuf = inbuf[:-1]
-			
+
 		elif ord(char) < 32:				# unhandled control
 			msnlib.debug('Got weird char: %d' % ord(char))
 			redraw_cli_cond(char)
-			
+
 		else:						# normal
 			if not in_esc:
-				redraw_cli_cond(char)
+				# Never allow lines longer than 1500, since
+				# that's the max for a single message.
+				# Actually this calculates based on the whole
+				# buffer and not on just the message, but the
+				# code is nicer and 16 bytes won't make a
+				# difference.
+				if len(inbuf) > 1500:
+					inbuf = inbuf[:1500]
+					beep()
+					redraw_cli()
+				else:
+					redraw_cli_cond(char)
 				continue
 
 			# comes from a escape code
@@ -719,14 +909,14 @@
 def parse_cmd(cmd):
 	"""Parses the commands introduced by the user. It's pretty long and
 	boring, as expected."""
-	
+
 	global c, last_sent, last_received	# ugly but necesary
-	
+
 	if len(cmd) == 0:
 		quit()
 	elif len(cmd) == 1:
 		return ''
-			
+
 	# cut trailing newline and clean up
 	if cmd[-1] == '\n':
 		cmd = cmd[:-1]
@@ -742,7 +932,7 @@
 		if not cmd: return ''
 		cmd = s[0]
 		params = ''
-	
+
 
 	# parse
 	if   cmd == 'status': 		# change status
@@ -753,32 +943,35 @@
 			out += '\tonline, away, busy, brb, phone, lunch, invisible or idle'
 			return out
 		return 'Status changed to: %s' % params
-		
+
 	elif cmd == 'q':		# quit
 		quit()
-	
+
 	elif cmd == 'reload':		# reload callbacks
 		reload(msncb)
 		m.cb = msncb.cb()
-	
+
 	elif cmd == 'w':		# list
 		print_grouped_list(m)
-	
+
 	elif cmd == 'ww':		# list, include emails
 		print_grouped_list(m, include_emails = 1)
-	
+
 	elif cmd == 'wr':		# reverse list
 		print_list(m, userlist = m.reverse, include_emails = 1)
-	
+
+	elif cmd == 'wd':		# difference list
+		print_diff(m)
+
 	elif cmd == 'e':		# list (online only)
 		print_list(m, only_online = 1)
-	
+
 	elif cmd == 'eg':
 		print_grouped_list(m, only_online = 1)
 
 	elif cmd == 'ee':
 		print_grouped_list(m, only_online = 1, include_emails = 1)
-	
+
 	elif cmd == 'g':		# list groups
 		print_group_list(m)
 
@@ -789,7 +982,7 @@
 		except:
 			return 'Error parsing command'
 		m._send(cmd, pars)
-	
+
 	elif cmd == 'debug':		# enable/disable debugging
 		p = params.split()
 		if len(p) != 1:
@@ -804,7 +997,7 @@
 			return 'Debugging enabled'
 		else:
 			return 'Unknown parameter - must be "on" or "off"'
-	
+
 	elif cmd == 'config':		# show config variables
 		keys = config.keys()
 		keys.sort()
@@ -815,7 +1008,7 @@
 			printl(c.bold + var + ' = ' + c.normal + value + '\n')
 		printl(c.bold + 'use_termios = ' + str(use_termios) + '\n')
 		printl(c.bold + 'screensize = ' + str(winsize) + '\n')
-	
+
 	elif cmd == 'color':		# configure/show colors
 		p = params.split()
 		if len(p) != 1:
@@ -828,23 +1021,23 @@
 		else:
 			c = color_classes[p[0]]()
 			return "Changed theme to " + p[0]
-	
+
 	elif cmd == 'close':		# close a connection
 		p = params.split()
-		if len(p) != 1: 
+		if len(p) != 1:
 			return 'Error parsing command'
 		email = nick2email(p[0])
-		if not email: 
+		if not email:
 			return 'Unknown nick (%s)' % p[0]
 		if not m.users[email].sbd:
 			return 'No socket opened for %s' % p[0]
 		desc = str(m.users[email].sbd)
 		m.close(m.users[email].sbd)
 		return 'Closed socket %s' % desc
-	
+
 	elif cmd == 'privacy':		# set privacy mode
 		p = params.split()
-		if len(p) != 2: 
+		if len(p) != 2:
 			return 'Error parsing command'
 		try:
 			public = int(p[0])
@@ -854,7 +1047,7 @@
 		except:
 			return 'Error: both parameters must be 1 or 0'
 		m.privacy(public, auth)
-	
+
 	elif cmd == 'lignore':		# ignore a user locally
 		p = params.split()
 		if len(p) == 0:
@@ -865,21 +1058,21 @@
 		email = nick2email(p[0])
 		if not email:
 			return 'Unknown nick (%s)' % p[0]
-		if email in ignored: 
+		if email in ignored:
 			return 'User is already being locally ignored'
 		ignored.append(email)
 		return 'User is now being locally ignored'
-	
+
 	elif cmd == 'lunignore':	# unignore a locally ignored user
 		p = params.split()
 		if len(p) == 0:
 			return 'Error parsing command'
 		email = nick2email(p[0])
 		if email not in ignored:
-			return 'User is not being locally ignored' 
+			return 'User is not being locally ignored'
 		ignored.remove(email)
 		return 'User is no longer locally ignored'
-	
+
 	elif cmd == 'block':
 		p = params.split()
 		if len(p) == 0:
@@ -889,7 +1082,7 @@
 			return 'Unknown nick (%s)' % p[0]
 		m.userblock(email)
 		return 'User %s blocked' % email
-	
+
 	elif cmd == 'unblock':
 		p = params.split()
 		if len(p) == 0:
@@ -899,15 +1092,15 @@
 			return 'Unknown nick (%s)' % p[0]
 		m.userunblock(email)
 		return 'User %s unblocked' % email
-	
+
 	elif cmd == 'add':		# add a user
 		p = params.split()
-		if   len(p) == 0: 
+		if   len(p) == 0:
 			return 'Error parsing command'
-		elif len(p) == 1: 
+		elif len(p) == 1:
 			email = nick = p[0]
 			gid = '0'
-		elif len(p) == 2: 
+		elif len(p) == 2:
 			email = p[0]
 			nick = p[1]
 			gid = '0'
@@ -920,7 +1113,7 @@
 			if gid not in m.groups.keys():
 				return 'Unknown group'
 		m.useradd(email, nick, gid)
-	
+
 	elif cmd == 'del':		# delete a user
 		p = params.split()
 		if len(p) != 1: return 'Error parsing command'
@@ -928,23 +1121,22 @@
 		if not email:
 			return 'Unknown nick (%s)' % p[0]
 		m.userdel(email)
-	
+
 	elif cmd == 'ren':		# rename a user
-		p = params.split()
-		if len(p) != 2: return 'Error parsing command'
+		p = params.split(None, 1)
+		if len(p) < 2: return 'Error parsing command'
 		email = nick2email(p[0])
 		if not email:
 			return 'Unkown nick (%s)' % p[0]
-		newnick = p[1]
+		newnick = p[1].strip()
 		u = m.users[email]
-		m.userdel(email)
-		m.useradd(email, newnick, u.gid)
-	
+		m.userren(email, newnick)
+
 	elif cmd == 'gadd':		# add a group
 		p = params.split()
 		if len(p) != 1: return 'Error parsing command'
 		m.groupadd(p[0])
-	
+
 	elif cmd == 'gdel':		# delete a group
 		p = params.split()
 		if len(p) != 1: return 'Error parsing command'
@@ -959,7 +1151,7 @@
 				printl('User %s (%s) will be deleted\n' % \
 					(u.nick, e), bold = 1)
 		m.groupdel(gid)
-	
+
 	elif cmd == 'gren':		# rename a group
 		p = params.split()
 		if len(p) != 2: return 'Error parsing command'
@@ -970,7 +1162,7 @@
 		if gid not in m.groups.keys():
 			return 'Unknown group'
 		m.groupren(gid, newname)
-	
+
 	elif cmd == 'invite':		# invite a user to an existing sbd
 		p = params.split()
 		if len(p) != 3: return 'Error parsing command'
@@ -986,12 +1178,12 @@
 		if not dst_sbd:
 			return 'No current chat with user %s' % dst
 		m.invite(email, dst_sbd)
-			
+
 	elif cmd == 'nick':		# change our nick
 		if len(params) < 1: return 'Error parsing command'
 		nick = params
 		m.change_nick(nick)
-	
+
 	elif cmd == 'info':		# user info
 		p = params.split()
 		if len(p) != 1:
@@ -1016,10 +1208,10 @@
 			if not email:
 				return 'Unknown nick (%s)' % str(p[0])
 			print_user_info(email)
-	
+
 	elif cmd == 'sync':		# manual sync
 		m.sync()
-	
+
 	elif cmd == 'h':		# show history
 		printl('Incoming Message History (last %d messages)\n' \
 			% config['history size'], c.green, 1)
@@ -1058,7 +1250,7 @@
 			return 'Unable to send message: User is offline'
 		if (m.status == 'FLN' or m.status == 'HDN') and not m.users[email].sbd:
 			return 'Unable to send message: Not allowed when offline'
-			
+
 		r = m.sendmsg(email, msg)
 		last_sent = email
 		if r == 1:
@@ -1074,12 +1266,12 @@
 			return 'Message too big'
 		else:
 			return 'Error %d sending message' % r
-		
+
 	elif cmd == 'help' or cmd == '?':
 		return help
 	else:
 		return 'Unknown command, type "help" for help'
-	
+
 	return ''
 
 
@@ -1098,13 +1290,21 @@
 	t = params.split(' ')
 	status = msnlib.reverse_status[t[0]]
 	email = t[1]
+	rnick = urllib.unquote(t[2])
 	nick = md.users[email].nick
 	ctime = time.strftime('%I:%M:%S%p', now())
+
 	printl('\r%s ' % ctime, c.blue)
 	printl(nick, c.blue, 1)
-	printl(' changed status to ', c.magenta)
-	printl('%s\n' % status, c.magenta, 1)
+	printl(' is ', c.magenta)
+	printl('%s' % status, c.magenta, 1)
 	log_msg(email, 'status', status)
+	if config["show realnick changes"]:
+		printl(' with realnick ', c.magenta)
+		printl('%s' % rnick, c.magenta, 1)
+		log_msg(email, 'realnick', rnick)
+	printl('\n')
+
 	msncb.cb_iln(md, type, tid, params)
 m.cb.iln = cb_iln
 
@@ -1112,13 +1312,36 @@
 	status = msnlib.reverse_status[tid]
 	t = params.split(' ')
 	email = t[0]
+	if len(params) > 1:
+		rnick = urllib.unquote(t[1])
+	else:
+		rnick = ''
+
 	nick = md.users[email].nick
+	realnick = md.users[email].realnick
 	ctime = time.strftime('%I:%M:%S%p', now())
-	printl('\r%s ' % ctime, c.blue)
-	printl(nick, c.blue, 1)
-	printl(' changed status to ', c.magenta)
-	printl('%s\n' % status, c.magenta, 1)
-	log_msg(email, 'status', status)
+
+	if tid != md.users[email].status:
+		printl('\r%s ' % ctime, c.blue)
+		printl(nick, c.blue, 1)
+		printl(' changed status to ', c.magenta)
+		printl('%s' % status, c.magenta, 1)
+		log_msg(email, 'status', status)
+		# if we don't know the realnick yet, include it in the same line
+		if not realnick and config["show realnick changes"]:
+			printl(' with realnick ', c.magenta)
+			printl('%s' % rnick, c.magenta, 1)
+			log_msg(email, 'realnick', rnick)
+		printl("\n")
+
+	if realnick and rnick and realnick != rnick \
+			and config["show realnick changes"]:
+		printl("\r%s " % ctime, c.blue)
+		printl(nick, c.blue, 1)
+		printl(' changed the realnick to ', c.magenta)
+		printl('%s\n' % rnick, c.magenta, 1)
+		log_msg(email, 'realnick', rnick)
+
 	msncb.cb_nln(md, type, tid, params)
 m.cb.nln = cb_nln
 
@@ -1162,7 +1385,7 @@
 	global last_received
 	t = tid.split(' ')
 	email = t[0]
-	
+
 	# parse
 	lines = params.split('\n')
 	headers = {}
@@ -1177,13 +1400,13 @@
 		headers[type] = value
 		eoh += 1
 	eoh +=1
-	
+
 	# handle special hotmail messages
 	if email == 'Hotmail':
 		if not headers.has_key('Content-Type'):
 			return
 		hotmail_info = {}
-		
+
 		# parse the body
 		for i in lines:
 			i = i.strip()
@@ -1193,7 +1416,7 @@
 			type = tv[0]
 			value = tv[1].strip()
 			hotmail_info[type] = value
-					
+
 		msnlib.debug(params)
 		if headers['Content-Type'] == 'text/x-msmsgsinitialemailnotification; charset=UTF-8':
 			newmsgs = int(hotmail_info['Inbox-Unread'])
@@ -1211,7 +1434,7 @@
 				c.green, 1)
 			printl('\r\tSubject: %s\n' % subject, c.green, 1)
 		return
-	
+
 	if headers.has_key('Content-Type') and headers['Content-Type'] == 'text/x-msmsgscontrol':
 		# the typing notices
 		nick = email2nick(email)
@@ -1240,10 +1463,10 @@
 			log_msg(email, 'in', string.join(lines[eoh:], '\n'))
 
 		# append the message to the history, keeping it below the configured limit
-		if len(history_ring) > config['history size']: 
+		if len(history_ring) > config['history size']:
 			del(history_ring[0])
 		history_ring.append((time.time(), email, lines[eoh:]))
-	
+
 	last_received = email
 	msncb.cb_msg(md, type, tid, params, sbd)
 m.cb.msg = cb_msg
@@ -1275,7 +1498,7 @@
 	uid, ucount, email, realnick = p
 	nick = email2nick(email)
 	if not nick: nick = email
-	
+
 	if ucount == '1':
 		# do nothing if we only have one participant
 		pass
@@ -1304,7 +1527,7 @@
 	perror(desc)
 	msncb.cb_err(md, errno, params)
 m.cb.err = cb_err
-	
+
 # users add, delete and modify
 def cb_add(md, type, tid, params):
 	t = params.split(' ')
@@ -1341,6 +1564,21 @@
 	msncb.cb_rem(md, type, tid, params)
 m.cb.rem = cb_rem
 
+def cb_rea(md, type, tid, params):
+	t = params.split(' ')
+	email = t[1]
+	nick = urllib.unquote(t[2])
+	if email != md.email:
+		out = '\r' + c.blue + c.bold + email + ' ' + c.magenta \
+			+ 'has been renamed to ' + c.bold + nick + '\n'
+		printl(out)
+	else:
+		out = '\r' + c.magenta + 'Your nick has been changed to ' \
+			+ c.bold + nick + '\n'
+		printl(out)
+	msncb.cb_rea(md, type, tid, params)
+m.cb.rea = cb_rea
+
 def cb_adg(md, type, tid, params):
 	t = params.split(' ')
 	lver, name, gid = t[0:3]
@@ -1383,7 +1621,7 @@
 #
 # now the real thing
 #
-printl('* MSN Client (3.4) *\n', c.yellow, 1)
+printl('* MSN Client (3.5) *\n', c.yellow, 1)
 
 # first, the configuration
 printl('Loading config... ', c.green, 1)
@@ -1407,9 +1645,9 @@
 config['profile'] = profile
 
 # set the mandatory values
-if config.has_key('email'): 
+if config.has_key('email'):
 	m.email = config['email']
-else: 
+else:
 	perror('Error: email not specified in config file\n')
 	quit(1)
 
@@ -1427,7 +1665,7 @@
 
 # and the optional ones, setting the defaults if not present
 # history size
-if not config.has_key('history size'): 
+if not config.has_key('history size'):
 	config['history size'] = 10
 else:
 	try:
@@ -1447,7 +1685,7 @@
 		config['input history size'] = 10
 
 # initial status
-if not config.has_key('initial status'): 
+if not config.has_key('initial status'):
 	config['initial status'] = 'online'
 elif config['initial status'] not in msnlib.status_table.keys():
 	perror('unknown initial status, using default\n')
@@ -1477,6 +1715,12 @@
 if not config.has_key('history directory'):
 	config['history directory'] = os.environ['HOME'] + '/.msn/history'
 
+# show realnick changes
+if not config.has_key('show realnick changes'):
+	config['show realnick changes'] = 0
+elif config['show realnick changes'] != 'yes':
+	config['show realnick changes'] = 0
+
 # auto away time
 if not config.has_key('auto away'):
 	config['auto away'] = 0
@@ -1497,7 +1741,7 @@
 		config['encoding'] = os.environ['LC_ALL']
 	elif os.environ.has_key('LANG') and os.environ['LANG']:
 		config['encoding'] = os.environ['LANG']
-	else:	
+	else:
 		config['encoding'] = 'iso-8859-1'
 m.encoding = config['encoding']
 
@@ -1568,14 +1812,14 @@
 		fds = select.select(infd, outfd, [], timeout)
 	except KeyboardInterrupt:
 		quit()
-	
+
 	if timeout and len(fds[0] + fds[1]) == 0:
 		# timeout, set auto away
 		if m.status == 'NLN':
 			m.change_status('away')
 			auto_away = 1
 			printl('\rAutomatically changing status to away\n', c.green, 1)
-	
+
 	for i in fds[0] + fds[1]:		# see msnlib.msnd.pollable.__doc__
 		if i == sys.stdin:
 			# auto away revival
@@ -1588,7 +1832,7 @@
 		else:
 			try:
 				m.read(i)
-				
+
 				# see if we got all the user list, so we can
 				# change our initial status (doing it earlier
 				# as we used to seems to break things for some
@@ -1601,8 +1845,8 @@
 					else:
 						perror('\rError setting status: unknown status %s\n' % config['initial status'])
 
-				
-					
+
+
 			except ('SocketError', socket.error), err:
 				if i != m:
 					if i.msgqueue:
diff -rN -u old-msnlib/msncb.py new-msnlib/msncb.py
--- old-msnlib/msncb.py	2005-05-27 13:16:48.000000000 -0300
+++ new-msnlib/msncb.py	2005-04-09 15:50:02.000000000 -0300
@@ -75,7 +75,7 @@
 		self.bye = cb_bye	# switchboard user disconnect
 
 
-	
+
 error_table = {
 	-10: 'Local error',
 	200: 'Syntax error',
@@ -136,7 +136,7 @@
 	debug('Error! unknown event type "%s"' % type)
 	debug('params: ' + str(params))
 
-	
+
 def cb_chl(md, type, tid, params):
 	"Handles the challenges"
 	if type != 'CHL': raise 'CallbackMess', (md, type, params)
@@ -154,7 +154,7 @@
 def cb_out(md, type, tid, params):
 	"Server disconnected us"
 	debug('!!! Server closed the connection: ' + params)
-	
+
 
 def cb_iln(md, type, tid, params):
 	"Handles a friend status change"
@@ -183,7 +183,7 @@
 	email = t[0]
 	if len(t) > 1: nick = urllib.unquote(t[1])
 	else: nick = ''
-	
+
 	md.users[email].status = status
 	md.users[email].realnick = nick
 	debug('FRIEND %s (%s) changed status to :%s:' % (nick, email, status))
@@ -222,15 +222,15 @@
 	t = params.split()
 	if len(t) != 3:
 		raise "SYNError"
-	
+
 	lver = int(t[0])
 	total = int(t[1])
 	ngroups = int(t[2])
-	
+
 	md.syn_lver = lver
 	md.syn_total = total
 	md.syn_ngroups = ngroups
-	
+
 
 def cb_lst(md, type, tid, params):
 	p = params.split(' ')
@@ -241,15 +241,15 @@
 		groups = p[2]
 	else:
 		groups = '0'
-	
+
 	# we only use one main group id
 	gid = groups.split(',')[0]
-	
+
 	if email in md.users.keys():
 		user = md.users[email]
 	else:
 		user = msnlib.user(email, nick, gid)
-	
+
 	# the list mask is a bitmask, composed of:
 	# FL: 1
 	# AL: 2
@@ -260,22 +260,22 @@
 	if listmask & 1:
 		user.lists.append('F')
 		md.users[email] = user
-	
+
 	# in reverse
 	if listmask & 8:
 		user.lists.append('R')
 		md.reverse[email] = user
-	
+
 	# in allow
 	if listmask & 2:
 		user.lists.append('A')
-	
+
 	# in block
 	if listmask & 4:
 		user.lists.append('B')
-	
+
 	md.lst_total += 1
-	
+
 	# save in the global last_lst the email, because BPRs might need it
 	md._last_lst = email
 
@@ -297,7 +297,7 @@
 	type = t[0]
 	if len(t) > 1: param = urllib.unquote(t[1])
 	else: param = ''
-	
+
 	if   type == 'PHH': md.homep = param
 	elif type == 'PHW': md.workp = param
 	elif type == 'PHM': md.mobilep = param
@@ -305,7 +305,7 @@
 
 
 def cb_add(md, type, tid, params):
-	"Handles a user add; both you adding a user and a user adding you" 
+	"Handles a user add; both you adding a user and a user adding you"
 	t = params.split(' ')
 	type = t[0]
 	if type == 'RL':
@@ -387,14 +387,14 @@
 	port = int(port)
 	hash = t[2]
 	email = t[3]
-	
+
 	fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 	# we set the socket nonblocking so we don't block (duh!) on connect();
 	# it will be picked up later from the select loop and handled via the
 	# main read() call, which you will have to see to find out the rest.
 	fd.setblocking(0)
 	fd.connect_ex((ip, port))
-	
+
 	sbd = msnlib.sbd()
 	sbd.fd = fd
 	sbd.block = 0
@@ -417,7 +417,7 @@
 	fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 	fd.setblocking(0)		# see cb_rng
 	fd.connect_ex((ip, port))
-	
+
 	# look for the sbd, matching the tid
 	sbd = None
 	for i in md.sb_fds:
@@ -427,7 +427,7 @@
 	if not sbd:
 		debug('AIEEE: XFR without sbd!')
 		raise 'XFRError', (type, tid, params)
-	
+
 	sbd.fd = fd
 	sbd.block = 0
 	sbd.state = 'cp'
@@ -492,7 +492,7 @@
 def cb_nak(md, type, tid, params, sbd):
 	"Get a message negative acknowledge"
 	debug('NAK: tid:%s' % tid)
-	
+
 
 def cb_bye(md, type, tid, params, sbd):
 	"Handles a user sb disconnect"
diff -rN -u old-msnlib/msnlib.py new-msnlib/msnlib.py
--- old-msnlib/msnlib.py	2005-05-27 13:16:48.000000000 -0300
+++ new-msnlib/msnlib.py	2005-05-27 12:59:42.000000000 -0300
@@ -14,7 +14,7 @@
 """
 
 # constants
-VERSION = 0x0304
+VERSION = 0x0305
 LOGIN_HOST = 'messenger.hotmail.com'
 LOGIN_PORT = 1863
 
@@ -47,6 +47,12 @@
 	sys.stderr.write('\r' + str(s) + '\n')
 	sys.stderr.flush()
 
+def nickquote(nick):
+	"""Quotes a nick the way the server likes it: replacing spaces with
+	'%20' but leaving extender characters alone, as they get sent UTF-8
+	encoded."""
+	nick = nick.replace(' ', '%20')
+	return nick
 
 class user:
 	"""User class, used to store your 'friends'"""
@@ -63,7 +69,7 @@
 		self.sbd = None
 		self.priv = {}
 		self.lists = []
-	
+
 	def __repr__(self):
 		return '<user email:%s nick:"%s" gid:%s>' % (self.email,
 				self.nick, self.gid)
@@ -73,18 +79,18 @@
 	"""SwitchBoard Descriptor
 	Used as a pseudo-fd to store per-switchboard connection information.
 	The state is either one of (too many):
-	
+
 	[answer]
 	cp	connect pending (just came from rng)
 	re	ready (just came from connect)
 	an	waiting for answer reply
-	
+
 	[invite]
 	xf	waiting for xfr response (not even connected yet)
 	us	waiting for usr response
 	ca	waiting for cal response
 	jo	waiting for a join response
-	
+
 	es	established (waiting in boredom)
 
 	You will find more information in the doc directory.
@@ -103,12 +109,12 @@
 					# unique for consistency
 		self.block = 1		# blocking state
 		self.orig_tid = None	# tid of the original XFR
-	
+
 	def __repr__(self):
 		return '<sbd: emails:%s state:%s fd:%d endpoint:%s>' % \
 			(str(self.emails), self.state, \
 			self.fileno(), self.endpoint)
-	
+
 	def fileno(self):
 		return self.fd.fileno()
 
@@ -117,13 +123,13 @@
 		self.tid = self.tid + 1
 		return str(self.tid - 1)
 
-	
+
 
 class msnd:
 	"""MSN Descriptor
 	This is the main and most important class; it represents a msn
 	instance.
-	
+
 	It's, afaik, nonblocking (not through setblocking() but mainly because
 	it forces a select() i/o model (which you would probably have used
 	anyway, unless you think async/signal io worths the mess for a stupid
@@ -131,10 +137,10 @@
 	always succed. Note that we sanely assume that writes do not block.
 
 	Yes yes, you can use poll() too =)
-	
+
 	The only blocking call is the login() which is in charge of doing the
 	initial connection and setup, all the rest are cpu bound.
-	
+
 	Once you have created an instance you should assign an email and a
 	password at least, then do the login and i recommend you to call sync
 	after that (and everyonce in a while doesn't hurt either). Finally you
@@ -153,12 +159,12 @@
 	that should have come with this file; also the callback file has good
 	working code.
 	"""
-	
+
 	def __init__(self):
 		self.fd = None			# socket fd
 		self.sb_fds = []		# switchboard fds
 		self.tid = 1			# transaction id
-		
+
 		self.email = None		# login email
 		self.pwd = None			# login pwd
 		self.nick = None		# nick
@@ -169,7 +175,7 @@
 
 		self.status = 'FLN'		# status
 		self.encoding = 'iso-8859-1'	# local encoding
-		
+
 		self.lhost = LOGIN_HOST
 		self.lport = LOGIN_PORT
 		self.ns = (None, None)		# notification server
@@ -180,18 +186,18 @@
 		self.syn_ngroups = 0		# qty. of groups from SYN
 
 		self.lst_total = 0		# qty. of LSTs got
-		
+
 		self.cb = None			# callbacks
 
 		self.users = {}			# forward user list
 		self.reverse = {}		# reverse user list
 		self.groups = {}		# group list
-		
-	
+
+
 	def __repr__(self):
 		return '<msnd object, fd:%s, email:%s, tid:%s>' % (self.fd,
 			self.email, self.tid)
-	
+
 	def fileno(self):
 		"Useful for select()"
 		return self.fd.fileno()
@@ -210,24 +216,24 @@
 		except:
 			return s
 
-	
+
 	def pollable(self):
 		"""Return a pair of lists of poll()/select()ables network
 		descriptors (ie. they are not fds, but actually classes that
 		implement fileno() methods, like this one and the sbd). We do
 		it this way because then it's simpler to read().
-		
+
 		The reason behind the tuple is that for connect-pending fds we
 		need to wait for writing readiness, so we must tell the
 		userspace so. Notice that it still goes with the read() path.
 
 		Yes, it is a mess but i couldn't find anything better yet. It
 		works, it's efficient; let's pretend it's correct =)
-		
+
 		It includes the main file descriptor, and all the switchboards
 		connections; then you call self.read(fd) on what this returns,
 		and magic happens."""
-		
+
 		iwtd = []
 		owtd = []
 		iwtd.append(self)
@@ -240,18 +246,18 @@
 			else:			# readable!
 				iwtd.append(nd)
 		return (iwtd, owtd)
-		
-	
+
+
 	def get_tid(self):
 		"Returns a valid tid as string"
 		self.tid = self.tid + 1
 		return str(self.tid - 1)
-	
-	
+
+
 	def _send(self, cmd, params = '', nd = None, raw = 0):
 		"""Sends a command to the server, building it first as a
 		string; uses, if specified, the pseudo fd (it can be either
-		msnd or sbd).""" 
+		msnd or sbd)."""
 		if not nd:
 			nd = self
 		tid = nd.get_tid()
@@ -263,8 +269,8 @@
 			c = c + '\r\n'
 		c = self.encode(c)
 		return fd.send(c)
-	
-	
+
+
 	def _recv(self, fd = None):
 		"Reads a command from the server, returns (cmd, tid, params)"
 		if not fd:
@@ -275,15 +281,15 @@
 		while c != '\n' and c != '':
 			buf = buf + c
 			c = fd.recv(1)
-		
+
 		if c == '':
 			raise 'SocketError'
-		
+
 		buf = buf.strip()
 		pbuf = buf.split(' ')
-		
+
 		cmd = pbuf[0]
-		
+
 		# it's possible that we don't have any params (errors being
 		# the most common) so we cover our backs
 		if len(pbuf) >= 3:
@@ -295,11 +301,11 @@
 		else:
 			tid = '0'
 			params = ''
-		
+
 		debug(str(fd.fileno()) + ' <<< ' + buf)
 		return (cmd, tid, params)
-	
-	
+
+
 	def _recvmsg(self, msglen, fd = None):
 		"Read a message from the server, returns it"
 		if not fd:
@@ -311,10 +317,10 @@
 			#debug(str(fd.fileno()) + ' <<< ' + buf)
 			buf = buf + c
 			left = left - len(c)
-			
+
 		return self.decode(buf)
-	
-	
+
+
 	def submit_sbd(self, sbd):
 		"""Submits a switchboard descriptor to add to our list; it is
 		also put on our global list.
@@ -322,7 +328,7 @@
 		Note that if there is no such user, we create it in order to
 		be able to do operations on users that are not in our server
 		list."""
-		
+
 		self.sb_fds.append(sbd)
 		email = sbd.emails[0]
 		if email not in self.users.keys():
@@ -333,8 +339,8 @@
 			self.close(self.users[email].sbd)
 		self.users[email].sbd = sbd
 		return
-	
-	
+
+
 	def change_status(self, st):
 		"""Changes the current status to: online, away, busy, brb,
 		phone, lunch, invisible, idle, offline"""
@@ -342,8 +348,8 @@
 		self.status = status_table[st]
 		self._send('CHG', self.status)
 		return 1
-	
-	
+
+
 	def privacy(self, public = 1, auth = 0):
 		"""Sets our privacy state. First parameter define if you get
 		messages from everybody or only from people on your list; the
@@ -354,63 +360,69 @@
 
 		if auth:	self._send('GTC', 'A')	# ask for auth
 		else:		self._send('GTC', 'N')	# let them add you
-		
+
 		return 1
-	
-	
+
+
 	def change_nick(self, nick):
 		"Changes our nick"
-		nick = urllib.quote(nick)
+		nick = nickquote(nick)
 		self._send('REA', self.email + ' ' + nick)
 		return 1
-	
-	
+
+
 	def sync(self):
 		"Syncronizes the tables"
 		self._send('SYN', '0')
 		return 1
-	
+
 
 	def useradd(self, email, nick = None, gid = '0'):
 		"Adds a user"
 		if not nick: nick = email
-		nick = urllib.quote(nick)
+		nick = nickquote(nick)
 		self._send('ADD', 'AL ' + email + ' ' + nick)
 		self._send('ADD', 'FL ' + email + ' ' + nick + ' ' + gid)
 		return 1
-	
+
 
 	def userdel(self, email):
 		"Removes a user"
 		self._send('REM', 'AL ' + email)
 		self._send('REM', 'FL ' + email)
 		return 1
-	
+
+	def userren(self, email, newnick):
+		"Renames a user"
+		newnick = nickquote(newnick)
+		self._send('REA', email + ' ' + newnick)
+		return 1
+
 	def userblock(self, email):
 		self._send('REM', 'AL ' + email)
 		self._send('ADD', 'BL ' + email + ' ' + email)
 		if 'B' not in self.users[email].lists:
 			self.users[email].lists.append('B')
-	
+
 	def userunblock(self, email):
 		self._send('REM', 'BL ' + email)
 		self._send('ADD', 'AL ' + email + ' ' + email)
 		if 'B' in self.users[email].lists:
 			self.users[email].lists.remove('B')
-	
+
 	def groupadd(self, name):
 		"Adds a group"
-		name = urllib.quote(name)
+		name = nickquote(name)
 		self._send('ADG', name + ' 0')
 		return 1
-	
+
 	def groupdel(self, gid):
 		"Removes a group"
 		self._send('RMG', gid)
 		return 1
-	
+
 	def groupren(self, gid, newname):
-		newname = urllib.quote(newname)
+		newname = nickquote(newname)
 		self._send('REG', gid + ' ' + newname)
 		return 1
 
@@ -418,8 +430,8 @@
 		"Disconnect from the server"
 		self.fd.send('OUT\r\n')
 		self.fd.close()
-	
-	
+
+
 	def close(self, sb):
 		"Closes a given sbd"
 		self.sb_fds.remove(sb)
@@ -430,30 +442,30 @@
 		except:
 			pass
 		del(sb)
-	
-	
+
+
 	def invite(self, email, sbd):
 		"Invites a user into an existing sbd"
 		self._send('CAL', email, nd = sbd)
-	
+
 	def login(self):
 		"Logins to the server, really boring"
 
 		# open socket
 		self.fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 		self.fd.connect((self.lhost, self.lport))
-		
+
 		# version information
 		self._send('VER', 'MSNP8 CVR0')
-		
+
 		r = self._recv()
 		if r[0] != 'VER' and r[2][0:4] != 'MSNP8':
 			raise 'VersionError', r
-		
+
 		# lie the version, just in case
 		self._send('CVR', '0x0409 win 4.10 i386 MSNMSGR 5.0.0544 MSMSGS ' + self.email)
 		self._recv()	# we just don't care what we get
-	
+
 		# ask for notification server
 		self._send('USR', 'TWN I ' + self.email)
 
@@ -466,7 +478,7 @@
 		self.ns = ns.split(':')
 		self.ns[1] = int(self.ns[1])
 		self.ns = tuple(self.ns)
-		
+
 		# close the fd and reopen it on the ns
 		self.fd.close()
 		self.fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -477,19 +489,19 @@
 		r = self._recv()
 		if r[0] != 'VER' and r[2][0:4] != 'MSNP8':
 			raise 'VersionError', r
-		
+
 		# lie the version, just in case
 		self._send('CVR', '0x0409 win 4.10 i386 MSNMSGR 5.0.0544 MSMSGS	' + self.email)
 		self._recv()	# we just don't care what we get
-	
+
 		# auth: send user, get hash
 		self._send('USR', 'TWN I ' + self.email)
-		
+
 		r = self._recv()
 		if r[0] != 'USR':
 			raise 'AuthError', r
 		hash = string.split(r[2])[2]
-		
+
 		# get and use the passport id
 		passportid = self.passport_auth(hash)
 		self._send('USR', 'TWN S ' + passportid)
@@ -499,7 +511,7 @@
 			raise 'AuthError', r
 		self.nick = string.split(r[2])[2]
 		self.nick = urllib.unquote(self.nick)
-		
+
 		return 1
 
 
@@ -508,23 +520,23 @@
 		authorization; it's a helper function for login"""
 		import urllib
 		import httplib
-		
+
 		# initial connection
 		debug('PASSPORT begin')
 		nexus = urllib.urlopen('https://nexus.passport.com/rdr/pprdr.asp')
 		h = nexus.headers
 		purl = h['PassportURLs']
-		
+
 		# parse the info
 		d = {}
 		for i in purl.split(','):
 		        key, val = i.split('=', 1)
 			d[key] = val
-		
+
 		# get the login server
 		login_server = 'https://' + d['DALogin']
 		login_host = d['DALogin'].split('/')[0]
-		
+
 		# build the authentication headers
 		ahead =  'Passport1.4 OrgVerb=GET'
 		ahead += ',OrgURL=http%3A%2F%2Fmessenger%2Emsn%2Ecom'
@@ -534,16 +546,16 @@
 		ahead += 'ru=http%3A%2F%2Fmessenger%2Emsn%2Ecom,ct=1062764229,'
 		ahead += 'kpp=1,kv=5,ver=2.1.0173.1,tpf=' + hash
 		headers = { 'Authorization': ahead }
-		
+
 		# connect to the given server
 		debug('SSL Connect to %s' % login_server)
 		ls = httplib.HTTPSConnection(login_host)
-		
+
 		# make the request
 		debug('SSL GET')
 		ls.request('GET', login_server, '', headers)
 		resp = ls.getresponse()
-		
+
 		# loop if we get redirects until we get a definitive answer
 		debug('SSL Response %d' % resp.status)
 		while resp.status == 302:
@@ -555,7 +567,7 @@
 			ls.request('GET', login_server, '', headers)
 			resp = ls.getresponse()
 			debug('SSL Response %d' % resp.status)
-		
+
 		# now we have a definitive answer, if it's not 200 (success)
 		# just raise AuthError
 		if resp.status != 200:
@@ -569,12 +581,12 @@
 			ainfo = resp.getheader('Authentication-Info')
 		except:
 			ainfo = resp.getheader('WWW-Authenticate')
-		
+
 		d = {}
 		for i in ainfo.split(','):
 			key, val = i.split('=', 1)
 			d[key] = val
-			
+
 		passportid = d['from-PP']
 		passportid = passportid[1:-1]		# remove the "'"
 		return passportid
@@ -587,7 +599,7 @@
 		"""
 		if not nd:
 			nd = self
-		
+
 		# handle different stages of switchboard initialization
 		if nd in self.sb_fds:
 			# connect pending
@@ -600,7 +612,7 @@
 				nd.fd.setblocking(1)
 				nd.block = 1
 				nd.state = 're'
-				
+
 			# need to send the answer to the remote invitation
 			if nd.type == 'answer' and nd.state == 're':
 				params = self.email + ' ' + nd.hash + ' ' + \
@@ -613,9 +625,9 @@
 				self._send('USR', params, nd)
 				nd.state = 'us'
 				return
-		
-		
-		
+
+
+
 		r = self._recv(nd.fd)
 		type = r[0]
 		tid = r[1]
@@ -642,7 +654,7 @@
 		elif type == 'RMG': self.cb.rmg(self, type, tid, params)
 		elif type == 'REG': self.cb.reg(self, type, tid, params)
 		elif type == 'RNG': self.cb.rng(self, type, tid, params)
-		
+
 		elif type == 'IRO': self.cb.iro(self, type, tid, params, nd)
 		elif type == 'ANS': self.cb.ans(self, type, tid, params, nd)
 		elif type == 'XFR': self.cb.xfr(self, type, tid, params)
@@ -654,20 +666,20 @@
 		elif type == 'NAK': self.cb.nak(self, type, tid, params, nd)
 		elif type == 'BYE': self.cb.bye(self, type, tid, params, nd)
 
-		
+
 		elif type == 'MSG':
 			params = tid + ' ' + params
 			mlen = int(r[2].split()[-1])
 			msg = self._recvmsg(mlen, nd.fd)
 			self.cb.msg(self, type, params, msg, nd)
-		
+
 		else:
 			# catch server errors - always numeric type
 			try:
 				errno = int(type)
 			except:
 				errno = None
-			
+
 			if errno:
 				self.cb.err(self, errno, \
 					str(tid) + ' ' + str(params))
@@ -692,13 +704,13 @@
 
 		if email and email not in self.users.keys():
 			self.users[email] = user(email)
-		
+
 		if len(msg) > 1500:
 			return -2
-		
+
 		if not sb:
 			sb = self.users[email].sbd
-		
+
 		# we don't have a connection
 		if not sb:
 			sb = sbd()
@@ -714,12 +726,12 @@
 			sb.orig_tid = str(self.tid)
 			self._send('XFR', 'SB')
 			return 1
-		
+
 		# it's not ready yet
 		elif sb.state != 'es':
 			sb.msgqueue.append(msg)
 			return 1
-		
+
 		# no more excuses, send it
 		else:
 			# we make a list with all the messages to send
@@ -736,8 +748,8 @@
 				params = 'A ' + str(msize) + '\r\n' + m
 				self._send('MSG', params, sb, raw = 1)
 				del(pend[0])
-		
+
 			return 2
-		
-		
+
+
 
diff -rN -u old-msnlib/msnrc.sample new-msnlib/msnrc.sample
--- old-msnlib/msnrc.sample	2005-05-27 13:16:48.000000000 -0300
+++ new-msnlib/msnrc.sample	2005-05-14 00:33:20.000000000 -0300
@@ -34,12 +34,16 @@
 # defaults to $HOME/.msn/history
 history directory = /home/myself/.msn/history
 
+# show changes in real nicks
+# the default is no
+show realnick changes = no
+
 # number of seconds after, if no command was received, we set automatically
 # away. defaults to 0, which disables it.
 auto away = 0
 
 # initial status when we first connect
-# defaults to online and must be valid, that is, one of online, away, busy,
+# defaults to online and must be valid (that is, one of online, away, busy,
 # and so on)
 initial status = online
 
diff -rN -u old-msnlib/setup.py new-msnlib/setup.py
--- old-msnlib/setup.py	2005-05-27 13:16:48.000000000 -0300
+++ new-msnlib/setup.py	2005-05-27 12:59:34.000000000 -0300
@@ -2,7 +2,7 @@
 from distutils.core import setup
 
 setup(name="msnlib",
-	version="3.4",
+	version="3.5",
 	description="MSN Messenger Library and Client",
 	author="Alberto Bertogli",
 	author_email="albertogli@telpin.com.ar",
diff -rN -u old-msnlib/utils/exporter new-msnlib/utils/exporter
--- old-msnlib/utils/exporter	1969-12-31 21:00:00.000000000 -0300
+++ new-msnlib/utils/exporter	2005-04-09 15:51:45.000000000 -0300
@@ -0,0 +1,127 @@
+#!/usr/bin/python
+
+import sys
+import os
+from xml.sax import saxutils
+from xml.sax import make_parser
+from xml.sax.handler import feature_namespaces
+
+
+class Patch:
+	"Represents a single patch/record"
+	def __init__(self):
+		self.hash = ''
+		self.author = ''
+		self.date = ''
+		self.local_date = ''
+		self.name = ''
+		self.comment = ''
+
+	def tostr(self):
+		s = "%s\n\tAuthor: %s\n\tDate: %s\n\tHash: %s\n" % \
+			(self.name, self.author, self.date, self.hash)
+		return s
+
+	def export(self, order, path):
+		# '/'s are not allowed in filenames
+		name = self.name.replace('/', '-')
+
+		# avoid 'name..patch'
+		if name[-1] == '.':
+			name = name[:-1]
+
+		file = "%s/%.2d - %s.patch" % (path, order, name)
+		cmd = 'darcs diff -u --match "hash %s" > "%s"' % \
+				(self.hash, file)
+		if os.system(cmd):
+			print "Command failed: '%s'" % cmd
+
+
+class BuildPatchList(saxutils.DefaultHandler):
+	def __init__(self):
+		self.db = {}
+		self.list = []
+		self.cur_hash = ''
+		self.cur_elem = None
+		self.cur_val = ''
+
+	def startElement(self, name, attrs):
+		if name == 'patch':
+			p = Patch()
+			p.author = attrs.get('author', None)
+			p.date = attrs.get('date', None)
+			p.local_date = attrs.get('local_date', None)
+			p.hash = attrs.get('hash')
+			self.db[p.hash] = p
+			self.current = p.hash
+			self.list.append(p.hash)
+		elif name == 'name':
+			self.db[self.current].name = ''
+			self.cur_elem = 'name'
+		elif name == 'comment':
+			self.db[self.current].comment = ''
+			self.cur_elem = 'name'
+		else:
+			self.cur_elem = None
+
+	def characters(self, s):
+		if not self.cur_elem:
+			return
+		self.cur_val += s
+
+	def endElement(self, name):
+		if name == 'name':
+			self.db[self.current].name = self.cur_val
+		elif name == 'comment':
+			self.db[self.current].current = self.cur_val
+
+		self.cur_elem = None
+		self.cur_val = ''
+
+
+# main
+
+if len(sys.argv) < 3:
+	print "Use: exporter [xmlfile|-] [list|export destdir]"
+	print
+	print "Examples:"
+	print " # darcs changes --xml-output | exporter - export /tmp"
+	print " # darcs changes --xml-output | exporter - list"
+	sys.exit(1)
+
+if sys.argv[1] == '-':
+	file = sys.stdin
+else:
+	file = sys.argv[1]
+
+parser = make_parser()
+parser.setFeature(feature_namespaces, 0)
+
+handler = BuildPatchList()
+parser.setContentHandler(handler)
+parser.parse(file)
+
+# reverse the list so the oldest is the first, and the newest is the last
+handler.list.reverse()
+
+# we now have two main structures: handler.db is the hash table of Patches,
+# indexed by their hash, and handler.list is the ordered list of hashes.
+
+if sys.argv[2] == 'list':
+	c = 1
+	for h in handler.list:
+		print "%.2d:" % c, handler.db[h].tostr()
+		c += 1
+elif sys.argv[2] == 'export':
+	if len(sys.argv) < 4:
+		print "Destination directory missing"
+		sys.exit(1)
+	c = 1
+	for h in handler.list:
+		p = handler.db[h]
+		print "%.2d: %s" % (c, p.name)
+		p.export(c, sys.argv[3])
+		c += 1
+else:
+	print "Unknown parameter"
+
diff -rN -u old-msnlib/utils/msnlog.vim new-msnlib/utils/msnlog.vim
--- old-msnlib/utils/msnlog.vim	2005-05-27 13:16:48.000000000 -0300
+++ new-msnlib/utils/msnlog.vim	2005-05-12 15:33:43.000000000 -0300
@@ -14,8 +14,9 @@
 syntax match	mlogMultiStr	"^\t.*$"
 syntax match	mlogIMsg	"<<< .*$"
 syntax match	mlogOMsg	">>> .*$"
-syntax match	mlogStatus	".*\*\*\* .*$"
+syntax match	mlogStatus	"\*\*\* .*$"
 syntax match	mlogMchat	"+++ .*$"
+syntax match	mlogRnick	"--- .*$"
 syntax match	mlogDate	"^../.../.... ..:..:.."
 
 
@@ -24,6 +25,7 @@
 hi mlogOMsg		ctermfg=cyan
 hi mlogStatus		ctermfg=yellow
 hi mlogMchat		ctermfg=yellow
+hi mlogRnick		ctermfg=yellow
 hi mlogMultiStr		ctermfg=magenta
 
 

