diff -ruN msnlib-3.2/doc/Changelog msnlib-3.3/doc/Changelog
--- msnlib-3.2/doc/Changelog	2003-09-29 09:19:11.000000000 -0300
+++ msnlib-3.3/doc/Changelog	2003-11-12 12:53:17.000000000 -0300
@@ -1,3 +1,43 @@
+12 Nov 03 12.52.27 - Alberto <albertogli@telpin.com.ar>
+ * tag: 3.3 tag
+
+12 Nov 03 00.06.32 - Alberto <albertogli@telpin.com.ar>
+ * msn: handle socket exceptions more properly so we cleanup the console on
+		the exit path even when the md is down
+
+11 Nov 03 23.35.05 - Alberto <albertogli@telpin.com.ar>
+ * msnlib: handle some server errors while logging in
+
+08 Nov 03 18.29.47 - Alberto <albertogli@telpin.com.ar>
+ * msn: handle \r input for mac keyboards
+
+03 Nov 03 20.19.07 - Alberto <albertogli@telpin.com.ar>
+ * msntk: add msntk, a tk-based client
+
+30 Oct 03 02.48.30 - Alberto <albertogli@telpin.com.ar>
+ * doc: various small updates
+
+29 Oct 03 22.46.13 - Alberto <albertogli@telpin.com.ar>
+ * msn: fix a crash when passing an unknown nick to block/unblock
+ * msn: do proper nick checking in lignore
+
+28 Oct 03 23.13.56 - Alberto <albertogli@telpin.com.ar>
+ * msn: fix a crash when the configuration file was passed as a parameter
+
+27 Oct 03 12.49.14 - Alberto <albertogli@telpin.com.ar>
+ * msnlib: do proper connect() checking using getsockopt()
+ * msnlib: fix user.__repr__()
+
+18 Oct 03 10.12.12 - Alberto <albertogli@telpin.com.ar>
+ * msn: mark blocked users in listings
+ * msn: use string.join() for multi-user chat filenames
+ * msnlib: remove FIXME from message lenght check; the way it's done is safe
+ * msnlib: allow sendmsg to get a destination sbd directly
+
+30 Sep 03 15.06.19 - Alberto <albertogli@telpin.com.ar>
+ * doc: upgrade documentation to reflect the need for at least Python 2.2.2
+ * doc: minor change to TODO list
+
 29 Sep 03 09.17.33 - Alberto <albertogli@telpin.com.ar>
  * tag: 3.2 tag
 
diff -ruN msnlib-3.2/doc/commands msnlib-3.3/doc/commands
--- msnlib-3.2/doc/commands	2003-04-23 10:53:00.000000000 -0300
+++ msnlib-3.3/doc/commands	2003-10-30 02:39:55.000000000 -0300
@@ -7,7 +7,7 @@
 
 status [mode]	
 	Shows the current status, or changes it to "mode", which can be one of:
-	online, away, busy, brb, phone, lunch, invisible, idle or offline.
+	online, away, busy, brb, phone, lunch, invisible or idle.
 
 q
 	Quits the program.
@@ -25,6 +25,9 @@
 ee
 	Prints your online contacts, including email addresses.
 
+eg
+	Prints your online contacts with the groups
+	
 wr
 	Prints your reverse contact list.
 	(see the FAQ for more details)
@@ -54,6 +57,25 @@
 lunignore nick
 	Removes a user from the locally ignored users list.
 
+block nick
+	Blocks a user
+
+unblock nick
+	Unblocks a blocked user
+
+g
+	Shows the group list
+
+gadd gname
+	Adds the group "gname"
+
+gdel gname
+	Deletes the group "gname". Note that all the users in the group will
+	be deleted too.
+
+gren old new
+	Renames the group "old" with the name "new"
+
 color [theme]
 	Shows the available color themes, or set the color theme to "theme".
 
@@ -87,6 +109,8 @@
 	Sends a message with "text" to the last person that sent you a
 	message.
 
+invite u1 to u2
+	Invites u1 into the chat with u2
 
 
 In most cases, where you are asked for a nick, you can alternatively enter the email.
diff -ruN msnlib-3.2/doc/dependencies msnlib-3.3/doc/dependencies
--- msnlib-3.2/doc/dependencies	2003-09-22 00:45:55.000000000 -0300
+++ msnlib-3.3/doc/dependencies	2003-09-30 15:04:54.000000000 -0300
@@ -2,8 +2,5 @@
 The only thing you need is a working python installation with SSL support
 (most of them have it by default).
 
-Now, i've tried with python 2.2 but it will probably work with lower versions
-too, it doesn't do weird things with the language so i don't see many possible
-problems. It's been reported to run with Python 2.1 and 2.0.
-
+Since release 3.0, it requires at least Python 2.2.2 because of SSL support.
 
diff -ruN msnlib-3.2/doc/portability msnlib-3.3/doc/portability
--- msnlib-3.2/doc/portability	2003-01-20 14:26:15.000000000 -0300
+++ msnlib-3.3/doc/portability	2003-10-30 02:36:46.000000000 -0300
@@ -1,23 +1,28 @@
 
-The code should be portable, as it doesn't contain any specific stuff.
-I tend to code based on posix/sus, but I think it's pretty much generic
-python both the library and the client, specially the former.
-I'm almost sure it will run unmodified on unix platforms (and the only reason
-i say 'almost' is because i didn't test it myself, but it certanly should).
+The library itself should be portable, as it doesn't contain any specific
+stuff that might have problems, and it has been reported to work under
+different unixes and even under windows.
+
+I tend to code based on posix/sus, but I think it's pretty much generic python
+both the library and the client, specially the former.  I'm almost sure it
+will run unmodified on unix platforms (and the only reason i say 'almost' is
+because i didn't test it myself, but it certanly should).
+
+
+About the text mode client the only thing that is tied to a unix environment
+is the client terminal handling (which requires termios and fcntl modules),
+but it's isolated and has runtime detection, so if you don't have any of these
+modules, or they fail for some reason, the client will fall back to the normal
+behaviour. Also, doing select() on stdin isn't ok for some platforms (windows
+being the most popular one), but it's really safe for unix.
+
+Another thing that might be conflictive for non-unix platforms is that I
+assume the python interpreter is callable using "/usr/bin/env python"; these
+are the closest thing to a standard location on unix boxes. If you need to
+change this, the places are the first line of 'msn', and somewhere inside
+'install'.
 
-
-The only thing that is tied to a unix environment is the client terminal
-handling (which requires termios and fcntl modules), but it's isolated and has
-runtime detection, so if you don't have any of these modules, or they fail for
-some reason, the client will fall back to the normal behaviour.
-
-A thing that might be conflictive for non-unix platforms is that I assume the
-python interpreter is callable using "/usr/bin/env python"; these are the
-closest thing to a standard location on unix boxes. If you need to change
-this, the places are the first line of 'msn', and somewhere inside 'install'.
-
-
-The next possible hot point (always talking about non-unix platforms) is
+The next possible problem (always talking about non-unix platforms) is
 'msnsetup' and the configuration file location; the first one requires bash,
 so if you don't have it, you can just create your own msnrc file based on
 'msnrc.sample'; but the location is assumed to be $HOME/.msn/msnrc, and maybe
@@ -26,20 +31,6 @@
 only argument to msn: "msn /path/to/msnrc".
 
 
-Note that the code is only tested initially with Python 2.2 under Linux, which
-is my development platform.
-
-It has also been reported to run successfuly under:
- * BeOS (Dano release) - Python 2.1
-	Needed to change the python interpreter location, as expected. Also,
-	there was a problem with BeOS's bash that didn't like the 'read'
-	command, which is used in msnsetup.
- * Linux (i386) - Python 2.0
- 	Releases up to D3 needed small fixes because of changes in the time
-	module in Python 2.1. The following versions had the fix included, so
-	they're fully 2.0 compatible.
-
-
 If you run it under a different platform, please let me know; specially if you
 had (or have) any problems.
 
diff -ruN msnlib-3.2/doc/TODO msnlib-3.3/doc/TODO
--- msnlib-3.2/doc/TODO	2003-09-22 02:18:07.000000000 -0300
+++ msnlib-3.3/doc/TODO	2003-09-30 15:10:20.000000000 -0300
@@ -14,8 +14,8 @@
 Future / In doubt
 -----------------
 (things listed here are either marked to do in some future (because we have to
-wait on some feature being popular) or are in doubt of ever being implemented
-at all)
+wait on some feature becoming popular) or are in doubt of ever being
+implemented at all)
 
 msn client
 * signal handling
diff -ruN msnlib-3.2/msn msnlib-3.3/msn
--- msnlib-3.2/msn	2003-09-29 09:18:17.000000000 -0300
+++ msnlib-3.3/msn	2003-11-12 00:06:29.000000000 -0300
@@ -166,7 +166,10 @@
 			hl = 0
 		status = msnlib.reverse_status[u.status]
 		printl('%7.7s :: %s ' % (status, u.nick), bold = hl)
-		if include_emails: printl('(%s) ' % (email), bold = hl)
+		if include_emails:
+			printl('(%s) ' % (email), bold = hl)
+		if 'B' in u.lists:
+			printl('[!]')
 		printl('\n')
 
 def print_group_list(md):
@@ -199,7 +202,10 @@
 				hl = 0
 			status = msnlib.reverse_status[u.status]
 			printl('\t%7.7s :: %s ' % (status, u.nick), bold = hl)
-			if include_emails: printl('(%s) ' % (email), bold = hl)
+			if include_emails:
+				printl('(%s) ' % (email), bold = hl)
+			if 'B' in u.lists:
+				printl('[!]')
 			printl('\n')
 			
 def print_user_info(email):
@@ -311,7 +317,10 @@
 	"Exits"
 	printl('Closing\n', c.green, 1)
 	try:
-		m.disconnect()
+		try:
+			m.disconnect()
+		except:
+			pass
 		global oldtermattr
 		termios.tcsetattr(sys.stdin.fileno(), termios.TCSAFLUSH, oldtermattr)
 	except:
@@ -407,13 +416,11 @@
 	if users:
 		# copy and sort the user list, so we log always to the same
 		# file regarding the order the users were joined
+		# FIXME: sometimes we crash because filename is too long
 		usorted = users[:]
 		usorted.sort()
 		file = config['history directory'] + '/' + prepend + 'M::'
-		for i in usorted:
-			file += i + ','
-		# strip the last ','
-		file = file[:-1]
+		file += string.join(usorted, ',')
 	else:
 		file = config['history directory'] + '/' + prepend + email
 	if not mtime:
@@ -519,6 +526,10 @@
 	in_esc = 0
 	input = sys.stdin.read()
 	for char in input:
+		if char == '\r':
+			# replace \r with \n, so we handle mac keyboard input
+			# properly (it breaks \r\n tho, but nobody uses it)
+			char = '\n'
 		inbuf = inbuf + char
 		if char == '\n':
 			# command history
@@ -615,7 +626,7 @@
 			inbuf = inbuf[:-1]
 			
 		elif ord(char) < 32:				# unhandled control
-			print 'Got weird char: %d' % ord(char)
+			msnlib.debug('Got weird char: %d' % ord(char))
 			redraw_cli_cond(char)
 			
 		else:						# normal
@@ -842,6 +853,8 @@
 				printl(email2nick(e) + ' (' + e + ')\n')
 			return ''
 		email = nick2email(p[0])
+		if not email:
+			return 'Unknown nick (%s)' % p[0]
 		if email in ignored: 
 			return 'User is already being locally ignored'
 		ignored.append(email)
@@ -862,6 +875,8 @@
 		if len(p) == 0:
 			return 'Error parsing command'
 		email = nick2email(p[0])
+		if not email:
+			return 'Unknown nick (%s)' % p[0]
 		m.userblock(email)
 		return 'User %s blocked' % email
 	
@@ -870,6 +885,8 @@
 		if len(p) == 0:
 			return 'Error parsing command'
 		email = nick2email(p[0])
+		if not email:
+			return 'Unknown nick (%s)' % p[0]
 		m.userunblock(email)
 		return 'User %s unblocked' % email
 	
@@ -1345,13 +1362,14 @@
 #
 # now the real thing
 #
-printl('* MSN Client (3.2) *\n', c.yellow, 1)
+printl('* MSN Client (3.3) *\n', c.yellow, 1)
 
 # first, the configuration
 printl('Loading config... ', c.green, 1)
 if len(sys.argv) > 1:
 	# first, try the arg as file
 	config = get_config(sys.argv[1])
+	profile = None
 	if not config:
 		# then, as the profile
 		profile = sys.argv[1]
diff -ruN msnlib-3.2/msnlib.py msnlib-3.3/msnlib.py
--- msnlib-3.2/msnlib.py	2003-09-29 09:18:27.000000000 -0300
+++ msnlib-3.3/msnlib.py	2003-11-11 23:34:10.000000000 -0300
@@ -14,7 +14,7 @@
 """
 
 # constants
-VERSION = 0x0302
+VERSION = 0x0303
 LOGIN_HOST = 'messenger.hotmail.com'
 LOGIN_PORT = 1863
 
@@ -65,7 +65,8 @@
 		self.lists = []
 	
 	def __repr__(self):
-		return '<user email:%s nick:"%s" gid:%s>' % (email, nick, gid)
+		return '<user email:%s nick:"%s" gid:%s>' % (self.email,
+				self.nick, self.gid)
 
 
 class sbd:
@@ -479,6 +480,8 @@
 		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
@@ -583,10 +586,11 @@
 		if nd in self.sb_fds:
 			# connect pending
 			if nd.state == 'cp':
-				try:
-					nd.fd.connect(nd.endpoint)
-				except:
-					raise 'SocketError'
+				# see if the connect went well
+				r = nd.fd.getsockopt(socket.SOL_SOCKET,
+					socket.SO_ERROR)
+				if r != 0:
+					raise 'SocketError', 'ConnectFailed'
 				nd.fd.setblocking(1)
 				nd.block = 1
 				nd.state = 're'
@@ -667,7 +671,7 @@
 		return
 
 
-	def sendmsg(self, email, msg = ''):
+	def sendmsg(self, email, msg = '', sb = None):
 		"""Sends a message to the user identified by 'email', either
 		the one specified or flush the queue.
 		Returns:
@@ -679,14 +683,15 @@
 		Message sending order is guaranteed within a sbd; but not the
 		acknowledge; that's what the ACK/NAK callbacks are for.
 		"""
-		
-		if email not in self.users.keys():
+
+		if email and email not in self.users.keys():
 			self.users[email] = user(email)
 		
-		if len(msg) > 1500:	# is this accurate? FIXME?
+		if len(msg) > 1500:
 			return -2
 		
-		sb = self.users[email].sbd
+		if not sb:
+			sb = self.users[email].sbd
 		
 		# we don't have a connection
 		if not sb:
diff -ruN msnlib-3.2/README msnlib-3.3/README
--- msnlib-3.2/README	2003-09-24 14:27:58.000000000 -0300
+++ msnlib-3.3/README	2003-11-08 16:50:34.000000000 -0300
@@ -10,7 +10,7 @@
 and the library.
 
 The client is really simple, uses a text-mode interface with commands similar
-to 'micq' (http://micq.ukeer.de/), which is a great ICQ client.
+to 'micq' (http://micq.ukeer.de/), which is a great ICQ client. 
 
 If you're looking for a good messaging system, forget about messenger and try
 Jabber (http://www.jabber.org), it's the only one which is run in the open
diff -ruN msnlib-3.2/setup.py msnlib-3.3/setup.py
--- msnlib-3.2/setup.py	2003-09-29 09:18:38.000000000 -0300
+++ msnlib-3.3/setup.py	2003-11-09 09:01:56.000000000 -0300
@@ -2,7 +2,7 @@
 from distutils.core import setup
 
 setup(name="msnlib",
-	version="3.2",
+	version="3.3",
 	description="MSN Messenger Library and Client",
 	author="Alberto Bertogli",
 	author_email="albertogli@telpin.com.ar",
diff -ruN msnlib-3.2/utils/msntk msnlib-3.3/utils/msntk
--- msnlib-3.2/utils/msntk	1969-12-31 21:00:00.000000000 -0300
+++ msnlib-3.3/utils/msntk	2003-11-07 00:26:02.000000000 -0300
@@ -0,0 +1,453 @@
+#!/usr/bin/env python
+
+import sys
+import time
+import string
+import socket
+import select
+from Tkinter import *
+import tkMessageBox
+import tkSimpleDialog
+
+import msnlib
+import msncb
+
+"""
+MSN Tk Client
+
+This is a beta msn client based on msnlib. As you see, it's GUI based on the
+Tk bindings, which provide an abstraction to create graphical interfaces; it
+works both under linux, windows and probably others too.
+
+For further information refer to the documentation or the source (which is
+always preferred).
+Please direct any comments to the msnlib mailing list,
+msnlib-devel@auriga.wearlab.de.
+You can find more information, and the package itself, at
+http://users.auriga.wearlab.de/~alb/msnlib
+"""
+
+
+# main msnlib classes
+m = msnlib.msnd()
+m.cb = msncb.cb()
+
+# void debug output
+def void(s): pass
+msnlib.debug = msncb.debug = void
+
+
+
+#
+# useful functions
+#
+
+def nick2email(nick):
+	"Returns an email according to the given nick, or None if noone matches"
+	for email in m.users.keys():
+		if m.users[email].nick == nick:
+			return email
+	if nick in m.users.keys():
+		return nick
+	return None
+
+def email2nick(email):
+	"Returns a nick accoriding to the given email, or None if noone matches"
+	if email in m.users.keys():
+		return m.users[email].nick
+	else:
+		return None
+
+def now():
+	"Returns the current time in format HH:MM:SSTT"
+	return time.strftime('%I:%M:%S%p', time.localtime(time.time()) )
+
+def quit():
+	"Cleans up and quits everything"
+	try:
+		m.disconnect()
+	except:
+		pass
+	root.quit()
+	sys.exit(0)
+
+
+
+#
+# GUI classes
+#
+
+class userlist(Frame):
+	"The user list"
+	def __init__(self, master):
+		Frame.__init__(self, master)
+		self.scrollbar = Scrollbar(self, orient = VERTICAL)
+		self.list = Listbox(self, 
+				yscrollcommand = self.scrollbar.set)
+		self.list.config(font = "Courier")
+		self.scrollbar.config(command = self.list.yview)
+		self.scrollbar.pack(side = RIGHT, fill = Y)
+		self.list.pack(side = LEFT, fill = BOTH, expand = 1)
+		
+		self.list.bind("<Double-Button-1>", self.create_chat)
+			
+	def create_chat(self, evt = None):
+		"Creates a chat window"
+		if m.status == 'HDN':
+			tkMessageBox.showwarning("Warning", 
+				"You can't open chats when you're invisible")
+			return
+		nick = self.list.get(self.list.curselection())[4:]
+		email = nick2email(nick)
+		if email in emwin.keys():
+			emwin[email].lift()
+		elif m.users[email].status == 'FLN':
+			tkMessageBox.showwarning("Warning",
+				"The user is offline")
+		else:
+			emwin[email] = chatwindow(root, email)
+	
+
+class mainmenu(Menu):
+	"Main menu used in the main window"
+	def __init__(self, master):
+		Menu.__init__(self, master)
+		self.status_menu = Menu(self, tearoff = 0)
+		self.add_cascade(label = "Status", menu = self.status_menu)
+		self.status_menu.add_command(label = "Online",
+			command = self.chst_online)
+		self.status_menu.add_command(label = "Away",
+			command = self.chst_away)
+		self.status_menu.add_command(label = "Busy",
+			command = self.chst_busy)
+		self.status_menu.add_command(label = "Be Right Back",
+			command = self.chst_brb)
+		self.status_menu.add_command(label = "Lunch",
+			command = self.chst_lunch)
+		self.status_menu.add_command(label = "Phone", 
+			command = self.chst_phone)
+		self.status_menu.add_command(label = "Invisible", 
+			command = self.chst_invisible)
+			
+		self.add_command(label = 'Info', command = self.show_info)
+	
+	def show_info(self, evt = None):
+		csel = mainlist.list.curselection()
+		if not csel:
+			return
+		nick = mainlist.list.get(csel)[4:]
+		email = nick2email(nick)
+		infowindow(root, email)
+
+	# status change callbacks
+	def clear_heads(self):
+		for i in emwin.keys():
+			emwin[i].head.config(text = '')
+	
+	def chst_online(self):
+		self.clear_heads()
+		m.change_status('online')
+	def chst_away(self):
+		self.clear_heads()
+		m.change_status('away')
+	def chst_busy(self):
+		self.clear_heads()
+		m.change_status('busy')
+	def chst_brb(self):
+		self.clear_heads()
+		m.change_status('brb')
+	def chst_lunch(self):
+		self.clear_heads()
+		m.change_status('lunch')
+	def chst_phone(self):
+		self.clear_heads()
+		m.change_status('phone')
+	def chst_invisible(self):
+		warn = "Warning: as you are invisible, it is possible that\n"
+		warn += "the messages you type here never get to the user."
+		for i in emwin.keys():
+			emwin[i].head.config(text = warn)
+		m.change_status('invisible')
+
+
+class chatwindow(Toplevel):
+	"Represents a chat window"
+	def __init__(self, master, email):
+		Toplevel.__init__(self, master)
+		self.email = email
+		self.protocol("WM_DELETE_WINDOW", self.destroy_window)
+		nick = email2nick(email)
+		# FIXME: update the title with status change
+		status = msnlib.reverse_status[m.users[email].status]
+		if nick:
+			self.wm_title(nick + ' (' + status + ')')
+		else:
+			self.wm_title(email + ' (' + status + ')')
+
+		# head label
+		self.head = Label(self)
+		self.head.pack(side = TOP, fill = X, expand = 0)
+		self.head.config(justify = LEFT)
+		self.head.config(text = "")
+
+		# text box (with scrollbar), where the message goes
+		self.frame = Frame(self)
+		self.scrollbar = Scrollbar(self.frame, orient = VERTICAL)
+		self.text = Text(self.frame, 
+				yscrollcommand = self.scrollbar.set)
+		self.scrollbar.config(command = self.text.yview)
+		self.scrollbar.pack(side = RIGHT, fill = Y)
+		self.text.pack(side = TOP, fill = BOTH, expand = 1)
+		self.frame.pack(side = TOP, fill = BOTH, expand = 1)
+		
+		self.text.config(state = DISABLED)
+		self.text.tag_config('from', foreground = 'blue')
+		self.text.tag_config('to', foreground = 'red')
+		self.text.tag_config('typing', foreground = 'lightblue')
+		
+		# entry, where the user types
+		self.entry = Entry(self)
+		self.entry.pack(side = BOTTOM, fill = X, expand = 0)
+		self.entry.bind('<Return>', self.send_line)
+	
+	def append(self, s, direction, scroll = 1):
+		"Adds text to the window's text box"
+		self.text.config(state = NORMAL)
+		self.text.insert(END, s, direction)
+		self.text.yview(SCROLL, scroll, UNITS)
+		self.text.config(state = DISABLED)
+	
+	def send_line(self, evt = None):
+		"Sends the current entry as a message"
+		msg = self.entry.get()
+		lines = msg.split('\n')
+		if len(lines) == 1:
+			s = now() + ' >>> ' + msg + '\n'
+		else:
+			s = now() + ' >>>\n\t'
+			s += string.join(lines, '\n\t')
+			s = s[:-1]
+		self.append(s, 'to', scroll = len(lines))
+		
+		# we need to encode it before sending because msg is already
+		# an unicode string; so use utf-8
+		msg = msg.encode('utf-8')
+
+		m.sendmsg(self.email, msg)
+		self.entry.delete(0, END)
+	
+	def destroy_window(self, evt = None):
+		"Clean up when the window is closed"
+		del(emwin[self.email])
+		self.destroy()
+
+
+class infowindow(Toplevel):
+	"Represents a window with user information"
+	def __init__(self, master, email):
+		Toplevel.__init__(self, master)
+		self.email = email
+		self.wm_title('Info on ' + email)
+		u = m.users[email]
+		out = ''
+		out += 'Information for user ' + email + '\n\n'
+		out += 'Nick: ' + u.nick + '\n'
+		out += 'Status: ' + msnlib.reverse_status[u.status] + '\n'
+		if 'B' in u.lists:
+			out += 'Mode: ' + 'blocked' + '\n'
+		if u.gid != None:
+			out += 'Group: ' + m.groups[u.gid] + '\n'
+		if u.realnick:
+			out += 'Real Nick: ' + u.realnick + '\n'
+		if u.homep:
+			out += 'Home phone: ' + u.homep + '\n'
+		if u.workp:
+			out += 'Work phone: ' + u.workp + '\n'
+		if u.mobilep:
+			out += 'Mobile phone: ' + u.mobilep + '\n'
+
+		self.label = Label(self)
+		self.label.pack(side = TOP, fill = BOTH, expand = 1)
+		self.label.config(justify = LEFT)
+		self.label.config(text = out)
+
+
+def redraw_main():
+	"Redraws the main screen"
+	# sync the user list - FIXME: instead of redrawing, use the callbacks
+	# for status change notifications
+	nicks = []
+	for i in m.users.keys():
+		if m.users[i].status == 'FLN':
+			s = '[X] '
+		elif m.users[i].status in ('NLN', 'IDL'):
+			s = '[ ] '
+		else:
+			s = '[-] '
+		if 'B' in m.users[i].lists:
+			s = '[!] '
+		
+		s += m.users[i].nick
+		nicks.append(s)
+	nicks.sort()
+	mainlist.list.delete(0, END)
+	for i in nicks:
+		mainlist.list.insert(END, i)
+	
+	# update status
+	s = msnlib.reverse_status[m.status]
+	status.config(text = s)
+
+
+
+#
+# callbacks
+#
+
+def cb_msg(md, type, tid, params, sbd):
+	"Gets a message"
+	t = tid.split(' ')
+	email = t[0]
+
+	# parse
+	lines = params.split('\n')
+	headers = {} 
+	eoh = 0
+	for i in lines:
+		# end of headers
+		if i == '\r':
+			break
+		tv = i.split(':', 1)
+		type = tv[0]
+		value = tv[1].strip()
+		headers[type] = value
+		eoh += 1
+	eoh +=1
+
+	# ignore hotmail messages
+	if email == 'Hotmail':
+		return
+	
+	if email not in emwin.keys():
+		emwin[email] = chatwindow(root, email)
+		
+	# typing notifications
+	if (headers.has_key('Content-Type') and 
+			headers['Content-Type'] == 'text/x-msmsgscontrol'):
+		if not m.users[email].priv.has_key('typing'):
+			m.users[email].priv['typing'] = 1
+			msg = now() + ' --- is typing\n'
+			emwin[email].append(msg, 'typing')
+			
+	# normal message
+	else:
+		if len(lines[eoh:]) > 1:
+			msg = now() + ' <<<\n\t'
+			msg += string.join(lines[eoh:], '\n\t')
+			msg = msg.replace('\r', '')
+		else:
+			msg = now() + ' <<< ' + lines[eoh] + '\n'
+			
+		if m.users[email].priv.has_key('typing'):
+			del(m.users[email].priv['typing'])
+			
+		emwin[email].append(msg, 'from')
+		root.bell()
+
+	msncb.cb_msg(md, type, tid, params, sbd)
+m.cb.msg = cb_msg
+
+
+
+#
+# main
+#
+
+# email - chatwindow dictionary
+emwin = {}
+
+# gui init
+root = Tk()
+root.wm_title('msnlib')
+
+mainlist = userlist(root)
+mainlist.pack(side = TOP, fill = BOTH, expand = 1)
+
+status = Label(root, text = "logging in...", bd=1, relief = SUNKEN, anchor = W)
+status.pack(side = BOTTOM, fill = X, expand = 0)
+
+menu = mainmenu(root)
+root.config(menu = menu)
+
+# initial update, to display at least something while we log in
+root.update()
+
+# ask for username and password if not given in the command line
+if len(sys.argv) < 3:
+	m.email = tkSimpleDialog.askstring("Username",
+		"Please insert your email")
+	if not m.email:
+		quit()
+	
+	m.pwd = tkSimpleDialog.askstring("Password",
+		"Please insert your password")
+	if not m.pwd:
+		quit()
+else:
+	m.email = sys.argv[1]
+	m.pwd = sys.argv[2]
+
+m.email = m.email.strip()
+m.pwd = m.pwd.strip()
+
+# the encoding is utf-8 because the text class uses unicode directly
+m.encoding = 'utf-8'
+
+root.update()
+
+# login
+try:
+	m.login()
+	m.sync()
+except 'AuthError':
+	tkMessageBox.showerror("Login", "Error logging in: wrong password")
+	quit()
+
+# start as invisible
+m.change_status('invisible')
+
+
+# main loop
+while 1:
+	fds = m.pollable()
+	infd = fds[0]
+	outfd = fds[1]
+	
+	try:
+		# both network and gui checks
+		fds = select.select(infd, outfd, [], 0)
+		root.update()
+	except KeyboardInterrupt:
+		quit()
+	except TclError:
+		quit()
+
+	for i in fds[0] + fds[1]:
+		try:
+			m.read(i)
+		except ('SocketError', socket.error), err:
+			if i != m:
+				m.close(i)
+			else:
+				tkMessageBox.showwarning("Warning",
+					"Server disconnected us - you " +
+					"probably logged in somewhere else")
+				quit()
+		
+		# always redraw after a network event
+		redraw_main()
+	
+	# sleep a bit so we don't take over the cpu
+	time.sleep(0.05)
+
+
