]> www.infradead.org Git - pidgin-chime.git/commitdiff
Start to make sense of conversations.c vs. chime-conversation.c
authorDavid Woodhouse <dwmw@amazon.co.uk>
Tue, 8 Aug 2017 01:26:17 +0000 (02:26 +0100)
committerDavid Woodhouse <dwmw@amazon.co.uk>
Tue, 8 Aug 2017 01:26:17 +0000 (02:26 +0100)
Use ChimeConversation objects, handle actual messages using a hacked
variant of the original code. Next up, a sane ChimeMessages object
for collecting messages for ChimeRooms and ChimeConversations, and
fixing up some of the remaining incest between Purple bits and Chime
bits. This is kind of ugly but it basically works and can be cleaned
up piecemeal from here... and it's been sitting around half-complete
for a week or so, so it's about time it got pushed.

chime-connection-private.h
chime-connection.c
chime-conversation.c
chime-conversation.h
chime.c
chime.h
conversations.c

index 9e551a8d6f0bd6e52889bbc282215f3261e39814..0c9cdc7be37f13e664c5aa7c98023d0afc304d54 100644 (file)
@@ -181,6 +181,12 @@ ChimeContact *chime_connection_parse_conversation_contact(ChimeConnection *cxn,
 /* chime-juggernaut.c */
 gboolean chime_connection_jugg_send(ChimeConnection *self, JsonNode *node);
 
+/* chime-conversation.c */
+void chime_init_conversations(ChimeConnection *cxn);
+void chime_destroy_conversations(ChimeConnection *cxn);
+/* TEMPORARILY exported while transitioning conversations.c over... */
+ChimeConversation *chime_connection_parse_conversation(ChimeConnection *cxn, JsonNode *node,
+                                                      GError **error);
 
 
 #endif /* __CHIME_CONNECTION_PRIVATE_H__ */
index 5defd13ab28e76a997432e4ebb4d0ec64f0440a3..f1cac79caa315e7662cb2b6ab75901488cd24077 100644 (file)
@@ -87,7 +87,8 @@ chime_connection_disconnect(ChimeConnection    *self)
        }
 
        chime_destroy_rooms(self);
-       chime_destroy_conversations_old(self);
+       purple_chime_destroy_conversations(self);
+       chime_destroy_conversations(self);
        chime_destroy_chats(self);
        chime_destroy_contacts(self);
        chime_destroy_juggernaut(self);
@@ -457,7 +458,8 @@ static void register_cb(ChimeConnection *self, SoupMessage *msg,
 
        chime_init_contacts(self);
        chime_init_rooms(self);
-       chime_init_conversations_old(self);
+       purple_chime_init_conversations(self);
+       chime_init_conversations(self);
        chime_init_chats(self);
 }
 
index 92790fdca8b156cb5d6167a2a830bba1bb4d90a8..321caffae25d21f4ea922b015852c770da5867b5 100644 (file)
@@ -42,6 +42,7 @@ static GParamSpec *props[LAST_PROP];
 
 enum {
        TYPING,
+       MESSAGE,
        LAST_SIGNAL
 };
 
@@ -52,7 +53,7 @@ struct _ChimeConversation {
 
        ChimeConnection *cxn; /* For unsubscribing from jugg channels */
 
-       GSList *members; /* Not including ourself */
+       GHashTable *members; /* Not including ourself */
 
        gchar *channel;
        gboolean favourite;
@@ -74,6 +75,10 @@ chime_conversation_dispose(GObject *object)
        ChimeConversation *self = CHIME_CONVERSATION(object);
 
        unsubscribe_conversation(NULL, self, NULL);
+       if (self->members) {
+               g_hash_table_destroy(self->members);
+               self->members = NULL;
+       }
        printf("Conversation disposed: %p\n", self);
 
        G_OBJECT_CLASS(chime_conversation_parent_class)->dispose(object);
@@ -257,10 +262,22 @@ static void chime_conversation_class_init(ChimeConversationClass *klass)
                g_signal_new ("typing",
                              G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST,
                              0, NULL, NULL, NULL, G_TYPE_NONE, 2, CHIME_TYPE_CONTACT, G_TYPE_BOOLEAN);
+
+       signals[MESSAGE] =
+               g_signal_new ("message",
+                             G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST,
+                             0, NULL, NULL, NULL, G_TYPE_NONE, 1, JSON_TYPE_NODE);
 }
 
+static void unref_member(gpointer obj)
+{
+       printf("Unref member %p\n", obj);
+       g_object_unref(obj);
+}
 static void chime_conversation_init(ChimeConversation *self)
 {
+       self->members = g_hash_table_new_full(g_str_hash, g_str_equal,
+                                             NULL, unref_member);
 }
 
 const gchar *chime_conversation_get_id(ChimeConversation *self)
@@ -298,6 +315,20 @@ gboolean chime_conversation_get_visibility(ChimeConversation *self)
        return self->visibility;
 }
 
+GList *chime_conversation_get_members(ChimeConversation *self)
+{
+       g_return_val_if_fail(CHIME_IS_CONVERSATION(self), FALSE);
+
+       return g_hash_table_get_values(self->members);
+}
+
+const gchar *chime_conversation_get_last_sent(ChimeConversation *self)
+{
+       g_return_val_if_fail(CHIME_IS_CONVERSATION(self), FALSE);
+
+       return self->last_sent;
+}
+
 static gboolean parse_boolean(JsonNode *node, const gchar *member, gboolean *val)
 {
        gint64 intval;
@@ -338,8 +369,26 @@ static gboolean conv_typing_jugg_cb(ChimeConnection *cxn, gpointer _conv, JsonNo
        return TRUE;
 }
 
-static gboolean conv_membership_jugg_cb(ChimeConnection *cxn, gpointer _conv, JsonNode *data_node)
+static gboolean conv_membership_jugg_cb(ChimeConnection *cxn, gpointer _conv, JsonNode *node)
 {
+       ChimeConversation *conv = CHIME_CONVERSATION(_conv);
+
+       JsonObject *obj = json_node_get_object(node);
+       JsonNode *record = json_object_get_member(obj, "record");
+       if (!record)
+               return FALSE;
+
+       obj = json_node_get_object(record);
+       JsonNode *member_node = json_object_get_member(obj, "Member");
+       if (!member_node)
+               return FALSE;
+
+       ChimeContact *member = chime_connection_parse_conversation_contact(cxn, member_node, NULL);
+       if (member_node) {
+               const gchar *id = chime_contact_get_profile_id(member);
+               g_hash_table_insert(conv->members, (gpointer)id, member);
+               return TRUE;
+               }
        return FALSE;
 }
 
@@ -354,8 +403,24 @@ subscribe_conversation(ChimeConnection *cxn, ChimeConversation *conv)
                             conv_typing_jugg_cb, conv);
 }
 
-static ChimeConversation *chime_connection_parse_conversation(ChimeConnection *cxn, JsonNode *node,
-                                                             GError **error)
+static void parse_members(ChimeConnection *cxn, ChimeConversation *conv, JsonNode *node)
+{
+       JsonArray *arr = json_node_get_array(node);
+       int i, len = json_array_get_length(arr);
+
+       for (i = 0; i < len; i++) {
+               ChimeContact *member = chime_connection_parse_conversation_contact(cxn,
+                                                                                  json_array_get_element(arr, i), NULL);
+               if (member) {
+                       const gchar *id = chime_contact_get_profile_id(member);
+                       g_hash_table_insert(conv->members, (gpointer)id, member);
+                       printf("Added %p %s\n", member, id);
+               }
+       }
+}
+
+ChimeConversation *chime_connection_parse_conversation(ChimeConnection *cxn, JsonNode *node,
+                                                      GError **error)
 {
        ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE(cxn);
        const gchar *id, *name, *channel, *created_on, *updated_on,
@@ -409,9 +474,9 @@ static ChimeConversation *chime_connection_parse_conversation(ChimeConnection *c
                subscribe_conversation(cxn, conversation);
 
                chime_object_collection_hash_object(&priv->conversations, CHIME_OBJECT(conversation), TRUE);
+               parse_members(cxn, conversation, members_node);
 
                /* Emit signal on ChimeConnection to admit existence of new conversation */
-               printf("new conv %s %p\n", id, conversation);
                chime_connection_new_conversation(cxn, conversation);
 
                return conversation;
@@ -459,6 +524,7 @@ static ChimeConversation *chime_connection_parse_conversation(ChimeConnection *c
        }
 
        chime_object_collection_hash_object(&priv->conversations, CHIME_OBJECT(conversation), TRUE);
+       parse_members(cxn, conversation, members_node);
 
        return conversation;
 }
@@ -545,6 +611,87 @@ static void fetch_conversations(ChimeConnection *cxn, const gchar *next_token)
        chime_connection_queue_http_request(cxn, NULL, uri, "GET", conversations_cb,
                                            NULL);
 }
+
+
+struct deferred_conv_jugg {
+       JuggernautCallback cb;
+       JsonNode *node;
+};
+static void fetch_new_conv_cb(ChimeConnection *cxn, SoupMessage *msg, JsonNode *node,
+                             gpointer _defer)
+{
+       ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
+       struct deferred_conv_jugg *defer = _defer;
+
+       if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) {
+               JsonObject *obj = json_node_get_object(node);
+               node = json_object_get_member(obj, "Conversation");
+               if (!node)
+                       goto bad;
+
+               ChimeConversation *conv = chime_connection_parse_conversation(cxn, node, NULL);
+               if (!conv)
+                       goto bad;
+
+               /* Sanity check; we don't want to just keep looping for ever if it goes wrong */
+               const gchar *conv_id;
+               if (!parse_string(node, "ConversationId", &conv_id))
+                       goto bad;
+
+               conv = g_hash_table_lookup(priv->conversations.by_id, conv_id);
+               if (!conv)
+                       goto bad;
+
+               /* OK, now we know about the new conversation we can play the msg node */
+               defer->cb(cxn, NULL, defer->node);
+               goto out;
+       }
+ bad:
+       ;
+ out:
+       json_node_unref(defer->node);
+       g_free(defer);
+}
+
+static gboolean conv_msg_jugg_cb(ChimeConnection *cxn, gpointer _unused, JsonNode *data_node)
+{
+       ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
+       JsonObject *obj = json_node_get_object(data_node);
+       JsonNode *record = json_object_get_member(obj, "record");
+       if (!record)
+               return FALSE;
+
+       const gchar *conv_id;
+       if (!parse_string(record, "ConversationId", &conv_id))
+               return FALSE;
+
+       ChimeConversation *conv = g_hash_table_lookup(priv->conversations.by_id,
+                                                     conv_id);
+       if (!conv) {
+               /* It seems they don't do the helpful thing and send the notification
+                * of a new conversation before they send the first message. So let's
+                * go looking for it... */
+               struct deferred_conv_jugg *defer = g_new0(struct deferred_conv_jugg, 1);
+               defer->node = json_node_ref(data_node);
+               defer->cb = conv_msg_jugg_cb;
+
+               SoupURI *uri = soup_uri_new_printf(priv->messaging_url, "/conversations/%s", conv_id);
+               if (chime_connection_queue_http_request(cxn, NULL, uri, "GET", fetch_new_conv_cb, defer))
+                       return TRUE;
+
+               json_node_unref(defer->node);
+               g_free(defer);
+               return FALSE;
+       }
+
+       const gchar *id;
+       if (!parse_string(record, "MessageId", &id))
+               return FALSE;
+
+       g_signal_emit(conv, signals[MESSAGE], 0, record);
+       return TRUE;
+}
+
 static gboolean conv_jugg_cb(ChimeConnection *cxn, gpointer _unused, JsonNode *data_node)
 {
        JsonObject *obj = json_node_get_object(data_node);
@@ -563,6 +710,9 @@ void chime_init_conversations(ChimeConnection *cxn)
 
        chime_jugg_subscribe(cxn, priv->device_channel, "Conversation",
                             conv_jugg_cb, NULL);
+       chime_jugg_subscribe(cxn, priv->device_channel, "ConversationMessage",
+                            conv_msg_jugg_cb, NULL);
+
        fetch_conversations(cxn, NULL);
 }
 
@@ -585,6 +735,8 @@ void chime_destroy_conversations(ChimeConnection *cxn)
 
        chime_jugg_unsubscribe(cxn, priv->device_channel, "Conversation",
                               conv_jugg_cb, NULL);
+       chime_jugg_unsubscribe(cxn, priv->device_channel, "ConversationMessage",
+                            conv_msg_jugg_cb, NULL);
 
        if (priv->conversations.by_id)
                g_hash_table_foreach(priv->conversations.by_id, unsubscribe_conversation, NULL);
index 807e0f1b33707899f024e554ae3a806b4ea9acd1..2638f6ddbd08bf81b1ac11a516fd23ece5304e97 100644 (file)
@@ -40,6 +40,10 @@ gboolean chime_conversation_get_favourite(ChimeConversation *self);
 
 gboolean chime_conversation_get_visibility(ChimeConversation *self);
 
+GList *chime_conversation_get_members(ChimeConversation *self);
+
+const gchar *chime_conversation_get_last_sent(ChimeConversation *self);
+
 ChimeConversation *chime_connection_conversation_by_name(ChimeConnection *cxn,
                                         const gchar *name);
 ChimeConversation *chime_connection_conversation_by_id(ChimeConnection *cxn,
diff --git a/chime.c b/chime.c
index 013a92a82544182f5da03740bad219b6dfd02fd3..fadaac6e6f0a70b838f621cd482a31cc8463ce30 100644 (file)
--- a/chime.c
+++ b/chime.c
@@ -211,6 +211,8 @@ static void chime_purple_login(PurpleAccount *account)
                         G_CALLBACK(on_chime_disconnected), conn);
        g_signal_connect(cxn, "progress",
                         G_CALLBACK(on_chime_progress), conn);
+       g_signal_connect(cxn, "new-conversation",
+                        G_CALLBACK(on_chime_new_conversation), conn);
        /* We don't use 'conn' for this one as we don't want it disconnected
           on close, and it doesn't use it anyway. */
        g_signal_connect(cxn, "log-message",
diff --git a/chime.h b/chime.h
index d96da664ba8478774278180673466126cc0a46e5..5b9ed31dc71c443336a3d474ace44f1c367df38b 100644 (file)
--- a/chime.h
+++ b/chime.h
@@ -23,6 +23,7 @@
 
 #include "chime-connection.h"
 #include "chime-contact.h"
+#include "chime-conversation.h"
 #include "chime-room.h"
 
 #define CHIME_DEVICE_CAP_PUSH_DELIVERY_RECEIPTS                (1<<1)
@@ -104,12 +105,12 @@ void chime_purple_join_chat(PurpleConnection *conn, GHashTable *data);
 void chime_purple_chat_leave(PurpleConnection *conn, int id);
 int chime_purple_chat_send(PurpleConnection *conn, int id, const char *message, PurpleMessageFlags flags);
 void on_chime_new_room(ChimeConnection *cxn, ChimeRoom *room, PurpleConnection *conn);
+char *chime_purple_cb_real_name(PurpleConnection *conn, int id, const char *who);
 
 /* conversations.c */
-void chime_init_conversations_old(ChimeConnection *cxn);
-void chime_destroy_conversations_old(ChimeConnection *cxn);
-void chime_init_conversations(ChimeConnection *cxn);
-void chime_destroy_conversations(ChimeConnection *cxn);
+void on_chime_new_conversation(ChimeConnection *cxn, ChimeConversation *conv, PurpleConnection *conn);
+void purple_chime_init_conversations(ChimeConnection *cxn);
+void purple_chime_destroy_conversations(ChimeConnection *cxn);
 int chime_purple_send_im(PurpleConnection *gc, const char *who, const char *message, PurpleMessageFlags flags);
 unsigned int chime_send_typing(PurpleConnection *conn, const char *name, PurpleTypingState state);
 
index 516f682c5373c76d50e5d81c9adb17705b969b8a..cea4331772f387f95d1dc8d03cdf3a9cb0f25c8a 100644 (file)
 
 #include <prpl.h>
 #include <blist.h>
+#include <debug.h>
 
 #include "chime.h"
 #include "chime-connection-private.h"
 
 #include <libsoup/soup.h>
 
-struct chime_conversation {
-       struct chime_msgs *msgs;
+struct chime_im {
+       struct chime_msgs msgs;
 
-       ChimeConnection *cxn;
+       PurpleConnection *conn;
+       ChimeConversation *conv;
+       ChimeContact *peer;
 
-       gchar *channel;
-       gchar *id;
-       gchar *name;
-       gchar *visibility;
-       gboolean favourite;
-
-       GHashTable *members;
        GHashTable *sent_msgs;
 };
-static gboolean conv_membership_jugg_cb(ChimeConnection *cxn, gpointer _unused, JsonNode *record);
-static gboolean conv_typing_jugg_cb(ChimeConnection *cxn, gpointer _unused, JsonNode *record);
 
 /* Called for all deliveries of incoming conversation messages, at startup and later */
-static gboolean do_conv_deliver_msg(ChimeConnection *cxn, struct chime_conversation *conv,
+static gboolean do_conv_deliver_msg(ChimeConnection *cxn, struct chime_im *im,
                                    JsonNode *record, time_t msg_time)
 {
        ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
@@ -62,11 +56,6 @@ static gboolean do_conv_deliver_msg(ChimeConnection *cxn, struct chime_conversat
        if (sys)
                flags |= PURPLE_MESSAGE_SYSTEM;
 
-       if (g_hash_table_size(conv->members) != 2) {
-               /* Only 1:1 IM so far; representing multi-party conversations as chats comes later */
-               return FALSE;
-       }
-
        if (strcmp(sender, priv->profile_id)) {
                ChimeContact *contact = g_hash_table_lookup(priv->contacts.by_id,
                                                            sender);
@@ -80,26 +69,11 @@ static gboolean do_conv_deliver_msg(ChimeConnection *cxn, struct chime_conversat
        } else {
                const gchar *msg_id;
                if (parse_string(record, "MessageId", &msg_id) &&
-                   g_hash_table_remove(conv->sent_msgs, msg_id)) {
+                   g_hash_table_remove(im->sent_msgs, msg_id)) {
                        /* This was a message sent from this client. No need to display it. */
                        return TRUE;
                }
-               const gchar **member_ids;
-               int peer;
-
-               member_ids = (const gchar **)g_hash_table_get_keys_as_array(conv->members, NULL);
-               if (!strcmp(member_ids[0], priv->profile_id))
-                       peer = 1;
-               else
-                       peer = 0;
-
-               ChimeContact *contact = g_hash_table_lookup(priv->contacts.by_id,
-                                                           member_ids[peer]);
-               g_free(member_ids);
-               if (!contact)
-                       return FALSE;
-
-               const gchar *email = chime_contact_get_email(contact);
+               const gchar *email = chime_contact_get_email(im->peer);
 
                /* Ick, how do we inject a message from ourselves? */
                PurpleAccount *account = cxn->prpl_conn->account;
@@ -125,398 +99,119 @@ static gboolean do_conv_deliver_msg(ChimeConnection *cxn, struct chime_conversat
 static void conv_deliver_msg(ChimeConnection *cxn, struct chime_msgs *msgs,
                             JsonNode *node, time_t msg_time)
 {
-       ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
-       struct chime_conversation *conv = g_hash_table_lookup(priv->conversations_by_id, msgs->id);
+       struct chime_im *im = (struct chime_im *)msgs;
 
-       do_conv_deliver_msg(cxn, conv, node, msg_time);
+       do_conv_deliver_msg(cxn, im, node, msg_time);
 }
 
-static void fetch_conversation_messages(struct chime_conversation *conv)
-{
-       if (conv->msgs) {
-               if (conv->msgs->msgs_done)
-                       conv->msgs->msgs_done = FALSE;
-               else
-                       return; /* Already in progress */
-       } else {
-               conv->msgs = g_new0(struct chime_msgs, 1);
-               conv->msgs->id = conv->id;
-               conv->msgs->members_done = TRUE;
-               conv->msgs->cb = conv_deliver_msg;
-       }
-       printf("Fetch conv messages for %s\n", conv->id);
-       fetch_messages(conv->cxn, conv->msgs, NULL);
-}
 
-static struct chime_conversation *one_conversation_cb(ChimeConnection *cxn, JsonNode *node)
+static void on_conv_msg(ChimeConversation *conv, JsonNode *record, struct chime_im *im)
 {
-       ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
-       struct chime_conversation *conv;
-       const gchar *channel, *id, *name, *visibility;
-       gint64 favourite;
-       JsonNode *members_node;
-
-       if (!parse_string(node, "Channel", &channel) ||
-           !parse_string(node, "ConversationId", &id) ||
-           !parse_string(node, "Name", &name) ||
-           !parse_string(node, "Visibility", &visibility) ||
-           !parse_int(node, "Favorite", &favourite) ||
-           !(members_node = json_object_get_member(json_node_get_object(node), "Members")))
-               return NULL;
-
-       conv = g_hash_table_lookup(priv->conversations_by_id, id);
-       if (conv) {
-               g_hash_table_remove(priv->conversations_by_name, conv->name);
-               g_free(conv->channel);
-               g_free(conv->name);
-               g_free(conv->visibility);
-       } else {
-               conv = g_new0(struct chime_conversation, 1);
-               conv->cxn = cxn;
-               conv->id = g_strdup(id);
-               g_hash_table_insert(priv->conversations_by_id, conv->id, conv);
-               chime_jugg_subscribe(cxn, channel, NULL, NULL, NULL);
-               chime_jugg_subscribe(cxn, channel, "ConversationMembership",
-                                    conv_membership_jugg_cb, conv);
-               chime_jugg_subscribe(cxn, channel, "TypingIndicator",
-                                    conv_typing_jugg_cb, conv);
-
-               /* Do we need to fetch new messages? */
-               const gchar *last_seen, *last_sent;
-
-               if (!chime_read_last_msg(cxn, FALSE, conv->id, &last_seen, NULL))
-                       last_seen = "1970-01-01T00:00:00.000Z";
-
-               if (!parse_string(node, "LastSent", &last_sent) ||
-                   strcmp(last_seen, last_sent))
-                       fetch_conversation_messages(conv);
-       }
-
-       conv->channel = g_strdup(channel);
-       conv->name = g_strdup(name);
-       conv->visibility = g_strdup(visibility);
-       conv->favourite = favourite;
-       if (!conv->members)
-               conv->members = g_hash_table_new(g_str_hash, g_str_equal);
-       if (!conv->sent_msgs)
-               conv->sent_msgs = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
-
-       JsonArray *arr = json_node_get_array(members_node);
-       int i, len = json_array_get_length(arr);
-       for (i = 0; i < len; i++) {
-               ChimeContact *member = chime_connection_parse_conversation_contact(cxn,
-                                                                                  json_array_get_element(arr, i), NULL);
-               if (member) {
-                       const gchar *id = chime_contact_get_profile_id(member);
-                       g_hash_table_insert(conv->members, (gpointer)id, member);
-                       if (len == 2 && strcmp(id, priv->profile_id)) {
-                               /* A two-party conversation contains only us (presumably!)
-                                * and one other. Index 1:1 conversations on that "other" */
-                               g_hash_table_insert(priv->im_conversations_by_peer_id,
-                                                   (void *)id, conv);
-
-                               printf("im_member for %s (%d) %s\n", conv->id, len, id);
-                       }
-               }
-       }
-
-       g_hash_table_insert(priv->conversations_by_name, conv->name, conv);
-       return conv;
-}
-
-static void fetch_conversations(ChimeConnection *cxn, const gchar *next_token);
-static void conversationlist_cb(ChimeConnection *cxn, SoupMessage *msg,
-                       JsonNode *node, gpointer convlist)
-{
-       const gchar *next_token;
-
-       if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code) && node) {
-               JsonNode *convs_node;
-               JsonObject *obj;
-               JsonArray *convs_arr;
-
-               obj = json_node_get_object(node);
-               convs_node = json_object_get_member(obj, "Conversations");
-               if (convs_node) {
-                       convs_arr = json_node_get_array(convs_node);
-                       guint i, len = json_array_get_length(convs_arr);
-                       for (i = 0; i < len; i++)
-                               one_conversation_cb(cxn, json_array_get_element(convs_arr, i));
-               }
-               if (parse_string(node, "NextToken", &next_token))
-                       fetch_conversations(cxn, next_token);
-               else {
-                       ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
-                       if (!priv->convs_online) {
-                               priv->convs_online = TRUE;
-                               chime_connection_calculate_online(cxn);
-                       }
-               }
-       }
-}
-
-static void fetch_conversations(ChimeConnection *cxn, const gchar *next_token)
-{
-       ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
-       SoupURI *uri = soup_uri_new_printf(priv->messaging_url, "/conversations");
-
-       soup_uri_set_query_from_fields(uri, "max-results", "50",
-                                      next_token ? "next-token" : NULL, next_token,
-                                      NULL);
-       chime_connection_queue_http_request(cxn, NULL, uri, "GET", conversationlist_cb, NULL);
-}
-
-static void destroy_conversation(gpointer _conv)
-{
-       struct chime_conversation *conv = _conv;
-
-       chime_jugg_unsubscribe(conv->cxn, conv->channel, NULL, NULL, NULL);
-       chime_jugg_unsubscribe(conv->cxn, conv->channel, "ConversationMembership",
-                              conv_membership_jugg_cb, conv);
-       chime_jugg_unsubscribe(conv->cxn, conv->channel, "TypingIndicator",
-                              conv_typing_jugg_cb, conv);
-       g_free(conv->id);
-       g_free(conv->channel);
-       g_free(conv->name);
-       g_free(conv->visibility);
-       g_hash_table_destroy(conv->members);
-       g_hash_table_destroy(conv->sent_msgs);
-       conv->members = NULL;
-       g_free(conv);
-}
-
-static gboolean conv_jugg_cb(ChimeConnection *cxn, gpointer _unused, JsonNode *data_node)
-{
-       JsonObject *obj = json_node_get_object(data_node);
-       JsonNode *record = json_object_get_member(obj, "record");
-       if (!record)
-               return FALSE;
-
-       return !!one_conversation_cb(cxn, record);
-}
-
-struct deferred_conv_jugg {
-       JuggernautCallback cb;
-       JsonNode *node;
-};
-static void fetch_new_conv_cb(ChimeConnection *cxn, SoupMessage *msg, JsonNode *node,
-                             gpointer _defer)
-{
-       ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
-       struct deferred_conv_jugg *defer = _defer;
-
-       if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) {
-               JsonObject *obj = json_node_get_object(node);
-               node = json_object_get_member(obj, "Conversation");
-               if (!node)
-                       goto bad;
-
-               if (!one_conversation_cb(cxn, node))
-                       goto bad;
-
-               /* Sanity check; we don't want to just keep looping for ever if it goes wrong */
-               const gchar *conv_id;
-               if (!parse_string(node, "ConversationId", &conv_id))
-                       goto bad;
-
-               struct chime_conversation *conv = g_hash_table_lookup(priv->conversations_by_id,
-                                                                     conv_id);
-               if (!conv)
-                       goto bad;
-
-               /* OK, now we know about the new conversation we can play the msg node */
-               defer->cb(cxn, NULL, defer->node);
-               goto out;
-       }
- bad:
-       ;
- out:
-       json_node_unref(defer->node);
-       g_free(defer);
-}
-
-static gboolean conv_msg_jugg_cb(ChimeConnection *cxn, gpointer _unused, JsonNode *data_node)
-{
-       ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
-       JsonObject *obj = json_node_get_object(data_node);
-       JsonNode *record = json_object_get_member(obj, "record");
-       if (!record)
-               return FALSE;
-
-       const gchar *conv_id;
-       if (!parse_string(record, "ConversationId", &conv_id))
-               return FALSE;
-
-       struct chime_conversation *conv = g_hash_table_lookup(priv->conversations_by_id,
-                                                             conv_id);
-       if (!conv) {
-               /* It seems they don't do the helpful thing and send the notification
-                * of a new conversation before they send the first message. So let's
-                * go looking for it... */
-               struct deferred_conv_jugg *defer = g_new0(struct deferred_conv_jugg, 1);
-               defer->node = json_node_ref(data_node);
-               defer->cb = conv_msg_jugg_cb;
-
-               SoupURI *uri = soup_uri_new_printf(priv->messaging_url, "/conversations/%s", conv_id);
-               if (chime_connection_queue_http_request(cxn, NULL, uri, "GET", fetch_new_conv_cb, defer))
-                       return TRUE;
-
-               json_node_unref(defer->node);
-               g_free(defer);
-               return FALSE;
-       }
-
+       ChimeConnection *cxn = CHIME_CONNECTION(purple_connection_get_protocol_data(im->conn));
        const gchar *id;
        if (!parse_string(record, "MessageId", &id))
-               return FALSE;
-       if (conv->msgs && conv->msgs->messages) {
+               return;
+       if (im->msgs.messages) {
                /* Still gathering messages. Add to the table, to avoid dupes */
-               g_hash_table_insert(conv->msgs->messages, (gchar *)id, json_node_ref(record));
-               return TRUE;
+               g_hash_table_insert(im->msgs.messages, (gchar *)id, json_node_ref(record));
+               return;
        }
        GTimeVal tv;
        const gchar *created;
        if (!parse_time(record, "CreatedOn", &created, &tv))
-               return FALSE;
+               return;
 
-       chime_update_last_msg(cxn, FALSE, conv->id, created, id);
+       chime_update_last_msg(cxn, FALSE, chime_object_get_id(CHIME_OBJECT(im->conv)), created, id);
 
-       return do_conv_deliver_msg(cxn, conv, record, tv.tv_sec);
+       do_conv_deliver_msg(cxn, im, record, tv.tv_sec);
 }
 
-static gboolean conv_membership_jugg_cb(ChimeConnection *cxn, gpointer _unused, JsonNode *data_node)
+static void on_conv_typing(ChimeConversation *conv, ChimeContact *contact, gboolean state, struct chime_im *im)
 {
-       ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
-       JsonObject *obj = json_node_get_object(data_node);
-       JsonNode *record = json_object_get_member(obj, "record");
-       if (!record)
-               return FALSE;
-
-       const gchar *conv_id;
-       if (!parse_string(record, "ConversationId", &conv_id))
-               return FALSE;
-
-       struct chime_conversation *conv = g_hash_table_lookup(priv->conversations_by_id,
-                                                             conv_id);
-       if (!conv) {
-               /* It seems they don't do the helpful thing and send the notification
-                * of a new conversation before they send the first message. So let's
-                * go looking for it... */
-               struct deferred_conv_jugg *defer = g_new0(struct deferred_conv_jugg, 1);
-               defer->node = json_node_ref(data_node);
-               defer->cb = conv_membership_jugg_cb;
-
-               SoupURI *uri = soup_uri_new_printf(priv->messaging_url, "/conversations/%s", conv_id);
-               if (chime_connection_queue_http_request(cxn, NULL, uri, "GET", fetch_new_conv_cb, defer))
-                       return TRUE;
-
-               json_node_unref(defer->node);
-               g_free(defer);
-               return FALSE;
-       }
-
-#if 0 /* We think we fixed this by reregistering the device as OSX not Android */
-       /* WTF? Some users get only ConversationMembership updates when a new message
-          comes in? */
-       obj = json_node_get_object(record);
-       JsonNode *member = json_object_get_member(obj, "Member");
-       if (!member)
-               return FALSE;
-
-       /* Has this member seen a message later than the last one we have? */
-       const gchar *last_seen, *last_delivered;
-
-       if (!chime_read_last_msg(cxn, FALSE, conv->id, &last_seen, NULL))
-               last_seen = "1970-01-01T00:00:00.000Z";
+       ChimeConnection *cxn = CHIME_CONNECTION(purple_connection_get_protocol_data(im->conn));
+       const gchar *email = chime_contact_get_email(contact);
 
-       if (!parse_string(member, "LastDelivered", &last_delivered) ||
-           strcmp(last_seen, last_delivered)) {
-               printf("WTF refetching messages for ConversationMembership update\n");
-               fetch_conversation_messages(conv);
-       } else printf("no fetch last %s\n", last_seen);
-#endif
-       return TRUE;
+       if (state)
+               serv_got_typing(cxn->prpl_conn, email, 0, PURPLE_TYPING);
+       else
+               serv_got_typing_stopped(cxn->prpl_conn, email);
 }
 
-static gboolean conv_typing_jugg_cb(ChimeConnection *cxn, gpointer _conv, JsonNode *data_node)
+void on_chime_new_conversation(ChimeConnection *cxn, ChimeConversation *conv, PurpleConnection *conn)
 {
        ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
-       struct chime_conversation *conv = _conv;
-
-       gint64 state;
-       if (!parse_int(data_node, "state", &state))
-               return FALSE;
-
-       JsonNode *node = json_node_get_parent(data_node);
-       if (!node)
-               return FALSE;
-
-       JsonObject *obj = json_node_get_object(node);
-       node = json_object_get_member(obj, "from");
-
-       const gchar *from;
-       if (!node || !parse_string(node, "id", &from))
-               return FALSE;
 
-       ChimeContact *contact = g_hash_table_lookup(priv->contacts.by_id, from);
-       if (!contact)
-               return FALSE;
-
-       if (g_hash_table_size(conv->members) != 2) {
-               /* Only 1:1 so far */
-               return FALSE;
+       GList *members = chime_conversation_get_members(conv);
+       if (g_list_length(members) != 2) {
+               /* We don't support non-1:1 conversations yet. We need to handle them as 'chats'. */
+               return;
        }
+       struct chime_im *im = g_new0(struct chime_im, 1);
+       im->msgs.id = chime_object_get_id(CHIME_OBJECT(conv));
+       im->msgs.members_done = TRUE;
+       im->msgs.cb = conv_deliver_msg;
+       im->conn = conn;
+       im->sent_msgs = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+       im->conv = g_object_ref(conv);
+       im->peer = members->data;
+       if (!strcmp(priv->profile_id, chime_object_get_id(CHIME_OBJECT(im->peer))))
+               im->peer = members->next->data;
+       g_list_free(members);
+       g_object_ref(im->peer);
+       purple_debug(PURPLE_DEBUG_INFO, "chime", "New conversation %s with %s\n", chime_object_get_id(CHIME_OBJECT(im->peer)),
+                    chime_contact_get_email(im->peer));
+
+       g_hash_table_insert(priv->im_conversations_by_peer_id,
+                           (void *)chime_object_get_id(CHIME_OBJECT(im->peer)), im);
+
+       g_signal_connect(conv, "typing", G_CALLBACK(on_conv_typing), im);
+       g_signal_connect(conv, "message", G_CALLBACK(on_conv_msg), im);
+
+       /* Do we need to fetch new messages? */
+       const gchar *last_seen, *last_sent;
+
+       last_sent = chime_conversation_get_last_sent(conv);
+
+       if (!chime_read_last_msg(cxn, FALSE, chime_object_get_id(CHIME_OBJECT(conv)), &last_seen, NULL))
+               last_seen = "1970-01-01T00:00:00.000Z";
 
-       const gchar *email = chime_contact_get_email(contact);
+       if (last_sent && strcmp(last_seen, last_sent)) {
+               purple_debug(PURPLE_DEBUG_INFO, "chime", "Fetch conv messages for %s\n", im->msgs.id);
+               fetch_messages(purple_connection_get_protocol_data(im->conn), &im->msgs, NULL);
+       }
+}
 
-       if (state)
-               serv_got_typing(cxn->prpl_conn, email, 0, PURPLE_TYPING);
-       else
-               serv_got_typing_stopped(cxn->prpl_conn, email);
+static void im_destroy(gpointer _im)
+{
+       struct chime_im *im = _im;
 
-       return TRUE;
+       g_hash_table_destroy(im->sent_msgs);
+       g_object_unref(im->conv);
+       g_object_unref(im->peer);
+       g_free(im);
 }
 
-void chime_init_conversations_old(ChimeConnection *cxn)
+void purple_chime_init_conversations(ChimeConnection *cxn)
 {
        ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
-
-       priv->im_conversations_by_peer_id = g_hash_table_new(g_str_hash, g_str_equal);
-       priv->conversations_by_name = g_hash_table_new(g_str_hash, g_str_equal);
-       priv->conversations_by_id = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, destroy_conversation);
-       chime_jugg_subscribe(cxn, priv->device_channel, "ConversationMembership",
-                            conv_membership_jugg_cb, NULL);
-       chime_jugg_subscribe(cxn, priv->device_channel, "ConversationMessage",
-                            conv_msg_jugg_cb, NULL);
-       chime_jugg_subscribe(cxn, priv->device_channel, "Conversation",
-                            conv_jugg_cb, NULL);
-       fetch_conversations(cxn, NULL);
+       priv->im_conversations_by_peer_id = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, im_destroy);
 }
 
-void chime_destroy_conversations_old(ChimeConnection *cxn)
+void purple_chime_destroy_conversations(ChimeConnection *cxn)
 {
        ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
 
-       chime_jugg_unsubscribe(cxn, priv->device_channel, "ConversationMembership",
-                              conv_membership_jugg_cb, NULL);
-       chime_jugg_unsubscribe(cxn, priv->device_channel, "ConversationMessage",
-                              conv_msg_jugg_cb, NULL);
-       chime_jugg_unsubscribe(cxn, priv->device_channel, "Conversation",
-                              conv_jugg_cb, NULL);
-       g_hash_table_destroy(priv->im_conversations_by_peer_id);
-       g_hash_table_destroy(priv->conversations_by_name);
-       g_hash_table_destroy(priv->conversations_by_id);
-       priv->conversations_by_name = priv->conversations_by_id = NULL;
+       g_clear_pointer(&priv->im_conversations_by_peer_id, g_hash_table_destroy);
 }
 
 struct im_send_data {
-       struct chime_conversation *conv;
+       struct chime_im *im;
        gchar *who;
        gchar *message;
        PurpleMessageFlags flags;
 };
 
-static void im_error(ChimeConnection *cxn, struct im_send_data *im,
+static void im_error(ChimeConnection *cxn, struct im_send_data *imd,
                     const gchar *format, ...)
 {
        va_list args;
@@ -526,7 +221,7 @@ static void im_error(ChimeConnection *cxn, struct im_send_data *im,
        va_end(args);
 
        PurpleConversation *pconv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY,
-                                                                         im->who,
+                                                                         imd->who,
                                                                          cxn->prpl_conn->account);
        if (pconv)
                purple_conversation_write(pconv, NULL, msg, PURPLE_MESSAGE_ERROR, time(NULL));
@@ -534,24 +229,24 @@ static void im_error(ChimeConnection *cxn, struct im_send_data *im,
        g_free(msg);
 }
 
-static void send_im_cb(ChimeConnection *cxn, SoupMessage *msg, JsonNode *node, gpointer _im)
+static void send_im_cb(ChimeConnection *cxn, SoupMessage *msg, JsonNode *node, gpointer _imd)
 {
-       struct im_send_data *im = _im;
+       struct im_send_data *imd = _imd;
 
        /* Nothing to do o nsuccess */
        if (!SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) {
-               im_error(cxn, im, _("Failed to send message(%d): %s\n"),
+               im_error(cxn, imd, _("Failed to send message(%d): %s\n"),
                         msg->status_code, msg->reason_phrase);
        } else {
                JsonObject *obj = json_node_get_object(node);
                JsonNode *node = json_object_get_member(obj, "Message");
                const gchar *msg_id;
                if (node && parse_string(node, "MessageId", &msg_id)) {
-                       g_hash_table_add(im->conv->sent_msgs, g_strdup(msg_id));
+                       g_hash_table_add(imd->im->sent_msgs, g_strdup(msg_id));
                }
        }
-       g_free(im->message);
-       g_free(im);
+       g_free(imd->message);
+       g_free(imd);
 }
 
 unsigned int chime_send_typing(PurpleConnection *conn, const char *name, PurpleTypingState state)
@@ -568,15 +263,14 @@ unsigned int chime_send_typing(PurpleConnection *conn, const char *name, PurpleT
                return 0;
 
        const gchar *id = chime_contact_get_profile_id(contact);
-       struct chime_conversation *conv = g_hash_table_lookup(priv->im_conversations_by_peer_id,
-                                                             id);
-       if (!conv)
+       struct chime_im *imd = g_hash_table_lookup(priv->im_conversations_by_peer_id, id);
+       if (!imd)
                return 0;
 
        JsonBuilder *jb = json_builder_new();
        jb = json_builder_begin_object(jb);
        jb = json_builder_set_member_name(jb, "channel");
-       jb = json_builder_add_string_value(jb, conv->channel);
+       jb = json_builder_add_string_value(jb, chime_conversation_get_channel(imd->conv));
        jb = json_builder_set_member_name(jb, "data");
        jb = json_builder_begin_object(jb);
        jb = json_builder_set_member_name(jb, "klass");
@@ -601,7 +295,7 @@ unsigned int chime_send_typing(PurpleConnection *conn, const char *name, PurpleT
        return 0;
 }
 
-static int send_im(ChimeConnection *cxn, struct im_send_data *im)
+static int send_im(ChimeConnection *cxn, struct im_send_data *imd)
 {
        ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
 
@@ -611,22 +305,22 @@ static int send_im(ChimeConnection *cxn, struct im_send_data *im)
        JsonBuilder *jb = json_builder_new();
        jb = json_builder_begin_object(jb);
        jb = json_builder_set_member_name(jb, "Content");
-       jb = json_builder_add_string_value(jb, im->message);
+       jb = json_builder_add_string_value(jb, imd->message);
        jb = json_builder_set_member_name(jb, "ClientRequestToken");
        jb = json_builder_add_string_value(jb, uuid);
        jb = json_builder_end_object(jb);
 
        int ret;
        SoupURI *uri = soup_uri_new_printf(priv->messaging_url, "/conversations/%s/messages",
-                                          im->conv->id);
+                                          chime_object_get_id(CHIME_OBJECT(imd->im->conv)));
        JsonNode *node = json_builder_get_root(jb);
-       if (chime_connection_queue_http_request(cxn, node, uri, "POST", send_im_cb, im))
+       if (chime_connection_queue_http_request(cxn, node, uri, "POST", send_im_cb, imd))
                ret = 1;
        else {
                ret = -1;
-               g_free(im->who);
-               g_free(im->message);
-               g_free(im);
+               g_free(imd->who);
+               g_free(imd->message);
+               g_free(imd);
        }
 
        json_node_unref(node);
@@ -634,9 +328,10 @@ static int send_im(ChimeConnection *cxn, struct im_send_data *im)
        return ret;
 }
 
-static void conv_create_cb(ChimeConnection *cxn, SoupMessage *msg, JsonNode *node, gpointer _im)
+static void conv_create_cb(ChimeConnection *cxn, SoupMessage *msg, JsonNode *node, gpointer _imd)
 {
-       struct im_send_data *im = _im;
+       ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
+       struct im_send_data *imd = _imd;
 
        if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) {
                JsonObject *obj = json_node_get_object(node);
@@ -644,21 +339,35 @@ static void conv_create_cb(ChimeConnection *cxn, SoupMessage *msg, JsonNode *nod
                if (!node)
                        goto bad;
 
-               im->conv = one_conversation_cb(cxn, node);
-               if (!im->conv)
+               if (!chime_connection_parse_conversation(cxn, node, NULL)) {
+                       printf("No conversation");
+                       goto bad;
+               }
+
+               ChimeContact *contact = g_hash_table_lookup(priv->contacts.by_name, imd->who);
+               if (!contact) {
+                       printf("no contact for %s\n", imd->who);
+                       goto bad;
+               }
+
+               const gchar *id = chime_contact_get_profile_id(contact);
+               imd->im = g_hash_table_lookup(priv->im_conversations_by_peer_id, id);
+               if (!imd->im) {
+                       printf("No im for %s\n", id);
                        goto bad;
+               }
 
-               send_im(cxn, im);
+               send_im(cxn, imd);
                return;
        }
  bad:
-       im_error(cxn, im, _("Failed to create IM conversation"));
-       g_free(im->who);
-       g_free(im->message);
-       g_free(im);
+       im_error(cxn, imd, _("Failed to create IM conversation"));
+       g_free(imd->who);
+       g_free(imd->message);
+       g_free(imd);
 }
 
- static int create_im_conv(ChimeConnection *cxn, struct im_send_data *im, const gchar *profile_id)
+static int create_im_conv(ChimeConnection *cxn, struct im_send_data *im, const gchar *profile_id)
 {
        ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
        JsonBuilder *jb = json_builder_new();
@@ -687,9 +396,9 @@ static void conv_create_cb(ChimeConnection *cxn, SoupMessage *msg, JsonNode *nod
        return ret;
 }
 
-static void autocomplete_im_cb(ChimeConnection *cxn, SoupMessage *msg, JsonNode *node, gpointer _im)
+static void autocomplete_im_cb(ChimeConnection *cxn, SoupMessage *msg, JsonNode *node, gpointer _imd)
 {
-       struct im_send_data *im = _im;
+       struct im_send_data *imd = _imd;
 
        if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) {
                JsonArray *arr = json_node_get_array(node);
@@ -701,16 +410,16 @@ static void autocomplete_im_cb(ChimeConnection *cxn, SoupMessage *msg, JsonNode
 
                        if (parse_string(node, "email", &email) &&
                            parse_string(node, "id", &profile_id) &&
-                           !strcmp(im->who, email)) {
-                               create_im_conv(cxn, im, profile_id);
+                           !strcmp(imd->who, email)) {
+                               create_im_conv(cxn, imd, profile_id);
                                return;
                        }
                }
        }
-       im_error(cxn, im, _("Failed to find user"));
-       g_free(im->who);
-       g_free(im->message);
-       g_free(im);
+       im_error(cxn, imd, _("Failed to find user"));
+       g_free(imd->who);
+       g_free(imd->message);
+       g_free(imd);
 }
 
 int chime_purple_send_im(PurpleConnection *gc, const char *who, const char *message, PurpleMessageFlags flags)
@@ -718,20 +427,19 @@ int chime_purple_send_im(PurpleConnection *gc, const char *who, const char *mess
        ChimeConnection *cxn = purple_connection_get_protocol_data(gc);
        ChimeConnectionPrivate *priv = CHIME_CONNECTION_GET_PRIVATE (cxn);
 
-       struct im_send_data *im = g_new0(struct im_send_data, 1);
-       im->message = purple_unescape_html(message);
-       im->who = g_strdup(who);
-       im->flags = flags;
+       struct im_send_data *imd = g_new0(struct im_send_data, 1);
+       imd->message = purple_unescape_html(message);
+       imd->who = g_strdup(who);
+       imd->flags = flags;
 
        ChimeContact *contact = g_hash_table_lookup(priv->contacts.by_name, who);
        if (contact) {
                const gchar *id = chime_contact_get_profile_id(contact);
-               im->conv = g_hash_table_lookup(priv->im_conversations_by_peer_id,
-                                              id);
-               if (im->conv)
-                       return send_im(cxn, im);
+               imd->im = g_hash_table_lookup(priv->im_conversations_by_peer_id, id);
+               if (imd->im)
+                       return send_im(cxn, imd);
 
-               return create_im_conv(cxn, im, id);
+               return create_im_conv(cxn, imd, id);
        }
 
        SoupURI *uri = soup_uri_new_printf(priv->contacts_url, "/registered_auto_completes");
@@ -743,13 +451,13 @@ int chime_purple_send_im(PurpleConnection *gc, const char *who, const char *mess
 
        int ret;
        JsonNode *node = json_builder_get_root(jb);
-       if (chime_connection_queue_http_request(cxn, node, uri, "POST", autocomplete_im_cb, im))
+       if (chime_connection_queue_http_request(cxn, node, uri, "POST", autocomplete_im_cb, imd))
                ret = 1;
        else {
                ret = -1;
-               g_free(im->who);
-               g_free(im->message);
-               g_free(im);
+               g_free(imd->who);
+               g_free(imd->message);
+               g_free(imd);
        }
 
        json_node_unref(node);