changeset 19454:a650c8294bd2

merge of '0bf01031d132e0c8d3ab091041ec704fcfc2b152' and '5b0dfe36f723f3de64d94f962693b9df13b36a40'
author Kevin Stange <kevin@simguy.net>
date Sun, 26 Aug 2007 19:08:29 +0000
parents 90a41215bcb1 (current diff) 52a67d7b82ac (diff)
children 6950ffd2291f 752c33e7fdde
files
diffstat 59 files changed, 1980 insertions(+), 1767 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sun Aug 26 19:07:22 2007 +0000
+++ b/ChangeLog	Sun Aug 26 19:08:29 2007 +0000
@@ -1,5 +1,12 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+version 2.1.2:
+	* New protocol plugin: MySpaceIM (Jeff Connelly, Google Summer of
+	  Code)
+
+	Finch:
+	* Per-conversation mute and logging options (accessible from the menu)
+
 version 2.1.1 (08/20/2007):
 	Yahoo:
 	* Added an account action to open your inbox in the yahoo prpl.
--- a/ChangeLog.API	Sun Aug 26 19:07:22 2007 +0000
+++ b/ChangeLog.API	Sun Aug 26 19:08:29 2007 +0000
@@ -6,6 +6,18 @@
 		* pidgin_set_accessible_relations, sets up label-for and labelled-by
 		  ATK relations (broken out from pidgin_set_accessible_label)
 
+	Finch:
+		Added:
+		* finch_sound_is_enabled
+		* The reserved field in the FinchConv is now used to store information
+		  about the conversation (using FinchConversationFlag)
+
+		libgnt:
+		* gnt_slider_set_small_step, gnt_slider_set_large_step to allow more
+		  fine tuned updates of a GntSlider
+		* gnt_util_parse_xhtml_to_textview to parse XHTML strings in a
+		  GntTextView (this works only if libxml2 is available)
+
 Version 2.1.1 (08/20/2007):
 	libpurple:
 		Changed:
--- a/finch/gntaccount.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/finch/gntaccount.c	Sun Aug 26 19:08:29 2007 +0000
@@ -222,6 +222,11 @@
 
 	/* XXX: Proxy options */
 
+	if (accounts.window && accounts.tree) {
+		gnt_tree_set_selected(GNT_TREE(accounts.tree), account);
+		gnt_box_give_focus_to_child(GNT_BOX(accounts.window), accounts.tree);
+	}
+
 	gnt_widget_destroy(dialog->window);
 }
 
--- a/finch/gntconv.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/finch/gntconv.c	Sun Aug 26 19:08:29 2007 +0000
@@ -37,6 +37,7 @@
 #include "gntdebug.h"
 #include "gntplugin.h"
 #include "gntprefs.h"
+#include "gntsound.h"
 #include "gntstatus.h"
 #include "gntpounce.h"
 
@@ -347,6 +348,53 @@
 }
 
 static void
+toggle_logging_cb(GntMenuItem *item, gpointer ggconv)
+{
+	FinchConv *fc = ggconv;
+	PurpleConversation *conv = fc->active_conv;
+	gboolean logging = gnt_menuitem_check_get_checked(GNT_MENU_ITEM_CHECK(item));
+	GList *iter;
+
+	if (logging == purple_conversation_is_logging(conv))
+		return;
+
+	/* Xerox */
+	if (logging) {
+		/* Enable logging first so the message below can be logged. */
+		purple_conversation_set_logging(conv, TRUE);
+
+		purple_conversation_write(conv, NULL,
+				_("Logging started. Future messages in this conversation will be logged."),
+				conv->logs ? (PURPLE_MESSAGE_SYSTEM) :
+				(PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG),
+				time(NULL));
+	} else {
+		purple_conversation_write(conv, NULL,
+				_("Logging stopped. Future messages in this conversation will not be logged."),
+				conv->logs ? (PURPLE_MESSAGE_SYSTEM) :
+				(PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG),
+				time(NULL));
+
+		/* Disable the logging second, so that the above message can be logged. */
+		purple_conversation_set_logging(conv, FALSE);
+	}
+
+	/* Each conversation with the same person will have the same logging setting */
+	for (iter = fc->list; iter; iter = iter->next) {
+		if (iter->data == conv)
+			continue;
+		purple_conversation_set_logging(iter->data, logging);
+	}
+}
+
+static void
+toggle_sound_cb(GntMenuItem *item, gpointer ggconv)
+{
+	FinchConv *fc = ggconv;
+	fc->flags ^= FINCH_CONV_NO_SOUND;
+}
+
+static void
 send_to_cb(GntMenuItem *m, gpointer n)
 {
 	PurpleAccount *account = g_object_get_data(G_OBJECT(m), "purple_account");
@@ -452,6 +500,18 @@
 
 		generate_send_to_menu(ggc);
 	}
+
+	item = gnt_menuitem_check_new(_("Enable Logging"));
+	gnt_menuitem_check_set_checked(GNT_MENU_ITEM_CHECK(item),
+			purple_conversation_is_logging(ggc->active_conv));
+	gnt_menu_add_item(GNT_MENU(sub), item);
+	gnt_menuitem_set_callback(item, toggle_logging_cb, ggc);
+
+	item = gnt_menuitem_check_new(_("Enable Sounds"));
+	gnt_menuitem_check_set_checked(GNT_MENU_ITEM_CHECK(item),
+			!(ggc->flags & FINCH_CONV_NO_SOUND));
+	gnt_menu_add_item(GNT_MENU(sub), item);
+	gnt_menuitem_set_callback(item, toggle_sound_cb, ggc);
 }
 
 static void
@@ -497,6 +557,12 @@
 	else
 		ggc = g_new0(FinchConv, 1);
 
+	/* Each conversation with the same person will have the same logging setting */
+	if (ggc->list) {
+		purple_conversation_set_logging(conv,
+				purple_conversation_is_logging(ggc->list->data));
+	}
+
 	ggc->list = g_list_prepend(ggc->list, conv);
 	ggc->active_conv = conv;
 	conv->ui_data = ggc;
@@ -586,6 +652,9 @@
 		g_signal_connect(G_OBJECT(ggc->entry), "text_changed", G_CALLBACK(send_typing_notification), ggc);
 	}
 
+	if (!finch_sound_is_enabled())
+		ggc->flags |= FINCH_CONV_NO_SOUND;
+
 	gg_create_menu(ggc);
 
 	g_free(title);
@@ -830,6 +899,23 @@
 	gnt_tree_change_text(GNT_TREE(ggc->u.chat->userlist), (gpointer)user, 0, chat_flag_text(cb->flags));
 }
 
+static void
+finch_conv_present(PurpleConversation *conv)
+{
+	FinchConv *fc = FINCH_CONV(conv);
+	if (fc && fc->window)
+		return gnt_window_present(fc->window);
+}
+
+static gboolean
+finch_conv_has_focus(PurpleConversation *conv)
+{
+	FinchConv *fc = FINCH_CONV(conv);
+	if (fc && fc->window)
+		return gnt_widget_has_focus(fc->window);
+	return FALSE;
+}
+
 static PurpleConversationUiOps conv_ui_ops = 
 {
 	finch_create_conversation,
@@ -841,8 +927,8 @@
 	finch_chat_rename_user,
 	finch_chat_remove_users,
 	finch_chat_update_user,
-	NULL, /* present */
-	NULL, /* has_focus */
+	finch_conv_present, /* present */
+	finch_conv_has_focus, /* has_focus */
 	NULL, /* custom_smiley_add */
 	NULL, /* custom_smiley_write */
 	NULL, /* custom_smiley_close */
--- a/finch/gntconv.h	Sun Aug 26 19:07:22 2007 +0000
+++ b/finch/gntconv.h	Sun Aug 26 19:08:29 2007 +0000
@@ -43,6 +43,11 @@
 typedef struct _FinchConvChat FinchConvChat;
 typedef struct _FinchConvIm FinchConvIm;
 
+typedef enum
+{
+	FINCH_CONV_NO_SOUND     = 1 << 0,
+} FinchConversationFlag;
+
 struct _FinchConv
 {
 	GList *list;
@@ -53,7 +58,7 @@
 	GntWidget *tv;            /* text-view */
 	GntWidget *menu;
 	GntWidget *info;
-	void *pad;
+	FinchConversationFlag flags;
 
 	union
 	{
--- a/finch/gntsound.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/finch/gntsound.c	Sun Aug 26 19:08:29 2007 +0000
@@ -40,6 +40,8 @@
 #include "sound.h"
 #include "util.h"
 
+#include "gntconv.h"
+
 #include "gntbox.h"
 #include "gntwindow.h"
 #include "gntcombobox.h"
@@ -173,7 +175,8 @@
 
 		has_focus = purple_conversation_has_focus(conv);
 
-		if (has_focus && !purple_prefs_get_bool(make_pref("/conv_focus")))
+		if ((gntconv->flags & FINCH_CONV_NO_SOUND) ||
+			(has_focus && !purple_prefs_get_bool(make_pref("/conv_focus"))))
 		{
 			return;
 		}
@@ -409,14 +412,14 @@
 	GError *err = NULL;
 
 	switch (GST_MESSAGE_TYPE (msg)) {
-	case GST_MESSAGE_EOS:
-		gst_element_set_state(play, GST_STATE_NULL);
-		gst_object_unref(GST_OBJECT(play));
-		break;
 	case GST_MESSAGE_ERROR:
 		gst_message_parse_error(msg, &err, NULL);
 		purple_debug_error("gstreamer", "%s\n", err->message);
 		g_error_free(err);
+		/* fall-through and clean up */
+	case GST_MESSAGE_EOS:
+		gst_element_set_state(play, GST_STATE_NULL);
+		gst_object_unref(GST_OBJECT(play));
 		break;
 	case GST_MESSAGE_WARNING:
 		gst_message_parse_warning(msg, &err, NULL);
@@ -670,28 +673,34 @@
 {
 	PurpleSoundEventID id = GPOINTER_TO_INT(gnt_tree_get_selection_data(GNT_TREE(pref_dialog->events)));
 	FinchSoundEvent * event = &sounds[id];
-	char *enabled, *file, *tmpfile;
+	char *enabled, *file, *tmpfile, *volpref;
 	gboolean temp_value;
+	int volume;
 
 	enabled = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/enabled/%s",
 			finch_sound_get_active_profile(), event->pref);
 	file = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/file/%s",
 			finch_sound_get_active_profile(), event->pref);
+	volpref = g_strdup(make_pref("/volume"));
 
 	temp_value = purple_prefs_get_bool(enabled);
 	tmpfile = g_strdup(purple_prefs_get_string(file));
+	volume = purple_prefs_get_int(volpref);
 
 	purple_prefs_set_string(file, event->file);
 	if (!temp_value) purple_prefs_set_bool(enabled, TRUE);
+	purple_prefs_set_int(volpref, gnt_slider_get_value(GNT_SLIDER(pref_dialog->volume)));
 
 	purple_sound_play_event(id, NULL);
 
 	if (!temp_value) purple_prefs_set_bool(enabled, FALSE);
 	purple_prefs_set_string(file, tmpfile);
+	purple_prefs_set_int(volpref, volume);
 
 	g_free(enabled);
 	g_free(file);
 	g_free(tmpfile);
+	g_free(volpref);
 }
 
 static void
@@ -990,6 +999,8 @@
 
 	pref_dialog->volume = slider = gnt_slider_new(FALSE, 100, 0);
 	gnt_slider_set_step(GNT_SLIDER(slider), 5);
+	gnt_slider_set_small_step(GNT_SLIDER(slider), 1);
+	gnt_slider_set_large_step(GNT_SLIDER(slider), 20);
 	label = gnt_label_new("");
 	gnt_slider_reflect_label(GNT_SLIDER(slider), GNT_LABEL(label));
 	gnt_box_set_pad(GNT_BOX(tmpbox), 1);
@@ -1053,7 +1064,22 @@
 	load_pref_window(finch_sound_get_active_profile());
 
 	gnt_widget_show(win);
-}	
+}
+
+gboolean finch_sound_is_enabled(void)
+{
+	const char *pref = make_pref("/method");
+	const char *method = purple_prefs_get_string(pref);
+
+	if (!method)
+		return FALSE;
+	if (strcmp(method, "nosound") == 0)
+		return FALSE;
+	if (purple_prefs_get_int(make_pref("/volume")) <= 0)
+		return FALSE;
+
+	return TRUE;
+}
 
 static PurpleSoundUiOps sound_ui_ops =
 {
--- a/finch/gntsound.h	Sun Aug 26 19:07:22 2007 +0000
+++ b/finch/gntsound.h	Sun Aug 26 19:08:29 2007 +0000
@@ -55,6 +55,14 @@
 GList *finch_sound_get_profiles(void);
 
 /**
+ * Determine whether any sound will be played or not.
+ *
+ * @return Returns FALSE if preference is set to 'No sound', or if volume is
+ *         set to zero.
+ */
+gboolean finch_sound_is_enabled(void);
+
+/**
  * Gets GNT sound UI ops.
  *
  * @return The UI operations structure.
--- a/finch/libgnt/gntcolors.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/finch/libgnt/gntcolors.c	Sun Aug 26 19:08:29 2007 +0000
@@ -133,6 +133,7 @@
 		restore_colors();
 }
 
+#if GLIB_CHECK_VERSION(2,6,0)
 static int
 get_color(char *key)
 {
@@ -164,7 +165,6 @@
 	return color;
 }
 
-#if GLIB_CHECK_VERSION(2,6,0)
 void gnt_colors_parse(GKeyFile *kfile)
 {
 	GError *error = NULL;
--- a/finch/libgnt/gntfilesel.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/finch/libgnt/gntfilesel.c	Sun Aug 26 19:08:29 2007 +0000
@@ -200,7 +200,7 @@
 	const char *tmp;
 	tmp = sel->suggest ? sel->suggest :
 		(const char*)gnt_tree_get_selection_data(sel->dirsonly ? GNT_TREE(sel->dirs) : GNT_TREE(sel->files));
-	old = g_strdup_printf("%s%s%s", sel->current, sel->current[1] ? G_DIR_SEPARATOR_S : "", tmp ? tmp : "");
+	old = g_strdup_printf("%s%s%s", SAFE(sel->current), SAFE(sel->current)[1] ? G_DIR_SEPARATOR_S : "", tmp ? tmp : "");
 	gnt_entry_set_text(GNT_ENTRY(sel->location), old);
 	g_free(old);
 }
--- a/finch/libgnt/gntslider.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/finch/libgnt/gntslider.c	Sun Aug 26 19:08:29 2007 +0000
@@ -125,22 +125,66 @@
 step_back(GntBindable *bindable, GList *null)
 {
 	GntSlider *slider = GNT_SLIDER(bindable);
-	if (slider->current <= slider->min)
-		return FALSE;
 	gnt_slider_advance_step(slider, -1);
 	return TRUE;
 }
 
 static gboolean
+small_step_back(GntBindable *bindable, GList *null)
+{
+	GntSlider *slider = GNT_SLIDER(bindable);
+	gnt_slider_set_value(slider, slider->current - slider->smallstep);
+	return TRUE;
+}
+
+static gboolean
+large_step_back(GntBindable *bindable, GList *null)
+{
+	GntSlider *slider = GNT_SLIDER(bindable);
+	gnt_slider_set_value(slider, slider->current - slider->largestep);
+	return TRUE;
+}
+
+static gboolean
 step_forward(GntBindable *bindable, GList *list)
 {
 	GntSlider *slider = GNT_SLIDER(bindable);
-	if (slider->current >= slider->max)
-		return FALSE;
 	gnt_slider_advance_step(slider, 1);
 	return TRUE;
 }
 
+static gboolean
+small_step_forward(GntBindable *bindable, GList *null)
+{
+	GntSlider *slider = GNT_SLIDER(bindable);
+	gnt_slider_set_value(slider, slider->current + slider->smallstep);
+	return TRUE;
+}
+
+static gboolean
+large_step_forward(GntBindable *bindable, GList *null)
+{
+	GntSlider *slider = GNT_SLIDER(bindable);
+	gnt_slider_set_value(slider, slider->current + slider->largestep);
+	return TRUE;
+}
+
+static gboolean
+move_min_value(GntBindable *bindable, GList *null)
+{
+	GntSlider *slider = GNT_SLIDER(bindable);
+	gnt_slider_set_value(slider, slider->min);
+	return TRUE;
+}
+
+static gboolean
+move_max_value(GntBindable *bindable, GList *null)
+{
+	GntSlider *slider = GNT_SLIDER(bindable);
+	gnt_slider_set_value(slider, slider->max);
+	return TRUE;
+}
+
 static void
 gnt_slider_class_init(GntSliderClass *klass)
 {
@@ -165,8 +209,14 @@
 	gnt_bindable_register_binding(bindable, "step-backward", GNT_KEY_DOWN, NULL);
 	gnt_bindable_class_register_action(bindable, "step-forward", step_forward, GNT_KEY_RIGHT, NULL);
 	gnt_bindable_register_binding(bindable, "step-forward", GNT_KEY_UP, NULL);
-
-	/* XXX: how would home/end work? */
+	gnt_bindable_class_register_action(bindable, "small-step-backward", small_step_back, GNT_KEY_CTRL_LEFT, NULL);
+	gnt_bindable_register_binding(bindable, "small-step-backward", GNT_KEY_CTRL_DOWN, NULL);
+	gnt_bindable_class_register_action(bindable, "small-step-forward", small_step_forward, GNT_KEY_CTRL_RIGHT, NULL);
+	gnt_bindable_register_binding(bindable, "small-step-forward", GNT_KEY_CTRL_UP, NULL);
+	gnt_bindable_class_register_action(bindable, "large-step-backward", large_step_back, GNT_KEY_PGDOWN, NULL);
+	gnt_bindable_class_register_action(bindable, "large-step-forward", large_step_forward, GNT_KEY_PGUP, NULL);
+	gnt_bindable_class_register_action(bindable, "min-value", move_min_value, GNT_KEY_HOME, NULL);
+	gnt_bindable_class_register_action(bindable, "max-value", move_max_value, GNT_KEY_END, NULL);
 
 	gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass), GNT_BINDABLE_CLASS(klass));
 }
@@ -233,10 +283,14 @@
 
 void gnt_slider_set_value(GntSlider *slider, int value)
 {
+	int old;
 	if (slider->current == value)
 		return;
+	old = slider->current;
 	slider->current = value;
 	sanitize_value(slider);
+	if (old == slider->current)
+		return;
 	redraw_slider(slider);
 	slider_value_changed(slider);
 }
@@ -248,10 +302,7 @@
 
 int gnt_slider_advance_step(GntSlider *slider, int steps)
 {
-	slider->current += steps * slider->step;
-	sanitize_value(slider);
-	redraw_slider(slider);
-	slider_value_changed(slider);
+	gnt_slider_set_value(slider, slider->current + steps * slider->step);
 	return slider->current;
 }
 
@@ -260,6 +311,16 @@
 	slider->step = step;
 }
 
+void gnt_slider_set_small_step(GntSlider *slider, int step)
+{
+	slider->smallstep = step;
+}
+
+void gnt_slider_set_large_step(GntSlider *slider, int step)
+{
+	slider->largestep = step;
+}
+
 void gnt_slider_set_range(GntSlider *slider, int max, int min)
 {
 	slider->max = MAX(max, min);
--- a/finch/libgnt/gntslider.h	Sun Aug 26 19:07:22 2007 +0000
+++ b/finch/libgnt/gntslider.h	Sun Aug 26 19:08:29 2007 +0000
@@ -56,6 +56,8 @@
 	int min;        /* minimum value */
 	int step;       /* amount to change at each step */
 	int current;    /* current value */
+	int smallstep;
+	int largestep;
 };
 
 struct _GntSliderClass
@@ -103,11 +105,27 @@
  * Sets the amount of change at each step.
  * 
  * @param slider  The slider
- * @param step    The amount for each ste
+ * @param step    The amount for each step
  */
 void gnt_slider_set_step(GntSlider *slider, int step);
 
 /**
+ * Sets the amount of change a small step.
+ * 
+ * @param slider  The slider
+ * @param step    The amount for a small step (for the slider)
+ */
+void gnt_slider_set_small_step(GntSlider *slider, int step);
+
+/**
+ * Sets the amount of change a large step.
+ * 
+ * @param slider  The slider
+ * @param step    The amount for a large step (for the slider)
+ */
+void gnt_slider_set_large_step(GntSlider *slider, int step);
+
+/**
  * Advance the slider forward or backward.
  *
  * @param slider   The slider
--- a/finch/libgnt/gntstyle.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/finch/libgnt/gntstyle.c	Sun Aug 26 19:08:29 2007 +0000
@@ -55,6 +55,8 @@
 	if (!group)
 		group = "general";
 	return g_key_file_get_value(gkfile, group, key, NULL);
+#else
+	return NULL;
 #endif
 }
 
@@ -93,6 +95,7 @@
 	return def;
 }
 
+#if GLIB_CHECK_VERSION(2,6,0)
 static void
 refine(char *text)
 {
@@ -133,6 +136,7 @@
 {
 	return (char *)gnt_key_translate(key);
 }
+#endif
 
 void gnt_style_read_workspaces(GntWM *wm)
 {
--- a/finch/libgnt/gntutils.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/finch/libgnt/gntutils.c	Sun Aug 26 19:08:29 2007 +0000
@@ -385,6 +385,7 @@
 	xmlNode *ch;
 	gboolean processed = FALSE;
 	char *url = NULL;
+	gboolean insert_nl_s = FALSE, insert_nl_e = FALSE;
 
 	if (node == NULL || node->name == NULL || node->type != XML_ELEMENT_NODE)
 		return;
@@ -398,19 +399,34 @@
 	} else if (g_ascii_strcasecmp(name, "u") == 0) {
 		flag |= GNT_TEXT_FLAG_UNDERLINE;
 	} else if (g_ascii_strcasecmp(name, "br") == 0) {
-		gnt_text_view_append_text_with_flags(tv, "\n", flag);
+		insert_nl_e = TRUE;
 	} else if (g_ascii_strcasecmp(name, "a") == 0) {
 		flag |= GNT_TEXT_FLAG_UNDERLINE;
 		url = (char *)xmlGetProp(node, (xmlChar*)"href");
+	} else if (g_ascii_strcasecmp(name, "h1") == 0 ||
+			g_ascii_strcasecmp(name, "h2") == 0 ||
+			g_ascii_strcasecmp(name, "h3") == 0 ||
+			g_ascii_strcasecmp(name, "h4") == 0 ||
+			g_ascii_strcasecmp(name, "h5") == 0 ||
+			g_ascii_strcasecmp(name, "h6") == 0) {
+		insert_nl_s = TRUE;
+		insert_nl_e = TRUE;
+	} else if (g_ascii_strcasecmp(name, "title") == 0) {
+		insert_nl_s = TRUE;
+		insert_nl_e = TRUE;
+		flag |= GNT_TEXT_FLAG_BOLD | GNT_TEXT_FLAG_UNDERLINE;
 	} else {
 		/* XXX: Process other possible tags */
 	}
 
+	if (insert_nl_s)
+		gnt_text_view_append_text_with_flags(tv, "\n", flag);
+
 	for (ch = node->children; ch; ch = ch->next) {
 		if (ch->type == XML_ELEMENT_NODE) {
 			processed = TRUE;
+			util_parse_html_to_tv(ch, tv, flag);
 		}
-		util_parse_html_to_tv(ch, tv, flag);
 	}
 
 	if (!processed) {
@@ -425,6 +441,9 @@
 		g_free(href);
 		xmlFree(url);
 	}
+
+	if (insert_nl_e)
+		gnt_text_view_append_text_with_flags(tv, "\n", flag);
 }
 #endif
 
--- a/finch/libgnt/gntwm.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/finch/libgnt/gntwm.c	Sun Aug 26 19:08:29 2007 +0000
@@ -738,7 +738,7 @@
 			print = ch;
 #ifndef NO_WIDECHAR
 			if (wch.chars[0] > 255) {
-				snprintf(unicode, sizeof(unicode), "&#x%x;", wch.chars[0]);
+				snprintf(unicode, sizeof(unicode), "&#x%x;", (unsigned int)wch.chars[0]);
 				print = unicode;
 			}
 #endif
--- a/libpurple/Makefile.am	Sun Aug 26 19:07:22 2007 +0000
+++ b/libpurple/Makefile.am	Sun Aug 26 19:08:29 2007 +0000
@@ -22,9 +22,9 @@
 		win32/giowin32.c \
 		win32/win32dep.h
 
-if USE_GCONFTOOL
-GCONF_DIR=gconf
-endif
+# if USE_GCONFTOOL
+# GCONF_DIR=gconf
+# endif
 
 pkgconfigdir = $(libdir)/pkgconfig
 pkgconfig_DATA = purple.pc
--- a/libpurple/blist.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/libpurple/blist.c	Sun Aug 26 19:08:29 2007 +0000
@@ -1190,6 +1190,12 @@
 			group = purple_group_new(_("Chats"));
 			purple_blist_add_group(group,
 					purple_blist_get_last_sibling(purplebuddylist->root));
+		} else {
+			/* Add group to blist if isn't already on it. Fixes #2752. */
+			if (!purple_find_group(group->name)) {
+				purple_blist_add_group(group,
+						purple_blist_get_last_sibling(purplebuddylist->root));
+			}
 		}
 	} else {
 		group = (PurpleGroup*)node->parent;
@@ -1284,6 +1290,12 @@
 		g = (PurpleGroup *)((PurpleBlistNode *)c)->parent;
 	} else {
 		if (group) {
+			/* Add chat to blist if isn't already on it. Fixes #2752. */
+			if (!purple_find_group(group->name)) {
+				purple_blist_add_group(group,
+						purple_blist_get_last_sibling(purplebuddylist->root));
+			}
+
 			g = group;
 		} else {
 			g = purple_group_new(_("Buddies"));
--- a/libpurple/idle.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/libpurple/idle.c	Sun Aug 26 19:08:29 2007 +0000
@@ -163,8 +163,8 @@
 		{
 			if (!no_away)
 			{
+				no_away = TRUE;
 				purple_savedstatus_set_idleaway(FALSE);
-				no_away = TRUE;
 			}
 			time_until_next_idle_event = 0;
 			return;
--- a/libpurple/network.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/libpurple/network.c	Sun Aug 26 19:08:29 2007 +0000
@@ -436,7 +436,7 @@
 static gint
 wpurple_get_connected_network_count(void)
 {
-	guint net_cnt = 0;
+	gint net_cnt = 0;
 
 	WSAQUERYSET qs;
 	HANDLE h;
@@ -521,7 +521,7 @@
 		HANDLE hLookup, DWORD dwControlCode, LPVOID lpvInBuffer,
 		DWORD cbInBuffer, LPVOID lpvOutBuffer, DWORD cbOutBuffer,
 		LPDWORD lpcbBytesReturned, LPWSACOMPLETION lpCompletion) = NULL;
- 
+
 	if (!(MyWSANSPIoctl = (void*) wpurple_find_and_loadproc("ws2_32.dll", "WSANSPIoctl"))) {
 		g_thread_exit(NULL);
 		return NULL;
@@ -636,7 +636,7 @@
 purple_network_get_handle(void)
 {
 	static int handle;
-	
+
 	return &handle;
 }
 
@@ -675,7 +675,7 @@
 
 	purple_signal_register(purple_network_get_handle(), "network-configuration-changed",
 						   purple_marshal_VOID, NULL, 0);
-	
+
 	purple_pmp_init();
 	purple_upnp_init();
 }
--- a/libpurple/protocols/Makefile.mingw	Sun Aug 26 19:07:22 2007 +0000
+++ b/libpurple/protocols/Makefile.mingw	Sun Aug 26 19:08:29 2007 +0000
@@ -8,7 +8,7 @@
 PIDGIN_TREE_TOP := ../..
 include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
 
-SUBDIRS = gg irc jabber msn novell null oscar qq sametime silc10 simple yahoo bonjour
+SUBDIRS = gg irc jabber msn novell null oscar qq sametime silc10 simple yahoo bonjour myspace
 
 .PHONY: all install clean
 
--- a/libpurple/protocols/bonjour/jabber.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/libpurple/protocols/bonjour/jabber.c	Sun Aug 26 19:08:29 2007 +0000
@@ -82,8 +82,8 @@
 	BonjourJabberConversation *bconv = g_new0(BonjourJabberConversation, 1);
 	bconv->socket = -1;
 	bconv->tx_buf = purple_circ_buffer_new(512);
-	bconv->tx_handler = -1;
-	bconv->rx_handler = -1;
+	bconv->tx_handler = 0;
+	bconv->rx_handler = 0;
 
 	return bconv;
 }
@@ -234,7 +234,7 @@
 
 	if (writelen == 0) {
 		purple_input_remove(bconv->tx_handler);
-		bconv->tx_handler = -1;
+		bconv->tx_handler = 0;
 		return;
 	}
 
@@ -272,7 +272,7 @@
 	BonjourJabberConversation *bconv = bb->conversation;
 
 	/* If we're not ready to actually send, append it to the buffer */
-	if (bconv->tx_handler != -1
+	if (bconv->tx_handler != 0
 			|| bconv->connect_data != NULL
 			|| !bconv->sent_stream_start
 			|| !bconv->recv_stream_start
@@ -304,7 +304,7 @@
 	}
 
 	if (ret < len) {
-		if (bconv->tx_handler == -1)
+		if (bconv->tx_handler == 0)
 			bconv->tx_handler = purple_input_add(bconv->socket, PURPLE_INPUT_WRITE,
 				_send_data_write_cb, pb);
 		purple_circ_buffer_append(bconv->tx_buf, message + ret, len - ret);
@@ -455,7 +455,7 @@
 
 	/* Stream started; process the send buffer if there is one */
 	purple_input_remove(bconv->tx_handler);
-	bconv->tx_handler= -1;
+	bconv->tx_handler= 0;
 	bconv->sent_stream_start = TRUE;
 
 	bonjour_jabber_stream_started(pb);
@@ -779,7 +779,7 @@
 			/* TODO: We're really supposed to wait for "</stream:stream>" before closing the socket */
 			close(bconv->socket);
 		}
-		if (bconv->rx_handler != -1)
+		if (bconv->rx_handler != 0)
 			purple_input_remove(bconv->rx_handler);
 		if (bconv->tx_handler > 0)
 			purple_input_remove(bconv->tx_handler);
--- a/libpurple/protocols/bonjour/mdns_win32.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_win32.c	Sun Aug 26 19:08:29 2007 +0000
@@ -99,7 +99,7 @@
 
 			/* We've got what we need; stop listening */
 			purple_input_remove(idata->null_query_handler);
-			idata->null_query_handler = -1;
+			idata->null_query_handler = 0;
 			DNSServiceRefDeallocate(idata->null_query);
 			idata->null_query = NULL;
 		}
--- a/libpurple/protocols/msn/servconn.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/libpurple/protocols/msn/servconn.c	Sun Aug 26 19:08:29 2007 +0000
@@ -51,7 +51,7 @@
 	servconn->num = session->servconns_count++;
 
 	servconn->tx_buf = purple_circ_buffer_new(MSN_BUF_LEN);
-	servconn->tx_handler = -1;
+	servconn->tx_handler = 0;
 
 	return servconn;
 }
@@ -303,7 +303,7 @@
 
 	if (writelen == 0) {
 		purple_input_remove(servconn->tx_handler);
-		servconn->tx_handler = -1;
+		servconn->tx_handler = 0;
 		return;
 	}
 
@@ -328,7 +328,7 @@
 
 	if (!servconn->session->http_method)
 	{
-		if (servconn->tx_handler == -1) {
+		if (servconn->tx_handler == 0) {
 			switch (servconn->type)
 			{
 				case MSN_SERVCONN_NS:
@@ -353,7 +353,7 @@
 		if (ret < 0 && errno == EAGAIN)
 			ret = 0;
 		if (ret >= 0 && ret < len) {
-			if (servconn->tx_handler == -1)
+			if (servconn->tx_handler == 0)
 				servconn->tx_handler = purple_input_add(
 					servconn->fd, PURPLE_INPUT_WRITE,
 					servconn_write_cb, servconn);
--- a/libpurple/protocols/myspace/CHANGES	Sun Aug 26 19:07:22 2007 +0000
+++ b/libpurple/protocols/myspace/CHANGES	Sun Aug 26 19:08:29 2007 +0000
@@ -1,3 +1,28 @@
+2007-08-23 Jeff Connelly <pidgin@xyzzy.cjb.net> - 0.16
+* Add option to add all friends from myspace.com to your buddy list (#2660)
+* If a user doesn't have a picture, don't display an icon (instead of
+  displaying MySpace's "no photo" icon)
+* Fix #2725, a common crash related to buddy icon data
+* Fix #2752, which led to duplicate groups
+* Fix #2720, crash/disconnect when adding a buddy that doesn't exist
+  (You'll now receive an error when looking up invalid usernames).
+
+2007-08-22 Jeff Connelly <pidgin@xyzzy.cjb.net> - 0.15
+* Incomplete implementation of adding friends from myspace.com.
+* Change msim_msg_get() to start at the given node instead of the beginning.
+* Add msim_msg_get_*_from_element() to access data in MsimMessagElement *'s.
+* Use MsimMessage dictionaries everywhere in incoming messages, instead of
+  the old GHashTable method. Dictionary type is now fully implemented.
+* Add functions to loop over MsimMessages.
+* Link to myspace.com profile in Get Info.
+* Conditionally use my proposed attention API if defined.
+* Propagate to im.pidgin.pidgin branch for 2.1.2.
+* GSoC ended on 2007-08-20. The code in this release hasn't changed since
+  then. I did, however, bump the version number to 0.15 to distinguish this
+  release from the previous one. But there were no code changes. I updated
+  the text files, too.
+* Note: msimprpl will continue to be developed as time permits.
+
 2007-08-12 Jeff Connelly <jeff2@soc.pidgin.im> - 0.14
 * Full emoticon support (except no difference between nerd and geek emoticons),
   thanks to a number of new icons from Hylke Bons.
--- a/libpurple/protocols/myspace/Makefile.am	Sun Aug 26 19:07:22 2007 +0000
+++ b/libpurple/protocols/myspace/Makefile.am	Sun Aug 26 19:08:29 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 19:07:22 2007 +0000
+++ b/libpurple/protocols/myspace/Makefile.mingw	Sun Aug 26 19:08:29 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)
 
--- a/libpurple/protocols/myspace/README	Sun Aug 26 19:07:22 2007 +0000
+++ b/libpurple/protocols/myspace/README	Sun Aug 26 19:08:29 2007 +0000
@@ -1,4 +1,4 @@
-MySpaceIM Protocol Plugin      by Jeff Connelly 20070807
+MySpaceIM Protocol Plugin for Libpurple     by Jeff Connelly 20070807
 
 
 Greetings. This package contains a plugin for libpurple (as used in
@@ -6,7 +6,7 @@
 network and send/receive messages. Functionality is only basic as of yet, 
 and this code should be considered alpha quality.
 
-This code is being developed under Google Summer of Code 2007.
+This code was initially developed under Google Summer of Code 2007.
 
 For features and TODO, see http://developer.pidgin.im/wiki/MySpaceIM
 
@@ -28,7 +28,5 @@
 
 Enjoy,
 -Jeff Connelly
-California Polytechnic State University at San Luis Obispo
-myspaceim@xyzzy.cjb.net
-jeff2@soc.pidgin.im
+msimprpl@xyzzy.cjb.net
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/markup.c	Sun Aug 26 19:08:29 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 19:08:29 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 19:07:22 2007 +0000
+++ b/libpurple/protocols/myspace/message.c	Sun Aug 26 19:08:29 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 19:07:22 2007 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Sun Aug 26 19:08:29 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, des, incoming, outgoing)              \
-		attn = g_new0(MsimAttentionType, 1);                     \
-		attn->icon = icn;                                          \
-		attn->description = des;                                   \
-		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, 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->description;
-		++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 (!strcmp(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 (!strcmp(root->name, "f")) {
-		msim_markup_f_to_html(session, root, begin, end);
-	} else if (!strcmp(root->name, "a")) {
-		msim_markup_a_to_html(session, root, begin, end);
-	} else if (!strcmp(root->name, "p")) {
-		msim_markup_p_to_html(session, root, begin, end);
-	} else if (!strcmp(root->name, "c")) {
-		msim_markup_c_to_html(session, root, begin, end);
-	} else if (!strcmp(root->name, "b")) {
-		msim_markup_b_to_html(session, root, begin, end);
-	} else if (!strcmp(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 (!strcmp(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, 
@@ -1669,8 +746,8 @@
 	 * by Alexandr Shutko, who maintains OSCAR protocol documentation). */
 
 	purple_debug_info("msim", "Unrecognized data on account for %s\n", 
-			session->account->username ? session->account->username
-			: "(NULL)");
+			(session && session->account && session->account->username) ? 
+			session->account->username : "(NULL)");
 	if (note) {
 		purple_debug_info("msim", "(Note: %s)\n", note);
 	}
@@ -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 = 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.
  *
@@ -1761,13 +784,13 @@
 	purple_debug_info("msim", "msim_incoming_action: action <%s> from <%d>\n", 
 			msg_text, username);
 
-	if (strcmp(msg_text, "%typing%") == 0) {
+	if (g_str_equal(msg_text, "%typing%")) {
 		/* TODO: find out if msim repeatedly sends typing messages, so we can 
 		 * give it a timeout. Right now, there does seem to be an inordinately 
 		 * amount of time between typing stopped-typing notifications. */
 		serv_got_typing(session->gc, username, 0, PURPLE_TYPING);
 		rc = TRUE;
-	} else if (strcmp(msg_text, "%stoptyping%") == 0) {
+	} else if (g_str_equal(msg_text, "%stoptyping%")) {
 		serv_got_typing_stopped(session->gc, username);
 		rc = TRUE;
 	} else if (strstr(msg_text, "!!!ZAP_SEND!!!=RTE_BTN_ZAPS_")) {
@@ -1784,7 +807,7 @@
 	return rc;
 }
 
-/* Process an incoming media (buddy icon) message. */
+/* Process an incoming media (message background?) message. */
 static gboolean
 msim_incoming_media(MsimSession *session, MsimMessage *msg)
 {
@@ -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 
@@ -2377,42 +1312,27 @@
 	guint i, n;
 	const gchar *froms[5], *tos[5], *urls[5], *subjects[5];
 
-	/* Three parallel arrays for each new inbox message type. */
-	static const gchar *inbox_keys[] = 
-	{ 
-		"Mail", 
-		"BlogComment", 
-		"ProfileComment", 
-		"FriendRequest", 
-		"PictureComment" 
+	/* Information for each new inbox message type. */
+	static struct 
+	{
+		const gchar *key;
+		guint bit;
+		const gchar *url;
+		const gchar *text;
+	} message_types[] = {
+		{ "Mail", MSIM_INBOX_MAIL, "http://messaging.myspace.com/index.cfm?fuseaction=mail.inbox", NULL },
+		{ "BlogComment", MSIM_INBOX_BLOG_COMMENT, "http://blog.myspace.com/index.cfm?fuseaction=blog", NULL },
+		{ "ProfileComment", MSIM_INBOX_PROFILE_COMMENT, "http://home.myspace.com/index.cfm?fuseaction=user", NULL },
+		{ "FriendRequest", MSIM_INBOX_FRIEND_REQUEST, "http://messaging.myspace.com/index.cfm?fuseaction=mail.friendRequests", NULL },
+		{ "PictureComment", MSIM_INBOX_PICTURE_COMMENT, "http://home.myspace.com/index.cfm?fuseaction=user", NULL }
 	};
 
-	static const guint inbox_bits[] = 
-	{ 
-		MSIM_INBOX_MAIL, 
-		MSIM_INBOX_BLOG_COMMENT,
-		MSIM_INBOX_PROFILE_COMMENT,
-		MSIM_INBOX_FRIEND_REQUEST,
-		MSIM_INBOX_PICTURE_COMMENT
-	};
-
-	static const gchar *inbox_urls[] =
-	{
-		"http://messaging.myspace.com/index.cfm?fuseaction=mail.inbox",
-		"http://blog.myspace.com/index.cfm?fuseaction=blog",
-		"http://home.myspace.com/index.cfm?fuseaction=user",
-		"http://messaging.myspace.com/index.cfm?fuseaction=mail.friendRequests",
-		"http://home.myspace.com/index.cfm?fuseaction=user"
-	};
-
-	static const gchar *inbox_text[5];
-
 	/* Can't write _()'d strings in array initializers. Workaround. */
-	inbox_text[0] = _("New mail messages");
-	inbox_text[1] = _("New blog comments");
-	inbox_text[2] = _("New profile comments");
-	inbox_text[3] = _("New friend requests!");
-	inbox_text[4] = _("New picture comments");
+	message_types[0].text = _("New mail messages");
+	message_types[1].text = _("New blog comments");
+	message_types[2].text = _("New profile comments");
+	message_types[3].text = _("New friend requests!");
+	message_types[4].text = _("New picture comments");
 
 	g_return_if_fail(reply != NULL);
 
@@ -2427,12 +1347,12 @@
 
 	n = 0;
 
-	for (i = 0; i < sizeof(inbox_keys) / sizeof(inbox_keys[0]); ++i) {
+	for (i = 0; i < sizeof(message_types) / sizeof(message_types[0]); ++i) {
 		const gchar *key;
 		guint bit;
 		
-		key = inbox_keys[i];
-		bit = inbox_bits[i];
+		key = message_types[i].key;
+		bit = message_types[i].bit;
 
 		if (msim_msg_get(body, key)) {
 			/* Notify only on when _changes_ from no mail -> has mail
@@ -2441,13 +1361,13 @@
 				purple_debug_info("msim", "msim_check_inbox_cb: got %s, at %d\n",
 						key ? key : "(NULL)", n);
 
-				subjects[n] = inbox_text[i];
+				subjects[n] = message_types[i].text;
 				froms[n] = _("MySpace");
 				tos[n] = session->username;
 				/* TODO: append token, web challenge, so automatically logs in.
 				 * Would also need to free strings because they won't be static
 				 */
-				urls[n] = inbox_urls[i];
+				urls[n] = message_types[i].url;
 
 				++n;
 			} else {
@@ -2520,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, 
@@ -2597,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;
 }
@@ -2641,166 +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);
-
-	purple_buddy_icons_set_for_user(user->buddy->account,
-			user->buddy->name,
-			(gchar *)url_text, len, 
-			/*  Use URL itself as buddy icon "checksum" */
-			user->image_url);
-}
-
-/** 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 (!strcmp(key_str, "UserID") || !strcmp(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 (!strcmp(key_str, "Age")) {
-		user->age = atol(value_str);
-	} else if (!strcmp(key_str, "Gender")) {
-		user->gender = g_strdup(value_str);
-	} else if (!strcmp(key_str, "Location")) {
-		user->location = g_strdup(value_str);
-	} else if (!strcmp(key_str, "TotalFriends")) {
-		user->total_friends = atol(value_str);
-	} else if (!strcmp(key_str, "DisplayName")) {
-		user->display_name = g_strdup(value_str);
-	} else if (!strcmp(key_str, "BandName")) {
-		user->band_name = g_strdup(value_str);
-	} else if (!strcmp(key_str, "SongName")) {
-		user->song_name = g_strdup(value_str);
-	} else if (!strcmp(key_str, "UserName") || !strcmp(key_str, "IMName") || !strcmp(key_str, "NickName")) {
-		/* Ignore because PurpleBuddy knows this already */
-		;
-	} else if (!strcmp(key_str, "ImageURL") || !strcmp(key_str, "AvatarURL")) {
-		const gchar *previous_url;
-
-		user->image_url = g_strdup(value_str);
-		
-		previous_url = purple_buddy_icons_get_checksum_for_user(user->buddy);
-
-		/* Only download if URL changed */
-		if (!previous_url || strcmp(previous_url, user->image_url)) {
-			purple_util_fetch_url(user->image_url, TRUE, NULL, TRUE, msim_downloaded_buddy_icon, (gpointer)user);
-		}
-	} else if (!strcmp(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)
@@ -2986,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();
 
@@ -3012,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) {
@@ -3034,9 +1800,9 @@
 			break;
 
 		default:
-				purple_debug_info("msim", "msim_status for %s, unknown status code %d, treating as available\n",
+			purple_debug_info("msim", "msim_status for %s, unknown status code %d, treating as available\n",
 						username, status_code);
-				purple_status_code = PURPLE_STATUS_AVAILABLE;
+			purple_status_code = PURPLE_STATUS_AVAILABLE;
 	}
 
 	purple_prpl_got_user_status(session->account, username, purple_primitive_get_id_from_type(purple_status_code), NULL);
@@ -3056,6 +1822,14 @@
 	}
 #endif
 
+	if (status_code != MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN) {
+		/* Get information when they come online.
+		 * TODO: periodically refresh?
+		 */
+		purple_debug_info("msim_incoming_status", "%s came online, looking up\n", username);
+		msim_lookup_user(session, username, NULL, NULL);
+	}
+
 	g_free(username);
 	msim_msg_list_free(list);
 
@@ -3207,7 +1981,7 @@
 msim_postprocess_outgoing_cb(MsimSession *session, MsimMessage *userinfo, 
 		gpointer data)
 {
-	gchar *uid_field_name, *uid_before;
+	gchar *uid_field_name, *uid_before, *username;
 	guint uid;
 	MsimMessage *msg, *body;
 
@@ -3222,6 +1996,19 @@
 	uid = msim_msg_get_integer(body, "UserID");
 	msim_msg_free(body);
 
+	username = msim_msg_get_string(msg, "_username");
+
+	if (!uid) {
+		gchar *msg;
+
+		msg = g_strdup_printf(_("No such user: %s"), username);
+		purple_notify_error(NULL, NULL, _("User lookup"), msg);
+		g_free(msg);
+		g_free(username);
+		//msim_msg_free(msg);
+		return;
+	}
+
 	uid_field_name = msim_msg_get_string(msg, "_uid_field_name");
 	uid_before = msim_msg_get_string(msg, "_uid_before");
 
@@ -3238,6 +2025,7 @@
 	 */
 	g_free(uid_field_name);
 	g_free(uid_before);
+	g_free(username);
 	//msim_msg_free(msg);
 }
 
@@ -3532,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)
 {
@@ -3580,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.
  * 
@@ -3686,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);
-	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.
@@ -3908,6 +2524,8 @@
 	group_name = msim_msg_get_string(contact_info, "GroupName");
 	if (group_name) {
 		group = purple_group_new(group_name);
+		purple_debug_info("msim_add_contact_from_server_cb",
+				"adding to GroupName: %s\n", group_name);
 		g_free(group_name);
 	} else {
 		group = purple_group_new(_("IM Friends"));
@@ -3916,13 +2534,17 @@
 	/* 2. Get or create buddy */
 	buddy = purple_find_buddy(session->account, username);
 	if (!buddy) {
+		purple_debug_info("msim_add_contact_from_server_cb",
+				"creating new buddy: %s\n", username);
 		buddy = purple_buddy_new(session->account, username, NULL);
 	}
 
+	/* Add group to beginning. See #2752. */
+	purple_blist_add_group(group, NULL);
+
 	/* TODO: use 'Position' in contact_info to take into account where buddy is */
 	purple_blist_add_buddy(buddy, NULL, group, NULL /* insertion point */);
 
-
 	/* 3. Update buddy information */
 	user = msim_get_user_from_buddy(buddy);
 
@@ -3943,14 +2565,14 @@
  *
  * @return TRUE if added.
  * */
-static void 
+static gboolean
 msim_add_contact_from_server(MsimSession *session, MsimMessage *contact_info)
 {
 	guint uid;
 	const gchar *username;
 
 	uid = msim_msg_get_integer(contact_info, "ContactID");
-	g_return_if_fail(uid != 0);
+	g_return_val_if_fail(uid != 0, FALSE);
 
 	/* Lookup the username, since NickName and IMName is unreliable */
 	username = msim_uid2username_from_blist(session, uid);
@@ -3965,6 +2587,10 @@
 	} else {
 		msim_add_contact_from_server_cb(session, NULL, (gpointer)msim_msg_clone(contact_info));
 	}
+
+	/* Say that the contact was added, even if we're still looking up
+	 * their username. */
+	return TRUE;
 }
 
 /** Called when contact list is received from server. */
@@ -3972,12 +2598,16 @@
 msim_got_contact_list(MsimSession *session, MsimMessage *reply, gpointer user_data)
 {
 	MsimMessage *body, *body_node;
+	gchar *msg;
+	guint buddy_count;
 
 	msim_msg_dump("msim_got_contact_list: reply=%s", reply);
 
 	body = msim_msg_get_dictionary(reply, "body");
 	g_return_if_fail(body != NULL);
 
+	buddy_count = 0;
+
 	for (body_node = body;
 		body_node != NULL;
 		body_node = msim_msg_get_next_element_node(body_node))
@@ -3986,13 +2616,21 @@
 
 		elem = (MsimMessageElement *)body_node->data;
 
-		if (!strcmp(elem->name, "ContactID"))
+		if (g_str_equal(elem->name, "ContactID"))
 		{
 			/* Will look for first contact in body_node */
-			msim_add_contact_from_server(session, body_node);
+			if (msim_add_contact_from_server(session, body_node)) {
+				++buddy_count;
+			}
 		}
 	}
 
+	msg = g_strdup_printf(_("%d buddies were added or updated"), buddy_count);
+
+	purple_notify_info(session->account, _("Add contacts from server"), msg, NULL);
+
+	g_free(msg);
+
 	msim_msg_free(body);
 }
 
@@ -4010,7 +2648,7 @@
 	completed = msim_msg_get_string(body, "Completed");
 	g_return_if_fail(body != NULL);
 	msim_msg_free(body);
-	if (strcmp(completed, "True"))
+	if (!g_str_equal(completed, "True"))
 	{
 		purple_debug_info("msim_import_friends_cb",
 				"failed to import friends: %s", completed);
@@ -4263,7 +2901,7 @@
 	packed_expected = "\\bx\\WFhY\\k1\\v1\\k1\\42\\k1"
 		"\\v43\\k1\\v52/1xxx/2yyy\\k1\\v7\\final\\";
 
-	if (0 != strcmp(packed, packed_expected)) {
+	if (!g_str_equal(packed, packed_expected)) {
 		purple_debug_info("msim", "!!!(%d), msim_msg_pack not what expected: %s != %s\n",
 				++failures, packed, packed_expected);
 	}
@@ -4273,7 +2911,7 @@
 	packed_cloned = msim_msg_pack(msg_cloned);
 
 	purple_debug_info("msim", "msg cloned=%s\n", packed_cloned);
-	if (0 != strcmp(packed, packed_cloned)) {
+	if (!g_str_equal(packed, packed_cloned)) {
 		purple_debug_info("msim", "!!!(%d), msim_msg_pack on cloned message not equal to original: %s != %s\n",
 				++failures, packed_cloned, packed);
 	}
@@ -4328,7 +2966,7 @@
 	escaped = msim_escape(raw);
 	purple_debug_info("msim", "msim_test_escaping: raw=%s, escaped=%s\n", raw, escaped);
 	expected = "hello/1world/2hello/1world";
-	if (0 != strcmp(escaped, expected)) {
+	if (!g_str_equal(escaped, expected)) {
 		purple_debug_info("msim", "!!!(%d), msim_escape failed: %s != %s\n",
 				++failures, escaped, expected);
 	}
@@ -4337,7 +2975,7 @@
 	unescaped = msim_unescape(escaped);
 	g_free(escaped);
 	purple_debug_info("msim", "msim_test_escaping: unescaped=%s\n", unescaped);
-	if (0 != strcmp(raw, unescaped)) {
+	if (!g_str_equal(raw, unescaped)) {
 		purple_debug_info("msim", "!!!(%d), msim_unescape failed: %s != %s\n",
 				++failures, raw, unescaped);
 	}
@@ -4346,16 +2984,81 @@
 }
 #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;
+
+	/* Parameters are case-insensitive. */
+	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;
+
+	/* Need a contact. */
+	g_return_val_if_fail(cid != 0, FALSE);
+
+	/* 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;
+	}
+
+	/* TODO: msim_lookup_user() on cid, so can add by username? */
+
+	/* 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)  {
+			purple_debug_info("msim_uri_handler", "creating new conversation for %s\n", cid_str);
+			conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, cid_str);
+		}
+
+		/* Just open the window so the user can send an IM. */
+		purple_conversation_present(conv);
+
+	/* 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).  */
@@ -4386,21 +3089,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 19:07:22 2007 +0000
+++ b/libpurple/protocols/myspace/myspace.h	Sun Aug 26 19:08:29 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) */
@@ -69,7 +75,7 @@
 
 /* Use the attention API for zaps? */
 /* Can't have until >=2.2.0, since is a new API. */
-/*#define MSIM_USE_ATTENTION_API 	 */
+#define MSIM_USE_ATTENTION_API
 
 /* Constants */
 
@@ -90,7 +96,7 @@
 #define MSIM_LANGUAGE_NAME_ENGLISH  "ENGLISH"
 
 /* msimprpl version string of this plugin */
-#define MSIM_PRPL_VERSION_STRING    "0.14"
+#define MSIM_PRPL_VERSION_STRING    "0.16"
 
 /* Default server */
 #define MSIM_SERVER                 "im.myspace.akadns.net"
@@ -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,53 +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;
-} MsimUser;
-
-
 #ifdef MSIM_USE_ATTENTION_API
 #define MsimAttentionType PurpleAttentionType
 #else
@@ -248,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, 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);
@@ -283,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);
@@ -298,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 19:07:22 2007 +0000
+++ b/libpurple/protocols/myspace/persist.h	Sun Aug 26 19:08:29 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
--- a/libpurple/protocols/myspace/release.sh	Sun Aug 26 19:07:22 2007 +0000
+++ b/libpurple/protocols/myspace/release.sh	Sun Aug 26 19:08:29 2007 +0000
@@ -4,7 +4,7 @@
 
 # Package a new msimprpl for release. Must be run with bash.
 
-VERSION=0.14
+VERSION=0.16
 make
 # Include 'myspace' directory in archive, so it can easily be unextracted
 # into ~/pidgin/libpurple/protocols at the correct location.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/session.c	Sun Aug 26 19:08:29 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 19:08:29 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 19:08:29 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 19:08:29 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 */
--- a/libpurple/protocols/oscar/oscar.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Sun Aug 26 19:08:29 2007 +0000
@@ -3529,6 +3529,7 @@
 	PurpleConnection *gc;
 	PurpleAccount *account;
 	PurpleStatus *status;
+	PurplePresence *presence;
 	const char *message, *itmsurl;
 	char *tmp;
 	va_list ap;
@@ -3572,7 +3573,8 @@
 	aim_srv_setextrainfo(od, FALSE, 0, TRUE, tmp, itmsurl);
 	g_free(tmp);
 
-	aim_srv_setidle(od, 0);
+	presence = purple_status_get_presence(status);
+	aim_srv_setidle(od, purple_presence_is_idle(presence) ? 0 : time(NULL) - purple_presence_get_idle_time(presence));
 
 	if (od->icq) {
 		aim_icq_reqofflinemsgs(od);
--- a/libpurple/protocols/sametime/sametime.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/libpurple/protocols/sametime/sametime.c	Sun Aug 26 19:08:29 2007 +0000
@@ -3183,13 +3183,12 @@
   PurpleConnection *gc;
   struct mwPurplePluginData *pd;
   struct mwAwareIdBlock t = { mwAware_USER, b->name, NULL };
-  const char *ret;
-
-  gc = b->account->gc;
-  pd = gc->proto_data;
-
-  ret = mwServiceAware_getText(pd->srvc_aware, &t);
-  
+  const char *ret = NULL;
+
+  if ((gc = purple_account_get_connection(b->account))
+      && (pd = gc->proto_data))
+    ret = mwServiceAware_getText(pd->srvc_aware, &t);
+
   return (ret && g_utf8_validate(ret, -1, NULL)) ? g_markup_escape_text(ret, -1): NULL;
 }
 
@@ -3242,17 +3241,17 @@
 
 static void mw_prpl_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full) {
   PurpleConnection *gc;
-  struct mwPurplePluginData *pd;
+  struct mwPurplePluginData *pd = NULL;
   struct mwAwareIdBlock idb = { mwAware_USER, b->name, NULL };
 
-  const char *message;
+  const char *message = NULL;
   const char *status;
   char *tmp;
 
-  gc = b->account->gc;
-  pd = gc->proto_data;
-
-  message = mwServiceAware_getText(pd->srvc_aware, &idb);
+  if ((gc = purple_account_get_connection(b->account))
+      && (pd = gc->proto_data))
+     message = mwServiceAware_getText(pd->srvc_aware, &idb);
+
   status = status_text(b);
 
   if(message != NULL && g_utf8_validate(message, -1, NULL) && purple_utf8_strcasecmp(status, message)) {
@@ -3264,7 +3263,7 @@
 	purple_notify_user_info_add_pair(user_info, _("Status"), status);
   }
 
-  if(full) {
+  if(full && pd != NULL) {
     tmp = user_supports_text(pd->srvc_aware, b->name);
     if(tmp) {
 	  purple_notify_user_info_add_pair(user_info, _("Supports"), tmp);
--- a/libpurple/protocols/yahoo/yahoo.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo.c	Sun Aug 26 19:08:29 2007 +0000
@@ -238,14 +238,18 @@
 		case 8: /* how many online buddies we have */
 			break;
 		case 7: /* the current buddy */
-			if (name && f) /* update the previous buddy before changing the variables */
-				yahoo_update_status(gc, name, f);
-			name = pair->value;
-			if (name && g_utf8_validate(name, -1, NULL))
+			/* update the previous buddy before changing the variables */
+			if (f) {
+				if (message)
+					yahoo_friend_set_status_message(f, yahoo_string_decode(gc, message, unicode));
+				if (name)
+					yahoo_update_status(gc, name, f);
+			}
+			name = message = NULL;
+			f = NULL;
+			if (pair->value && g_utf8_validate(pair->value, -1, NULL)) {
+				name = pair->value;
 				f = yahoo_friend_find_or_new(gc, name);
-			else {
-				f = NULL;
-				name = NULL;
 			}
 			break;
 		case 10: /* state */
@@ -779,7 +783,7 @@
 		purple_conversation_write(conv, NULL, buf, PURPLE_MESSAGE_SYSTEM|PURPLE_MESSAGE_NOTIFY, time(NULL));
 		g_free(buf);
 	}
-    	
+
 }
 
 
@@ -2920,7 +2924,7 @@
 	purple_connection_set_display_name(gc, purple_account_get_username(account));
 
 	yd->fd = -1;
-	yd->txhandler = -1;
+	yd->txhandler = 0;
 	/* TODO: Is there a good grow size for the buffer? */
 	yd->txbuf = purple_circ_buffer_new(0);
 	yd->friends = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, yahoo_friend_free);
--- a/libpurple/protocols/yahoo/yahoo_packet.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo_packet.c	Sun Aug 26 19:08:29 2007 +0000
@@ -294,7 +294,7 @@
 
 	if (writelen == 0) {
 		purple_input_remove(yd->txhandler);
-		yd->txhandler = -1;
+		yd->txhandler = 0;
 		return;
 	}
 
@@ -355,7 +355,7 @@
 	len = yahoo_packet_build(pkt, 0, yd->wm, yd->jp, &data);
 
 	yahoo_packet_dump(data, len);
-	if (yd->txhandler == -1)
+	if (yd->txhandler == 0)
 		ret = write(yd->fd, data, len);
 	else {
 		ret = -1;
@@ -371,7 +371,7 @@
 	}
 
 	if (ret < len) {
-		if (yd->txhandler == -1)
+		if (yd->txhandler == 0)
 			yd->txhandler = purple_input_add(yd->fd, PURPLE_INPUT_WRITE,
 				yahoo_packet_send_can_write, yd);
 		purple_circ_buffer_append(yd->txbuf, data + ret, len - ret);
--- a/libpurple/protocols/yahoo/yahoochat.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoochat.c	Sun Aug 26 19:08:29 2007 +0000
@@ -62,8 +62,9 @@
 	}
 
 	pkt = yahoo_packet_new(YAHOO_SERVICE_CHATONLINE, YAHOO_STATUS_AVAILABLE,0);
-	yahoo_packet_hash(pkt, "sss", 1, purple_connection_get_display_name(gc),
-	                  109, purple_connection_get_display_name(gc), 6, "abcde");
+	yahoo_packet_hash(pkt, "ssss", 1, purple_connection_get_display_name(gc),
+	                  109, purple_connection_get_display_name(gc), 6, "abcde",
+	                  135, "ym8.1.0.415");
 	yahoo_packet_send_and_free(pkt, yd);
 }
 
@@ -155,7 +156,7 @@
 	if (members) {
 		g_hash_table_replace(components, g_strdup("members"), g_strdup(members->str));
 	}
-	if (!yahoo_privacy_check(gc, who) || 
+	if (!yahoo_privacy_check(gc, who) ||
 		(purple_account_get_bool(purple_connection_get_account(gc), "ignore_invites", FALSE))) {
 		purple_debug_info("yahoo",
 		    "Invite to conference %s from %s has been dropped.\n", room, who);
@@ -640,7 +641,7 @@
 	GList *w;
 
 	purple_debug_misc("yahoo", "leaving conference %s\n", room);
-	
+
 	pkt = yahoo_packet_new(YAHOO_SERVICE_CONFLOGOFF, YAHOO_STATUS_AVAILABLE, 0);
 
 	yahoo_packet_hash_str(pkt, 1, dn);
@@ -732,7 +733,7 @@
 			continue;
 		yahoo_packet_hash(pkt, "ss", 52, name, 53, name);
 	}
-	
+
 	yahoo_packet_send_and_free(pkt, yd);
 	g_free(msg2);
 }
--- a/libpurple/prpl.h	Sun Aug 26 19:07:22 2007 +0000
+++ b/libpurple/prpl.h	Sun Aug 26 19:08:29 2007 +0000
@@ -30,6 +30,7 @@
 #define _PURPLE_PRPL_H_
 
 typedef struct _PurplePluginProtocolInfo PurplePluginProtocolInfo;
+typedef struct _PurpleAttentionType PurpleAttentionType;
 
 /**************************************************************************/
 /** @name Basic Protocol Information                                      */
@@ -91,6 +92,14 @@
 	gboolean secret;
 };
 
+struct _PurpleAttentionType
+{
+	const char *icon_name;             /**< Icon to display (optional) */
+	const char *name;                  /**< Shown in GUI elements */
+	const char *incoming_description;  /**< Shown when sent */
+	const char *outgoing_description;  /**< Shown when receied */
+};
+
 /**
  * Protocol options
  *
@@ -332,8 +341,10 @@
 	/* room list serialize */
 	char *(*roomlist_room_serialize)(PurpleRoomlistRoom *room);
 
-	void (*_purple_reserved1)(void);
-	void (*_purple_reserved2)(void);
+	/* Attention API for sending & receiving zaps/nudges/buzzes etc. */
+	gboolean (*send_attention)(PurpleConnection *gc, 
+			const char *username, guint type);
+	GList *(*attention_types)(PurpleAccount *acct);
 	void (*_purple_reserved3)(void);
 	void (*_purple_reserved4)(void);
 };
--- a/libpurple/request.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/libpurple/request.c	Sun Aug 26 19:08:29 2007 +0000
@@ -1172,7 +1172,7 @@
 
 void *
 purple_request_choice(void *handle, const char *title, const char *primary,
-					const char *secondary, unsigned int default_value,
+					const char *secondary, int default_value,
 					const char *ok_text, GCallback ok_cb,
 					const char *cancel_text, GCallback cancel_cb,
 					PurpleAccount *account, const char *who, PurpleConversation *conv,
@@ -1197,7 +1197,7 @@
 void *
 purple_request_choice_varg(void *handle, const char *title,
 			 const char *primary, const char *secondary,
-			 unsigned int default_value,
+			 int default_value,
 			 const char *ok_text, GCallback ok_cb,
 			 const char *cancel_text, GCallback cancel_cb,
 			 PurpleAccount *account, const char *who, PurpleConversation *conv,
@@ -1233,7 +1233,7 @@
 
 void *
 purple_request_action(void *handle, const char *title, const char *primary,
-					const char *secondary, unsigned int default_action,
+					const char *secondary, int default_action,
 					PurpleAccount *account, const char *who, PurpleConversation *conv,
 					void *user_data, size_t action_count, ...)
 {
@@ -1254,7 +1254,7 @@
 void *
 purple_request_action_varg(void *handle, const char *title,
 						 const char *primary, const char *secondary,
-						 unsigned int default_action,
+						 int default_action,
 						 PurpleAccount *account, const char *who, PurpleConversation *conv,
 						  void *user_data, size_t action_count, va_list actions)
 {
--- a/libpurple/request.h	Sun Aug 26 19:07:22 2007 +0000
+++ b/libpurple/request.h	Sun Aug 26 19:08:29 2007 +0000
@@ -190,13 +190,13 @@
 						   PurpleAccount *account, const char *who, PurpleConversation *conv,
 						   void *user_data);
 	void *(*request_choice)(const char *title, const char *primary,
-							const char *secondary, unsigned int default_value,
+							const char *secondary, int default_value,
 							const char *ok_text, GCallback ok_cb,
 							const char *cancel_text, GCallback cancel_cb,
 							PurpleAccount *account, const char *who, PurpleConversation *conv,
 							void *user_data, va_list choices);
 	void *(*request_action)(const char *title, const char *primary,
-							const char *secondary, unsigned int default_action,
+							const char *secondary, int default_action,
 							PurpleAccount *account, const char *who, PurpleConversation *conv,
 							void *user_data, size_t action_count,
 							va_list actions);
@@ -1215,7 +1215,7 @@
  * @param cancel_cb     The callback for the @c Cancel button.
  * @param account		The PurpleAccount associated with this request, or NULL if none is
  * @param who			The username of the buddy assocaited with this request, or NULL if none is
- * @param conv			The PurpleConversation associated with this request, or NULL if none is 
+ * @param conv			The PurpleConversation associated with this request, or NULL if none is
  * @param user_data     The data to pass to the callback.
  * @param ...           The choices.  This argument list should be
  *                      terminated with a NULL parameter.
@@ -1224,7 +1224,7 @@
  */
 void *purple_request_choice(void *handle, const char *title,
 						  const char *primary, const char *secondary,
-						  unsigned int default_value,
+						  int default_value,
 						  const char *ok_text, GCallback ok_cb,
 						  const char *cancel_text, GCallback cancel_cb,
 						  PurpleAccount *account, const char *who, PurpleConversation *conv,
@@ -1246,7 +1246,7 @@
  * @param cancel_cb     The callback for the @c Cancel button.
  * @param account		The PurpleAccount associated with this request, or NULL if none is
  * @param who			The username of the buddy assocaited with this request, or NULL if none is
- * @param conv			The PurpleConversation associated with this request, or NULL if none is 
+ * @param conv			The PurpleConversation associated with this request, or NULL if none is
  * @param user_data     The data to pass to the callback.
  * @param choices       The choices.  This argument list should be
  *                      terminated with a @c NULL parameter.
@@ -1255,7 +1255,7 @@
  */
 void *purple_request_choice_varg(void *handle, const char *title,
 							   const char *primary, const char *secondary,
-							   unsigned int default_value,
+							   int default_value,
 							   const char *ok_text, GCallback ok_cb,
 							   const char *cancel_text, GCallback cancel_cb,
 							   PurpleAccount *account, const char *who, PurpleConversation *conv,
@@ -1275,7 +1275,7 @@
  * @param default_action The default value.
  * @param account		 The PurpleAccount associated with this request, or NULL if none is
  * @param who			 The username of the buddy assocaited with this request, or NULL if none is
- * @param conv			 The PurpleConversation associated with this request, or NULL if none is 
+ * @param conv			 The PurpleConversation associated with this request, or NULL if none is
  * @param user_data      The data to pass to the callback.
  * @param action_count   The number of actions.
  * @param ...            A list of actions.  These are pairs of
@@ -1290,7 +1290,7 @@
  */
 void *purple_request_action(void *handle, const char *title,
 						  const char *primary, const char *secondary,
-						  unsigned int default_action,
+						  int default_action,
 						  PurpleAccount *account, const char *who, PurpleConversation *conv,
 						  void *user_data, size_t action_count, ...);
 
@@ -1308,7 +1308,7 @@
  * @param default_action The default value.
  * @param account		 The PurpleAccount associated with this request, or NULL if none is
  * @param who			 The username of the buddy assocaited with this request, or NULL if none is
- * @param conv			 The PurpleConversation associated with this request, or NULL if none is 
+ * @param conv			 The PurpleConversation associated with this request, or NULL if none is
  * @param user_data      The data to pass to the callback.
  * @param action_count   The number of actions.
  * @param actions        A list of actions and callbacks.
@@ -1317,7 +1317,7 @@
  */
 void *purple_request_action_varg(void *handle, const char *title,
 							   const char *primary, const char *secondary,
-							   unsigned int default_action,
+							   int default_action,
 							   PurpleAccount *account, const char *who, PurpleConversation *conv,
 							   void *user_data, size_t action_count,
 							   va_list actions);
@@ -1338,7 +1338,7 @@
  * @param cancel_cb   The callback for the @c Cancel button.
  * @param account	  The PurpleAccount associated with this request, or NULL if none is
  * @param who		  The username of the buddy associated with this request, or NULL if none is
- * @param conv		  The PurpleConversation associated with this request, or NULL if none is 
+ * @param conv		  The PurpleConversation associated with this request, or NULL if none is
  * @param user_data   The data to pass to the callback.
  *
  * @return A UI-specific handle.
@@ -1411,7 +1411,7 @@
  * @param cancel_cb   The callback for the @c Cancel button.
  * @param account	  The PurpleAccount associated with this request, or NULL if none is
  * @param who		  The username of the buddy assocaited with this request, or NULL if none is
- * @param conv		  The PurpleConversation associated with this request, or NULL if none is 
+ * @param conv		  The PurpleConversation associated with this request, or NULL if none is
  * @param user_data   The data to pass to the callback.
  *
  * @return A UI-specific handle.
@@ -1435,7 +1435,7 @@
  * @param cancel_cb   The callback for the @c Cancel button.
  * @param account	  The PurpleAccount associated with this request, or NULL if none is
  * @param who		  The username of the buddy assocaited with this request, or NULL if none is
- * @param conv		  The PurpleConversation associated with this request, or NULL if none is 
+ * @param conv		  The PurpleConversation associated with this request, or NULL if none is
  * @param user_data   The data to pass to the callback.
  *
  * @return A UI-specific handle.
--- a/libpurple/server.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/libpurple/server.c	Sun Aug 26 19:08:29 2007 +0000
@@ -242,6 +242,75 @@
 	}
 }
 
+/** Indicate that an attention message was sent or received. */
+void
+serv_got_attention(PurpleConnection *gc, const char *who, PurpleAttentionType *attn, gboolean incoming)
+{
+	PurpleConversation *conv;
+	PurpleMessageFlags flags;
+	gchar *description;
+	int plugin_return;
+
+
+	/* For incoming messages, block the attention message if requested (privacy) */
+	if (incoming) {
+		gchar *who_copy;
+
+		if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->set_permit_deny == NULL)
+			if (!purple_privacy_check(gc->account, who))
+				return;
+
+		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, gc->account);
+
+		who_copy = g_strdup(who);
+
+		plugin_return = GPOINTER_TO_INT(
+			purple_signal_emit_return_1(purple_conversations_get_handle(),
+									  "receiving-im-msg", gc->account,
+									  &who_copy, &attn, conv));
+
+		if (!attn || !who_copy || plugin_return) {
+			g_free(who_copy);
+			return;
+		}
+
+		purple_signal_emit(purple_conversations_get_handle(), "received-im-msg", gc->account,
+						 who, attn, conv);
+	}
+
+	/* The attention message was allowed. Create a string representing the message. */
+	flags = PURPLE_MESSAGE_SYSTEM;
+
+	/* TODO: if (attn->icon_name) is non-null, use it to lookup an emoticon and display
+	 * it next to the attention command. And if it is null, display a generic icon. */
+
+	if (incoming) {
+		if (attn->incoming_description) {
+			description = g_strdup_printf(_("Attention! You have been %s."), attn->incoming_description);
+		} else {
+			description = g_strdup(_("Attention!"));
+		}
+		flags |= PURPLE_MESSAGE_RECV;
+	} else {
+		if (attn->outgoing_description) {
+			description = g_strdup_printf(_("Attention! %s %s."), attn->outgoing_description, who);
+		} else {
+			description = g_strdup(_("Attention!"));
+		}
+		flags |= PURPLE_MESSAGE_SEND;
+	}
+
+	/* Display it in the conversation window to the user. */
+	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, gc->account);
+	if (!conv)
+		conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, gc->account, who);
+
+	purple_conv_im_write(PURPLE_CONV_IM(conv), NULL, description, flags, time(NULL));
+
+	g_free(description);
+}
+
+
 /*
  * Move a buddy from one group to another on server.
  *
--- a/libpurple/server.h	Sun Aug 26 19:07:22 2007 +0000
+++ b/libpurple/server.h	Sun Aug 26 19:08:29 2007 +0000
@@ -67,6 +67,7 @@
 int  serv_chat_send(PurpleConnection *, int, const char *, PurpleMessageFlags flags);
 void serv_alias_buddy(PurpleBuddy *);
 void serv_got_alias(PurpleConnection *gc, const char *who, const char *alias);
+void serv_got_attention(PurpleConnection *gc, const char *who, struct _PurpleAttentionType *attn, gboolean incoming);
 
 /**
  * Receive a typing message from a remote user.  Either PURPLE_TYPING
--- a/libpurple/util.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/libpurple/util.c	Sun Aug 26 19:08:29 2007 +0000
@@ -1374,7 +1374,7 @@
 								g_string_free(cdata, TRUE);
 								cdata = NULL;
 							}
-							
+
 						}
 						if(tags == tag)
 							break;
@@ -1425,7 +1425,7 @@
 				ALLOW_TAG("strong");
 				ALLOW_TAG("ul");
 
-				
+
 				/* we skip <HR> because it's not legal in XHTML-IM.  However,
 				 * we still want to send something sensible, so we put a
 				 * linebreak in its place. <BR> also needs special handling
@@ -2539,7 +2539,7 @@
  * people's settings if there is a problem writing the new values.
  */
 gboolean
-purple_util_write_data_to_file(const char *filename, const char *data, size_t size)
+purple_util_write_data_to_file(const char *filename, const char *data, gssize size)
 {
 	const char *user_dir = purple_user_dir();
 	gchar *filename_temp, *filename_full;
@@ -4305,7 +4305,7 @@
 	}
 }
 
-gboolean purple_message_meify(char *message, size_t len)
+gboolean purple_message_meify(char *message, gssize len)
 {
 	char *c;
 	gboolean inside_html = FALSE;
--- a/libpurple/util.h	Sun Aug 26 19:07:22 2007 +0000
+++ b/libpurple/util.h	Sun Aug 26 19:08:29 2007 +0000
@@ -586,7 +586,7 @@
  * @return TRUE if the file was written successfully.  FALSE otherwise.
  */
 gboolean purple_util_write_data_to_file(const char *filename, const char *data,
-									  size_t size);
+									  gssize size);
 
 /**
  * Read the contents of a given file and parse the results into an
@@ -1113,7 +1113,7 @@
  * @return TRUE if it starts with "/me ", and it has been removed, otherwise
  *         FALSE
  */
-gboolean purple_message_meify(char *message, size_t len);
+gboolean purple_message_meify(char *message, gssize len);
 
 /**
  * Removes the underscore characters from a string used identify the mnemonic
--- a/libpurple/win32/global.mak	Sun Aug 26 19:07:22 2007 +0000
+++ b/libpurple/win32/global.mak	Sun Aug 26 19:08:29 2007 +0000
@@ -11,7 +11,7 @@
 # Locations of our various dependencies
 WIN32_DEV_TOP ?= $(PIDGIN_TREE_TOP)/../win32-dev
 ASPELL_TOP ?= $(WIN32_DEV_TOP)/aspell-dev-0-50-3-3
-GTKSPELL_TOP ?= $(WIN32_DEV_TOP)/gtkspell-2.0.6
+GTKSPELL_TOP ?= $(WIN32_DEV_TOP)/gtkspell-2.0.11
 GTK_TOP ?= $(WIN32_DEV_TOP)/gtk_2_0
 GTK_BIN ?= $(GTK_TOP)/bin
 BONJOUR_TOP ?= $(WIN32_DEV_TOP)/Bonjour_SDK
--- a/libpurple/xmlnode.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/libpurple/xmlnode.c	Sun Aug 26 19:08:29 2007 +0000
@@ -272,6 +272,8 @@
 	if(NULL != node->parent) {
 		if(node->parent->child == node) {
 			node->parent->child = node->next;
+			if (node->parent->lastchild == node)
+				node->parent->lastchild = node->next;
 		} else {
 			xmlnode *prev = node->parent->child;
 			while(prev && prev->next != node) {
@@ -279,6 +281,8 @@
 			}
 			if(prev) {
 				prev->next = node->next;
+				if (node->parent->lastchild == node)
+					node->parent->lastchild = prev;
 			}
 		}
 	}
--- a/pidgin/gtkaccount.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/pidgin/gtkaccount.c	Sun Aug 26 19:08:29 2007 +0000
@@ -272,7 +272,8 @@
 	add_user_options(dialog,     dialog->top_vbox);
 	add_protocol_options(dialog, dialog->bottom_vbox);
 
-	if (!dialog->prpl_info || !dialog->prpl_info->register_user) {
+	if (!dialog->prpl_info || !dialog->prpl_info->register_user || 
+	    g_object_get_data(G_OBJECT(item), "fake")) {
 		gtk_widget_hide(dialog->register_button);
 	} else {
 		if (dialog->prpl_info != NULL &&
@@ -1394,7 +1395,9 @@
 		purple_signal_emit(pidgin_account_get_handle(), "account-modified", account);
 
 	/* If this is a new account, then sign on! */
-	if (new && !dialog->registering) {
+	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dialog->register_button))) {
+		purple_account_register(account);
+	} else if (new) {
 		const PurpleSavedStatus *saved_status;
 
 		saved_status = purple_savedstatus_get_current();
@@ -1410,19 +1413,6 @@
 	return account;
 }
 
-static void
-register_account_prefs_cb(GtkWidget *w, AccountPrefsDialog *dialog)
-{
-	PurpleAccount *account;
-
-	dialog->registering = TRUE;
-
-	account = ok_account_prefs_cb(NULL, dialog);
-
-	purple_account_register(account);
-}
-
-
 static const GtkTargetEntry dnd_targets[] = {
 	{"text/plain", 0, 0},
 	{"text/uri-list", 0, 1},
@@ -1501,6 +1491,18 @@
 	add_login_options(dialog, vbox);
 	add_user_options(dialog, vbox);
 
+	button = gtk_check_button_new_with_label(_("Create this new account on the server"));
+	gtk_box_pack_start(GTK_BOX(main_vbox), button, FALSE, FALSE, 0);
+	gtk_widget_show(button);
+	dialog->register_button = button;
+	if (dialog->account == NULL)
+		gtk_widget_set_sensitive(button, FALSE);
+
+	if (!dialog->prpl_info || !dialog->prpl_info->register_user)
+		gtk_widget_hide(button);
+
+
+
 	/* Setup the page with 'Advanced'. */
 	dialog->bottom_vbox = dbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
 	gtk_container_set_border_width(GTK_CONTAINER(dbox), PIDGIN_HIG_BORDER);
@@ -1519,22 +1521,6 @@
 	gtk_box_pack_end(GTK_BOX(main_vbox), bbox, FALSE, TRUE, 0);
 	gtk_widget_show(bbox);
 
-	/* Register button */
-	button = gtk_button_new_with_label(_("Register"));
-	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
-
-	g_signal_connect(G_OBJECT(button), "clicked",
-			G_CALLBACK(register_account_prefs_cb), dialog);
-
-	dialog->register_button = button;
-
-	if (dialog->account == NULL)
-		gtk_widget_set_sensitive(button, FALSE);
-
-	if (!dialog->prpl_info || !dialog->prpl_info->register_user)
-		gtk_widget_hide(button);
-
 	/* Cancel button */
 	button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
 	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
--- a/pidgin/gtkrequest.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/pidgin/gtkrequest.c	Sun Aug 26 19:08:29 2007 +0000
@@ -442,7 +442,7 @@
 
 static void *
 pidgin_request_choice(const char *title, const char *primary,
-			const char *secondary, unsigned int default_value,
+			const char *secondary, int default_value,
 			const char *ok_text, GCallback ok_cb,
 			const char *cancel_text, GCallback cancel_cb,
 			PurpleAccount *account, const char *who, PurpleConversation *conv,
@@ -548,7 +548,7 @@
 
 static void *
 pidgin_request_action(const char *title, const char *primary,
-						const char *secondary, unsigned int default_action,
+						const char *secondary, int default_action,
 					    PurpleAccount *account, const char *who, PurpleConversation *conv,
 						void *user_data, size_t action_count, va_list actions)
 {
@@ -1083,7 +1083,7 @@
 	data->cbs[0] = ok_cb;
 	data->cbs[1] = cancel_cb;
 
-	
+
 #ifdef _WIN32
 	data->dialog = win = pidgin_create_window(PIDGIN_ALERT_TITLE, PIDGIN_HIG_BORDER, "multifield", TRUE) ;
 #else /* !_WIN32 */
--- a/pidgin/gtksavedstatuses.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/pidgin/gtksavedstatuses.c	Sun Aug 26 19:08:29 2007 +0000
@@ -1450,7 +1450,9 @@
 	GtkWidget *win;
 	GtkTreeIter iter;
 	GtkCellRenderer *rend;
-	const char *status_id = NULL;
+	char *status_id = NULL;
+	char *message = NULL;
+	gboolean parent_dialog_has_substatus = FALSE;
 	GList *list;
 	gboolean select = FALSE;
 
@@ -1553,25 +1555,29 @@
 					 G_CALLBACK(substatus_editor_ok_cb), dialog);
 
 	/* Seed the input widgets with the current values */
-	/* TODO: Get the current values from our parent's list store, not the saved_status! */
-	if (status_editor->original_title != NULL)
-	{
+
+	/* Only look at the saved status if we can't find it in the parent status dialog's substatuses model */
+	gtk_tree_model_get(GTK_TREE_MODEL(status_editor->model), &iter, 
+		STATUS_EDITOR_COLUMN_ENABLE_SUBSTATUS, &parent_dialog_has_substatus, -1);
+	if (parent_dialog_has_substatus) {
+		gtk_tree_model_get(GTK_TREE_MODEL(status_editor->model), &iter,
+			STATUS_EDITOR_COLUMN_STATUS_ID, &status_id,
+			STATUS_EDITOR_COLUMN_STATUS_MESSAGE, &message, -1);
+	} else if (status_editor->original_title != NULL) {
 		PurpleSavedStatus *saved_status = NULL;
 		PurpleSavedStatusSub *substatus = NULL;
 
-		saved_status = purple_savedstatus_find(status_editor->original_title);
-		if (saved_status != NULL)
-			substatus = purple_savedstatus_get_substatus(saved_status, account);
+		if ((saved_status = purple_savedstatus_find(status_editor->original_title)) != NULL) {
+			if ((substatus = purple_savedstatus_get_substatus(saved_status, account)) != NULL) {
+				message = (char *)purple_savedstatus_substatus_get_message(substatus);
+				status_id = (char *)purple_status_type_get_id(purple_savedstatus_substatus_get_type(substatus));
+			}
+		}
+	}
+	/* TODO: Else get the generic status type from our parent */
 
-		if (substatus != NULL)
-		{
-			gtk_imhtml_append_text(dialog->message,
-								   purple_savedstatus_substatus_get_message(substatus),
-								   0);
-			status_id = purple_status_type_get_id(purple_savedstatus_substatus_get_type(substatus));
-		}
-		/* TODO: Else get the generic status type from our parent */
-	}
+	if (message)
+		gtk_imhtml_append_text(dialog->message, message, 0);
 
 	for (list = purple_account_get_status_types(account); list; list = list->next)
 	{
@@ -1607,6 +1613,12 @@
 	if (!select)
 		gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
 
+	if (parent_dialog_has_substatus) {
+		/* These two were gotten from the parent tree model, so they need to be freed */
+		g_free(status_id);
+		g_free(message);
+	}
+
 	gtk_widget_show_all(win);
 }
 
--- a/pidgin/gtksound.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/pidgin/gtksound.c	Sun Aug 26 19:08:29 2007 +0000
@@ -364,14 +364,14 @@
 	GError *err = NULL;
 
 	switch (GST_MESSAGE_TYPE (msg)) {
-	case GST_MESSAGE_EOS:
-		gst_element_set_state(play, GST_STATE_NULL);
-		gst_object_unref(GST_OBJECT(play));
-		break;
 	case GST_MESSAGE_ERROR:
 		gst_message_parse_error(msg, &err, NULL);
 		purple_debug_error("gstreamer", "%s\n", err->message);
 		g_error_free(err);
+		/* fall-through and clean up */
+	case GST_MESSAGE_EOS:
+		gst_element_set_state(play, GST_STATE_NULL);
+		gst_object_unref(GST_OBJECT(play));
 		break;
 	case GST_MESSAGE_WARNING:
 		gst_message_parse_warning(msg, &err, NULL);
--- a/pidgin/gtkstatusbox.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/pidgin/gtkstatusbox.c	Sun Aug 26 19:08:29 2007 +0000
@@ -1415,15 +1415,15 @@
 }
 
 
-static void
-toggled_cb(GtkWidget *widget, PidginStatusBox *box)
+static 
+gboolean
+toggled_cb(GtkWidget *widget, GdkEventButton *event, PidginStatusBox *box)
 {
-	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))  {
 		if (!box->popup_in_progress)
 			pidgin_status_box_popup (box);
-	}  else {
-		pidgin_status_box_popdown(box);
-	}
+		else
+			pidgin_status_box_popdown(box);
+return TRUE;
 }
 
 static void
@@ -1773,7 +1773,7 @@
 	g_signal_connect(G_OBJECT(status_box->toggle_button), "button-release-event",
 			 G_CALLBACK(button_released_cb), status_box);
 #endif
-	g_signal_connect(G_OBJECT(status_box->toggle_button), "toggled",
+	g_signal_connect(G_OBJECT(status_box->toggle_button), "button-press-event",
 	                 G_CALLBACK(toggled_cb), status_box);
 	g_signal_connect(G_OBJECT(buffer), "changed", G_CALLBACK(imhtml_changed_cb), status_box);
 	g_signal_connect(G_OBJECT(status_box->imhtml), "format_function_toggle",
--- a/pidgin/plugins/musicmessaging/musicmessaging.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/pidgin/plugins/musicmessaging/musicmessaging.c	Sun Aug 26 19:08:29 2007 +0000
@@ -71,10 +71,10 @@
 
 /* Globals */
 /* List of sessions */
-GList *conversations;
+static GList *conversations;
 
 /* Pointer to this plugin */
-PurplePlugin *plugin_pointer;
+static PurplePlugin *plugin_pointer;
 
 /* Define types needed for DBus */
 DBusGConnection *connection;
@@ -350,7 +350,16 @@
 static gboolean
 intercept_received(PurpleAccount *account, char **sender, char **message, PurpleConversation *conv, int *flags)
 {
-	MMConversation *mmconv = mmconv_from_conv(conv);
+	MMConversation *mmconv;
+	
+	if (conv == NULL) {
+		/* XXX: This is just to avoid a crash (#2726).
+		 *      We may want to create the conversation instead of returning from here
+		 */
+		return FALSE;
+	}
+
+	mmconv = mmconv_from_conv(conv);
 	
 	purple_debug_misc("purple-musicmessaging", "Intercepted: %s\n", *message);
 	if (strstr(*message, MUSICMESSAGING_PREFIX))
--- a/pidgin/plugins/spellchk.c	Sun Aug 26 19:07:22 2007 +0000
+++ b/pidgin/plugins/spellchk.c	Sun Aug 26 19:08:29 2007 +0000
@@ -667,7 +667,7 @@
 	return;
 }
 
-static int buf_get_line(char *ibuf, char **buf, int *position, int len)
+static int buf_get_line(char *ibuf, char **buf, int *position, gsize len)
 {
 	int pos = *position;
 	int spos = pos;
--- a/pidgin/win32/nsis/pidgin-installer.nsi	Sun Aug 26 19:07:22 2007 +0000
+++ b/pidgin/win32/nsis/pidgin-installer.nsi	Sun Aug 26 19:08:29 2007 +0000
@@ -554,6 +554,10 @@
     Push "msnim"
     Call RegisterURIHandler
   SectionEnd
+  Section /o "myim:" SecURI_MYIM
+    Push "myim"
+    Call RegisterURIHandler
+  SectionEnd
   Section /o "ymsgr:" SecURI_YMSGR
     Push "ymsgr"
     Call RegisterURIHandler
@@ -706,6 +710,7 @@
     Delete "$INSTDIR\plugins\libicq.dll"
     Delete "$INSTDIR\plugins\libirc.dll"
     Delete "$INSTDIR\plugins\libmsn.dll"
+    Delete "$INSTDIR\plugins\libmyspace.dll"
     Delete "$INSTDIR\plugins\libnapster.dll"
     Delete "$INSTDIR\plugins\libnovell.dll"
     Delete "$INSTDIR\plugins\libqq.dll"