changeset 19450:705eca1f82e2

merge of 'e6a26aab7c177405a9cbb8d8358228ed4a53d508' and 'e885dc139c026cbbb5d55f8509a1c63468bd80b6'
author Sadrul Habib Chowdhury <imadil@gmail.com>
date Sun, 26 Aug 2007 14:02:35 +0000
parents a8c9c42c3572 (diff) c040dac83934 (current diff)
children 4a7e5f947b1e
files
diffstat 12 files changed, 1321 insertions(+), 1554 deletions(-) [+]
line wrap: on
line diff
--- a/libpurple/protocols/myspace/Makefile.am	Sun Aug 26 14:02:30 2007 +0000
+++ b/libpurple/protocols/myspace/Makefile.am	Sun Aug 26 14:02:35 2007 +0000
@@ -2,7 +2,18 @@
 
 pkgdir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION)
 
-SOURCES = myspace.c message.c
+SOURCES = myspace.c \
+	  myspace.h \
+	  persist.h \
+	  message.c \
+	  message.h \
+	  zap.c \
+	  session.c \
+	  session.h \
+	  markup.c \
+	  markup.h \
+	  user.c \
+	  user.h
 
 AM_CFLAGS = $(st)
 
--- a/libpurple/protocols/myspace/Makefile.mingw	Sun Aug 26 14:02:30 2007 +0000
+++ b/libpurple/protocols/myspace/Makefile.mingw	Sun Aug 26 14:02:35 2007 +0000
@@ -37,7 +37,7 @@
 ##
 ##  SOURCES, OBJECTS
 ##
-C_SRC =			myspace.c message.c
+C_SRC =			myspace.c message.c zap.c session.c markup.c user.c
 
 OBJECTS = $(C_SRC:%.c=%.o)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/markup.c	Sun Aug 26 14:02:35 2007 +0000
@@ -0,0 +1,689 @@
+/* MySpaceIM Protocol Plugin - markup
+ *
+ * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "myspace.h"
+
+typedef void (*MSIM_XMLNODE_CONVERT)(MsimSession *, xmlnode *, gchar **, gchar **);
+
+/* Internal functions */
+
+static guint msim_point_to_purple_size(MsimSession *session, guint point);
+static guint msim_purple_size_to_point(MsimSession *session, guint size);
+static guint msim_height_to_point(MsimSession *session, guint height);
+static guint msim_point_to_height(MsimSession *session, guint point);
+
+static void msim_markup_tag_to_html(MsimSession *, xmlnode *root, gchar **begin, gchar **end);
+static void html_tag_to_msim_markup(MsimSession *, xmlnode *root, gchar **begin, gchar **end);
+static gchar *msim_convert_xml(MsimSession *, const gchar *raw, MSIM_XMLNODE_CONVERT f);
+static gchar *msim_convert_smileys_to_markup(gchar *before);
+static double msim_round(double round);
+
+
+/* Globals */
+
+/* The names in in emoticon_names (for <i n=whatever>) map to corresponding 
+ * entries in emoticon_symbols (for the ASCII representation of the emoticon).
+ *
+ * Multiple emoticon symbols in Pidgin can map to one name. List the
+ * canonical form, as inserted by the "Smile!" dialog, first. For example,
+ * :) comes before :-), because although both are recognized as 'happy',
+ * the first is inserted by the smiley button (first symbol in theme).
+ *
+ * Note that symbols are case-sensitive in Pidgin -- :-X is not :-x. */
+static struct MSIM_EMOTICON
+{
+	gchar *name;
+	gchar *symbol;
+} msim_emoticons[] = {
+	/* Unfortunately, this list duplicates much of the file
+	 * pidgin/pidgin/pixmaps/emotes/default/22/default.theme.in, because
+	 * that file is part of Pidgin, but we're part of libpurple.
+	 */
+	{ "bigsmile", ":D" },
+	{ "bigsmile", ":-D" },
+	{ "devil", "}:)" },
+	{ "frazzled", ":Z" },
+	{ "geek", "B)" },
+	{ "googles", "%)" },
+	{ "growl", ":E" },
+	{ "laugh", ":))" },		/* Must be before ':)' */
+	{ "happy", ":)" },
+	{ "happy", ":-)" },
+	{ "happi", ":)" },
+	{ "heart", ":X" },
+	{ "mohawk", "-:" },
+	{ "mad", "X(" },
+	{ "messed", "X)" },
+	{ "nerd", "Q)" },
+	{ "oops", ":G" },
+	{ "pirate", "P)" },
+	{ "scared", ":O" },
+	{ "sidefrown", ":{" },
+	{ "sinister", ":B" },
+	{ "smirk", ":," },
+	{ "straight", ":|" },
+	{ "tongue", ":P" },
+	{ "tongue", ":p" },
+	{ "tongy", ":P" },
+	{ "upset", "B|" },
+	{ "wink", ";-)" },
+	{ "wink", ";)" },
+	{ "winc", ";)" },
+	{ "worried", ":[" },
+	{ "kiss", ":x" },
+	{ NULL, NULL }
+};
+
+
+
+/* Indexes of this array + 1 map HTML font size to scale of normal font size. *
+ * Based on _point_sizes from libpurple/gtkimhtml.c 
+ *                                 1    2  3    4     5      6       7 */
+static gdouble _font_scale[] = { .85, .95, 1, 1.2, 1.44, 1.728, 2.0736 };
+
+#define MAX_FONT_SIZE                   7       /* Purple maximum font size */
+#define POINTS_PER_INCH                 72      /* How many pt's in an inch */
+
+/* Text formatting bits for <f s=#> */
+#define MSIM_TEXT_BOLD                  1
+#define MSIM_TEXT_ITALIC                2   
+#define MSIM_TEXT_UNDERLINE             4
+
+/* Default baseline size of purple's fonts, in points. What is size 3 in points. 
+ * _font_scale specifies scaling factor relative to this point size. Note this 
+ * is only the default; it is configurable in account options. */
+#define MSIM_BASE_FONT_POINT_SIZE       8
+
+/* Default display's DPI. 96 is common but it can differ. Also configurable
+ * in account options. */
+#define MSIM_DEFAULT_DPI                96
+
+
+/* round is part of C99, but sometimes is unavailable before then.
+ * Based on http://forums.belution.com/en/cpp/000/050/13.shtml
+ */
+double msim_round(double value)
+{
+	if (value < 0) {
+		return -(floor(-value + 0.5));
+	} else {
+		return   floor( value + 0.5);
+	}
+}
+
+
+/** Convert typographical font point size to HTML font size. 
+ * Based on libpurple/gtkimhtml.c */
+static guint
+msim_point_to_purple_size(MsimSession *session, guint point)
+{
+	guint size, this_point, base;
+	gdouble scale;
+	
+	base = purple_account_get_int(session->account, "base_font_size", MSIM_BASE_FONT_POINT_SIZE);
+   
+	for (size = 0; 
+			size < sizeof(_font_scale) / sizeof(_font_scale[0]);
+			++size) {
+		scale = _font_scale[CLAMP(size, 1, MAX_FONT_SIZE) - 1];
+		this_point = (guint)msim_round(scale * base);
+
+		if (this_point >= point) {
+			purple_debug_info("msim", "msim_point_to_purple_size: %d pt -> size=%d\n",
+					point, size);
+			return size;
+		}
+	}
+
+	/* No HTML font size was this big; return largest possible. */
+	return this_point;
+}
+
+/** Convert HTML font size to point size. */
+static guint
+msim_purple_size_to_point(MsimSession *session, guint size)
+{
+	gdouble scale;
+	guint point;
+	guint base;
+
+	scale = _font_scale[CLAMP(size, 1, MAX_FONT_SIZE) - 1];
+
+	base = purple_account_get_int(session->account, "base_font_size", MSIM_BASE_FONT_POINT_SIZE);
+
+	point = (guint)msim_round(scale * base);
+
+	purple_debug_info("msim", "msim_purple_size_to_point: size=%d -> %d pt\n",
+					size, point);
+
+	return point;
+}
+
+/** Convert a msim markup font pixel height to the more usual point size, for incoming messages. */
+static guint 
+msim_height_to_point(MsimSession *session, guint height)
+{
+	guint dpi;
+
+	dpi = purple_account_get_int(session->account, "port", MSIM_DEFAULT_DPI);
+
+	return (guint)msim_round((POINTS_PER_INCH * 1. / dpi) * height);
+
+	/* See also: libpurple/protocols/bonjour/jabber.c
+	 * _font_size_ichat_to_purple */
+}
+
+/** Convert point size to msim pixel height font size specification, for outgoing messages. */
+static guint
+msim_point_to_height(MsimSession *session, guint point)
+{
+	guint dpi;
+
+	dpi = purple_account_get_int(session->account, "port", MSIM_DEFAULT_DPI);
+
+	return (guint)msim_round((dpi * 1. / POINTS_PER_INCH) * point);
+}
+
+/** Convert the msim markup <f> (font) tag into HTML. */
+static void 
+msim_markup_f_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
+{
+	const gchar *face, *height_str, *decor_str;
+	GString *gs_end, *gs_begin;
+	guint decor, height;
+
+	face = xmlnode_get_attrib(root, "f");
+	height_str = xmlnode_get_attrib(root, "h");
+	decor_str = xmlnode_get_attrib(root, "s");
+
+	if (height_str) {
+		height = atol(height_str);
+	} else {
+		height = 12;
+	}
+
+	if (decor_str) {
+		decor = atol(decor_str);
+	} else {
+		decor = 0;
+	}
+
+	gs_begin = g_string_new("");
+	/* TODO: get font size working */
+	if (height && !face) {
+		g_string_printf(gs_begin, "<font size='%d'>", 
+				msim_point_to_purple_size(session, msim_height_to_point(session, height)));
+	} else if (height && face) {
+		g_string_printf(gs_begin, "<font face='%s' size='%d'>", face,  
+				msim_point_to_purple_size(session, msim_height_to_point(session, height)));
+	} else {
+		g_string_printf(gs_begin, "<font>");
+	}
+
+	/* No support for font-size CSS? */
+	/* g_string_printf(gs_begin, "<span style='font-family: %s; font-size: %dpt'>", face, 
+			msim_height_to_point(height)); */
+
+	gs_end = g_string_new("</font>");
+
+	if (decor & MSIM_TEXT_BOLD) {
+		g_string_append(gs_begin, "<b>");
+		g_string_prepend(gs_end, "</b>");
+	}
+
+	if (decor & MSIM_TEXT_ITALIC) {
+		g_string_append(gs_begin, "<i>");
+		g_string_append(gs_end, "</i>");
+	}
+
+	if (decor & MSIM_TEXT_UNDERLINE) {
+		g_string_append(gs_begin, "<u>");
+		g_string_append(gs_end, "</u>");
+	}
+
+
+	*begin = gs_begin->str;
+	*end = gs_end->str;
+}
+
+/** Convert a msim markup color to a color suitable for libpurple.
+  *
+  * @param msim Either a color name, or an rgb(x,y,z) code.
+  *
+  * @return A new string, either a color name or #rrggbb code. Must g_free(). 
+  */
+static char *
+msim_color_to_purple(const char *msim)
+{
+	guint red, green, blue;
+
+	if (!msim) {
+		return g_strdup("black");
+	}
+
+	if (sscanf(msim, "rgb(%d,%d,%d)", &red, &green, &blue) != 3) {
+		/* Color name. */
+		return g_strdup(msim);
+	}
+	/* TODO: rgba (alpha). */
+
+	return g_strdup_printf("#%.2x%.2x%.2x", red, green, blue);
+}
+
+/** Convert the msim markup <a> (anchor) tag into HTML. */
+static void 
+msim_markup_a_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
+{
+	const gchar *href;
+
+	href = xmlnode_get_attrib(root, "h");
+	if (!href) {
+		href = "";
+	}
+
+	*begin = g_strdup_printf("<a href=\"%s\">%s", href, href);
+	*end = g_strdup("</a>");
+}
+
+/** Convert the msim markup <p> (paragraph) tag into HTML. */
+static void 
+msim_markup_p_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
+{
+	/* Just pass through unchanged. 
+	 *
+	 * Note: attributes currently aren't passed, if there are any. */
+	*begin = g_strdup("<p>");
+	*end = g_strdup("</p>");
+}
+
+/** Convert the msim markup <c> tag (text color) into HTML. TODO: Test */
+static void 
+msim_markup_c_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
+{
+	const gchar *color;
+	gchar *purple_color;
+
+	color = xmlnode_get_attrib(root, "v");
+	if (!color) {
+		purple_debug_info("msim", "msim_markup_c_to_html: <c> tag w/o v attr");
+		*begin = g_strdup("");
+		*end = g_strdup("");
+		/* TODO: log as unrecognized */
+		return;
+	}
+
+	purple_color = msim_color_to_purple(color);
+
+	*begin = g_strdup_printf("<font color='%s'>", purple_color); 
+
+	g_free(purple_color);
+
+	/* *begin = g_strdup_printf("<span style='color: %s'>", color); */
+	*end = g_strdup("</font>");
+}
+
+/** Convert the msim markup <b> tag (background color) into HTML. TODO: Test */
+static void 
+msim_markup_b_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
+{
+	const gchar *color;
+	gchar *purple_color;
+
+	color = xmlnode_get_attrib(root, "v");
+	if (!color) {
+		*begin = g_strdup("");
+		*end = g_strdup("");
+		purple_debug_info("msim", "msim_markup_b_to_html: <b> w/o v attr");
+		/* TODO: log as unrecognized. */
+		return;
+	}
+
+	purple_color = msim_color_to_purple(color);
+
+	/* TODO: find out how to set background color. */
+	*begin = g_strdup_printf("<span style='background-color: %s'>", 
+			purple_color);
+	g_free(purple_color);
+
+	*end = g_strdup("</p>");
+}
+
+/** Convert the msim markup <i> tag (emoticon image) into HTML. */
+static void 
+msim_markup_i_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
+{
+	const gchar *name;
+	guint i;
+	struct MSIM_EMOTICON *emote;
+
+	name = xmlnode_get_attrib(root, "n");
+	if (!name) {
+		purple_debug_info("msim", "msim_markup_i_to_html: <i> w/o n");
+		*begin = g_strdup("");
+		*end = g_strdup("");
+		/* TODO: log as unrecognized */
+		return;
+	}
+
+	/* Find and use canonical form of smiley symbol. */
+	for (i = 0; (emote = &msim_emoticons[i]) && emote->name != NULL; ++i) {
+		if (g_str_equal(name, emote->name)) {
+			*begin = g_strdup(emote->symbol);
+			*end = g_strdup("");
+			return;
+		}
+	}
+
+	/* Couldn't find it, sorry. Try to degrade gracefully. */
+	*begin = g_strdup_printf("**%s**", name);
+	*end = g_strdup("");
+}
+
+/** Convert an individual msim markup tag to HTML. */
+static void 
+msim_markup_tag_to_html(MsimSession *session, xmlnode *root, gchar **begin, 
+		gchar **end)
+{
+	if (g_str_equal(root->name, "f")) {
+		msim_markup_f_to_html(session, root, begin, end);
+	} else if (g_str_equal(root->name, "a")) {
+		msim_markup_a_to_html(session, root, begin, end);
+	} else if (g_str_equal(root->name, "p")) {
+		msim_markup_p_to_html(session, root, begin, end);
+	} else if (g_str_equal(root->name, "c")) {
+		msim_markup_c_to_html(session, root, begin, end);
+	} else if (g_str_equal(root->name, "b")) {
+		msim_markup_b_to_html(session, root, begin, end);
+	} else if (g_str_equal(root->name, "i")) {
+		msim_markup_i_to_html(session, root, begin, end);
+	} else {
+		purple_debug_info("msim", "msim_markup_tag_to_html: "
+				"unknown tag name=%s, ignoring", 
+				(root && root->name) ? root->name : "(NULL)");
+		*begin = g_strdup("");
+		*end = g_strdup("");
+	}
+}
+
+/** Convert an individual HTML tag to msim markup. */
+static void 
+html_tag_to_msim_markup(MsimSession *session, xmlnode *root, gchar **begin, 
+		gchar **end)
+{
+	/* TODO: Coalesce nested tags into one <f> tag!
+	 * Currently, the 's' value will be overwritten when b/i/u is nested
+	 * within another one, and only the inner-most formatting will be 
+	 * applied to the text. */
+	if (!purple_utf8_strcasecmp(root->name, "root")) {
+		*begin = g_strdup("");
+		*end = g_strdup("");
+	} else if (!purple_utf8_strcasecmp(root->name, "b")) {
+		*begin = g_strdup_printf("<f s='%d'>", MSIM_TEXT_BOLD);
+		*end = g_strdup("</f>");
+	} else if (!purple_utf8_strcasecmp(root->name, "i")) {
+		*begin = g_strdup_printf("<f s='%d'>", MSIM_TEXT_ITALIC);
+		*end = g_strdup("</f>");
+	} else if (!purple_utf8_strcasecmp(root->name, "u")) {
+		*begin = g_strdup_printf("<f s='%d'>", MSIM_TEXT_UNDERLINE);
+		*end = g_strdup("</f>");
+	} else if (!purple_utf8_strcasecmp(root->name, "a")) {
+		const gchar *href, *link_text;
+
+		href = xmlnode_get_attrib(root, "href");
+
+		if (!href) {
+			href = xmlnode_get_attrib(root, "HREF");
+		}
+
+		link_text = xmlnode_get_data(root);
+
+		if (href) {
+			if (g_str_equal(link_text, href)) {
+				/* Purple gives us: <a href="URL">URL</a>
+				 * Translate to <a h='URL' />
+				 * Displayed as text of URL with link to URL
+				 */
+				*begin = g_strdup_printf("<a h='%s' />", href);
+			} else {
+				/* But if we get: <a href="URL">text</a>
+				 * Translate to: text: <a h='URL' />
+				 *
+				 * Because official client only supports self-closed <a>
+				 * tags; you can't change the link text.
+				 */
+				*begin = g_strdup_printf("%s: <a h='%s' />", link_text, href);
+			}
+		} else {
+			*begin = g_strdup("<a />");
+		}
+
+		/* Sorry, kid. MySpace doesn't support you within <a> tags. */
+		xmlnode_free(root->child);
+		root->child = NULL;
+
+		*end = g_strdup("");
+	} else if (!purple_utf8_strcasecmp(root->name, "font")) {
+		const gchar *size;
+		const gchar *face;
+
+		size = xmlnode_get_attrib(root, "size");
+		face = xmlnode_get_attrib(root, "face");
+
+		if (face && size) {
+			*begin = g_strdup_printf("<f f='%s' h='%d'>", face, 
+					msim_point_to_height(session,
+						msim_purple_size_to_point(session, atoi(size))));
+		} else if (face) {
+			*begin = g_strdup_printf("<f f='%s'>", face);
+		} else if (size) {
+			*begin = g_strdup_printf("<f h='%d'>", 
+					 msim_point_to_height(session,
+						 msim_purple_size_to_point(session, atoi(size))));
+		} else {
+			*begin = g_strdup("<f>");
+		}
+
+		*end = g_strdup("</f>");
+
+		/* TODO: color (bg uses <body>), emoticons */
+	} else {
+		*begin = g_strdup_printf("[%s]", root->name);
+		*end = g_strdup_printf("[/%s]", root->name);
+	}
+}
+
+/** Convert an xmlnode of msim markup or HTML to an HTML string or msim markup.
+ *
+ * @param f Function to convert tags.
+ *
+ * @return An HTML string. Caller frees.
+ */
+static gchar *
+msim_convert_xmlnode(MsimSession *session, xmlnode *root, MSIM_XMLNODE_CONVERT f)
+{
+	xmlnode *node;
+	gchar *begin, *inner, *end;
+	GString *final;
+
+	if (!root || !root->name) {
+		return g_strdup("");
+	}
+
+	purple_debug_info("msim", "msim_convert_xmlnode: got root=%s\n",
+			root->name);
+
+	begin = inner = end = NULL;
+
+	final = g_string_new("");
+
+	f(session, root, &begin, &end);
+	
+	g_string_append(final, begin);
+
+	/* Loop over all child nodes. */
+	for (node = root->child; node != NULL; node = node->next) {
+		switch (node->type) {
+		case XMLNODE_TYPE_ATTRIB:
+			/* Attributes handled above. */
+			break;
+
+		case XMLNODE_TYPE_TAG:
+			/* A tag or tag with attributes. Recursively descend. */
+			inner = msim_convert_xmlnode(session, node, f);
+			g_return_val_if_fail(inner != NULL, NULL);
+
+			purple_debug_info("msim", " ** node name=%s\n", 
+					(node && node->name) ? node->name : "(NULL)");
+			break;
+	
+		case XMLNODE_TYPE_DATA:
+			/* Literal text. */
+			inner = g_new0(char, node->data_sz + 1);
+			strncpy(inner, node->data, node->data_sz);
+			inner[node->data_sz] = 0;
+
+			purple_debug_info("msim", " ** node data=%s\n", 
+					inner ? inner : "(NULL)");
+			break;
+			
+		default:
+			purple_debug_info("msim",
+					"msim_convert_xmlnode: strange node\n");
+			inner = g_strdup("");
+		}
+
+		if (inner) {
+			g_string_append(final, inner);
+		}
+	}
+
+	/* TODO: Note that msim counts each piece of text enclosed by <f> as
+	 * a paragraph and will display each on its own line. You actually have
+	 * to _nest_ <f> tags to intersperse different text in one paragraph!
+	 * Comment out this line below to see. */
+	g_string_append(final, end);
+
+	purple_debug_info("msim", "msim_markup_xmlnode_to_gtkhtml: RETURNING %s\n",
+			(final && final->str) ? final->str : "(NULL)");
+
+	return final->str;
+}
+
+/** Convert XML to something based on MSIM_XMLNODE_CONVERT. */
+static gchar *
+msim_convert_xml(MsimSession *session, const gchar *raw, MSIM_XMLNODE_CONVERT f)
+{
+	xmlnode *root;
+	gchar *str;
+	gchar *enclosed_raw;
+
+	g_return_val_if_fail(raw != NULL, NULL);
+
+	/* Enclose text in one root tag, to try to make it valid XML for parsing. */
+	enclosed_raw = g_strconcat("<root>", raw, "</root>", NULL);
+
+	root = xmlnode_from_str(enclosed_raw, -1);
+
+	if (!root) {
+		purple_debug_info("msim", "msim_markup_to_html: couldn't parse "
+				"%s as XML, returning raw: %s\n", enclosed_raw, raw);
+		/* TODO: msim_unrecognized */
+		g_free(enclosed_raw);
+		return g_strdup(raw);
+	}
+
+	g_free(enclosed_raw);
+
+	str = msim_convert_xmlnode(session, root, f);
+	g_return_val_if_fail(str != NULL, NULL);
+	purple_debug_info("msim", "msim_markup_to_html: returning %s\n", str);
+
+	xmlnode_free(root);
+
+	return str;
+}
+
+/** Convert plaintext smileys to <i> markup tags.
+ *
+ * @param before Original text with ASCII smileys. Will be freed.
+ * @return A new string with <i> tags, if applicable. Must be g_free()'d.
+ */
+static gchar *
+msim_convert_smileys_to_markup(gchar *before)
+{
+	gchar *old, *new, *replacement;
+	guint i;
+	struct MSIM_EMOTICON *emote;
+
+	old = before;
+	new = NULL;
+
+	for (i = 0; (emote = &msim_emoticons[i]) && emote->name != NULL; ++i) {
+		gchar *name, *symbol;
+
+		name = emote->name;
+		symbol = emote->symbol;
+
+		replacement = g_strdup_printf("<i n=\"%s\"/>", name);
+
+		purple_debug_info("msim", "msim_convert_smileys_to_markup: %s->%s\n",
+				symbol ? symbol : "(NULL)", 
+				replacement ? replacement : "(NULL)");
+		new = str_replace(old, symbol, replacement);
+		
+		g_free(replacement);
+		g_free(old);
+
+		old = new;
+	}
+
+	return new;
+}
+	
+
+/** High-level function to convert MySpaceIM markup to Purple (HTML) markup. 
+ *
+ * @return Purple markup string, must be g_free()'d. */
+gchar *
+msim_markup_to_html(MsimSession *session, const gchar *raw)
+{
+	return msim_convert_xml(session, raw, 
+			(MSIM_XMLNODE_CONVERT)(msim_markup_tag_to_html));
+}
+
+/** High-level function to convert Purple (HTML) to MySpaceIM markup.
+ *
+ * @return HTML markup string, must be g_free()'d. */
+gchar *
+html_to_msim_markup(MsimSession *session, const gchar *raw)
+{
+	gchar *markup;
+
+	markup = msim_convert_xml(session, raw,
+			(MSIM_XMLNODE_CONVERT)(html_tag_to_msim_markup));
+	
+	if (purple_account_get_bool(session->account, "emoticons", TRUE)) {
+		/* Frees markup and allocates a new one. */
+		markup = msim_convert_smileys_to_markup(markup);
+	}
+
+	return markup;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/markup.h	Sun Aug 26 14:02:35 2007 +0000
@@ -0,0 +1,27 @@
+/* MySpaceIM Protocol Plugin - markup
+ *
+ * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _MYSPACE_MARKUP_H
+#define _MYSPACE_MARKUP_H
+
+/* High-level msim markup <=> Purple html conversion functions. */
+gchar *msim_markup_to_html(MsimSession *, const gchar *raw);
+gchar *html_to_msim_markup(MsimSession *, const gchar *raw);
+
+#endif /* !_MYSPACE_MARKUP_H */
--- a/libpurple/protocols/myspace/message.c	Sun Aug 26 14:02:30 2007 +0000
+++ b/libpurple/protocols/myspace/message.c	Sun Aug 26 14:02:35 2007 +0000
@@ -250,13 +250,21 @@
 	return new_list;
 }
 
-/** Free a GList * of gchar * strings. */
+/** Free a GList * of MsimMessageElement *'s. */
 void
 msim_msg_list_free(GList *l)
 {
 
 	for (; l != NULL; l = g_list_next(l)) {
-		g_free((gchar *)(l->data));
+		MsimMessageElement *elem;
+
+		elem = (MsimMessageElement *)l->data;
+
+		/* Note that name is almost never dynamically allocated elsewhere;
+		 * it is usually a static string, but not in lists. So cast it. */
+		g_free((gchar *)elem->name);
+		g_free(elem->data);
+		g_free(elem);
 	}
 	g_list_free(l);
 }
@@ -275,7 +283,19 @@
 	/* TODO: escape/unescape /3 <-> | within list elements */
 	
 	for (i = 0; array[i] != NULL; ++i) {
-		list = g_list_append(list, g_strdup(array[i]));
+		MsimMessageElement *elem;
+
+		/* Freed in msim_msg_list_free() */
+		elem = g_new0(MsimMessageElement, 1);
+
+		/* Give the element a name for debugging purposes.
+		 * Not supposed to be looked up by this name; instead,
+		 * lookup the elements by indexing the array. */
+		elem->name = g_strdup_printf("(list item #%d)", i);
+		elem->type = MSIM_TYPE_RAW;
+		elem->data = g_strdup(array[i]);
+
+		list = g_list_append(list, elem);
 	}
 
 	g_strfreev(array);
--- a/libpurple/protocols/myspace/myspace.c	Sun Aug 26 14:02:30 2007 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Sun Aug 26 14:02:35 2007 +0000
@@ -33,135 +33,37 @@
 
 #define PURPLE_PLUGIN
 
-#include "message.h"
-#include "persist.h"
 #include "myspace.h"
 
-/* Globals */
-
-/* The names in in emoticon_names (for <i n=whatever>) map to corresponding 
- * entries in emoticon_symbols (for the ASCII representation of the emoticon).
- *
- * Multiple emoticon symbols in Pidgin can map to one name. List the
- * canonical form, as inserted by the "Smile!" dialog, first. For example,
- * :) comes before :-), because although both are recognized as 'happy',
- * the first is inserted by the smiley button (first symbol in theme).
- *
- * Note that symbols are case-sensitive in Pidgin -- :-X is not :-x. */
-static struct MSIM_EMOTICON
-{
-	gchar *name;
-	gchar *symbol;
-} msim_emoticons[] = {
-	/* Unfortunately, this list duplicates much of the file
-	 * pidgin/pidgin/pixmaps/emotes/default/22/default.theme.in, because
-	 * that file is part of Pidgin, but we're part of libpurple.
-	 */
-	{ "bigsmile", ":D" },
-	{ "bigsmile", ":-D" },
-	{ "devil", "}:)" },
-	{ "frazzled", ":Z" },
-	{ "geek", "B)" },
-	{ "googles", "%)" },
-	{ "growl", ":E" },
-	{ "laugh", ":))" },		/* Must be before ':)' */
-	{ "happy", ":)" },
-	{ "happy", ":-)" },
-	{ "happi", ":)" },
-	{ "heart", ":X" },
-	{ "mohawk", "-:" },
-	{ "mad", "X(" },
-	{ "messed", "X)" },
-	{ "nerd", "Q)" },
-	{ "oops", ":G" },
-	{ "pirate", "P)" },
-	{ "scared", ":O" },
-	{ "sidefrown", ":{" },
-	{ "sinister", ":B" },
-	{ "smirk", ":," },
-	{ "straight", ":|" },
-	{ "tongue", ":P" },
-	{ "tongue", ":p" },
-	{ "tongy", ":P" },
-	{ "upset", "B|" },
-	{ "wink", ";-)" },
-	{ "wink", ";)" },
-	{ "winc", ";)" },
-	{ "worried", ":[" },
-	{ "kiss", ":x" },
-	{ NULL, NULL }
-};
-
 /* Internal functions */
-static gboolean msim_send_zap(MsimSession *session, const gchar *username, guint code);
-static void msim_send_zap_from_menu(PurpleBlistNode *node, gpointer zap_num_ptr);
 
 #ifdef MSIM_DEBUG_MSG
 static void print_hash_item(gpointer key, gpointer value, gpointer user_data);
 #endif
 
-static int msim_send_really_raw(PurpleConnection *gc, const char *buf,
-		int total_bytes);
+static int msim_send_really_raw(PurpleConnection *gc, const char *buf, int total_bytes);
 static gboolean msim_login_challenge(MsimSession *session, MsimMessage *msg);
-static const gchar *msim_compute_login_response(
-		const gchar nonce[2 * NONCE_SIZE], const gchar *email, 
-		const gchar *password, guint *response_len);
-static gboolean msim_send_bm(MsimSession *session, const gchar *who, 
-		const gchar *text, int type);
-
-static guint msim_point_to_purple_size(MsimSession *session, guint point);
-static guint msim_purple_size_to_point(MsimSession *session, guint size);
-static guint msim_height_to_point(MsimSession *session, guint height);
-static guint msim_point_to_height(MsimSession *session, guint point);
-
-static void msim_unrecognized(MsimSession *session, MsimMessage *msg, gchar *note);
-
-static void msim_markup_tag_to_html(MsimSession *, xmlnode *root, 
-		gchar **begin, gchar **end);
-static void html_tag_to_msim_markup(MsimSession *, xmlnode *root, 
-		gchar **begin, gchar **end);
-static gchar *msim_convert_xml(MsimSession *, const gchar *raw, 
-		MSIM_XMLNODE_CONVERT f);
-static gchar *msim_convert_smileys_to_markup(gchar *before);
-
-/* High-level msim markup <=> html conversion functions. */
-static gchar *msim_markup_to_html(MsimSession *, const gchar *raw);
-static gchar *html_to_msim_markup(MsimSession *, const gchar *raw);
-
-static MsimUser *msim_get_user_from_buddy(PurpleBuddy *buddy);
-static MsimUser *msim_find_user(MsimSession *session, const gchar *username);
-
-static gboolean msim_incoming_bm_record_cv(MsimSession *session, 
-		MsimMessage *msg);
+static const gchar *msim_compute_login_response(const gchar nonce[2 * NONCE_SIZE], const gchar *email, const gchar *password, guint *response_len);
+
+static gboolean msim_incoming_bm_record_cv(MsimSession *session, MsimMessage *msg);
 static gboolean msim_incoming_bm(MsimSession *session, MsimMessage *msg);
 static gboolean msim_incoming_status(MsimSession *session, MsimMessage *msg);
 static gboolean msim_incoming_im(MsimSession *session, MsimMessage *msg);
-static gboolean msim_incoming_zap(MsimSession *session, MsimMessage *msg);
+/* static gboolean msim_incoming_zap(MsimSession *session, MsimMessage *msg); - in zap.c */
 static gboolean msim_incoming_action(MsimSession *session, MsimMessage *msg);
 static gboolean msim_incoming_media(MsimSession *session, MsimMessage *msg);
 static gboolean msim_incoming_unofficial_client(MsimSession *session, 
 		MsimMessage *msg);
 
 #ifdef MSIM_SEND_CLIENT_VERSION
-static gboolean msim_send_unofficial_client(MsimSession *session, 
-		gchar *username);
+static gboolean msim_send_unofficial_client(MsimSession *session, gchar *username);
 #endif
 
 static void msim_get_info_cb(MsimSession *session, MsimMessage *userinfo, gpointer data);
-static gchar *msim_format_now_playing(gchar *band, gchar *song);
-
-static void msim_set_status_code(MsimSession *session, guint code, 
-		gchar *statstring);
-
-static void msim_append_user_info(MsimSession *session, PurpleNotifyUserInfo *user_info, MsimUser *user, gboolean full);
-
-static void msim_downloaded_buddy_icon(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text,
-		gsize len, const gchar *error_message);
-
-static void msim_store_user_info_each(const gchar *key_str, gchar *value_str, MsimUser *user);
-static gboolean msim_store_user_info(MsimSession *session, MsimMessage *msg, MsimUser *user);
-static gboolean msim_process_server_info(MsimSession *session, 
-		MsimMessage *msg);
+
+static void msim_set_status_code(MsimSession *session, guint code, gchar *statstring);
+
+static gboolean msim_process_server_info(MsimSession *session, MsimMessage *msg);
 static gboolean msim_web_challenge(MsimSession *session, MsimMessage *msg); 
 static gboolean msim_process_reply(MsimSession *session, MsimMessage *msg);
 
@@ -175,51 +77,22 @@
 
 static gboolean msim_process(MsimSession *session, MsimMessage *msg);
 
-static MsimMessage *msim_do_postprocessing(MsimMessage *msg, 
-		const gchar *uid_field_name, const gchar *uid_before, guint uid);
-static void msim_postprocess_outgoing_cb(MsimSession *session, 
-		MsimMessage *userinfo, gpointer data);
-static gboolean msim_postprocess_outgoing(MsimSession *session, 
-		MsimMessage *msg, const gchar *username, const gchar *uid_field_name, 
-		const gchar *uid_before); 
+static MsimMessage *msim_do_postprocessing(MsimMessage *msg, const gchar *uid_field_name, const gchar *uid_before, guint uid);
+static void msim_postprocess_outgoing_cb(MsimSession *session, MsimMessage *userinfo, gpointer data);
+static gboolean msim_postprocess_outgoing(MsimSession *session, MsimMessage *msg, const gchar *username, const gchar *uid_field_name, const gchar *uid_before); 
 
 static gboolean msim_error(MsimSession *session, MsimMessage *msg);
 
-static void msim_check_inbox_cb(MsimSession *session, MsimMessage *userinfo, 
-		gpointer data);
+static void msim_check_inbox_cb(MsimSession *session, MsimMessage *userinfo, gpointer data);
 static gboolean msim_check_inbox(gpointer data);
 
-static void msim_input_cb(gpointer gc_uncasted, gint source, 
-		PurpleInputCondition cond);
-
-static guint msim_new_reply_callback(MsimSession *session, 
-		MSIM_USER_LOOKUP_CB cb, gpointer data);
-
-static void msim_connect_cb(gpointer data, gint source, 
-		const gchar *error_message);
-
-static gboolean msim_is_userid(const gchar *user);
-static gboolean msim_is_email(const gchar *user);
-
-static void msim_lookup_user(MsimSession *session, const gchar *user, 
-		MSIM_USER_LOOKUP_CB cb, gpointer data);
+static void msim_input_cb(gpointer gc_uncasted, gint source, PurpleInputCondition cond);
+
+
+static void msim_connect_cb(gpointer data, gint source, const gchar *error_message);
 
 static void msim_import_friends(PurplePluginAction *action);
 
-double msim_round(double round);
-
-/* round is part of C99, but sometimes is unavailable before then.
- * Based on http://forums.belution.com/en/cpp/000/050/13.shtml
- */
-double msim_round(double value)
-{
-	if (value < 0) {
-		return -(floor(-value + 0.5));
-	} else {
-		return   floor( value + 0.5);
-	}
-}
-
 /** 
  * Load the plugin.
  */
@@ -283,195 +156,6 @@
 	return types;
 }
 
-/** Get zap types. */
-GList *
-msim_attention_types(PurpleAccount *acct)
-{
-	static GList *types = NULL;
-	MsimAttentionType* attn;
-
-	if (!types) {
-#define _MSIM_ADD_NEW_ATTENTION(icn, nme, incoming, outgoing)              \
-		attn = g_new0(MsimAttentionType, 1);                       \
-		attn->icon_name = icn;                                     \
-		attn->name = nme;                                          \
-		attn->incoming_description = incoming;                     \
-		attn->outgoing_description = outgoing;                     \
-		types = g_list_append(types, attn);
-
-		/* TODO: icons for each zap */
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("zap"), _("zapped"), _("Zapping"));
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("whack"), _("whacked"), _("Whacking"));
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("torch"), _("torched"), _("Torching"));
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("smooch"), _("smooched"), _("Smooching"));
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("hug"), _("hugged"), _("Hugging"));
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("bslap"), _("bslapped"), _("Bslapping"));
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("goose"), _("goosed"), _("Goosing"));
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("hi-five"), _("hi-fived"), _("Hi-fiving"));
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("punk"), _("punk'd"), _("Punking"));
-		_MSIM_ADD_NEW_ATTENTION(NULL, _("raspberry"), _("raspberried"), _("Raspberry'ing"));
-	}
-
-	return types;
-}
-
-/** Send a zap */
-gboolean
-msim_send_attention(PurpleConnection *gc, const gchar *username, guint code)
-{
-	GList *types;
-	MsimSession *session;
-	MsimAttentionType *attn;
-	PurpleBuddy *buddy;
-
-	session = (MsimSession *)gc->proto_data;
-
-	/* Look for this attention type, by the code index given. */
-	types = msim_attention_types(gc->account);
-	attn = (MsimAttentionType *)g_list_nth_data(types, code);
-
-	if (!attn) {
-		purple_debug_info("msim_send_attention", "got invalid zap code %d\n", code);
-		return FALSE;
-	}
-
-	buddy = purple_find_buddy(session->account, username);
-	if (!buddy) {
-		return FALSE;
-	}
-
-	/* TODO: make use of the MsimAttentionType we found, instead of
-	 * doing it all over in msim_send_zap_from_menu. */
-	msim_send_zap_from_menu(&buddy->node, GUINT_TO_POINTER(code));
-
-	return TRUE;
-}
-
-/** Send a zap to a user. */
-static gboolean
-msim_send_zap(MsimSession *session, const gchar *username, guint code)
-{
-	gchar *zap_string;
-#ifndef MSIM_USE_ATTENTION_API
-	gchar *zap_description;
-#endif
-	GList *types;
-	MsimAttentionType *attn;
-	gboolean rc;
-
-	g_return_val_if_fail(session != NULL, FALSE);
-	g_return_val_if_fail(username != NULL, FALSE);
-
-	types = msim_attention_types(session->account);
-
-	attn = g_list_nth_data(types, code);
-	if (!attn) {
-		return FALSE;
-	}
-
-
-#ifdef MSIM_USE_ATTENTION_API
-	serv_got_attention(session->gc, username, attn, FALSE);
-#else
-	zap_description = g_strdup_printf("*** Attention: %s %s ***", attn->outgoing_description,
-			username);
-
-	serv_got_im(session->gc, username, zap_description,
-			PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_SYSTEM, time(NULL));
-
-	g_free(zap_description);
-#endif
-
-	/* Construct and send the actual zap command. */
-	zap_string = g_strdup_printf("!!!ZAP_SEND!!!=RTE_BTN_ZAPS_%d", code);
-
-	if (!msim_send_bm(session, username, zap_string, MSIM_BM_ACTION)) {
-		purple_debug_info("msim_send_zap_from_menu", "msim_send_bm failed: zapping %s with %s",
-				username, zap_string);
-		rc = FALSE;
-	} else {
-		rc = TRUE;
-	}
-	
-	g_free(zap_string);
-
-	return rc;
-
-}
-
-/** Zap someone. Callback from msim_blist_node_menu zap menu. */
-static void
-msim_send_zap_from_menu(PurpleBlistNode *node, gpointer zap_num_ptr)
-{
-	PurpleBuddy *buddy;
-	PurpleAccount *account;
-	PurpleConnection *gc;
-	MsimSession *session;
-	guint zap;
-
-	if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) {
-		/* Only know about buddies for now. */
-		return;
-	}
-
-	g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
-
-	buddy = (PurpleBuddy *)node;
-
-	/* Find the session */
-	account = buddy->account;
-	gc = purple_account_get_connection(account);
-	session = (MsimSession *)gc->proto_data;
-
-	zap = GPOINTER_TO_INT(zap_num_ptr);
-
-	g_return_if_fail(msim_send_zap(session, buddy->name, zap));
-}
-
-
-/** Return menu, if any, for a buddy list node. */
-GList *
-msim_blist_node_menu(PurpleBlistNode *node)
-{
-	GList *menu, *zap_menu;
-	GList *types;
-	PurpleMenuAction *act;
-	/* Warning: hardcoded to match that in msim_attention_types. */
-	const gchar *zap_names[10];
-	guint i;
-
-	if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) {
-		/* Only know about buddies for now. */
-		return NULL;
-	}
-
-	/* Names from official client. */
-	types = msim_attention_types(NULL);
-	i = 0;
-	do
-	{
-		MsimAttentionType *attn;
-
-		attn = (MsimAttentionType *)types->data;
-		zap_names[i] = attn->name;
-		++i;
-	} while ((types = g_list_next(types)));
-
-	menu = zap_menu = NULL;
-
-	/* TODO: get rid of once is accessible directly in GUI */
-	for (i = 0; i < sizeof(zap_names) / sizeof(zap_names[0]); ++i) {
-		act = purple_menu_action_new(zap_names[i], PURPLE_CALLBACK(msim_send_zap_from_menu),
-				GUINT_TO_POINTER(i), NULL);
-		zap_menu = g_list_append(zap_menu, act);
-	}
-
-	act = purple_menu_action_new(_("Zap"), NULL, NULL, zap_menu);
-	menu = g_list_append(menu, act);
-
-	return menu;
-}
-
 /**
  * Return the icon name for a buddy and account.
  *
@@ -911,7 +595,7 @@
  * Buddy messages ('bm') include instant messages, action messages, status messages, etc.
  *
  */
-static gboolean 
+gboolean 
 msim_send_bm(MsimSession *session, const gchar *who, const gchar *text, 
 		int type)
 {
@@ -945,613 +629,6 @@
 	return rc;
 }
 
-/* Indexes of this array + 1 map HTML font size to scale of normal font size. *
- * Based on _point_sizes from libpurple/gtkimhtml.c 
- *                                 1    2  3    4     5      6       7 */
-static gdouble _font_scale[] = { .85, .95, 1, 1.2, 1.44, 1.728, 2.0736 };
-
-#define MAX_FONT_SIZE                   7       /* Purple maximum font size */
-#define POINTS_PER_INCH                 72      /* How many pt's in an inch */
-
-/** Convert typographical font point size to HTML font size. 
- * Based on libpurple/gtkimhtml.c */
-static guint
-msim_point_to_purple_size(MsimSession *session, guint point)
-{
-	guint size, this_point, base;
-	gdouble scale;
-	
-	base = purple_account_get_int(session->account, "base_font_size", MSIM_BASE_FONT_POINT_SIZE);
-   
-	for (size = 0; 
-			size < sizeof(_font_scale) / sizeof(_font_scale[0]);
-			++size) {
-		scale = _font_scale[CLAMP(size, 1, MAX_FONT_SIZE) - 1];
-		this_point = (guint)msim_round(scale * base);
-
-		if (this_point >= point) {
-			purple_debug_info("msim", "msim_point_to_purple_size: %d pt -> size=%d\n",
-					point, size);
-			return size;
-		}
-	}
-
-	/* No HTML font size was this big; return largest possible. */
-	return this_point;
-}
-
-/** Convert HTML font size to point size. */
-static guint
-msim_purple_size_to_point(MsimSession *session, guint size)
-{
-	gdouble scale;
-	guint point;
-	guint base;
-
-	scale = _font_scale[CLAMP(size, 1, MAX_FONT_SIZE) - 1];
-
-	base = purple_account_get_int(session->account, "base_font_size", MSIM_BASE_FONT_POINT_SIZE);
-
-	point = (guint)msim_round(scale * base);
-
-	purple_debug_info("msim", "msim_purple_size_to_point: size=%d -> %d pt\n",
-					size, point);
-
-	return point;
-}
-
-/** Convert a msim markup font pixel height to the more usual point size, for incoming messages. */
-static guint 
-msim_height_to_point(MsimSession *session, guint height)
-{
-	guint dpi;
-
-	dpi = purple_account_get_int(session->account, "port", MSIM_DEFAULT_DPI);
-
-	return (guint)msim_round((POINTS_PER_INCH * 1. / dpi) * height);
-
-	/* See also: libpurple/protocols/bonjour/jabber.c
-	 * _font_size_ichat_to_purple */
-}
-
-/** Convert point size to msim pixel height font size specification, for outgoing messages. */
-static guint
-msim_point_to_height(MsimSession *session, guint point)
-{
-	guint dpi;
-
-	dpi = purple_account_get_int(session->account, "port", MSIM_DEFAULT_DPI);
-
-	return (guint)msim_round((dpi * 1. / POINTS_PER_INCH) * point);
-}
-
-/** Convert the msim markup <f> (font) tag into HTML. */
-static void 
-msim_markup_f_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
-{
-	const gchar *face, *height_str, *decor_str;
-	GString *gs_end, *gs_begin;
-	guint decor, height;
-
-	face = xmlnode_get_attrib(root, "f");
-	height_str = xmlnode_get_attrib(root, "h");
-	decor_str = xmlnode_get_attrib(root, "s");
-
-	if (height_str) {
-		height = atol(height_str);
-	} else {
-		height = 12;
-	}
-
-	if (decor_str) {
-		decor = atol(decor_str);
-	} else {
-		decor = 0;
-	}
-
-	gs_begin = g_string_new("");
-	/* TODO: get font size working */
-	if (height && !face) {
-		g_string_printf(gs_begin, "<font size='%d'>", 
-				msim_point_to_purple_size(session, msim_height_to_point(session, height)));
-	} else if (height && face) {
-		g_string_printf(gs_begin, "<font face='%s' size='%d'>", face,  
-				msim_point_to_purple_size(session, msim_height_to_point(session, height)));
-	} else {
-		g_string_printf(gs_begin, "<font>");
-	}
-
-	/* No support for font-size CSS? */
-	/* g_string_printf(gs_begin, "<span style='font-family: %s; font-size: %dpt'>", face, 
-			msim_height_to_point(height)); */
-
-	gs_end = g_string_new("</font>");
-
-	if (decor & MSIM_TEXT_BOLD) {
-		g_string_append(gs_begin, "<b>");
-		g_string_prepend(gs_end, "</b>");
-	}
-
-	if (decor & MSIM_TEXT_ITALIC) {
-		g_string_append(gs_begin, "<i>");
-		g_string_append(gs_end, "</i>");
-	}
-
-	if (decor & MSIM_TEXT_UNDERLINE) {
-		g_string_append(gs_begin, "<u>");
-		g_string_append(gs_end, "</u>");
-	}
-
-
-	*begin = gs_begin->str;
-	*end = gs_end->str;
-}
-
-/** Convert a msim markup color to a color suitable for libpurple.
-  *
-  * @param msim Either a color name, or an rgb(x,y,z) code.
-  *
-  * @return A new string, either a color name or #rrggbb code. Must g_free(). 
-  */
-static char *
-msim_color_to_purple(const char *msim)
-{
-	guint red, green, blue;
-
-	if (!msim) {
-		return g_strdup("black");
-	}
-
-	if (sscanf(msim, "rgb(%d,%d,%d)", &red, &green, &blue) != 3) {
-		/* Color name. */
-		return g_strdup(msim);
-	}
-	/* TODO: rgba (alpha). */
-
-	return g_strdup_printf("#%.2x%.2x%.2x", red, green, blue);
-}
-
-/** Convert the msim markup <a> (anchor) tag into HTML. */
-static void 
-msim_markup_a_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
-{
-	const gchar *href;
-
-	href = xmlnode_get_attrib(root, "h");
-	if (!href) {
-		href = "";
-	}
-
-	*begin = g_strdup_printf("<a href=\"%s\">%s", href, href);
-	*end = g_strdup("</a>");
-}
-
-/** Convert the msim markup <p> (paragraph) tag into HTML. */
-static void 
-msim_markup_p_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
-{
-	/* Just pass through unchanged. 
-	 *
-	 * Note: attributes currently aren't passed, if there are any. */
-	*begin = g_strdup("<p>");
-	*end = g_strdup("</p>");
-}
-
-/** Convert the msim markup <c> tag (text color) into HTML. TODO: Test */
-static void 
-msim_markup_c_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
-{
-	const gchar *color;
-	gchar *purple_color;
-
-	color = xmlnode_get_attrib(root, "v");
-	if (!color) {
-		purple_debug_info("msim", "msim_markup_c_to_html: <c> tag w/o v attr");
-		*begin = g_strdup("");
-		*end = g_strdup("");
-		/* TODO: log as unrecognized */
-		return;
-	}
-
-	purple_color = msim_color_to_purple(color);
-
-	*begin = g_strdup_printf("<font color='%s'>", purple_color); 
-
-	g_free(purple_color);
-
-	/* *begin = g_strdup_printf("<span style='color: %s'>", color); */
-	*end = g_strdup("</font>");
-}
-
-/** Convert the msim markup <b> tag (background color) into HTML. TODO: Test */
-static void 
-msim_markup_b_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
-{
-	const gchar *color;
-	gchar *purple_color;
-
-	color = xmlnode_get_attrib(root, "v");
-	if (!color) {
-		*begin = g_strdup("");
-		*end = g_strdup("");
-		purple_debug_info("msim", "msim_markup_b_to_html: <b> w/o v attr");
-		/* TODO: log as unrecognized. */
-		return;
-	}
-
-	purple_color = msim_color_to_purple(color);
-
-	/* TODO: find out how to set background color. */
-	*begin = g_strdup_printf("<span style='background-color: %s'>", 
-			purple_color);
-	g_free(purple_color);
-
-	*end = g_strdup("</p>");
-}
-
-/** Convert the msim markup <i> tag (emoticon image) into HTML. */
-static void 
-msim_markup_i_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
-{
-	const gchar *name;
-	guint i;
-	struct MSIM_EMOTICON *emote;
-
-	name = xmlnode_get_attrib(root, "n");
-	if (!name) {
-		purple_debug_info("msim", "msim_markup_i_to_html: <i> w/o n");
-		*begin = g_strdup("");
-		*end = g_strdup("");
-		/* TODO: log as unrecognized */
-		return;
-	}
-
-	/* Find and use canonical form of smiley symbol. */
-	for (i = 0; (emote = &msim_emoticons[i]) && emote->name != NULL; ++i) {
-		if (g_str_equal(name, emote->name)) {
-			*begin = g_strdup(emote->symbol);
-			*end = g_strdup("");
-			return;
-		}
-	}
-
-	/* Couldn't find it, sorry. Try to degrade gracefully. */
-	*begin = g_strdup_printf("**%s**", name);
-	*end = g_strdup("");
-}
-
-/** Convert an individual msim markup tag to HTML. */
-static void 
-msim_markup_tag_to_html(MsimSession *session, xmlnode *root, gchar **begin, 
-		gchar **end)
-{
-	if (g_str_equal(root->name, "f")) {
-		msim_markup_f_to_html(session, root, begin, end);
-	} else if (g_str_equal(root->name, "a")) {
-		msim_markup_a_to_html(session, root, begin, end);
-	} else if (g_str_equal(root->name, "p")) {
-		msim_markup_p_to_html(session, root, begin, end);
-	} else if (g_str_equal(root->name, "c")) {
-		msim_markup_c_to_html(session, root, begin, end);
-	} else if (g_str_equal(root->name, "b")) {
-		msim_markup_b_to_html(session, root, begin, end);
-	} else if (g_str_equal(root->name, "i")) {
-		msim_markup_i_to_html(session, root, begin, end);
-	} else {
-		purple_debug_info("msim", "msim_markup_tag_to_html: "
-				"unknown tag name=%s, ignoring", 
-				(root && root->name) ? root->name : "(NULL)");
-		*begin = g_strdup("");
-		*end = g_strdup("");
-	}
-}
-
-/** Convert an individual HTML tag to msim markup. */
-static void 
-html_tag_to_msim_markup(MsimSession *session, xmlnode *root, gchar **begin, 
-		gchar **end)
-{
-	/* TODO: Coalesce nested tags into one <f> tag!
-	 * Currently, the 's' value will be overwritten when b/i/u is nested
-	 * within another one, and only the inner-most formatting will be 
-	 * applied to the text. */
-	if (!purple_utf8_strcasecmp(root->name, "root")) {
-		*begin = g_strdup("");
-		*end = g_strdup("");
-	} else if (!purple_utf8_strcasecmp(root->name, "b")) {
-		*begin = g_strdup_printf("<f s='%d'>", MSIM_TEXT_BOLD);
-		*end = g_strdup("</f>");
-	} else if (!purple_utf8_strcasecmp(root->name, "i")) {
-		*begin = g_strdup_printf("<f s='%d'>", MSIM_TEXT_ITALIC);
-		*end = g_strdup("</f>");
-	} else if (!purple_utf8_strcasecmp(root->name, "u")) {
-		*begin = g_strdup_printf("<f s='%d'>", MSIM_TEXT_UNDERLINE);
-		*end = g_strdup("</f>");
-	} else if (!purple_utf8_strcasecmp(root->name, "a")) {
-		const gchar *href, *link_text;
-
-		href = xmlnode_get_attrib(root, "href");
-
-		if (!href) {
-			href = xmlnode_get_attrib(root, "HREF");
-		}
-
-		link_text = xmlnode_get_data(root);
-
-		if (href) {
-			if (g_str_equal(link_text, href)) {
-				/* Purple gives us: <a href="URL">URL</a>
-				 * Translate to <a h='URL' />
-				 * Displayed as text of URL with link to URL
-				 */
-				*begin = g_strdup_printf("<a h='%s' />", href);
-			} else {
-				/* But if we get: <a href="URL">text</a>
-				 * Translate to: text: <a h='URL' />
-				 *
-				 * Because official client only supports self-closed <a>
-				 * tags; you can't change the link text.
-				 */
-				*begin = g_strdup_printf("%s: <a h='%s' />", link_text, href);
-			}
-		} else {
-			*begin = g_strdup("<a />");
-		}
-
-		/* Sorry, kid. MySpace doesn't support you within <a> tags. */
-		xmlnode_free(root->child);
-		root->child = NULL;
-
-		*end = g_strdup("");
-	} else if (!purple_utf8_strcasecmp(root->name, "font")) {
-		const gchar *size;
-		const gchar *face;
-
-		size = xmlnode_get_attrib(root, "size");
-		face = xmlnode_get_attrib(root, "face");
-
-		if (face && size) {
-			*begin = g_strdup_printf("<f f='%s' h='%d'>", face, 
-					msim_point_to_height(session,
-						msim_purple_size_to_point(session, atoi(size))));
-		} else if (face) {
-			*begin = g_strdup_printf("<f f='%s'>", face);
-		} else if (size) {
-			*begin = g_strdup_printf("<f h='%d'>", 
-					 msim_point_to_height(session,
-						 msim_purple_size_to_point(session, atoi(size))));
-		} else {
-			*begin = g_strdup("<f>");
-		}
-
-		*end = g_strdup("</f>");
-
-		/* TODO: color (bg uses <body>), emoticons */
-	} else {
-		*begin = g_strdup_printf("[%s]", root->name);
-		*end = g_strdup_printf("[/%s]", root->name);
-	}
-}
-
-/** Convert an xmlnode of msim markup or HTML to an HTML string or msim markup.
- *
- * @param f Function to convert tags.
- *
- * @return An HTML string. Caller frees.
- */
-static gchar *
-msim_convert_xmlnode(MsimSession *session, xmlnode *root, MSIM_XMLNODE_CONVERT f)
-{
-	xmlnode *node;
-	gchar *begin, *inner, *end;
-	GString *final;
-
-	if (!root || !root->name) {
-		return g_strdup("");
-	}
-
-	purple_debug_info("msim", "msim_convert_xmlnode: got root=%s\n",
-			root->name);
-
-	begin = inner = end = NULL;
-
-	final = g_string_new("");
-
-	f(session, root, &begin, &end);
-	
-	g_string_append(final, begin);
-
-	/* Loop over all child nodes. */
-	for (node = root->child; node != NULL; node = node->next) {
-		switch (node->type) {
-		case XMLNODE_TYPE_ATTRIB:
-			/* Attributes handled above. */
-			break;
-
-		case XMLNODE_TYPE_TAG:
-			/* A tag or tag with attributes. Recursively descend. */
-			inner = msim_convert_xmlnode(session, node, f);
-			g_return_val_if_fail(inner != NULL, NULL);
-
-			purple_debug_info("msim", " ** node name=%s\n", 
-					(node && node->name) ? node->name : "(NULL)");
-			break;
-	
-		case XMLNODE_TYPE_DATA:
-			/* Literal text. */
-			inner = g_new0(char, node->data_sz + 1);
-			strncpy(inner, node->data, node->data_sz);
-			inner[node->data_sz] = 0;
-
-			purple_debug_info("msim", " ** node data=%s\n", 
-					inner ? inner : "(NULL)");
-			break;
-			
-		default:
-			purple_debug_info("msim",
-					"msim_convert_xmlnode: strange node\n");
-			inner = g_strdup("");
-		}
-
-		if (inner) {
-			g_string_append(final, inner);
-		}
-	}
-
-	/* TODO: Note that msim counts each piece of text enclosed by <f> as
-	 * a paragraph and will display each on its own line. You actually have
-	 * to _nest_ <f> tags to intersperse different text in one paragraph!
-	 * Comment out this line below to see. */
-	g_string_append(final, end);
-
-	purple_debug_info("msim", "msim_markup_xmlnode_to_gtkhtml: RETURNING %s\n",
-			(final && final->str) ? final->str : "(NULL)");
-
-	return final->str;
-}
-
-/** Convert XML to something based on MSIM_XMLNODE_CONVERT. */
-static gchar *
-msim_convert_xml(MsimSession *session, const gchar *raw, MSIM_XMLNODE_CONVERT f)
-{
-	xmlnode *root;
-	gchar *str;
-	gchar *enclosed_raw;
-
-	g_return_val_if_fail(raw != NULL, NULL);
-
-	/* Enclose text in one root tag, to try to make it valid XML for parsing. */
-	enclosed_raw = g_strconcat("<root>", raw, "</root>", NULL);
-
-	root = xmlnode_from_str(enclosed_raw, -1);
-
-	if (!root) {
-		purple_debug_info("msim", "msim_markup_to_html: couldn't parse "
-				"%s as XML, returning raw: %s\n", enclosed_raw, raw);
-		/* TODO: msim_unrecognized */
-		g_free(enclosed_raw);
-		return g_strdup(raw);
-	}
-
-	g_free(enclosed_raw);
-
-	str = msim_convert_xmlnode(session, root, f);
-	g_return_val_if_fail(str != NULL, NULL);
-	purple_debug_info("msim", "msim_markup_to_html: returning %s\n", str);
-
-	xmlnode_free(root);
-
-	return str;
-}
-
-/** Convert plaintext smileys to <i> markup tags.
- *
- * @param before Original text with ASCII smileys. Will be freed.
- * @return A new string with <i> tags, if applicable. Must be g_free()'d.
- */
-static gchar *
-msim_convert_smileys_to_markup(gchar *before)
-{
-	gchar *old, *new, *replacement;
-	guint i;
-	struct MSIM_EMOTICON *emote;
-
-	old = before;
-	new = NULL;
-
-	for (i = 0; (emote = &msim_emoticons[i]) && emote->name != NULL; ++i) {
-		gchar *name, *symbol;
-
-		name = emote->name;
-		symbol = emote->symbol;
-
-		replacement = g_strdup_printf("<i n=\"%s\"/>", name);
-
-		purple_debug_info("msim", "msim_convert_smileys_to_markup: %s->%s\n",
-				symbol ? symbol : "(NULL)", 
-				replacement ? replacement : "(NULL)");
-		new = str_replace(old, symbol, replacement);
-		
-		g_free(replacement);
-		g_free(old);
-
-		old = new;
-	}
-
-	return new;
-}
-	
-
-/** High-level function to convert MySpaceIM markup to Purple (HTML) markup. 
- *
- * @return Purple markup string, must be g_free()'d. */
-static gchar *
-msim_markup_to_html(MsimSession *session, const gchar *raw)
-{
-	return msim_convert_xml(session, raw, 
-			(MSIM_XMLNODE_CONVERT)(msim_markup_tag_to_html));
-}
-
-/** High-level function to convert Purple (HTML) to MySpaceIM markup.
- *
- * @return HTML markup string, must be g_free()'d. */
-static gchar *
-html_to_msim_markup(MsimSession *session, const gchar *raw)
-{
-	gchar *markup;
-
-	markup = msim_convert_xml(session, raw,
-			(MSIM_XMLNODE_CONVERT)(html_tag_to_msim_markup));
-	
-	if (purple_account_get_bool(session->account, "emoticons", TRUE)) {
-		/* Frees markup and allocates a new one. */
-		markup = msim_convert_smileys_to_markup(markup);
-	}
-
-	return markup;
-}
-
-/** Get the MsimUser from a PurpleBuddy, creating it if needed. */
-static MsimUser *
-msim_get_user_from_buddy(PurpleBuddy *buddy)
-{
-	MsimUser *user;
-
-	if (!buddy) {
-		return NULL;
-	}
-
-	if (!buddy->proto_data) {
-		/* No MsimUser for this buddy; make one. */
-
-		/* TODO: where is this freed? */
-		user = g_new0(MsimUser, 1);
-		user->buddy = buddy;
-		buddy->proto_data = (gpointer)user;
-	} 
-
-	user = (MsimUser *)(buddy->proto_data);
-
-	return user;
-}
-
-/** Find and return an MsimUser * representing a user on the buddy list, or NULL. */
-static MsimUser *
-msim_find_user(MsimSession *session, const gchar *username)
-{
-	PurpleBuddy *buddy;
-	MsimUser *user;
-
-	buddy = purple_find_buddy(session->account, username);
-	if (!buddy) {
-		return NULL;
-	}
-
-	user = msim_get_user_from_buddy(buddy);
-
-	return user;
-}
-
 
 /** Record the client version in the buddy list, from an incoming message. */
 static gboolean
@@ -1655,7 +732,7 @@
  * @param msg An MsimMessage that was unrecognized, or NULL.
  * @param note Information on what was unrecognized, or NULL.
  */
-static void 
+void 
 msim_unrecognized(MsimSession *session, MsimMessage *msg, gchar *note)
 {
 	/* TODO: Some more context, outwardly equivalent to a backtrace, 
@@ -1680,60 +757,6 @@
 	}
 }
 
-/** Process an incoming zap. */
-static gboolean
-msim_incoming_zap(MsimSession *session, MsimMessage *msg)
-{
-	gchar *msg_text, *username;
-	gint zap;
-	const gchar *zap_past_tense[10];
-#ifdef MSIM_USE_ATTENTION_API
-	MsimAttentionType attn;
-#else
-	gchar *zap_text;
-#endif
-
-	zap_past_tense[0] = _("zapped");
-	zap_past_tense[1] = _("whacked");
-	zap_past_tense[2] = _("torched");
-	zap_past_tense[3] = _("smooched");
-	zap_past_tense[4] = _("hugged");
-	zap_past_tense[5] = _("bslapped");
-	zap_past_tense[6] = _("goosed");
-	zap_past_tense[7] = _("hi-fived");
-	zap_past_tense[8] = _("punk'd");
-	zap_past_tense[9] = _("raspberried");
-
-	msg_text = msim_msg_get_string(msg, "msg");
-	username = msim_msg_get_string(msg, "_username");
-
-	g_return_val_if_fail(msg_text != NULL, FALSE);
-	g_return_val_if_fail(username != NULL, FALSE);
-
-	g_return_val_if_fail(sscanf(msg_text, "!!!ZAP_SEND!!!=RTE_BTN_ZAPS_%d", &zap) == 1, FALSE);
-
-	zap = CLAMP(zap, 0, sizeof(zap_past_tense) / sizeof(zap_past_tense[0]));
-
-	/* TODO:ZAP: use msim_attention_types */
-#ifdef MSIM_USE_ATTENTION_API
-	attn.incoming_description = zap_past_tense[zap];
-	attn.outgoing_description = NULL;
-	attn.icon_name = NULL;		/* TODO: icon */
-
-	serv_got_attention(session->gc, username, &attn, TRUE);
-#else
-	zap_text = g_strdup_printf(_("*** You have been %s! ***"), zap_past_tense[zap]);
-	serv_got_im(session->gc, username, zap_text, 
-			PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_SYSTEM, time(NULL));
-	g_free(zap_text);
-#endif
-
-	g_free(msg_text);
-	g_free(username);
-
-	return TRUE;
-}
-
 /**
  * Handle an incoming action message.
  *
@@ -1902,95 +925,7 @@
 	return 0;
 }
 
-/** Format the "now playing" indicator, showing the artist and song.
- * @return Return a new string (must be g_free()'d), or NULL.
- */
-static gchar *
-msim_format_now_playing(gchar *band, gchar *song)
-{
-	if ((band && strlen(band)) || (song && strlen(song))) {
-		return g_strdup_printf("%s - %s",
-			(band && strlen(band)) ? band : "Unknown Artist",
-			(song && strlen(song)) ? song : "Unknown Song");
-	} else {
-		return NULL;
-	}
-}
-
-/** Append user information to a PurpleNotifyUserInfo, given an MsimUser. 
- * Used by msim_tooltip_text() and msim_get_info_cb() to show a user's profile.
- */
-static void
-msim_append_user_info(MsimSession *session, PurpleNotifyUserInfo *user_info, MsimUser *user, gboolean full)
-{
-	gchar *str;
-	guint uid;
-	guint cv;
-
-	/* Useful to identify the account the tooltip refers to. 
-	 *  Other prpls show this. */
-	if (user->username) {
-		purple_notify_user_info_add_pair(user_info, _("User"), user->username);
-	}
-
-	uid = purple_blist_node_get_int(&user->buddy->node, "UserID");
-
-	if (full) {
-		/* TODO: link to username, if available */
-		purple_notify_user_info_add_pair(user_info, _("Profile"),
-				g_strdup_printf("<a href=\"http://myspace.com/%d\">http://myspace.com/%d</a>",
-					uid, uid));
-	}
-
-
-	/* a/s/l...the vitals */
-	if (user->age) {
-		purple_notify_user_info_add_pair(user_info, _("Age"),
-				g_strdup_printf("%d", user->age));
-	}
-
-	if (user->gender && strlen(user->gender)) {
-		purple_notify_user_info_add_pair(user_info, _("Gender"), user->gender);
-	}
-
-	if (user->location && strlen(user->location)) {
-		purple_notify_user_info_add_pair(user_info, _("Location"), user->location);
-	}
-
-	/* Other information */
-	if (user->headline && strlen(user->headline)) {
-		purple_notify_user_info_add_pair(user_info, _("Headline"), user->headline);
-	}
-
-	str = msim_format_now_playing(user->band_name, user->song_name);
-	if (str && strlen(str)) {
-		purple_notify_user_info_add_pair(user_info, _("Song"), str);
-	}
-
-	/* Note: total friends only available if looked up by uid, not username. */
-	if (user->total_friends) {
-		purple_notify_user_info_add_pair(user_info, _("Total Friends"),
-			g_strdup_printf("%d", user->total_friends));
-	}
-
-	if (full) {
-		/* Client information */
-
-		str = user->client_info;
-		cv = user->client_cv;
-
-		if (str && cv != 0) {
-			purple_notify_user_info_add_pair(user_info, _("Client Version"),
-					g_strdup_printf("%s (build %d)", str, cv));
-		} else if (str) {
-			purple_notify_user_info_add_pair(user_info, _("Client Version"),
-					g_strdup(str));
-		} else if (cv) {
-			purple_notify_user_info_add_pair(user_info, _("Client Version"),
-					g_strdup_printf("Build %d", cv));
-		}
-	}
-}
+
 
 /** Callback for msim_get_info(), for when user info is received. */
 static void 
@@ -2505,6 +1440,9 @@
 	 * some of the time, but can vary. This is our own user ID. */
 	session->userid = msim_msg_get_integer(msg, "userid");
 
+	/* Save uid to account so this account can be looked up by uid. */
+	purple_account_set_int(session->account, "uid", session->userid);
+
 	/* Not sure what profileid is used for. */
 	if (msim_msg_get_integer(msg, "profileid") != session->userid) {
 		msim_unrecognized(session, msg, 
@@ -2582,10 +1520,12 @@
 			(GSourceFunc)msim_check_alive, session);
 #endif
 
-	purple_timeout_add(MSIM_MAIL_INTERVAL_CHECK, 
-			(GSourceFunc)msim_check_inbox, session);
-
-	msim_check_inbox(session);
+	if (purple_account_get_check_mail(session->account)) {
+		purple_timeout_add(MSIM_MAIL_INTERVAL_CHECK, 
+				(GSourceFunc)msim_check_inbox, session);
+		msim_check_inbox(session);
+	}
+
 
 	return TRUE;
 }
@@ -2626,186 +1566,6 @@
 	}
 }
 
-/** Callback for when a buddy icon finished being downloaded. */
-static void
-msim_downloaded_buddy_icon(PurpleUtilFetchUrlData *url_data,
-		gpointer user_data,
-		const gchar *url_text,
-		gsize len,
-		const gchar *error_message)
-{
-	MsimUser *user;
-
-	user = (MsimUser *)user_data;
-
-	purple_debug_info("msim_downloaded_buddy_icon",
-			"Downloaded %d bytes\n", len);
-
-	if (!url_text) {
-		purple_debug_info("msim_downloaded_buddy_icon",
-				"failed to download icon for %s",
-				user->buddy->name);
-		return;
-	}
-
-	purple_buddy_icons_set_for_user(user->buddy->account,
-			user->buddy->name,
-			g_memdup((gchar *)url_text, len), len, 
-			/* Use URL itself as buddy icon "checksum" (TODO: ETag) */
-			user->image_url);		/* checksum */
-}
-
-/** Store a field of information about a buddy. */
-static void 
-msim_store_user_info_each(const gchar *key_str, gchar *value_str, MsimUser *user)
-{
-	if (g_str_equal(key_str, "UserID") || g_str_equal(key_str, "ContactID")) {
-		/* Save to buddy list, if it exists, for quick cached uid lookup with msim_uid2username_from_blist(). */
-		if (user->buddy)
-		{
-			purple_debug_info("msim", "associating uid %s with username %s\n", key_str, user->buddy->name);
-			purple_blist_node_set_int(&user->buddy->node, "UserID", atol(value_str));
-		}
-		/* Need to store in MsimUser, too? What if not on blist? */
-	} else if (g_str_equal(key_str, "Age")) {
-		user->age = atol(value_str);
-	} else if (g_str_equal(key_str, "Gender")) {
-		user->gender = g_strdup(value_str);
-	} else if (g_str_equal(key_str, "Location")) {
-		user->location = g_strdup(value_str);
-	} else if (g_str_equal(key_str, "TotalFriends")) {
-		user->total_friends = atol(value_str);
-	} else if (g_str_equal(key_str, "DisplayName")) {
-		user->display_name = g_strdup(value_str);
-	} else if (g_str_equal(key_str, "BandName")) {
-		user->band_name = g_strdup(value_str);
-	} else if (g_str_equal(key_str, "SongName")) {
-		user->song_name = g_strdup(value_str);
-	} else if (g_str_equal(key_str, "UserName") || g_str_equal(key_str, "IMName") || g_str_equal(key_str, "NickName")) {
-		/* Ignore because PurpleBuddy knows this already */
-		;
-	} else if (g_str_equal(key_str, "ImageURL") || g_str_equal(key_str, "AvatarURL")) {
-		const gchar *previous_url;
-
-		user->image_url = g_strdup(value_str);
-
-		/* Instead of showing 'no photo' picture, show nothing. */
-		if (g_str_equal(user->image_url, "http://x.myspace.com/images/no_pic.gif"))
-		{
-			purple_buddy_icons_set_for_user(user->buddy->account,
-				user->buddy->name,
-				NULL, 0, NULL);
-			return;
-		}
-	
-		/* TODO: use ETag for checksum */
-		previous_url = purple_buddy_icons_get_checksum_for_user(user->buddy);
-
-		/* Only download if URL changed */
-		if (!previous_url || !g_str_equal(previous_url, user->image_url)) {
-			purple_util_fetch_url(user->image_url, TRUE, NULL, TRUE, msim_downloaded_buddy_icon, (gpointer)user);
-		}
-	} else if (g_str_equal(key_str, "LastImageUpdated")) {
-		/* TODO: use somewhere */
-		user->last_image_updated = atol(value_str);
-	} else if (g_str_equal(key_str, "Headline")) {
-		user->headline = g_strdup(value_str);
-	} else {
-		/* TODO: other fields in MsimUser */
-		gchar *msg;
-
-		msg = g_strdup_printf("msim_store_user_info_each: unknown field %s=%s",
-				key_str, value_str);
-
-		msim_unrecognized(NULL, NULL, msg);
-
-		g_free(msg);
-	}
-}
-
-/** Save buddy information to the buddy list from a user info reply message.
- *
- * @param session
- * @param msg The user information reply, with any amount of information.
- * @param user The structure to save to, or NULL to save in PurpleBuddy->proto_data.
- *
- * Variable information is saved to the passed MsimUser structure. Permanent
- * information (UserID) is stored in the blist node of the buddy list (and
- * ends up in blist.xml, persisted to disk) if it exists.
- *
- * If the function has no buddy information, this function
- * is a no-op (and returns FALSE).
- *
- */
-static gboolean 
-msim_store_user_info(MsimSession *session, MsimMessage *msg, MsimUser *user)
-{
-	gchar *username;
-	MsimMessage *body, *body_node;
-
-	g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
-	g_return_val_if_fail(msg != NULL, FALSE);
-
-	body = msim_msg_get_dictionary(msg, "body");
-	if (!body) {
-		return FALSE;
-	}
-
-	username = msim_msg_get_string(body, "UserName");
-
-	if (!username) {
-		purple_debug_info("msim", 
-			"msim_process_reply: not caching body, no UserName\n");
-		msim_msg_free(body);
-		g_free(username);
-		return FALSE;
-	}
-	
-	/* Null user = find and store in PurpleBuddy's proto_data */
-	if (!user) {
-		user = msim_find_user(session, username);
-		if (!user) {
-			msim_msg_free(body);
-			g_free(username);
-			return FALSE;
-		}
-	}
-
-	/* TODO: make looping over MsimMessage's easier. */
-	for (body_node = body; 
-		body_node != NULL; 
-		body_node = msim_msg_get_next_element_node(body_node))
-	{
-		const gchar *key_str;
-		gchar *value_str;
-		MsimMessageElement *elem;
-
-		elem = (MsimMessageElement *)body_node->data;
-		key_str = elem->name;
-
-		value_str = msim_msg_get_string_from_element(elem);
-		msim_store_user_info_each(key_str, value_str, user);
-		g_free(value_str);
-	}
-
-	if (msim_msg_get_integer(msg, "dsn") == MG_OWN_IM_INFO_DSN &&
-		msim_msg_get_integer(msg, "lid") == MG_OWN_IM_INFO_LID) {
-		/* TODO: do something with our own IM info, if we need it for some
-		 * specific purpose. Otherwise it is available on the buddy list,
-		 * if the user has themselves as their own buddy. 
-		 *
-		 * However, much of the info is already available in MsimSession,
-		 * stored in msim_we_are_logged_on(). */
-	} else if (msim_msg_get_integer(msg, "dsn") == MG_OWN_MYSPACE_INFO_DSN &&
-			msim_msg_get_integer(msg, "lid") == MG_OWN_MYSPACE_INFO_LID) {
-		/* TODO: same as above, but for MySpace info. */
-	}
-
-	msim_msg_free(body);
-
-	return TRUE;
-}
-
 /** Process the initial server information from the server. */
 static gboolean
 msim_process_server_info(MsimSession *session, MsimMessage *msg)
@@ -2991,9 +1751,9 @@
 	 */
 	list = msim_msg_get_list(msg, "msg");
 
-	status_code = atoi(g_list_nth_data(list, MSIM_STATUS_ORDINAL_ONLINE));
+	status_code = msim_msg_get_integer_from_element(g_list_nth_data(list, MSIM_STATUS_ORDINAL_ONLINE));
 	purple_debug_info("msim", "msim_status: %s's status code = %d\n", username, status_code);
-	status_headline = g_list_nth_data(list, MSIM_STATUS_ORDINAL_HEADLINE);
+	status_headline = msim_msg_get_string_from_element(g_list_nth_data(list, MSIM_STATUS_ORDINAL_HEADLINE));
 
 	blist = purple_get_blist();
 
@@ -3017,7 +1777,8 @@
 		purple_debug_info("msim", "msim_status: found buddy %s\n", username);
 	}
 
-	user->headline = g_strdup(status_headline);
+	/* don't copy; let the MsimUser own the headline, memory-wise */
+	user->headline = status_headline;
   
 	/* Set user status */
 	switch (status_code) {
@@ -3559,7 +2320,7 @@
  * 1) MSIM_USER_LOOKUP_CB - make it for PERSIST_REPLY, not just user lookup
  * 2) data - make it an MsimMessage?
  */
-static guint 
+guint 
 msim_new_reply_callback(MsimSession *session, MSIM_USER_LOOKUP_CB cb, 
 		gpointer data)
 {
@@ -3607,79 +2368,7 @@
 	gc->inpa = purple_input_add(source, PURPLE_INPUT_READ, msim_input_cb, gc);
 }
 
-/* Session methods */
-
-/**
- * Create a new MSIM session.
- *
- * @param acct The account to create the session from.
- *
- * @return Pointer to a new session. Free with msim_session_destroy.
- */
-MsimSession *
-msim_session_new(PurpleAccount *acct)
-{
-	MsimSession *session;
-
-	g_return_val_if_fail(acct != NULL, NULL);
-
-	session = g_new0(MsimSession, 1);
-
-	session->magic = MSIM_SESSION_STRUCT_MAGIC;
-	session->account = acct;
-	session->gc = purple_account_get_connection(acct);
-	session->sesskey = 0;
-	session->userid = 0;
-	session->username = NULL;
-	session->fd = -1;
-
-	/* TODO: Remove. */
-	session->user_lookup_cb = g_hash_table_new_full(g_direct_hash, 
-			g_direct_equal, NULL, NULL);  /* do NOT free function pointers! (values) */
-	session->user_lookup_cb_data = g_hash_table_new_full(g_direct_hash, 
-			g_direct_equal, NULL, NULL);/* TODO: we don't know what the values are,
-											 they could be integers inside gpointers
-											 or strings, so I don't freed them.
-											 Figure this out, once free cache. */
-
-	/* Created in msim_process_server_info() */
-	session->server_info = NULL;
-
-	session->rxoff = 0;
-	session->rxbuf = g_new0(gchar, MSIM_READ_BUF_SIZE);
-	session->next_rid = 1;
-	session->last_comm = time(NULL);
-	session->inbox_status = 0;
-	
-	return session;
-}
-
-/**
- * Free a session.
- *
- * @param session The session to destroy.
- */
-void 
-msim_session_destroy(MsimSession *session)
-{
-	g_return_if_fail(MSIM_SESSION_VALID(session));
-	
-	session->magic = -1;
-
-	g_free(session->rxbuf);
-	g_free(session->username);
-
-	/* TODO: Remove. */
-	g_hash_table_destroy(session->user_lookup_cb);
-	g_hash_table_destroy(session->user_lookup_cb_data);
-
-	if (session->server_info) {
-		msim_msg_free(session->server_info);
-	}
-	
-	g_free(session);
-}
-				 
+			 
 /** 
  * Close the connection.
  * 
@@ -3713,106 +2402,6 @@
 
 
 /**
- * Check if a string is a userid (all numeric).
- *
- * @param user The user id, email, or name.
- *
- * @return TRUE if is userid, FALSE if not.
- */
-static gboolean 
-msim_is_userid(const gchar *user)
-{
-	g_return_val_if_fail(user != NULL, FALSE);
-
-	return strspn(user, "0123456789") == strlen(user);
-}
-
-/**
- * Check if a string is an email address (contains an @).
- *
- * @param user The user id, email, or name.
- *
- * @return TRUE if is an email, FALSE if not.
- *
- * This function is not intended to be used as a generic
- * means of validating email addresses, but to distinguish
- * between a user represented by an email address from
- * other forms of identification.
- */ 
-static gboolean 
-msim_is_email(const gchar *user)
-{
-	g_return_val_if_fail(user != NULL, FALSE);
-
-	return strchr(user, '@') != NULL;
-}
-
-
-/**
- * Asynchronously lookup user information, calling callback when receive result.
- *
- * @param session
- * @param user The user id, email address, or username. Not freed.
- * @param cb Callback, called with user information when available.
- * @param data An arbitray data pointer passed to the callback.
- */
-/* TODO: change to not use callbacks */
-static void 
-msim_lookup_user(MsimSession *session, const gchar *user, MSIM_USER_LOOKUP_CB cb, gpointer data)
-{
-	MsimMessage *body;
-	gchar *field_name;
-	guint rid, cmd, dsn, lid;
-
-	g_return_if_fail(MSIM_SESSION_VALID(session));
-	g_return_if_fail(user != NULL);
-	/* Callback can be null to not call anything, just lookup & store information. */
-	/*g_return_if_fail(cb != NULL);*/
-
-	purple_debug_info("msim", "msim_lookup_userid: "
-			"asynchronously looking up <%s>\n", user);
-
-	msim_msg_dump("msim_lookup_user: data=%s\n", (MsimMessage *)data);
-
-	/* Setup callback. Response will be associated with request using 'rid'. */
-	rid = msim_new_reply_callback(session, cb, data);
-
-	/* Send request */
-
-	cmd = MSIM_CMD_GET;
-
-	if (msim_is_userid(user)) {
-		field_name = "UserID";
-		dsn = MG_MYSPACE_INFO_BY_ID_DSN; 
-		lid = MG_MYSPACE_INFO_BY_ID_LID; 
-	} else if (msim_is_email(user)) {
-		field_name = "Email";
-		dsn = MG_MYSPACE_INFO_BY_STRING_DSN;
-		lid = MG_MYSPACE_INFO_BY_STRING_LID;
-	} else {
-		field_name = "UserName";
-		dsn = MG_MYSPACE_INFO_BY_STRING_DSN;
-		lid = MG_MYSPACE_INFO_BY_STRING_LID;
-	}
-
-	body = msim_msg_new(
-			field_name, MSIM_TYPE_STRING, g_strdup(user),
-			NULL);
-
-	g_return_if_fail(msim_send(session,
-			"persist", MSIM_TYPE_INTEGER, 1,
-			"sesskey", MSIM_TYPE_INTEGER, session->sesskey,
-			"cmd", MSIM_TYPE_INTEGER, 1,
-			"dsn", MSIM_TYPE_INTEGER, dsn,
-			"uid", MSIM_TYPE_INTEGER, session->userid,
-			"lid", MSIM_TYPE_INTEGER, lid,
-			"rid", MSIM_TYPE_INTEGER, rid,
-			"body", MSIM_TYPE_DICTIONARY, body,
-			NULL));
-} 
-
-
-/**
  * Obtain the status text for a buddy.
  *
  * @param buddy The buddy to obtain status text for.
@@ -4395,16 +2984,74 @@
 }
 #endif
 
+static gboolean
+msim_uri_handler(const gchar *proto, const gchar *cmd, GHashTable *params)
+{
+	PurpleAccount *account;
+	GList *l;
+	gchar *uid_str, *cid_str;
+	guint uid, cid;
+
+	if (g_ascii_strcasecmp(proto, "myim"))
+		return FALSE;
+
+	uid_str = g_hash_table_lookup(params, "uID");
+	cid_str = g_hash_table_lookup(params, "cID");
+
+	uid = uid_str ? atol(uid_str) : 0;
+	cid = cid_str ? atol(cid_str) : 0;
+
+	/* Find our account with specified user id, or use first connected account if uid=0. */
+	account = NULL;
+	l = purple_accounts_get_all();
+	while (l) {
+		if (purple_account_is_connected(l->data) &&
+			(uid == 0 || purple_account_get_int(l->data, "uid", 0) == uid)) {
+			account = l->data;
+			break;
+		}
+		l = l->next;
+	}
+
+	if (!account) {
+		purple_notify_error(NULL, _("myim URL handler"), 
+				_("No suitable MySpaceIM account could be found to open this myim URL."),
+				_("Enable the proper MySpaceIM account and try again."));
+		return FALSE;
+	}
+
+	/* myim:sendIM?uID=USERID&cID=CONTACTID */
+	if (!g_ascii_strcasecmp(cmd, "sendIM")) {
+		PurpleConversation *conv;
+
+		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, cid_str, account);
+		if (!conv) 
+			conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, cid_str);
+		purple_conversation_present(conv);
+
+		/* TODO: where to get the message? or is there any? */
+		purple_conv_send_confirm(conv, "test");
+
+	/* myim:addContact?uID=USERID&cID=CONTACTID */
+	} else if (!g_ascii_strcasecmp(cmd, "addContact")) {
+		purple_blist_request_add_buddy(account, cid_str, _("Buddies"), NULL);
+	}
+
+	return FALSE;
+}
+
 /** Initialize plugin. */
 void 
 init_plugin(PurplePlugin *plugin) 
 {
-	PurpleAccountOption *option;
 #ifdef MSIM_SELF_TEST
 	msim_test_all();
 	exit(0);
 #endif /* MSIM_SELF_TEST */
 
+	PurpleAccountOption *option;
+	static gboolean initialized = FALSE;
+
 
 	/* TODO: default to automatically try different ports. Make the user be
 	 * able to set the first port to try (like LastConnectedPort in Windows client).  */
@@ -4435,21 +3082,14 @@
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 #endif
 
-	/* TODO: /zap command. Problem with this is that there are different kinds of zaps,
-	 * and the selection is best made available in a drop-down menu, instead of forcing
-	 * the user to type the kind of zap and memorizing available zaps (or putting it in the
-	 * help menu). A new "attention" API, for zap/buzz/nudge (different protocols) will
-	 * solve this. */
-#if 0
-	purple_cmd_register("zap",                                        /* cmd */
-			"w",                                              /* args - accept a single word */
-			PURPLE_CMD_P_PRPL,                                /* priority */
-			PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_PRPL_ONLY,   /* flags */
-			"prpl-myspace",                                   /* prpl_id */
-			msim_cmd_zap,                                     /* func */
-			_("zap: zap a user to get their attention"),      /* helpstr */
-			NULL);                                            /* data */
-#endif
+	/* Code below only runs once. Based on oscar.c's oscar_init(). */
+	if (initialized) 
+		return;
+
+	initialized = TRUE;
+
+	purple_signal_connect(purple_get_core(), "uri-handler", &initialized,
+			PURPLE_CALLBACK(msim_uri_handler), NULL);
 }
 
 PURPLE_INIT_PLUGIN(myspace, init_plugin, info);
--- a/libpurple/protocols/myspace/myspace.h	Sun Aug 26 14:02:30 2007 +0000
+++ b/libpurple/protocols/myspace/myspace.h	Sun Aug 26 14:02:35 2007 +0000
@@ -46,9 +46,15 @@
 #include "util.h"       /* for base64 */
 #include "debug.h"      /* for purple_debug_info */
 #include "xmlnode.h"
+#include "core.h"
 
 /* MySpaceIM includes */
+#include "persist.h"
 #include "message.h"
+#include "session.h"
+#include "zap.h"
+#include "markup.h"
+#include "user.h"
 
 /* Conditional compilation options */
 /* Send third-party client version? (Recognized by us and Miranda's plugin) */
@@ -157,23 +163,6 @@
 #define MSIM_STATUS_CODE_IDLE                 2
 #define MSIM_STATUS_CODE_AWAY                 5
 
-/* Text formatting bits for <f s=#> */
-#define MSIM_TEXT_BOLD                  1
-#define MSIM_TEXT_ITALIC                2   
-#define MSIM_TEXT_UNDERLINE             4
-
-/* Default baseline size of purple's fonts, in points. What is size 3 in points. 
- * _font_scale specifies scaling factor relative to this point size. Note this 
- * is only the default; it is configurable in account options. */
-#define MSIM_BASE_FONT_POINT_SIZE       8
-
-/* Default display's DPI. 96 is common but it can differ. Also configurable
- * in account options. */
-#define MSIM_DEFAULT_DPI                96
-
-
-/* Random number in every MsimSession, to ensure it is valid. */
-#define MSIM_SESSION_STRUCT_MAGIC       0xe4a6752b
 
 /* Inbox status bitfield values for MsimSession.inbox_status */
 #define MSIM_INBOX_MAIL                 (1 << 0)
@@ -182,54 +171,6 @@
 #define MSIM_INBOX_FRIEND_REQUEST       (1 << 3)
 #define MSIM_INBOX_PICTURE_COMMENT      (1 << 4)
 
-/* Everything needed to keep track of a session (proto_data field in PurpleConnection) */
-typedef struct _MsimSession
-{
-	guint magic;                        /**< MSIM_SESSION_STRUCT_MAGIC */
-	PurpleAccount *account;
-	PurpleConnection *gc;
-	guint sesskey;                      /**< Session key from server */
-	guint userid;                       /**< This user's numeric user ID */
-	gchar *username;                    /**< This user's unique username */
-	gint fd;                            /**< File descriptor to/from server */
-
-	/* TODO: Remove. */
-	GHashTable *user_lookup_cb;         /**< Username -> userid lookup callback */
-	GHashTable *user_lookup_cb_data;    /**< Username -> userid lookup callback data */
-
-	MsimMessage *server_info;           /**< Parameters from server */
-
-	gchar *rxbuf;                       /**< Receive buffer */
-	guint rxoff;                        /**< Receive buffer offset */
-	guint next_rid;                     /**< Next request/response ID */
-	time_t last_comm;                   /**< Time received last communication */
-	guint inbox_status;                 /**< Bit field of inbox notifications */
-} MsimSession;
-
-/* Check if an MsimSession is valid */
-#define MSIM_SESSION_VALID(s) (session != NULL && session->magic == MSIM_SESSION_STRUCT_MAGIC)
-
-/* Hold ephemeral information about buddies, for proto_data of PurpleBuddy. */
-/* GHashTable? */
-typedef struct _MsimUser
-{
-	PurpleBuddy *buddy;
-	guint client_cv;
-	gchar *client_info;
-	guint age;
-	gchar *gender;
-	gchar *location;
-	guint total_friends;
-	gchar *headline;
-	gchar *display_name;
-	/* Note: uid is in &buddy->node (set_blist_node_int), since it never changes */
-	gchar *username;
-	gchar *band_name, *song_name;
-	gchar *image_url;
-	guint last_image_updated;
-} MsimUser;
-
-
 #ifdef MSIM_USE_ATTENTION_API
 #define MsimAttentionType PurpleAttentionType
 #else
@@ -249,31 +190,17 @@
 
 gchar *str_replace(const gchar *str, const gchar *old, const gchar *new);
 
-/* Callback function pointer type for when a user's information is received, 
- * initiated from a user lookup. */
-typedef void (*MSIM_USER_LOOKUP_CB)(MsimSession *session, MsimMessage *userinfo, gpointer data);
-
 /* Functions */
 gboolean msim_load(PurplePlugin *plugin);
 GList *msim_status_types(PurpleAccount *acct);
 
-GList *msim_attention_types(PurpleAccount *acct);
-gboolean msim_send_attention(PurpleConnection *gc, const gchar *username, guint code);
-
-GList *msim_blist_node_menu(PurpleBlistNode *node);
-
 const gchar *msim_list_icon(PurpleAccount *acct, PurpleBuddy *buddy);
-
 gboolean msim_send_raw(MsimSession *session, const gchar *msg);
 
 void msim_login(PurpleAccount *acct);
-
-int msim_send_im(PurpleConnection *gc, const gchar *who, const gchar *message, 
-PurpleMessageFlags flags);
+int msim_send_im(PurpleConnection *gc, const gchar *who, const gchar *message, PurpleMessageFlags flags);
+unsigned int msim_send_typing(PurpleConnection *gc, const gchar *name, PurpleTypingState state);
 
-typedef void (*MSIM_XMLNODE_CONVERT)(MsimSession *, xmlnode *, gchar **, gchar **);
-
-unsigned int msim_send_typing(PurpleConnection *gc, const gchar *name, PurpleTypingState state);
 void msim_get_info(PurpleConnection *gc, const gchar *name);
 
 void msim_set_status(PurpleAccount *account, PurpleStatus *status);
@@ -284,9 +211,6 @@
 
 gboolean msim_offline_message(const PurpleBuddy *buddy);
 
-MsimSession *msim_session_new(PurpleAccount *acct);
-void msim_session_destroy(MsimSession *session);
-
 void msim_close(PurpleConnection *gc);
 
 char *msim_status_text(PurpleBuddy *buddy);
@@ -299,6 +223,13 @@
 int msim_test_escaping(void);
 #endif
 
+gboolean msim_send_bm(MsimSession *session, const gchar *who, const gchar *text, int type);
+
+
+void msim_unrecognized(MsimSession *session, MsimMessage *msg, gchar *note);
+guint msim_new_reply_callback(MsimSession *session, MSIM_USER_LOOKUP_CB cb, gpointer data);
+
+
 void init_plugin(PurplePlugin *plugin);
 
 #endif /* !_MYSPACE_MYSPACE_H */
--- a/libpurple/protocols/myspace/persist.h	Sun Aug 26 14:02:30 2007 +0000
+++ b/libpurple/protocols/myspace/persist.h	Sun Aug 26 14:02:35 2007 +0000
@@ -42,8 +42,8 @@
 
 /** Define a set of _DSN and _LID constants for a persistance request. */
 #define MSIM_PERSIST_DSN_LID(name,dsn,lid)             \
-    const int name##_DSN = dsn;                        \
-    const int name##_LID = lid;                        
+    static const int name##_DSN = dsn;                 \
+    static const int name##_LID = lid;                        
 
 /* Can't do this, errors:
  *     persist.h:51:3: error: '#' is not followed by a macro parameter
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/session.c	Sun Aug 26 14:02:35 2007 +0000
@@ -0,0 +1,95 @@
+/* MySpaceIM Protocol Plugin, session
+ *
+ * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#include "myspace.h"
+
+/* Session methods */
+
+/**
+ * Create a new MSIM session.
+ *
+ * @param acct The account to create the session from.
+ *
+ * @return Pointer to a new session. Free with msim_session_destroy.
+ */
+MsimSession *
+msim_session_new(PurpleAccount *acct)
+{
+	MsimSession *session;
+
+	g_return_val_if_fail(acct != NULL, NULL);
+
+	session = g_new0(MsimSession, 1);
+
+	session->magic = MSIM_SESSION_STRUCT_MAGIC;
+	session->account = acct;
+	session->gc = purple_account_get_connection(acct);
+	session->sesskey = 0;
+	session->userid = 0;
+	session->username = NULL;
+	session->fd = -1;
+
+	/* TODO: Remove. */
+	session->user_lookup_cb = g_hash_table_new_full(g_direct_hash, 
+			g_direct_equal, NULL, NULL);  /* do NOT free function pointers! (values) */
+	session->user_lookup_cb_data = g_hash_table_new_full(g_direct_hash, 
+			g_direct_equal, NULL, NULL);/* TODO: we don't know what the values are,
+											 they could be integers inside gpointers
+											 or strings, so I don't freed them.
+											 Figure this out, once free cache. */
+
+	/* Created in msim_process_server_info() */
+	session->server_info = NULL;
+
+	session->rxoff = 0;
+	session->rxbuf = g_new0(gchar, MSIM_READ_BUF_SIZE);
+	session->next_rid = 1;
+	session->last_comm = time(NULL);
+	session->inbox_status = 0;
+	
+	return session;
+}
+
+/**
+ * Free a session.
+ *
+ * @param session The session to destroy.
+ */
+void 
+msim_session_destroy(MsimSession *session)
+{
+	g_return_if_fail(MSIM_SESSION_VALID(session));
+	
+	session->magic = -1;
+
+	g_free(session->rxbuf);
+	g_free(session->username);
+
+	/* TODO: Remove. */
+	g_hash_table_destroy(session->user_lookup_cb);
+	g_hash_table_destroy(session->user_lookup_cb_data);
+
+	if (session->server_info) {
+		msim_msg_free(session->server_info);
+	}
+	
+	g_free(session);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/session.h	Sun Aug 26 14:02:35 2007 +0000
@@ -0,0 +1,57 @@
+/* MySpaceIM Protocol Plugin, session
+ *
+ * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _MYSPACE_SESSION_H
+#define _MYSPACE_SESSION_H
+
+/* Random number in every MsimSession, to ensure it is valid. */
+#define MSIM_SESSION_STRUCT_MAGIC       0xe4a6752b
+
+/* Everything needed to keep track of a session (proto_data field in PurpleConnection) */
+typedef struct _MsimSession
+{
+	guint magic;                        /**< MSIM_SESSION_STRUCT_MAGIC */
+	PurpleAccount *account;
+	PurpleConnection *gc;
+	guint sesskey;                      /**< Session key from server */
+	guint userid;                       /**< This user's numeric user ID */
+	gchar *username;                    /**< This user's unique username */
+	gint fd;                            /**< File descriptor to/from server */
+
+	/* TODO: Remove. */
+	GHashTable *user_lookup_cb;         /**< Username -> userid lookup callback */
+	GHashTable *user_lookup_cb_data;    /**< Username -> userid lookup callback data */
+
+	MsimMessage *server_info;           /**< Parameters from server */
+
+	gchar *rxbuf;                       /**< Receive buffer */
+	guint rxoff;                        /**< Receive buffer offset */
+	guint next_rid;                     /**< Next request/response ID */
+	time_t last_comm;                   /**< Time received last communication */
+	guint inbox_status;                 /**< Bit field of inbox notifications */
+} MsimSession;
+
+/* Check if an MsimSession is valid */
+#define MSIM_SESSION_VALID(s) (session != NULL && session->magic == MSIM_SESSION_STRUCT_MAGIC)
+
+
+MsimSession *msim_session_new(PurpleAccount *acct);
+void msim_session_destroy(MsimSession *session);
+
+#endif /* !_MYSPACE_SESSION_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/zap.c	Sun Aug 26 14:02:35 2007 +0000
@@ -0,0 +1,269 @@
+/* MySpaceIM Protocol Plugin - zap support
+ *
+ * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "myspace.h"
+#include "zap.h"
+
+static gboolean msim_send_zap(MsimSession *session, const gchar *username, guint code);
+static void msim_send_zap_from_menu(PurpleBlistNode *node, gpointer zap_num_ptr);
+
+
+/** Get zap types. */
+GList *
+msim_attention_types(PurpleAccount *acct)
+{
+	static GList *types = NULL;
+	MsimAttentionType* attn;
+
+	if (!types) {
+#define _MSIM_ADD_NEW_ATTENTION(icn, nme, incoming, outgoing)              \
+		attn = g_new0(MsimAttentionType, 1);                       \
+		attn->icon_name = icn;                                     \
+		attn->name = nme;                                          \
+		attn->incoming_description = incoming;                     \
+		attn->outgoing_description = outgoing;                     \
+		types = g_list_append(types, attn);
+
+		/* TODO: icons for each zap */
+		_MSIM_ADD_NEW_ATTENTION(NULL, _("zap"), _("zapped"), _("Zapping"));
+		_MSIM_ADD_NEW_ATTENTION(NULL, _("whack"), _("whacked"), _("Whacking"));
+		_MSIM_ADD_NEW_ATTENTION(NULL, _("torch"), _("torched"), _("Torching"));
+		_MSIM_ADD_NEW_ATTENTION(NULL, _("smooch"), _("smooched"), _("Smooching"));
+		_MSIM_ADD_NEW_ATTENTION(NULL, _("hug"), _("hugged"), _("Hugging"));
+		_MSIM_ADD_NEW_ATTENTION(NULL, _("bslap"), _("bslapped"), _("Bslapping"));
+		_MSIM_ADD_NEW_ATTENTION(NULL, _("goose"), _("goosed"), _("Goosing"));
+		_MSIM_ADD_NEW_ATTENTION(NULL, _("hi-five"), _("hi-fived"), _("Hi-fiving"));
+		_MSIM_ADD_NEW_ATTENTION(NULL, _("punk"), _("punk'd"), _("Punking"));
+		_MSIM_ADD_NEW_ATTENTION(NULL, _("raspberry"), _("raspberried"), _("Raspberry'ing"));
+	}
+
+	return types;
+}
+
+/** Send a zap */
+gboolean
+msim_send_attention(PurpleConnection *gc, const gchar *username, guint code)
+{
+	GList *types;
+	MsimSession *session;
+	MsimAttentionType *attn;
+	PurpleBuddy *buddy;
+
+	session = (MsimSession *)gc->proto_data;
+
+	/* Look for this attention type, by the code index given. */
+	types = msim_attention_types(gc->account);
+	attn = (MsimAttentionType *)g_list_nth_data(types, code);
+
+	if (!attn) {
+		purple_debug_info("msim_send_attention", "got invalid zap code %d\n", code);
+		return FALSE;
+	}
+
+	buddy = purple_find_buddy(session->account, username);
+	if (!buddy) {
+		return FALSE;
+	}
+
+	/* TODO: make use of the MsimAttentionType we found, instead of
+	 * doing it all over in msim_send_zap_from_menu. */
+	msim_send_zap_from_menu(&buddy->node, GUINT_TO_POINTER(code));
+
+	return TRUE;
+}
+
+/** Send a zap to a user. */
+static gboolean
+msim_send_zap(MsimSession *session, const gchar *username, guint code)
+{
+	gchar *zap_string;
+#ifndef MSIM_USE_ATTENTION_API
+	gchar *zap_description;
+#endif
+	GList *types;
+	MsimAttentionType *attn;
+	gboolean rc;
+
+	g_return_val_if_fail(session != NULL, FALSE);
+	g_return_val_if_fail(username != NULL, FALSE);
+
+	types = msim_attention_types(session->account);
+
+	attn = g_list_nth_data(types, code);
+	if (!attn) {
+		return FALSE;
+	}
+
+
+#ifdef MSIM_USE_ATTENTION_API
+	serv_got_attention(session->gc, username, attn, FALSE);
+#else
+	zap_description = g_strdup_printf("*** Attention: %s %s ***", attn->outgoing_description,
+			username);
+
+	serv_got_im(session->gc, username, zap_description,
+			PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_SYSTEM, time(NULL));
+
+	g_free(zap_description);
+#endif
+
+	/* Construct and send the actual zap command. */
+	zap_string = g_strdup_printf("!!!ZAP_SEND!!!=RTE_BTN_ZAPS_%d", code);
+
+	if (!msim_send_bm(session, username, zap_string, MSIM_BM_ACTION)) {
+		purple_debug_info("msim_send_zap_from_menu", "msim_send_bm failed: zapping %s with %s",
+				username, zap_string);
+		rc = FALSE;
+	} else {
+		rc = TRUE;
+	}
+	
+	g_free(zap_string);
+
+	return rc;
+
+}
+
+/** Zap someone. Callback from msim_blist_node_menu zap menu. */
+static void
+msim_send_zap_from_menu(PurpleBlistNode *node, gpointer zap_num_ptr)
+{
+	PurpleBuddy *buddy;
+	PurpleAccount *account;
+	PurpleConnection *gc;
+	MsimSession *session;
+	guint zap;
+
+	if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) {
+		/* Only know about buddies for now. */
+		return;
+	}
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
+
+	buddy = (PurpleBuddy *)node;
+
+	/* Find the session */
+	account = buddy->account;
+	gc = purple_account_get_connection(account);
+	session = (MsimSession *)gc->proto_data;
+
+	zap = GPOINTER_TO_INT(zap_num_ptr);
+
+	g_return_if_fail(msim_send_zap(session, buddy->name, zap));
+}
+
+/** Return menu, if any, for a buddy list node. */
+GList *
+msim_blist_node_menu(PurpleBlistNode *node)
+{
+	GList *menu, *zap_menu;
+	GList *types;
+	PurpleMenuAction *act;
+	/* Warning: hardcoded to match that in msim_attention_types. */
+	const gchar *zap_names[10];
+	guint i;
+
+	if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) {
+		/* Only know about buddies for now. */
+		return NULL;
+	}
+
+	/* Names from official client. */
+	types = msim_attention_types(NULL);
+	i = 0;
+	do
+	{
+		MsimAttentionType *attn;
+
+		attn = (MsimAttentionType *)types->data;
+		zap_names[i] = attn->name;
+		++i;
+	} while ((types = g_list_next(types)));
+
+	menu = zap_menu = NULL;
+
+	/* TODO: get rid of once is accessible directly in GUI */
+	for (i = 0; i < sizeof(zap_names) / sizeof(zap_names[0]); ++i) {
+		act = purple_menu_action_new(zap_names[i], PURPLE_CALLBACK(msim_send_zap_from_menu),
+				GUINT_TO_POINTER(i), NULL);
+		zap_menu = g_list_append(zap_menu, act);
+	}
+
+	act = purple_menu_action_new(_("Zap"), NULL, NULL, zap_menu);
+	menu = g_list_append(menu, act);
+
+	return menu;
+}
+
+/** Process an incoming zap. */
+gboolean
+msim_incoming_zap(MsimSession *session, MsimMessage *msg)
+{
+	gchar *msg_text, *username;
+	gint zap;
+	const gchar *zap_past_tense[10];
+#ifdef MSIM_USE_ATTENTION_API
+	MsimAttentionType attn;
+#else
+	gchar *zap_text;
+#endif
+
+	zap_past_tense[0] = _("zapped");
+	zap_past_tense[1] = _("whacked");
+	zap_past_tense[2] = _("torched");
+	zap_past_tense[3] = _("smooched");
+	zap_past_tense[4] = _("hugged");
+	zap_past_tense[5] = _("bslapped");
+	zap_past_tense[6] = _("goosed");
+	zap_past_tense[7] = _("hi-fived");
+	zap_past_tense[8] = _("punk'd");
+	zap_past_tense[9] = _("raspberried");
+
+	msg_text = msim_msg_get_string(msg, "msg");
+	username = msim_msg_get_string(msg, "_username");
+
+	g_return_val_if_fail(msg_text != NULL, FALSE);
+	g_return_val_if_fail(username != NULL, FALSE);
+
+	g_return_val_if_fail(sscanf(msg_text, "!!!ZAP_SEND!!!=RTE_BTN_ZAPS_%d", &zap) == 1, FALSE);
+
+	zap = CLAMP(zap, 0, sizeof(zap_past_tense) / sizeof(zap_past_tense[0]));
+
+	/* TODO:ZAP: use msim_attention_types */
+#ifdef MSIM_USE_ATTENTION_API
+	attn.incoming_description = zap_past_tense[zap];
+	attn.outgoing_description = NULL;
+	attn.icon_name = NULL;		/* TODO: icon */
+
+	serv_got_attention(session->gc, username, &attn, TRUE);
+#else
+	zap_text = g_strdup_printf(_("*** You have been %s! ***"), zap_past_tense[zap]);
+	serv_got_im(session->gc, username, zap_text, 
+			PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_SYSTEM, time(NULL));
+	g_free(zap_text);
+#endif
+
+	g_free(msg_text);
+	g_free(username);
+
+	return TRUE;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/zap.h	Sun Aug 26 14:02:35 2007 +0000
@@ -0,0 +1,28 @@
+/* MySpaceIM Protocol Plugin - zap support
+ *
+ * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _MYSPACE_ZAP_H
+#define _MYSPACE_ZAP_H
+
+GList *msim_attention_types(PurpleAccount *acct);
+gboolean msim_send_attention(PurpleConnection *gc, const gchar *username, guint code);
+GList *msim_blist_node_menu(PurpleBlistNode *node);
+gboolean msim_incoming_zap(MsimSession *session, MsimMessage *msg);
+
+#endif /* !_MYSPACE_ZAP_H */