# HG changeset patch # User mikanbako # Date 1213983148 -32400 # Node ID ddd164e7431287206acbae035dbf9d8e39c7cf56 # Parent 3f9148c1dc60027c21084824d44aa56ce0576d74 trying to show user's icon. in this revision, the default icon of twitter is showed. diff -r 3f9148c1dc60 -r ddd164e74312 pidgin-twitter.c --- 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 "Oops! Your update was over 140 characters. We sent the short version to your friends (they can view the entire update on the web).
" /* 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 "^(.*)" +#define P_USER "^\\(.+?\\)\\s*([A-Za-z0-9_]+):" +#define P_USER_FIRST_LINE "^\\(.+?\\)\\s*.+:\\s*([A-Za-z0-9_]+):" +#define P_USER_FORMATTED "^.*?([A-Za-z0-9_]+):" /* 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();