changeset 60:ddd164e74312

trying to show user's icon. in this revision, the default icon of twitter is showed.
author mikanbako <maoutwo@gmail.com>
date Sat, 21 Jun 2008 02:32:28 +0900
parents 3f9148c1dc60
children a44d15cfd8a2
files pidgin-twitter.c
diffstat 1 files changed, 356 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/pidgin-twitter.c	Thu May 22 21:59:49 2008 +0900
+++ b/pidgin-twitter.c	Sat Jun 21 02:32:28 2008 +0900
@@ -30,11 +30,16 @@
 #include "version.h"
 #include "sound.h"
 #include "gtkconv.h"
+#include "gtkimhtml.h"
 
-#define RECIPIENT   0
-#define SENDER      1
-#define COMMAND     2
-#define PSEUDO      3
+#define RECIPIENT        0
+#define SENDER           1
+#define COMMAND          2
+#define PSEUDO           3
+#define MESSAGE          4
+#define USER             5
+#define USER_FIRST_LINE  6
+#define USER_FORMATTED   7
 
 #define PLUGIN_ID	            "gtk-honeyplanet-pidgin_twitter"
 #define PLUGIN_NAME	            "pidgin-twitter"
@@ -61,10 +66,14 @@
 #define OOPS_MESSAGE            "<body>Oops! Your update was over 140 characters. We sent the short version to your friends (they can view the entire update on the web).<BR></body>"
 
 /* patterns */
-#define P_RECIPIENT     "@([A-Za-z0-9_]+)"
-#define P_SENDER        "^(\\r?\\n?)([A-Za-z0-9_]+): "
-#define P_COMMAND       "^(?:\\s*)([dDfFgGlLmMnNtTwW]{1}\\s+[A-Za-z0-9_]+)(?:\\s*\\Z)"
-#define P_PSEUDO        "^\\s*(?:[\"#$%&'()*+,\\-./:;<=>?\\[\\\\\\]_`{|}~]|[^\\s\\x21-\\x7E])*([dDfFgGlLmMnNtTwW]{1})(?:\\Z|\\s+|[^\\x21-\\x7E]+\\Z)"
+#define P_RECIPIENT        "@([A-Za-z0-9_]+)"
+#define P_SENDER           "^(\\r?\\n?)([A-Za-z0-9_]+): "
+#define P_COMMAND          "^(?:\\s*)([dDfFgGlLmMnNtTwW]{1}\\s+[A-Za-z0-9_]+)(?:\\s*\\Z)"
+#define P_PSEUDO           "^\\s*(?:[\"#$%&'()*+,\\-./:;<=>?\\[\\\\\\]_`{|}~]|[^\\s\\x21-\\x7E])*([dDfFgGlLmMnNtTwW]{1})(?:\\Z|\\s+|[^\\x21-\\x7E]+\\Z)"
+#define P_MESSAGE          "^<body>(.*)</body>"
+#define P_USER             "^\\(.+?\\)\\s*([A-Za-z0-9_]+):"
+#define P_USER_FIRST_LINE  "^\\(.+?\\)\\s*.+:\\s*([A-Za-z0-9_]+):"
+#define P_USER_FORMATTED   "^.*?<a .+?>([A-Za-z0-9_]+)</a>:"
 
 /* debug macros */
 #define twitter_debug(fmt, ...)	purple_debug(PURPLE_DEBUG_INFO, PLUGIN_NAME, "%s():%4d:  " fmt, __FUNCTION__, (int)__LINE__, ## __VA_ARGS__);
@@ -72,8 +81,12 @@
 
 
 /* globals */
-static GRegex *regp[4];
+static GRegex *regp[7];
 static gboolean suppress_oops = FALSE;
+static GHashTable *icon_id_by_user;
+static GList *requested_users = NULL;
+static GList *requestings = NULL;
+static GList *requested_icon_marks = NULL;
 
 /* prototypes */
 static void escape(gchar **str);
@@ -86,12 +99,18 @@
 static void delete_text_cb(GtkTextBuffer *textbuffer, GtkTextIter *start_pos, GtkTextIter *end_pos, gpointer user_data);
 static void detach_from_window(void);
 static void detach_from_gtkconv(PidginConversation *gtkconv, gpointer null);
+static void delete_requested_icon_marks(PidginConversation *gtkconv);
 static void attach_to_window(void);
 static void attach_to_gtkconv(PidginConversation *gtkconv, gpointer null);
 static gboolean is_twitter_account(PurpleAccount *account, const char *name);
 static gboolean is_twitter_conv(PurpleConversation *conv);
 static void conv_created_cb(PurpleConversation *conv, gpointer null);
+static void deleting_conv_cb(PurpleConversation *conv);
 static gboolean receiving_im_cb(PurpleAccount *account, char **sender, char **buffer, PurpleConversation *conv, PurpleMessageFlags *flags, void *data);
+static void insert_requested_icon(gpointer data, gpointer user_data);
+static void downloaded_icon_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message);
+static void request_icon(const char *buffer);
+static void displayed_im_cb(PurpleAccount *account, const char *who, char *message, PurpleConversation *conv, PurpleMessageFlags flags);
 static gboolean load_plugin(PurplePlugin *plugin);
 static gboolean unload_plugin(PurplePlugin *plugin);
 static void counter_prefs_cb(const char *name, PurplePrefType type, gconstpointer val, gpointer data);
@@ -541,6 +560,9 @@
 detach_from_gtkconv(PidginConversation *gtkconv, gpointer null)
 {
     GtkWidget *box, *counter = NULL, *sep = NULL;
+    GtkIMHtml *imhtml = GTK_IMHTML(gtkconv->imhtml);
+
+    twitter_debug("end : imhtml : %o\n", gtk_imhtml_get_format_functions(imhtml));
 
     g_signal_handlers_disconnect_by_func(G_OBJECT(gtkconv->entry_buffer),
                                          (GFunc) insert_text_cb, gtkconv);
@@ -569,6 +591,28 @@
 }
 
 static void
+delete_requested_icon_marks(PidginConversation* conv) {
+    GtkTextBuffer *text_buffer = gtk_text_view_get_buffer(
+        GTK_TEXT_VIEW(conv->imhtml));
+    GList *mark_list = g_list_first(requested_icon_marks);
+    
+    /* delete the marks in the buffer that will be closed. */
+    while(mark_list) {
+        GtkTextMark *mark = mark_list->data;
+        GList *next = g_list_next(mark_list);
+
+        if(gtk_text_mark_get_buffer(mark) == text_buffer) {
+            /* the mark will be freed in the function */
+            gtk_text_buffer_delete_mark(text_buffer, mark);
+            requested_icon_marks = g_list_delete_link(
+                requested_icon_marks, mark_list);
+        }
+
+        mark_list = next;
+    }
+}
+
+static void
 attach_to_window(void)
 {
     GList *list;
@@ -588,8 +632,10 @@
 attach_to_gtkconv(PidginConversation *gtkconv, gpointer null)
 {
     GtkWidget *box, *sep, *counter, *menus;
+    GtkIMHtml *imhtml;
 
     box = gtkconv->toolbar;
+    imhtml = GTK_IMHTML(gtkconv->imhtml);
 
     /* Disable widgets that decorate or add link to composing text
      * because Twitter cannot receive marked up string. For lean-view
@@ -603,10 +649,11 @@
     if(menus) {
         gtk_widget_set_sensitive(GTK_WIDGET(menus), FALSE);
     }
+
     purple_conversation_set_features(
         gtkconv->active_conv,
         purple_conversation_get_features(gtkconv->active_conv) &
-        ~PURPLE_CONNECTION_HTML);
+        ~PURPLE_CONNECTION_HTML); 
 
     /* check if the counter is enabled */
     if(!purple_prefs_get_bool(OPT_COUNTER))
@@ -675,6 +722,17 @@
         attach_to_gtkconv(gtkconv, NULL);
 }
 
+static void
+deleting_conv_cb(PurpleConversation *conv)
+{
+    PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
+    g_return_if_fail(gtkconv != NULL);
+
+    /* only attach to twitter conversation window */
+    if(is_twitter_conv(conv))
+        delete_requested_icon_marks(gtkconv);
+}
+
 static gboolean
 receiving_im_cb(PurpleAccount *account, char **sender, char **buffer,
                 PurpleConversation *conv, PurpleMessageFlags *flags, void *data)
@@ -691,6 +749,8 @@
         *flags |= PURPLE_MESSAGE_SYSTEM;
     }
 
+    request_icon(*buffer);
+
     if(!suppress_oops || !purple_prefs_get_bool(OPT_SUPPRESS_OOPS))
         return FALSE;
 
@@ -705,6 +765,254 @@
     return FALSE;
 }
 
+static void
+insert_requested_icon(gpointer data, gpointer user_data)
+{
+    GtkTextMark *requested_mark = (GtkTextMark *)data;
+    gchar *user_name = (gchar *)user_data;
+    GList *win_list;
+    GtkIMHtml *target_imhtml = NULL;
+    GtkTextBuffer *target_buffer = NULL;
+    GtkTextIter inserting_point, next_line;
+    gchar *message;
+    GMatchInfo *match_info = NULL;
+    gchar *user_name_in_message;
+    int icon_id;
+
+    /* find the conversation that contains the mark  */
+    for(win_list = pidgin_conv_windows_get_list(); win_list;
+            win_list = win_list->next) {
+        PidginWindow *win = win_list->data;
+        GList *conv_list;
+
+        for(conv_list = pidgin_conv_window_get_gtkconvs(win); conv_list;
+                conv_list = conv_list->next) {
+            PidginConversation *conv = conv_list->data;
+            PurpleConversation *purple_conv = conv->active_conv;
+
+            if(purple_conv && is_twitter_conv(purple_conv)) {
+                GtkIMHtml *current_imhtml = GTK_IMHTML(conv->imhtml); 
+                GtkTextBuffer *current_buffer = gtk_text_view_get_buffer(
+                     GTK_TEXT_VIEW(current_imhtml));
+    
+                if(current_buffer == gtk_text_mark_get_buffer(requested_mark)) {
+                     target_imhtml = current_imhtml;
+                     target_buffer = current_buffer;
+                     break;
+                }
+            }
+        }
+    }
+    if(!(target_imhtml && target_buffer)) {
+        return;
+    }
+
+    /* insert icon to the mark */
+
+    gtk_text_buffer_get_iter_at_mark(target_buffer,
+                                     &inserting_point, requested_mark);
+    next_line = inserting_point;
+    gtk_text_iter_forward_line(&next_line);
+
+    message = gtk_text_buffer_get_text(target_buffer, &inserting_point, &next_line, FALSE);
+
+    if(gtk_text_iter_get_line(&inserting_point) == 0) {
+        g_regex_match(regp[USER_FIRST_LINE], message, 0, &match_info);
+    }
+    else {
+        g_regex_match(regp[USER], message, 0, &match_info);
+    }
+    if(!g_match_info_matches(match_info)) {
+        twitter_debug("user's name was not matched : %s\n", message);
+        g_match_info_free(match_info);
+        g_free(message);
+        return;
+    }
+
+    user_name_in_message = g_match_info_fetch(match_info, 1);
+    g_match_info_free(match_info);
+    g_free(message);
+
+    /* Return if the message is not by the user that has the icon. */
+    if(!g_str_equal(user_name, user_name_in_message)) {
+        g_free(user_name_in_message);
+        return;
+    }
+    g_free(user_name_in_message);
+
+    /* insert icon */
+    icon_id = GPOINTER_TO_INT(g_hash_table_lookup(icon_id_by_user, user_name));
+    if(!icon_id) {
+        return;
+    }
+
+    gtk_imhtml_insert_image_at_iter(target_imhtml, icon_id, &inserting_point);
+
+    /* To prevent that requested_mark will be freed. */
+    g_object_ref(requested_mark);
+    gtk_text_buffer_delete_mark(target_buffer, requested_mark);
+}
+
+static void
+downloaded_icon_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
+                   const gchar *url_text, gsize len, const gchar *error_message) 
+{
+    gchar *user_name = (gchar *)user_data;
+    int icon_id;
+    GList* mark_list;
+
+    /* Return if user's icon had already been downloaded or
+     * the download is failure. */
+    if(g_hash_table_lookup(icon_id_by_user, user_name) || !url_text) {
+        if(!url_text) {
+            twitter_debug("downloading %s's icon was failure : %s\n",
+                          user_name, error_message);
+        }
+        else {
+            twitter_debug("%s's icon has already been downloaded\n", user_name);
+        }
+
+        requested_users = g_list_remove(requested_users, user_name);
+        g_free(user_name);
+        requestings = g_list_remove(requestings, url_data);
+        return;
+    }
+
+    icon_id = purple_imgstore_add_with_id(g_memdup(url_text, len), len,
+                                          user_name);
+    g_hash_table_insert(icon_id_by_user, user_name, GINT_TO_POINTER(icon_id));
+    requestings = g_list_remove(requestings, url_data);
+
+    twitter_debug("Downloading %s's icon has been complete.(icon_id = %d)\n",
+        user_name, icon_id);
+
+    /* Insert the icon to messages that had been received. */
+
+    g_list_foreach(requested_icon_marks, insert_requested_icon, user_name);
+
+    /* Remove marks that inserted the icon. */
+    mark_list = g_list_first(requested_icon_marks);
+    while(mark_list) {
+        GtkTextMark *mark = mark_list->data;
+        GList* next = g_list_next(mark_list);
+
+        if(gtk_text_mark_get_deleted(mark)) {
+            g_object_unref(mark);
+            requested_icon_marks = g_list_delete_link(
+                                       requested_icon_marks, mark_list);
+        }
+
+        mark_list = next;
+    }
+}
+
+static void
+request_icon(const char *buffer)
+{
+    GMatchInfo *match_info = NULL;
+    gchar* user_name = NULL;
+    gchar* message = NULL;
+    PurpleUtilFetchUrlData* fetch_data = NULL;
+
+    /* get user's name */
+
+    g_regex_match(regp[MESSAGE], buffer, 0, &match_info);
+    if(!g_match_info_matches(match_info)) {
+        twitter_debug("Message was not matched : %s\n", buffer);
+        g_match_info_free(match_info);
+        return;
+    }
+
+    message = g_match_info_fetch(match_info, 1);
+    g_match_info_free(match_info);
+    match_info = NULL;
+
+    g_regex_match(regp[SENDER], message, 0, &match_info);
+    if(!g_match_info_matches(match_info)) {
+        twitter_debug("user's name was not matched : %s\n", message);
+        g_match_info_free(match_info);
+        g_free(message);
+        return;
+    }
+
+    user_name = g_match_info_fetch(match_info, 2);
+    g_match_info_free(match_info);
+    g_free(message);
+
+    /* request user's icon */
+
+    /* Return if user's icon had already been requested. */
+    if(g_list_find_custom(requested_users, user_name, (GCompareFunc)strcmp)) {
+        g_free(user_name);
+        return;
+    }
+
+    /* The string object are owned by the list. */
+    requested_users = g_list_append(requested_users, user_name);
+    
+    fetch_data = purple_util_fetch_url(
+        "http://static.twitter.com/images/default_profile_normal.png",
+        TRUE, NULL, TRUE, downloaded_icon_cb, user_name);
+    requestings = g_list_append(requestings, fetch_data);
+
+    twitter_debug("request %s's icon\n", user_name);
+}
+
+static void
+displayed_im_cb(PurpleAccount *account, const char *who, char *message,
+                PurpleConversation *conv, PurpleMessageFlags flags)
+{
+    GMatchInfo *match_info = NULL;
+    gchar *user;
+    GtkIMHtml *imhtml;
+    GtkTextBuffer *text_buffer;
+    GtkTextIter inserting_point;
+    int icon_id;
+
+    /* Check that conv is not null to avoid a clash.
+     * conv is null when the conversation window has not opened yet.
+     */
+    if(!(conv && is_twitter_conv(conv))) {
+        return;
+    }
+
+    /* get user's name */
+    g_regex_match(regp[USER_FORMATTED], message, 0, &match_info);
+    if(!g_match_info_matches(match_info)) {
+        twitter_debug("message was not matched : %s\n", message);
+        g_match_info_free(match_info);
+        return;
+    }
+
+    user = g_match_info_fetch(match_info, 1);
+    g_match_info_free(match_info);
+
+    /* insert icon */
+
+    imhtml = GTK_IMHTML(PIDGIN_CONVERSATION(conv)->imhtml);
+    text_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(imhtml));
+
+    /* get GtkTextIter of the last line */
+    gtk_text_buffer_get_iter_at_line(text_buffer, &inserting_point,
+        gtk_text_buffer_get_line_count(text_buffer) - 1);
+
+    icon_id = GPOINTER_TO_INT(g_hash_table_lookup(icon_id_by_user, user));
+
+    /* If the user's icon has not been downloaded, mark the message */
+    if(!icon_id) {
+        requested_icon_marks = g_list_append(requested_icon_marks,
+                                   gtk_text_buffer_create_mark(
+                                       text_buffer, NULL,
+                                       &inserting_point, FALSE));
+        twitter_debug("%s's icon has not been downloaded.", user);
+        g_free(user);
+        return;
+    }
+
+    gtk_imhtml_insert_image_at_iter(imhtml, icon_id, &inserting_point);
+    g_free(user);
+}
+
 static gboolean
 load_plugin(PurplePlugin *plugin)
 {
@@ -716,14 +1024,27 @@
     purple_signal_connect(purple_conversations_get_handle(),
                           "conversation-created",
                           plugin, PURPLE_CALLBACK(conv_created_cb), NULL);
+    purple_signal_connect(purple_conversations_get_handle(),
+                          "deleting-conversation",
+                           plugin, PURPLE_CALLBACK(deleting_conv_cb), NULL);
     purple_signal_connect(purple_conversations_get_handle(), "receiving-im-msg",
                           plugin, PURPLE_CALLBACK(receiving_im_cb), NULL);
+    purple_signal_connect(pidgin_conversations_get_handle(), "displayed-im-msg",
+                          plugin, PURPLE_CALLBACK(displayed_im_cb), NULL);
 
     /* compile regex */
     regp[RECIPIENT] = g_regex_new(P_RECIPIENT, 0, 0, NULL);
     regp[SENDER]    = g_regex_new(P_SENDER,    0, 0, NULL);
     regp[COMMAND]   = g_regex_new(P_COMMAND, G_REGEX_RAW, 0, NULL);
     regp[PSEUDO]    = g_regex_new(P_PSEUDO,  G_REGEX_RAW, 0, NULL);
+    regp[MESSAGE]   = g_regex_new(P_MESSAGE, 0, 0, NULL);
+    regp[USER]      = g_regex_new(P_USER, 0, 0, NULL);
+    regp[USER_FIRST_LINE] = g_regex_new(P_USER_FIRST_LINE, 0, 0, NULL);
+    regp[USER_FORMATTED]  = g_regex_new(P_USER_FORMATTED, G_REGEX_RAW, 0, NULL);
+
+    /* hash table for user's icons 
+     * the key is owned by requested_user */
+    icon_id_by_user = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
 
     /* attach counter to the existing twitter window */
     if(purple_prefs_get_bool(OPT_COUNTER)) {
@@ -736,6 +1057,8 @@
 static gboolean
 unload_plugin(PurplePlugin *plugin)
 {
+    GList *list;
+
     twitter_debug("called\n");
 
     /* disconnect from signal */
@@ -749,14 +1072,37 @@
                              "conversation-created",
                              plugin, PURPLE_CALLBACK(conv_created_cb));
     purple_signal_disconnect(purple_conversations_get_handle(),
+                             "deleting-conversation",
+                             plugin, PURPLE_CALLBACK(deleting_conv_cb));
+    purple_signal_disconnect(purple_conversations_get_handle(),
                              "receiving-im-msg",
                              plugin, PURPLE_CALLBACK(receiving_im_cb));
+    purple_signal_disconnect(pidgin_conversations_get_handle(),
+                             "displayed-im-msg",
+                             plugin, PURPLE_CALLBACK(displayed_im_cb));
 
     /* unreference regp */
     g_regex_unref(regp[RECIPIENT]);
     g_regex_unref(regp[SENDER]);
     g_regex_unref(regp[COMMAND]);
     g_regex_unref(regp[PSEUDO]);
+    g_regex_unref(regp[MESSAGE]);
+    g_regex_unref(regp[USER]);
+    g_regex_unref(regp[USER_FIRST_LINE]);
+    g_regex_unref(regp[USER_FORMATTED]);
+
+    /* cancel request that has not been finished yet */
+    for(list = g_list_first(requestings); list; list = g_list_next(list)) {
+        purple_util_fetch_url_cancel(list->data);
+    }
+    g_list_free(requestings);
+
+    /* destroy hash table for icons */
+    for(list = g_list_first(requested_users); list; list = g_list_next(list)) {
+        g_free(list->data);
+    }
+    g_list_free(requested_users);
+    g_hash_table_destroy(icon_id_by_user);
 
     /* detach from twitter window */
     detach_from_window();