Mercurial > pidgin-twitter
view pidgin-twitter.c @ 107:eb77d409c235
- do neither save nor cache icon image if pixbuf can not be obtained.
- now icon size can be specified in prefs.xml.
- tweaked configuration dialog.
author | Yoshiki Yazawa <yaz@honeyplanet.jp> |
---|---|
date | Tue, 15 Jul 2008 12:32:06 +0900 |
parents | 084e1c6de8ca |
children | 01b6613a075a |
line wrap: on
line source
/* * Pidgin-Twitter plugin. * * 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. */ #define PURPLE_PLUGINS 1 #include "pidgin-twitter.h" #include <gdk-pixbuf/gdk-pixbuf.h> /* globals */ static GRegex *regp[9]; static gboolean suppress_oops = FALSE; static GHashTable *icon_data_by_user = NULL; // twitter static GHashTable *icon_data_by_user2 = NULL; // wassr static GHashTable *icon_data_by_user3 = NULL; // identi.ca static GHashTable *conv_hash = NULL; #define WASSR_POST_LEN (255 * 4) static gchar *wassr_post = NULL; typedef struct _icon_data { gint icon_id; // image id gboolean requested; // TRUE if download icon has been requested GList *request_list; // marker list PurpleUtilFetchUrlData *fetch_data; // icon fetch data } icon_data; enum { unknown_service = 0, twitter_service, wassr_service, identica_service }; typedef struct _eval_data { gint which; gint service; } eval_data; typedef struct _got_icon_data { gchar *user_name; gint service; } got_icon_data; /* this function is a modified clone of purple_markup_strip_html() */ static char * strip_html_markup(const char *str) { int i, j, k, entlen; gboolean visible = TRUE; gboolean closing_td_p = FALSE; gchar *str2; const gchar *cdata_close_tag = NULL, *ent; gchar *href = NULL; int href_st = 0; if(!str) return NULL; str2 = g_strdup(str); for (i = 0, j = 0; str2[i]; i++) { if (str2[i] == '<') { if (cdata_close_tag) { /* Note: Don't even assume any other tag is a tag in CDATA */ if (g_ascii_strncasecmp(str2 + i, cdata_close_tag, strlen(cdata_close_tag)) == 0) { i += strlen(cdata_close_tag) - 1; cdata_close_tag = NULL; } continue; } else if (g_ascii_strncasecmp(str2 + i, "<td", 3) == 0 && closing_td_p) { str2[j++] = '\t'; visible = TRUE; } else if (g_ascii_strncasecmp(str2 + i, "</td>", 5) == 0) { closing_td_p = TRUE; visible = FALSE; } else { closing_td_p = FALSE; visible = TRUE; } k = i + 1; if(g_ascii_isspace(str2[k])) visible = TRUE; else if (str2[k]) { /* Scan until we end the tag either implicitly (closed start * tag) or explicitly, using a sloppy method (i.e., < or > * inside quoted attributes will screw us up) */ while (str2[k] && str2[k] != '<' && str2[k] != '>') { k++; } /* If we've got an <a> tag with an href, save the address * to print later. */ if (g_ascii_strncasecmp(str2 + i, "<a", 2) == 0 && g_ascii_isspace(str2[i+2])) { int st; /* start of href, inclusive [ */ int end; /* end of href, exclusive ) */ char delim = ' '; /* Find start of href */ for (st = i + 3; st < k; st++) { if (g_ascii_strncasecmp(str2+st, "href=", 5) == 0) { st += 5; if (str2[st] == '"' || str2[st] == '\'') { delim = str2[st]; st++; } break; } } /* find end of address */ for (end = st; end < k && str2[end] != delim; end++) { /* All the work is done in the loop construct above. */ } /* If there's an address, save it. If there was * already one saved, kill it. */ if (st < k) { char *tmp; g_free(href); tmp = g_strndup(str2 + st, end - st); href = purple_unescape_html(tmp); g_free(tmp); href_st = j; } } /* Check for tags which should be mapped to newline */ else if (g_ascii_strncasecmp(str2 + i, "<p>", 3) == 0 || g_ascii_strncasecmp(str2 + i, "<tr", 3) == 0 || g_ascii_strncasecmp(str2 + i, "<br", 3) == 0 || g_ascii_strncasecmp(str2 + i, "<hr", 3) == 0 || g_ascii_strncasecmp(str2 + i, "<li", 3) == 0 || g_ascii_strncasecmp(str2 + i, "<div", 4) == 0 || g_ascii_strncasecmp(str2 + i, "</table>", 8) == 0) { str2[j++] = '\n'; } else if (g_ascii_strncasecmp(str2 + i, "<script", 7) == 0) { cdata_close_tag = "</script>"; } else if (g_ascii_strncasecmp(str2 + i, "<style", 6) == 0) { cdata_close_tag = "</style>"; } /* Update the index and continue checking after the tag */ i = (str2[k] == '<' || str2[k] == '\0')? k - 1: k; continue; } } else if (cdata_close_tag) { continue; } else if (!g_ascii_isspace(str2[i])) { visible = TRUE; } if (str2[i] == '&' && (ent = purple_markup_unescape_entity(str2 + i, &entlen)) != NULL) { while (*ent) str2[j++] = *ent++; i += entlen - 1; continue; } if (visible) str2[j++] = g_ascii_isspace(str2[i])? ' ': str2[i]; } g_free(href); str2[j] = '\0'; return str2; } /* this function has been taken from autoaccept plugin */ static gboolean ensure_path_exists(const char *dir) { if (!g_file_test(dir, G_FILE_TEST_IS_DIR)) { if (purple_build_dir(dir, S_IRUSR | S_IWUSR | S_IXUSR)) return FALSE; } return TRUE; } /**********************/ /* our implementation */ /**********************/ static void escape(gchar **str) { GMatchInfo *match_info = NULL; gchar *newstr = NULL, *match = NULL; gboolean flag = FALSE; /* search genuine command */ g_regex_match(regp[COMMAND], *str, 0, &match_info); while(g_match_info_matches(match_info)) { match = g_match_info_fetch(match_info, 1); twitter_debug("command = %s\n", match); g_free(match); g_match_info_next(match_info, NULL); flag = TRUE; } g_match_info_free(match_info); match_info = NULL; if(flag) return; /* if not found, check pseudo command */ g_regex_match(regp[PSEUDO], *str, 0, &match_info); while(g_match_info_matches(match_info)) { match = g_match_info_fetch(match_info, 1); twitter_debug("pseudo = %s\n", match); g_free(match); g_match_info_next(match_info, NULL); flag = TRUE; } g_match_info_free(match_info); match_info = NULL; /* if there is pseudo one, escape it */ if(flag) { /* put ". " to the beginning of buffer */ newstr = g_strdup_printf(". %s", *str); twitter_debug("*str = %s newstr = %s\n", *str, newstr); g_free(*str); *str = newstr; } } static void strip_markup(gchar **str) { char *plain; plain = strip_html_markup(*str); g_free(*str); *str = plain; } #define TWITTER_STATUS_POST "POST /statuses/update.xml HTTP/1.0\r\n" \ "Host: twitter.com\r\n" \ "User-Agent: Pidgin-Twitter\r\n" \ "Authorization: Basic %s\r\n" \ "Content-Length: %d\r\n\r\n" #define TWITTER_STATUS_FORMAT "status=%s" #define TWITTER_STATUS_TERMINATOR "\r\n\r\n" #define TWITTER_BASE_URL "http://twitter.com" typedef struct twitter_message { PurpleAccount *account; char *status; time_t time; } twitter_message_t; static void post_status_with_api_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, size_t len, const gchar *error_message) { twitter_message_t *tm = (struct twitter_message *)user_data; gchar *msg = NULL; char *p1 = NULL, *p2 = NULL; int error = 1; PurpleConversation *conv; conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, "twitter@twitter.com", tm->account); if (!conv) { twitter_debug("failed to get conversation\n"); goto fin; } if (error_message) { /* connection failed or something */ msg = g_strdup_printf("Local error: %s", error_message); } else { int code = -1; if ((strncmp(url_text, "HTTP/1.0", strlen("HTTP/1.0")) == 0 || strncmp(url_text, "HTTP/1.1", strlen("HTTP/1.1")) == 0)) { p1 = strchr(url_text, ' '); if (p1) { p1++; p2 = strchr(p1, ' '); if (p2) p2++; else p2 = NULL; } } code = atoi(p1); if (code == 200) { error = 0; } else { switch (code) { case 400: msg = g_strdup("Invalid request. Too many updates?"); break; case 401: msg = g_strdup("Authorization failed."); break; case 403: msg = g_strdup("Your update has been refused by Twitter server " "for some reason."); break; case 404: msg = g_strdup("Requested URI is not found."); break; case 500: msg = g_strdup("Server error."); break; case 502: msg = g_strdup("Twitter is down or under maintenance."); break; case 503: msg = g_strdup("Twitter is extremely crowded. " "Try again later."); break; default: msg = g_strdup_printf("Unknown error. (%d %s)", code, p2 ? p2 : ""); break; } } } if (!error) { purple_conv_im_write(conv->u.im, purple_account_get_username(tm->account), tm->status, PURPLE_MESSAGE_SEND, tm->time); } else { gchar *m; m = g_strdup_printf("%s<BR>%s", msg, tm->status); /* FIXME: too strong. it should be more smart */ purple_conv_im_write(conv->u.im, purple_account_get_username(tm->account), m, PURPLE_MESSAGE_ERROR, time(NULL)); g_free(m); } fin: if (msg) g_free(msg); if (tm) { if (tm->status) g_free(tm->status); g_free(tm); } } static void post_status_with_api(PurpleAccount *account, char **buffer) { char *request, *status, *header; const char *url_encoded = purple_url_encode(*buffer); char *basic_auth, *basic_auth_encoded; twitter_message_t *tm; const char *screen_name = purple_prefs_get_string(OPT_SCREEN_NAME); const char *password = purple_prefs_get_string(OPT_PASSWORD); twitter_debug("tm.account: %s\n", purple_account_get_username(account)); if (!screen_name || !password || !screen_name[0] || !password[0]) { twitter_debug("screen_name or password is empty\n"); return; } tm = g_new(twitter_message_t, 1); tm->account = account; tm->status = g_strdup(*buffer); tm->time = time(NULL); basic_auth = g_strdup_printf("%s:%s", screen_name, password); basic_auth_encoded = purple_base64_encode((unsigned char *)basic_auth, strlen(basic_auth)); g_free(basic_auth); status = g_strdup_printf(TWITTER_STATUS_FORMAT, url_encoded); header = g_strdup_printf(TWITTER_STATUS_POST, basic_auth_encoded, (int)strlen(status)); request = g_strconcat(header, status, TWITTER_STATUS_TERMINATOR, NULL); purple_util_fetch_url_request(TWITTER_BASE_URL, FALSE, NULL, TRUE, request, TRUE, post_status_with_api_cb, tm); g_free(header); g_free(basic_auth_encoded); g_free(status); g_free(request); } static gboolean sending_im_cb(PurpleAccount *account, char *recipient, char **buffer, void *data) { int utflen, bytes; gboolean twitter_ac = FALSE, wassr_ac = FALSE; twitter_debug("called\n"); twitter_ac = is_twitter_account(account, recipient); wassr_ac = is_wassr_account(account, recipient); if(wassr_ac) { /* store sending message to address parrot problem */ g_strlcpy(wassr_post, *buffer, WASSR_POST_LEN); twitter_debug("parrot pushed:%s\n", *buffer); } /* strip all markups */ if(twitter_ac || wassr_ac) strip_markup(buffer); /* return here if the message is not to twitter */ if(!twitter_ac) return FALSE; /* escape pseudo command */ if(twitter_ac && purple_prefs_get_bool(OPT_ESCAPE_PSEUDO)) { escape(buffer); } /* update status with Twitter API instead of IM protocol */ if (purple_prefs_get_bool(OPT_API_BASE_POST)) { if (buffer && *buffer) { post_status_with_api(account, buffer); (*buffer)[0] = '\0'; } return FALSE; } /* try to suppress oops message */ utflen = g_utf8_strlen(*buffer, -1); bytes = strlen(*buffer); twitter_debug("utflen = %d bytes = %d\n", utflen, bytes); if(bytes > 140 && utflen <= 140) suppress_oops = TRUE; return FALSE; } static gboolean eval(const GMatchInfo *match_info, GString *result, gpointer user_data) { eval_data *data = (eval_data *)user_data; gint which = data->which; gint service = data->service; gchar sub[128]; twitter_debug("which = %d service = %d\n", which, service); if(which == RECIPIENT) { gchar *match = g_match_info_fetch(match_info, 1); const gchar *format = NULL; switch(service) { case twitter_service: format = RECIPIENT_FORMAT_TWITTER; break; case wassr_service: format = RECIPIENT_FORMAT_WASSR; break; case identica_service: format = RECIPIENT_FORMAT_IDENTICA; break; default: twitter_debug("unknown service\n"); break; } snprintf(sub, 128, format, match, match); g_free(match); } else if(which == SENDER) { gchar *match1 = g_match_info_fetch(match_info, 1); //preceding CR|LF gchar *match2 = g_match_info_fetch(match_info, 2); //sender const gchar *format = NULL; switch(service) { case twitter_service: format = SENDER_FORMAT_TWITTER; break; case wassr_service: format = SENDER_FORMAT_WASSR; break; case identica_service: format = SENDER_FORMAT_IDENTICA; break; default: twitter_debug("unknown service\n"); break; } snprintf(sub, 128, format, match1 ? match1: "", match2, match2); g_free(match1); g_free(match2); } else if(service == wassr_service && which == CHANNEL) { gchar *match1 = g_match_info_fetch(match_info, 1); //before channel gchar *match2 = g_match_info_fetch(match_info, 2); //channel const gchar *format = CHANNEL_FORMAT_WASSR; snprintf(sub, 128, format, match1 ? match1: "", match2, match2); g_free(match1); g_free(match2); } g_string_append(result, sub); twitter_debug("sub = %s\n", sub); return FALSE; } static void translate(gchar **str, gint which, gint service) { gchar *newstr; eval_data *data = g_new0(eval_data, 1); data->which = which; data->service = service; newstr = g_regex_replace_eval(regp[which], // compiled regex *str, // subject string -1, // length of the subject string 0, // start position 0, // match options eval, // function to be called for each match data, // user data NULL); // error handler g_free(data); data = NULL; twitter_debug("which = %d *str = %s newstr = %s\n", which, *str, newstr); g_free(*str); *str = newstr; } static void playsound(gchar **str, gint which) { GMatchInfo *match_info; const gchar *list; gchar **candidates = NULL, **candidate = NULL; list = purple_prefs_get_string(which ? OPT_USERLIST_SENDER : OPT_USERLIST_RECIPIENT); g_return_if_fail(list != NULL); if(!strcmp(list, DEFAULT_LIST)) return; candidates = g_strsplit_set(list, " ,:;", 0); g_return_if_fail(candidates != NULL); g_regex_match(regp[which], *str, 0, &match_info); while(g_match_info_matches(match_info)) { gchar *user = NULL; if(which == RECIPIENT) user = g_match_info_fetch(match_info, 1); else if(which == SENDER) user = g_match_info_fetch(match_info, 2); twitter_debug("user = %s\n", user); for(candidate = candidates; *candidate; candidate++) { if(!strcmp(*candidate, "")) continue; twitter_debug("candidate = %s\n", *candidate); if(!strcmp(user, *candidate)) { twitter_debug("match. play sound\n"); purple_sound_play_event(purple_prefs_get_int (which ? OPT_SOUNDID_SENDER : OPT_SOUNDID_RECIPIENT), NULL); break; } } g_free(user); g_match_info_next(match_info, NULL); } g_strfreev(candidates); g_match_info_free(match_info); } static gboolean writing_im_cb(PurpleAccount *account, char *sender, char **buffer, PurpleConversation *conv, int flags, void *data) { twitter_debug("called\n"); gint service = get_service_type(conv); /* check if the conversation is between twitter */ if(service == unknown_service) return FALSE; /* Add screen_name if the current message is posted by owner */ if (flags & PURPLE_MESSAGE_SEND) { gchar *m = NULL; const char *screen_name = purple_prefs_get_string(OPT_SCREEN_NAME); if (screen_name) { m = g_strdup_printf("%s: %s", screen_name, *buffer); g_free(*buffer); *buffer = m; } } /* strip all markups */ strip_markup(buffer); // it causes missing of strings surrounded by <> /* playsound */ if(purple_prefs_get_bool(OPT_PLAYSOUND_SENDER)) { playsound(buffer, SENDER); } if(purple_prefs_get_bool(OPT_PLAYSOUND_RECIPIENT)) { playsound(buffer, RECIPIENT); } /* translate */ if(purple_prefs_get_bool(OPT_TRANSLATE_SENDER)) { translate(buffer, SENDER, service); } if(service == wassr_service && purple_prefs_get_bool(OPT_TRANSLATE_CHANNEL)) { translate(buffer, CHANNEL, service); } if(purple_prefs_get_bool(OPT_TRANSLATE_RECIPIENT)) { translate(buffer, RECIPIENT, service); } /* escape pseudo command (to show same result to sending message) */ if(is_twitter_conv(conv) && purple_prefs_get_bool(OPT_ESCAPE_PSEUDO)) { escape(buffer); } return FALSE; } static void insert_text_cb(GtkTextBuffer *textbuffer, GtkTextIter *position, gchar *new_text, gint new_text_length, gpointer user_data) { PurpleConversation *conv = (PurpleConversation *)user_data; PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); GtkWidget *box, *counter = NULL; gchar *markup = NULL; gint service = get_service_type(conv); guint count; gchar *text = NULL; GtkTextIter head, tail; guint bytes = 0; g_return_if_fail(gtkconv != NULL); switch(service) { case twitter_service: count = gtk_text_buffer_get_char_count(textbuffer) + (unsigned int)g_utf8_strlen(new_text, -1); markup = g_markup_printf_escaped("<span color=\"%s\">%u</span>", count <= 140 ? "black" : "red", count); break; case identica_service: gtk_text_buffer_get_start_iter(textbuffer, &head); gtk_text_buffer_get_end_iter(textbuffer, &tail); text = gtk_text_buffer_get_text(textbuffer, &head, &tail, TRUE); if(text) bytes = strlen(text) + new_text_length; g_free(text); markup = g_markup_printf_escaped("<span color=\"%s\">%u</span>", bytes <= 140 ? "black" : "red", bytes); break; case wassr_service: count = gtk_text_buffer_get_char_count(textbuffer) + (unsigned int)g_utf8_strlen(new_text, -1); markup = g_markup_printf_escaped("<span color=\"%s\">%u</span>", count <= 255 ? "black" : "red", count); break; default: twitter_debug("unknown service\n"); break; } box = gtkconv->toolbar; counter = g_object_get_data(G_OBJECT(box), PLUGIN_ID "-counter"); if(counter) gtk_label_set_markup(GTK_LABEL(counter), markup); g_free(markup); } static void delete_text_cb(GtkTextBuffer *textbuffer, GtkTextIter *start_pos, GtkTextIter *end_pos, gpointer user_data) { PurpleConversation *conv = (PurpleConversation *)user_data; PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); GtkWidget *box, *counter = NULL; gchar *markup = NULL; gint service = get_service_type(conv); guint count = 0; gchar *text = NULL; GtkTextIter head, tail; guint bytes = 0; g_return_if_fail(gtkconv != NULL); switch(service) { case twitter_service: count= gtk_text_buffer_get_char_count(textbuffer) - (gtk_text_iter_get_offset(end_pos) - gtk_text_iter_get_offset(start_pos)); markup = g_markup_printf_escaped("<span color=\"%s\">%u</span>", count <= 140 ? "black" : "red", count); break; case identica_service: gtk_text_buffer_get_start_iter(textbuffer, &head); gtk_text_buffer_get_end_iter(textbuffer, &tail); text = gtk_text_buffer_get_text(textbuffer, &head, &tail, TRUE); if(text) bytes = strlen(text); g_free(text); text = gtk_text_buffer_get_text(textbuffer, start_pos, end_pos, TRUE); if(text) bytes -= strlen(text); g_free(text); markup = g_markup_printf_escaped("<span color=\"%s\">%u</span>", bytes <= 140 ? "black" : "red", bytes); break; case wassr_service: count= gtk_text_buffer_get_char_count(textbuffer) - (gtk_text_iter_get_offset(end_pos) - gtk_text_iter_get_offset(start_pos)); markup = g_markup_printf_escaped("<span color=\"%s\">%u</span>", count <= 255 ? "black" : "red", count); break; default: twitter_debug("unknown service\n"); break; } box = gtkconv->toolbar; counter = g_object_get_data(G_OBJECT(box), PLUGIN_ID "-counter"); if(counter) gtk_label_set_markup(GTK_LABEL(counter), markup); g_free(markup); } static void detach_from_window(void) { GList *list; /* find twitter conv window out and detach from that */ for(list = pidgin_conv_windows_get_list(); list; list = list->next) { PidginWindow *win = list->data; PurpleConversation *conv = pidgin_conv_window_get_active_conversation(win); gint service = get_service_type(conv); switch(service) { case twitter_service: case wassr_service: case identica_service: detach_from_conv(conv, NULL); break; default: twitter_debug("unknown service\n"); break; } } } static void detach_from_conv(PurpleConversation *conv, gpointer null) { PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); GtkWidget *box, *counter = NULL, *sep = NULL; g_signal_handlers_disconnect_by_func(G_OBJECT(gtkconv->entry_buffer), (GFunc) insert_text_cb, conv); g_signal_handlers_disconnect_by_func(G_OBJECT(gtkconv->entry_buffer), (GFunc) delete_text_cb, conv); box = gtkconv->toolbar; /* remove counter */ counter = g_object_get_data(G_OBJECT(box), PLUGIN_ID "-counter"); if(counter) { gtk_container_remove(GTK_CONTAINER(box), counter); g_object_unref(counter); g_object_set_data(G_OBJECT(box), PLUGIN_ID "-counter", NULL); } /* remove separator */ sep = g_object_get_data(G_OBJECT(box), PLUGIN_ID "-sep"); if(sep) { gtk_container_remove(GTK_CONTAINER(box), sep); g_object_unref(sep); g_object_set_data(G_OBJECT(box), PLUGIN_ID "-sep", NULL); } gtk_widget_queue_draw(pidgin_conv_get_window(gtkconv)->window); } static void remove_marks_func(gpointer key, gpointer value, gpointer user_data) { icon_data *data = (icon_data *)value; GtkTextBuffer *text_buffer = (GtkTextBuffer *)user_data; GList *mark_list = NULL; GList *current; if(data && data->request_list) mark_list = data->request_list; /* remove the marks in its GtkTextBuffers */ for(current = g_list_first(mark_list); current; current = g_list_next(current)) { GtkTextMark *current_mark = current->data; GtkTextBuffer *current_text_buffer = gtk_text_mark_get_buffer( current_mark); if(!current_text_buffer) continue; if(text_buffer) { if(current_text_buffer == text_buffer) { /* the mark will be freed in this function */ gtk_text_buffer_delete_mark(current_text_buffer, current_mark); current->data = NULL; } } else { gtk_text_buffer_delete_mark(current_text_buffer, current_mark); current->data = NULL; } } /* end of for */ mark_list = g_list_remove_all(mark_list, NULL); data->request_list = mark_list; } static void delete_requested_icon_marks(PidginConversation *conv, GHashTable *table) { GtkTextBuffer *text_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(conv->imhtml)); g_hash_table_foreach(table, (GHFunc)remove_marks_func, (gpointer)text_buffer); } static void attach_to_window(void) { GList *list; /* find twitter conv window out and attach to that */ for(list = pidgin_conv_windows_get_list(); list; list = list->next) { PidginWindow *win = list->data; PurpleConversation *conv = pidgin_conv_window_get_active_conversation(win); gint service = get_service_type(conv); /* only attach to twitter conversation window */ switch(service) { case twitter_service: case wassr_service: case identica_service: attach_to_conv(conv, NULL); break; default: twitter_debug("unknown service\n"); break; } } } static void attach_to_conv(PurpleConversation *conv, gpointer null) { PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); 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 * and wide-view, see pidgin/gtkimhtmltoolbar.c. */ menus = g_object_get_data(G_OBJECT(box), "lean-view"); if(menus) { gtk_widget_set_sensitive(GTK_WIDGET(menus), FALSE); } menus = g_object_get_data(G_OBJECT(box), "wide-view"); 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); /* check if the counter is enabled */ if(!purple_prefs_get_bool(OPT_COUNTER)) return; /* get counter object */ counter = g_object_get_data(G_OBJECT(box), PLUGIN_ID "-counter"); g_return_if_fail(counter == NULL); /* make counter object */ counter = gtk_label_new(NULL); gtk_widget_set_name(counter, "counter_label"); gtk_label_set_text(GTK_LABEL(counter), "0"); gtk_box_pack_end(GTK_BOX(box), counter, FALSE, FALSE, 0); gtk_widget_show_all(counter); g_object_set_data(G_OBJECT(box), PLUGIN_ID "-counter", counter); /* make separator object */ sep = gtk_vseparator_new(); gtk_box_pack_end(GTK_BOX(box), sep, FALSE, FALSE, 0); gtk_widget_show_all(sep); g_object_set_data(G_OBJECT(box), PLUGIN_ID "-sep", sep); /* connect to signals */ g_signal_connect(G_OBJECT(gtkconv->entry_buffer), "insert_text", G_CALLBACK(insert_text_cb), conv); g_signal_connect(G_OBJECT(gtkconv->entry_buffer), "delete_range", G_CALLBACK(delete_text_cb), conv); /* redraw window */ gtk_widget_queue_draw(pidgin_conv_get_window(gtkconv)->window); } static gboolean is_twitter_account(PurpleAccount *account, const char *name) { const gchar *proto = purple_account_get_protocol_id(account); if(g_strstr_len(name, 19, "twitter@twitter.com") && g_strstr_len(proto, 11, "prpl-jabber")) { return TRUE; } return FALSE; } static gboolean is_twitter_conv(PurpleConversation *conv) { g_return_val_if_fail(conv != NULL, FALSE); const char *name = purple_conversation_get_name(conv); PurpleAccount *account = purple_conversation_get_account(conv); return is_twitter_account(account, name); } static gboolean is_wassr_account(PurpleAccount *account, const char *name) { const gchar *proto = purple_account_get_protocol_id(account); if(g_strstr_len(name, 18, "wassr-bot@wassr.jp") && g_strstr_len(proto, 11, "prpl-jabber")) { return TRUE; } return FALSE; } static gboolean is_wassr_conv(PurpleConversation *conv) { g_return_val_if_fail(conv != NULL, FALSE); const char *name = purple_conversation_get_name(conv); PurpleAccount *account = purple_conversation_get_account(conv); return is_wassr_account(account, name); } static gboolean is_identica_account(PurpleAccount *account, const char *name) { const gchar *proto = purple_account_get_protocol_id(account); if(g_strstr_len(name, 16, "update@identi.ca") && g_strstr_len(proto, 11, "prpl-jabber")) { return TRUE; } return FALSE; } static gboolean is_identica_conv(PurpleConversation *conv) { g_return_val_if_fail(conv != NULL, FALSE); const char *name = purple_conversation_get_name(conv); PurpleAccount *account = purple_conversation_get_account(conv); return is_identica_account(account, name); } static gint get_service_type(PurpleConversation *conv) { gint service = unknown_service; g_return_val_if_fail(conv != NULL, unknown_service); if(is_twitter_conv(conv)) service = twitter_service; else if(is_wassr_conv(conv)) service = wassr_service; else if(is_identica_conv(conv)) service = identica_service; return service; } static void conv_created_cb(PurpleConversation *conv, gpointer null) { PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); g_return_if_fail(gtkconv != NULL); gint service = get_service_type(conv); /* only attach to twitter conversation window */ switch(service) { case twitter_service: case wassr_service: case identica_service: attach_to_conv(conv, NULL); break; default: twitter_debug("unknown service\n"); break; } } static void deleting_conv_cb(PurpleConversation *conv) { PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); g_return_if_fail(gtkconv != NULL); gint service = get_service_type(conv); GHashTable *hash = NULL; /* only attach to twitter conversation window */ switch(service) { case twitter_service: hash = icon_data_by_user; break; case wassr_service: hash = icon_data_by_user2; break; case identica_service: hash = icon_data_by_user3; break; default: twitter_debug("unknown service\n"); break; } if(hash) delete_requested_icon_marks(gtkconv, hash); } static gboolean receiving_im_cb(PurpleAccount *account, char **sender, char **buffer, PurpleConversation *conv, PurpleMessageFlags *flags, void *data) { twitter_debug("called\n"); twitter_debug("buffer = %s suppress_oops = %d\n", *buffer, suppress_oops); if(is_twitter_conv(conv) || is_wassr_conv(conv) || is_identica_conv(conv)) { /* suppress notification of incoming messages. */ if(purple_prefs_get_bool(OPT_PREVENT_NOTIFICATION)) *flags |= PURPLE_MESSAGE_SYSTEM; } /* quick hack to suppress annoying completion message from wassr */ if(is_wassr_conv(conv)) { if(strstr(*buffer, "<body>投稿完了:") || strstr(*buffer, "<body>チャンネル投稿完了:")) { twitter_debug("clearing sender and buffer\n"); g_free(*sender); *sender = NULL; g_free(*buffer); *buffer = NULL; } /* fix for parrot problem during post to a channel */ else if(strlen(wassr_post) && strstr(*buffer, wassr_post)) { twitter_debug("parrot clearing: buf = %s post = %s\n", *buffer, wassr_post); g_free(*sender); *sender = NULL; g_free(*buffer); *buffer = NULL; } } if(!is_twitter_conv(conv)) { return FALSE; } if(!suppress_oops || !purple_prefs_get_bool(OPT_SUPPRESS_OOPS)) return FALSE; if(strstr(*buffer, OOPS_MESSAGE)) { twitter_debug("clearing sender and buffer\n"); g_free(*sender); *sender = NULL; g_free(*buffer); *buffer = NULL; suppress_oops = FALSE; } return FALSE; } static void insert_icon_at_mark(GtkTextMark *requested_mark, gpointer user_data) { got_icon_data *gotdata = (got_icon_data *)user_data; gchar *user_name = gotdata->user_name; gint service = gotdata->service; GList *win_list; GtkIMHtml *target_imhtml = NULL; GtkTextBuffer *target_buffer = NULL; GtkTextIter insertion_point; gint icon_id; icon_data *data = NULL; GHashTable *hash = NULL; twitter_debug("called: service = %d\n", service); /* 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; gint service = get_service_type(purple_conv); if(service != unknown_service) { 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, &insertion_point, requested_mark); /* insert icon */ switch(service) { case twitter_service: hash = icon_data_by_user; break; case wassr_service: hash = icon_data_by_user2; break; case identica_service: hash = icon_data_by_user3; break; default: twitter_debug("unknown service\n"); } if(hash) data = (icon_data *)g_hash_table_lookup(hash, user_name); if(data) icon_id = data->icon_id; else icon_id = 0; if(!icon_id) { return; } /* insert icon actually */ if(purple_prefs_get_bool(OPT_SHOW_ICON)) { PurpleStoredImage *img = purple_imgstore_find_by_id(icon_id); const GdkPixbuf *pixbuf = purple_imgstore_get_data(img); gtk_text_buffer_insert_pixbuf(target_buffer, &insertion_point, (GdkPixbuf *)pixbuf); } gtk_text_buffer_delete_mark(target_buffer, requested_mark); requested_mark = NULL; } static void insert_requested_icon(const gchar *user_name, gint service) { icon_data *data = NULL; GList *mark_list = NULL; GHashTable *hash = NULL; twitter_debug("called\n"); switch(service) { case twitter_service: hash = icon_data_by_user; break; case wassr_service: hash = icon_data_by_user2; break; case identica_service: hash = icon_data_by_user3; break; default: twitter_debug("unknown service\n"); break; } if(hash) data = (icon_data *)g_hash_table_lookup(hash, user_name); if(!data) return; mark_list = data->request_list; got_icon_data *gotdata = g_new0(got_icon_data, 1); gotdata->user_name = g_strdup(user_name); gotdata->service = service; twitter_debug("about to insert icon\n"); if(mark_list) { g_list_foreach(mark_list, (GFunc) insert_icon_at_mark, gotdata); mark_list = g_list_remove_all(mark_list, NULL); g_list_free(mark_list); data->request_list = NULL; } g_free(gotdata->user_name); g_free(gotdata); } static void got_page_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message) { got_icon_data *gotdata = (got_icon_data *)user_data; gchar *user_name = gotdata->user_name; // gint service = gotdata->service; GMatchInfo *match_info = NULL; icon_data *data = NULL; gchar *url = NULL; data = (icon_data *)g_hash_table_lookup(icon_data_by_user3, user_name); if(!url_text) { if(data) { data->requested = FALSE; data->fetch_data = NULL; } g_free(gotdata->user_name); g_free(gotdata); return; } /* setup image url */ g_regex_match(regp[IMAGE_IDENTICA], url_text, 0, &match_info); if(!g_match_info_matches(match_info)) { twitter_debug("no image found\n"); g_match_info_free(match_info); if(data) { data->requested = FALSE; data->fetch_data = NULL; } g_free(gotdata->user_name); g_free(gotdata); return; } url = g_match_info_fetch(match_info, 1); g_match_info_free(match_info); /* request fetch image */ if(url) { /* reuse gotdata. just pass given one */ /* gotdata will be released in got_icon_cb */ data->fetch_data = purple_util_fetch_url(url, TRUE, NULL, TRUE, got_icon_cb, gotdata); g_free(url); url = NULL; twitter_debug("request %s's icon\n", user_name); } } static GdkPixbuf * make_scaled_pixbuf(const gchar *url_text, gsize len) { /* make pixbuf */ GdkPixbufLoader *loader; GdkPixbuf *src = NULL, *dest = NULL; gint size; g_return_val_if_fail(url_text != NULL, NULL); g_return_val_if_fail(len > 0, NULL); loader = gdk_pixbuf_loader_new(); gdk_pixbuf_loader_write(loader, (guchar *)url_text, len, NULL); gdk_pixbuf_loader_close(loader, NULL); src = gdk_pixbuf_loader_get_pixbuf(loader); if(!src) return NULL; size = purple_prefs_get_int(OPT_ICON_SIZE); if(size == 0) size = 48; /* twitter icon size */ dest = gdk_pixbuf_scale_simple(src, size, size, GDK_INTERP_HYPER); gdk_pixbuf_unref (src); return dest; } static void got_icon_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message) { got_icon_data *gotdata = (got_icon_data *)user_data; gchar *user_name = gotdata->user_name; gint service = gotdata->service; gint icon_id; icon_data *data = NULL; GHashTable *hash = NULL; GdkPixbuf *pixbuf = NULL; const gchar *dirname = NULL; twitter_debug("called: service = %d\n", service); switch(service) { case twitter_service: hash = icon_data_by_user; break; case wassr_service: hash = icon_data_by_user2; break; case identica_service: hash = icon_data_by_user3; break; default: twitter_debug("unknown service\n"); } if(hash) data = (icon_data *)g_hash_table_lookup(hash, user_name); /* return if download failed */ if(!url_text) { twitter_debug("downloading %s's icon failed : %s\n", user_name, error_message); if(data) data->requested = FALSE; goto fin_got_icon_cb; return; } if(data) { /* remove download request */ data->requested = FALSE; data->fetch_data = NULL; /* return if user's icon had been downloaded */ if(data->icon_id > 0) { twitter_debug("%s's icon has already been downloaded\n", user_name); goto fin_got_icon_cb; return; } } pixbuf = make_scaled_pixbuf(url_text, len); if(!pixbuf) goto fin_got_icon_cb; icon_id = purple_imgstore_add_with_id(pixbuf, gdk_pixbuf_get_rowstride(pixbuf) * gdk_pixbuf_get_height(pixbuf), user_name); if(!data) { twitter_debug("allocate icon_data (shouldn't be called)\n"); data = g_new0(icon_data, 1); } data->icon_id = icon_id; if(hash) g_hash_table_insert(hash, g_strdup(user_name), data); dirname = purple_prefs_get_string(OPT_ICON_DIR); /* store retrieved image to a file in icon dir */ if(ensure_path_exists(dirname)) { gchar *filename = NULL; gchar *path = NULL; switch(service) { case twitter_service: filename = g_strdup_printf("%s_twitter.gif", user_name); break; case wassr_service: filename = g_strdup_printf("%s_wassr.png", user_name); break; case identica_service: filename = g_strdup_printf("%s_identica.png", user_name); break; default: twitter_debug("unknown service\n"); break; } path = g_build_filename(dirname, filename, NULL); g_free(filename); filename = NULL; g_file_set_contents(path, url_text, len, NULL); } 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. */ insert_requested_icon(user_name, service); fin_got_icon_cb: g_free(gotdata->user_name); g_free(gotdata); } static void request_icon(const char *user_name, gint service) { gchar *url = NULL; /* look local icon cache for the requested icon */ gchar *path = NULL; icon_data *data = NULL; GHashTable *hash = NULL; const gchar *suffix = NULL; switch(service) { case twitter_service: hash = icon_data_by_user; suffix = "twitter"; break; case wassr_service: hash = icon_data_by_user2; suffix = "wassr"; break; case identica_service: suffix = "identica"; hash = icon_data_by_user3; break; default: twitter_debug("unknown service\n"); break; } if(!hash) return; /* since this function is called after mark_icon_for_user(), data * must exist here. */ data = (icon_data *)g_hash_table_lookup(hash, user_name); g_hash_table_insert(hash, g_strdup(user_name), data); /* if img has been registerd, just return */ if(data->icon_id > 0) return; /* check if saved file exists */ if(suffix) { gchar *filename = NULL; filename = g_strdup_printf("%s_%s.gif", user_name, suffix); path = g_build_filename( purple_prefs_get_string(OPT_ICON_DIR), filename, NULL); if(!g_file_test(path, G_FILE_TEST_EXISTS)) { g_free(path); filename = g_strdup_printf("%s_%s.png", user_name, suffix); path = g_build_filename( purple_prefs_get_string(OPT_ICON_DIR), filename, NULL); } g_free(filename); } twitter_debug("path = %s\n", path); /* build image from file, if file exists */ if(g_file_test(path, G_FILE_TEST_EXISTS)) { gchar *imgdata = NULL; size_t len; GError *err = NULL; GdkPixbuf *pixbuf = NULL; if (!g_file_get_contents(path, &imgdata, &len, &err)) { twitter_debug("Error reading %s: %s\n", path, err->message); g_error_free(err); } pixbuf = make_scaled_pixbuf(imgdata, len); if(pixbuf) { data->icon_id = purple_imgstore_add_with_id(pixbuf, gdk_pixbuf_get_rowstride(pixbuf) * gdk_pixbuf_get_height(pixbuf), user_name); twitter_debug("icon data has been loaded from file\n"); insert_requested_icon(user_name, service); } g_free(path); return; } /* Return if user's icon has been requested already. */ if(data->requested) return; else data->requested = TRUE; /* Create the URL of the user's icon. * See http://twitter.g.hatena.ne.jp/ikko615/20080107/1199703400 */ switch(service) { case twitter_service: url = g_strdup_printf("http://img.twitty.jp/twitter/user/%s/m.gif", user_name); break; case wassr_service: url = g_strdup_printf("http://wassr.jp/user/%s/profile_img.png.64", user_name); break; case identica_service: url = g_strdup_printf("http://identi.ca/%s", user_name); break; default: twitter_debug("unknown service\n"); break; } if(url) { got_icon_data *gotdata = g_new0(got_icon_data, 1); gotdata->user_name = g_strdup(user_name); gotdata->service = service; /* gotdata will be released in got_icon_cb */ if(service == identica_service) { data->fetch_data = purple_util_fetch_url(url, TRUE, NULL, TRUE, got_page_cb, gotdata); } else { data->fetch_data = purple_util_fetch_url(url, TRUE, NULL, TRUE, got_icon_cb, gotdata); } g_free(url); url = NULL; twitter_debug("request %s's icon\n", user_name); } } static void mark_icon_for_user(GtkTextMark *mark, const gchar *user_name, gint service) { icon_data *data = NULL; GHashTable *hash = NULL; twitter_debug("called\n"); switch(service) { case twitter_service: hash = icon_data_by_user; break; case wassr_service: hash = icon_data_by_user2; break; case identica_service: hash = icon_data_by_user3; break; default: twitter_debug("unknown service\n"); break; } if(hash) data = (icon_data *)g_hash_table_lookup(hash, user_name); if(!data) { data = g_new0(icon_data, 1); g_hash_table_insert(hash, g_strdup(user_name), data); } data->request_list = g_list_append(data->request_list, mark); } static gboolean displaying_im_cb(PurpleAccount *account, const char *who, char **message, PurpleConversation *conv, PurpleMessageFlags flags, void *unused) { GtkIMHtml *imhtml; GtkTextBuffer *text_buffer; gint service = get_service_type(conv); gint linenumber = 0; twitter_debug("called\n"); if(service == unknown_service) { twitter_debug("neither twitter or wassr conv\n"); return FALSE; } /* get text buffer */ imhtml = GTK_IMHTML(PIDGIN_CONVERSATION(conv)->imhtml); text_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(imhtml)); /* store number of lines */ linenumber = gtk_text_buffer_get_line_count(text_buffer); g_hash_table_insert(conv_hash, conv, GINT_TO_POINTER(linenumber)); twitter_debug("conv = %p linenumber = %d\n", conv, linenumber); return FALSE; } static void displayed_im_cb(PurpleAccount *account, const char *who, char *message, PurpleConversation *conv, PurpleMessageFlags flags) { GMatchInfo *match_info = NULL; gchar *user_name = NULL; GtkIMHtml *imhtml; GtkTextBuffer *text_buffer; GtkTextIter insertion_point; gint icon_id = 0; gint service = get_service_type(conv); icon_data *data = NULL; gint linenumber; GHashTable *hash = NULL; twitter_debug("called\n"); if(service == unknown_service) { twitter_debug("unknown service\n"); 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_name = 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 in the target line */ linenumber = GPOINTER_TO_INT(g_hash_table_lookup(conv_hash, conv)); gtk_text_buffer_get_iter_at_line(text_buffer, &insertion_point, linenumber); switch(service) { case twitter_service: hash = icon_data_by_user; break; case wassr_service: hash = icon_data_by_user2; break; case identica_service: hash = icon_data_by_user3; break; default: twitter_debug("unknown service\n"); break; } if(hash) data = g_hash_table_lookup(hash, user_name); if(data) icon_id = data->icon_id; /* if we don't have the icon for this user, put a mark instead and * request the icon */ if(!icon_id) { twitter_debug("%s's icon is not in memory.\n", user_name); mark_icon_for_user(gtk_text_buffer_create_mark( text_buffer, NULL, &insertion_point, FALSE), user_name, service); /* request to attach icon to the buffer */ request_icon(user_name, service); g_free(user_name); user_name = NULL; return; } /* if we have icon for this user, insert icon immediately */ if(purple_prefs_get_bool(OPT_SHOW_ICON)) { PurpleStoredImage *img = purple_imgstore_find_by_id(icon_id); const GdkPixbuf *pixbuf = purple_imgstore_get_data(img); gtk_text_buffer_insert_pixbuf(text_buffer, &insertion_point, (GdkPixbuf *)pixbuf); } g_free(user_name); user_name = NULL; twitter_debug("reach end of function\n"); } static gboolean load_plugin(PurplePlugin *plugin) { /* connect to signal */ purple_signal_connect(purple_conversations_get_handle(), "writing-im-msg", plugin, PURPLE_CALLBACK(writing_im_cb), NULL); purple_signal_connect(purple_conversations_get_handle(), "sending-im-msg", plugin, PURPLE_CALLBACK(sending_im_cb), NULL); purple_signal_connect(purple_conversations_get_handle(), "conversation-created", plugin, PURPLE_CALLBACK(conv_created_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(), "displaying-im-msg", plugin, PURPLE_CALLBACK(displaying_im_cb), NULL); purple_signal_connect(pidgin_conversations_get_handle(), "displayed-im-msg", plugin, PURPLE_CALLBACK(displayed_im_cb), NULL); purple_signal_connect(purple_conversations_get_handle(), "deleting-conversation", plugin, PURPLE_CALLBACK(deleting_conv_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[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); regp[CHANNEL] = g_regex_new(P_CHANNEL, 0, 0, NULL); regp[IMAGE_IDENTICA] = g_regex_new(P_IMAGE_IDENTICA, 0, 0, NULL); icon_data_by_user = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); icon_data_by_user2 = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); icon_data_by_user3 = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); conv_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, NULL); /* attach counter to the existing twitter window */ if(purple_prefs_get_bool(OPT_COUNTER)) { attach_to_window(); } /* allocate wassr_post */ wassr_post = g_new0(gchar, WASSR_POST_LEN + 1); return TRUE; } static void cancel_fetch_func(gpointer key, gpointer value, gpointer user_data) { icon_data *data = (icon_data *)value; if(!data) return; if(data->fetch_data) { purple_util_fetch_url_cancel(data->fetch_data); data->fetch_data = NULL; } if(data->request_list) { twitter_debug("somehow, request_list != NULL\n"); } return; } static gboolean unload_plugin(PurplePlugin *plugin) { twitter_debug("called\n"); /* disconnect from signal */ purple_signal_disconnect(purple_conversations_get_handle(), "writing-im-msg", plugin, PURPLE_CALLBACK(writing_im_cb)); purple_signal_disconnect(purple_conversations_get_handle(), "sending-im-msg", plugin, PURPLE_CALLBACK(sending_im_cb)); purple_signal_disconnect(purple_conversations_get_handle(), "conversation-created", plugin, PURPLE_CALLBACK(conv_created_cb)); purple_signal_disconnect(pidgin_conversations_get_handle(), "displayed-im-msg", plugin, PURPLE_CALLBACK(displayed_im_cb)); purple_signal_disconnect(purple_conversations_get_handle(), "receiving-im-msg", plugin, PURPLE_CALLBACK(receiving_im_cb)); purple_signal_disconnect(purple_conversations_get_handle(), "deleting-conversation", plugin, PURPLE_CALLBACK(deleting_conv_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[USER]); g_regex_unref(regp[USER_FIRST_LINE]); g_regex_unref(regp[USER_FORMATTED]); g_regex_unref(regp[CHANNEL]); g_regex_unref(regp[IMAGE_IDENTICA]); /* remove mark list in each hash entry */ g_hash_table_foreach(icon_data_by_user, (GHFunc)remove_marks_func, NULL); g_hash_table_foreach(icon_data_by_user2, (GHFunc)remove_marks_func, NULL); g_hash_table_foreach(icon_data_by_user3, (GHFunc)remove_marks_func, NULL); /* cancel request that has not been finished yet */ g_hash_table_foreach(icon_data_by_user, (GHFunc)cancel_fetch_func, NULL); g_hash_table_foreach(icon_data_by_user2, (GHFunc)cancel_fetch_func, NULL); g_hash_table_foreach(icon_data_by_user3, (GHFunc)cancel_fetch_func, NULL); /* destroy hash table for icon_data */ g_hash_table_destroy(icon_data_by_user); //XXX all memory freed? --yaz g_hash_table_destroy(icon_data_by_user2); g_hash_table_destroy(icon_data_by_user3); g_hash_table_destroy(conv_hash); /* detach from twitter window */ detach_from_window(); /* free wassr_post */ g_free(wassr_post); wassr_post = NULL; return TRUE; } static void counter_prefs_cb(const char *name, PurplePrefType type, gconstpointer val, gpointer data) { gboolean enabled = purple_prefs_get_bool(OPT_COUNTER); if(enabled) { attach_to_window(); } else { detach_from_window(); } } static PurplePluginPrefFrame * get_plugin_pref_frame(PurplePlugin *plugin) { /* create gtk elements for the plugin preferences */ PurplePluginPref *pref; PurplePluginPrefFrame *frame = purple_plugin_pref_frame_new(); /************************/ /* user config heading */ /************************/ pref = purple_plugin_pref_new_with_label("User Account"); purple_plugin_pref_frame_add(frame, pref); /* screenname configurations */ pref = purple_plugin_pref_new_with_name_and_label(OPT_SCREEN_NAME, "Screen Name"); purple_plugin_pref_frame_add(frame, pref); pref = purple_plugin_pref_new_with_name_and_label(OPT_PASSWORD, "Password"); purple_plugin_pref_set_masked(pref, TRUE); purple_plugin_pref_frame_add(frame, pref); /************************/ /* translation heading */ /************************/ pref = purple_plugin_pref_new_with_label("Translation Configurations"); purple_plugin_pref_frame_add(frame, pref); /* translation settings */ pref = purple_plugin_pref_new_with_name_and_label(OPT_TRANSLATE_RECIPIENT, "Translate @username to link"); purple_plugin_pref_frame_add(frame, pref); pref = purple_plugin_pref_new_with_name_and_label(OPT_TRANSLATE_SENDER, "Translate sender name to link"); purple_plugin_pref_frame_add(frame, pref); pref = purple_plugin_pref_new_with_name_and_label(OPT_TRANSLATE_CHANNEL, "Translate channel name to link (wassr only)"); purple_plugin_pref_frame_add(frame, pref); /***********************/ /* GUI config heading */ /***********************/ pref = purple_plugin_pref_new_with_label("GUI Configurations"); purple_plugin_pref_frame_add(frame, pref); /* show icon */ pref = purple_plugin_pref_new_with_name_and_label(OPT_SHOW_ICON, "Show icons in conversation"); purple_plugin_pref_frame_add(frame, pref); #if 0 /* icon size */ pref = purple_plugin_pref_new_with_name_and_label(OPT_ICON_SIZE, "Icon size in pixel"); purple_plugin_pref_set_bounds(pref, 16, 128); purple_plugin_pref_frame_add(frame, pref); /* XXX should invalidate pixbufs in memory --yaz */ #endif /* show text counter */ pref = purple_plugin_pref_new_with_name_and_label(OPT_COUNTER, "Show text counter widget"); purple_plugin_pref_frame_add(frame, pref); purple_prefs_connect_callback(plugin, OPT_COUNTER, counter_prefs_cb, NULL); /****************************/ /* advanced config heading */ /****************************/ pref = purple_plugin_pref_new_with_label("Advanced Configurations"); purple_plugin_pref_frame_add(frame, pref); /* escape pseudo command setting */ pref = purple_plugin_pref_new_with_name_and_label(OPT_ESCAPE_PSEUDO, "Escape pseudo command string"); purple_plugin_pref_frame_add(frame, pref); /* suppress oops message */ pref = purple_plugin_pref_new_with_name_and_label(OPT_SUPPRESS_OOPS, "Suppress oops message"); purple_plugin_pref_frame_add(frame, pref); /* notification setting */ pref = purple_plugin_pref_new_with_name_and_label(OPT_PREVENT_NOTIFICATION, "Do not notify incoming message"); purple_plugin_pref_frame_add(frame, pref); /******************/ /* sound heading */ /******************/ pref = purple_plugin_pref_new_with_label("Sound Configurations"); purple_plugin_pref_frame_add(frame, pref); /* sound settings for recipient */ pref = purple_plugin_pref_new_with_name_and_label(OPT_PLAYSOUND_RECIPIENT, "Play sound on a reply to the user in the recipient list"); purple_plugin_pref_frame_add(frame, pref); /* recipient list */ pref = purple_plugin_pref_new_with_name_and_label(OPT_USERLIST_RECIPIENT, "Recipient List"); purple_plugin_pref_frame_add(frame, pref); /* sound id selector */ pref = purple_plugin_pref_new_with_name_and_label(OPT_SOUNDID_RECIPIENT, "Recipient Sound"); purple_plugin_pref_set_type(pref, PURPLE_PLUGIN_PREF_CHOICE); purple_plugin_pref_add_choice(pref, "Arrive", GINT_TO_POINTER(0)); purple_plugin_pref_add_choice(pref, "Leave", GINT_TO_POINTER(1)); purple_plugin_pref_add_choice(pref, "Receive", GINT_TO_POINTER(2)); purple_plugin_pref_add_choice(pref, "Fist Receive", GINT_TO_POINTER(3)); purple_plugin_pref_add_choice(pref, "Send", GINT_TO_POINTER(4)); purple_plugin_pref_add_choice(pref, "Chat Join", GINT_TO_POINTER(5)); purple_plugin_pref_add_choice(pref, "Chat Leave", GINT_TO_POINTER(6)); purple_plugin_pref_add_choice(pref, "Chat You Say", GINT_TO_POINTER(7)); purple_plugin_pref_add_choice(pref, "Chat Someone Say", GINT_TO_POINTER(8)); purple_plugin_pref_add_choice(pref, "Pounce Default", GINT_TO_POINTER(9)); purple_plugin_pref_add_choice(pref, "Chat Nick Said", GINT_TO_POINTER(10)); purple_plugin_pref_frame_add(frame, pref); /* sound setting for sender */ pref = purple_plugin_pref_new_with_name_and_label(OPT_PLAYSOUND_SENDER, "Play sound if sender of a message is in the sender list"); purple_plugin_pref_frame_add(frame, pref); /* sender list */ pref = purple_plugin_pref_new_with_name_and_label(OPT_USERLIST_SENDER, "Sender List"); purple_plugin_pref_frame_add(frame, pref); /* sound id selector */ pref = purple_plugin_pref_new_with_name_and_label(OPT_SOUNDID_SENDER, "Sender Sound"); purple_plugin_pref_set_type(pref, PURPLE_PLUGIN_PREF_CHOICE); purple_plugin_pref_add_choice(pref, "Arrive", GINT_TO_POINTER(0)); purple_plugin_pref_add_choice(pref, "Leave", GINT_TO_POINTER(1)); purple_plugin_pref_add_choice(pref, "Receive", GINT_TO_POINTER(2)); purple_plugin_pref_add_choice(pref, "Fist Receive", GINT_TO_POINTER(3)); purple_plugin_pref_add_choice(pref, "Send", GINT_TO_POINTER(4)); purple_plugin_pref_add_choice(pref, "Chat Join", GINT_TO_POINTER(5)); purple_plugin_pref_add_choice(pref, "Chat Leave", GINT_TO_POINTER(6)); purple_plugin_pref_add_choice(pref, "Chat You Say", GINT_TO_POINTER(7)); purple_plugin_pref_add_choice(pref, "Chat Someone Say", GINT_TO_POINTER(8)); purple_plugin_pref_add_choice(pref, "Pounce Default", GINT_TO_POINTER(9)); purple_plugin_pref_add_choice(pref, "Chat Nick Said", GINT_TO_POINTER(10)); purple_plugin_pref_frame_add(frame, pref); /****************/ /* API heading */ /****************/ pref = purple_plugin_pref_new_with_label("API Based Post Configuration"); purple_plugin_pref_frame_add(frame, pref); /* post configuration */ pref = purple_plugin_pref_new_with_name_and_label(OPT_API_BASE_POST, "Post Status via API"); purple_plugin_pref_frame_add(frame, pref); return frame; } static PurplePluginUiInfo pref_info = { get_plugin_pref_frame }; static PurplePluginInfo info = { PURPLE_PLUGIN_MAGIC, PURPLE_MAJOR_VERSION, PURPLE_MINOR_VERSION, PURPLE_PLUGIN_STANDARD, /**< type */ NULL, /**< ui_req */ 0, /**< flags */ NULL, /**< deps */ PURPLE_PRIORITY_DEFAULT, /**< priority */ PLUGIN_ID, /**< id */ "Pidgin-Twitter", /**< name */ "0.7.0 alpha2d1", /**< version */ "provides useful features for twitter", /** summary */ "provides useful features for twitter", /** desc */ "Yoshiki Yazawa, mikanbako, \nKonosuke Watanabe, IWATA Ray, mojin, \nthe pidging-twitter team", /**< author */ "http://www.honeyplanet.jp/", /**< homepage */ load_plugin, /**< load */ unload_plugin, /**< unload */ NULL, /**< destroy */ NULL, /**< ui_info */ NULL, /**< extra_info */ &pref_info, /**< pref info */ NULL }; static void init_plugin(PurplePlugin *plugin) { char *dirname = NULL; g_type_init(); dirname = g_build_filename(purple_user_dir(), "pidgin-twitter", "icons", NULL); if(dirname) purple_prefs_add_string(OPT_ICON_DIR, dirname); g_free(dirname); /* add plugin preferences */ purple_prefs_add_none(OPT_PIDGINTWITTER); purple_prefs_add_bool(OPT_TRANSLATE_RECIPIENT, TRUE); purple_prefs_add_bool(OPT_TRANSLATE_SENDER, TRUE); purple_prefs_add_bool(OPT_TRANSLATE_CHANNEL, TRUE); purple_prefs_add_bool(OPT_ESCAPE_PSEUDO, TRUE); purple_prefs_add_bool(OPT_PLAYSOUND_RECIPIENT, TRUE); purple_prefs_add_bool(OPT_PLAYSOUND_SENDER, TRUE); purple_prefs_add_int(OPT_SOUNDID_RECIPIENT, PURPLE_SOUND_POUNCE_DEFAULT); purple_prefs_add_string(OPT_USERLIST_RECIPIENT, DEFAULT_LIST); purple_prefs_add_int(OPT_SOUNDID_SENDER, PURPLE_SOUND_POUNCE_DEFAULT); purple_prefs_add_string(OPT_USERLIST_SENDER, DEFAULT_LIST); purple_prefs_add_bool(OPT_COUNTER, TRUE); purple_prefs_add_bool(OPT_SUPPRESS_OOPS, TRUE); purple_prefs_add_bool(OPT_PREVENT_NOTIFICATION, FALSE); purple_prefs_add_bool(OPT_SHOW_ICON, TRUE); purple_prefs_add_bool(OPT_API_BASE_POST, FALSE); purple_prefs_add_string(OPT_SCREEN_NAME, EMPTY); purple_prefs_add_string(OPT_PASSWORD, EMPTY); purple_prefs_add_int(OPT_ICON_SIZE, 48); } PURPLE_INIT_PLUGIN(pidgin_twitter, init_plugin, info)