Mercurial > pidgin-twitter
view pidgin-twitter.c @ 149:0594a81d967d
back to 64 pixel icon for wassr. 128 pixel ones are usually better, but sometimes would be smaller image with broad white border so that resulting image would be extremely small.
author | Yoshiki Yazawa <yaz@honeyplanet.jp> |
---|---|
date | Thu, 24 Jul 2008 21:07:11 +0900 |
parents | 4e9d0fd93fb6 |
children | f2748067a8c2 |
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" /***********/ /* globals */ /***********/ #define NUM_REGP 10 static GRegex *regp[NUM_REGP]; static gboolean suppress_oops = FALSE; static GHashTable *icon_hash[3]; /* twitter, wassr, identica. */ static GHashTable *conv_hash = NULL; static GList *statuseslist = NULL; static GList *postedlist = NULL; static gchar *wassr_post = NULL; static gchar *identica_post = NULL; /*************/ /* functions */ /*************/ /* 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 gchar *html_tags[] = { "<a href=", "</a>", "<b>", "</b>", "<p>", "</p>", "<div ", "</div>", "<span ", "</span>", "<body>", "<body ", "</body>", "<i>", "</i>", "<font ", "</font>", "<br>", "<br/>", "<img ", "<html>", "<html ", "</html>", NULL }; static gchar * strip_html_markup(const gchar *src) { gchar *head, *tail; /* head and tail of html */ gchar *begin, *end; /* begin:< end:> */ gchar *html, *str; /* copied src and str to be returned */ /* gchar *vis1, *vis2; */ /* begin and end of address part */ gchar *startp; /* starting point marker */ gchar **tagp; /* tag iterator */ gchar *tmp, *tmp2; /* scratches */ g_return_val_if_fail(src != NULL, NULL); const gchar *ptr, *ent; gchar *ptr2; gint entlen; /* unescape &x; */ html = g_malloc0(strlen(src) + 1); ptr2 = html; for(ptr = src; *ptr; ) { if(*ptr == '&') { ent = purple_markup_unescape_entity(ptr, &entlen); if(ent != NULL) { while(*ent) { *ptr2++ = *ent++; } ptr += entlen; } else { *ptr2++ = *ptr++; } } else { *ptr2++ = *ptr++; } } /* for */ str = g_strdup("\0"); head = html; tail = head + strlen(html); startp = head; loop: begin = NULL; end = NULL; if(startp >= tail) { g_free(html); return str; } begin = strchr(startp, '<'); if(begin) end = strchr(begin + 1, '>'); if(!end) { tmp = g_strconcat(str, startp, NULL); g_free(str); str = tmp; g_free(html); return str; /* no corresponding >, we have done. */ } /* here, both < and > are found */ /* concatenate leading part to dest */ tmp = g_strndup(startp, begin - startp); tmp2 = g_strconcat(str, tmp, NULL); g_free(tmp); g_free(str); str = tmp2; /* find tag */ for(tagp = html_tags; *tagp; tagp++) { if(!g_ascii_strncasecmp(begin, *tagp, strlen(*tagp))) { /* we found a valid tag */ /* if tag is <a href=, extract address. */ #if 0 if(!strcmp(*tagp, "<a href=")) { vis1 = NULL; vis2 = NULL; vis1 = strchr(begin, '\''); if(vis1) vis2 = strchr(vis1+1, '\''); if(!vis1) { vis1 = strchr(begin, '\"'); if(vis1) vis2 = strchr(vis1+1, '\"'); } if(vis1 && vis2) { *vis2 = '\0'; /* generate "[ http://example.com/ ] anchor " */ tmp = g_strconcat(str, "[ ", vis1+1, " ]", " ", NULL); g_free(str); str = tmp; } startp = end + 1; goto loop; } /* <a href= */ #else if(0) { } #endif else { /* anything else: discard whole <>. */ startp = end + 1; goto loop; } } /* valid tag */ } /* no valid tag was found: copy <brabra> */ tmp = g_strndup(begin, end - begin + 1); tmp2 = g_strconcat(str, tmp, NULL); g_free(tmp); g_free(str); str = tmp2; startp = end + 1; goto loop; } /* string utilities */ 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, gboolean escape) { gchar *plain; plain = strip_html_markup(*str); g_free(*str); if(escape) { *str = g_markup_escape_text(plain, -1); g_free(plain); } else { *str = plain; } twitter_debug("result=%s\n", *str); } /**************************/ /* API base get functions */ /**************************/ /* xml parser */ static void parse_user(xmlNode *user, status_t *st) { xmlNode *nptr; for(nptr = user->children; nptr != NULL; nptr = nptr->next) { if(nptr->type == XML_ELEMENT_NODE) { if(!xmlStrcmp(nptr->name, (xmlChar *)"screen_name")) { gchar *str = (gchar *)xmlNodeGetContent(nptr); st->screen_name = g_strdup(str); xmlFree(str); } else if(!xmlStrcmp(nptr->name, (xmlChar *)"profile_image_url")) { gchar *str = (gchar *)xmlNodeGetContent(nptr); st->profile_image_url = g_strdup(str); xmlFree(str); } } } } static char *day_of_week_name[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", NULL }; static char *month_name[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL }; static void read_timestamp(const char *str, struct tm *res) { char day_of_week[4]; char month[4]; char time_offset[6]; int day, hour, minute, second, year; int i; if(str == NULL || res == NULL) return; sscanf(str, "%s %s %d %d:%d:%d %s %d", day_of_week, month, &day, &hour, &minute, &second, time_offset, &year); for(i=0; i<7; i++) { if(!strcmp(day_of_week_name[i], day_of_week)) { res->tm_wday = i; } } for(i=0; i<12; i++) { if(!strcmp(month_name[i], month)) { res->tm_mon = i; } } res->tm_mday = day; res->tm_hour = hour; res->tm_min = minute; res->tm_sec = second; res->tm_year = year - 1900; #ifndef _WIN32 int offset = atoi(time_offset); res->tm_gmtoff = -1 * (60 * 60 * offset / 100); #endif } static void parse_status(xmlNode *status, status_t *st) { xmlNode *nptr; for(nptr = status->children; nptr != NULL; nptr = nptr->next) { if(nptr->type == XML_ELEMENT_NODE) { if(!xmlStrcmp(nptr->name, (xmlChar *)"created_at")) { gchar *str = (gchar *)xmlNodeGetContent(nptr); st->created_at = g_strdup(str); /* read time stamp */ struct tm res; memset(&res, 0x00, sizeof(struct tm)); read_timestamp(str, &res); tzset(); #ifdef _WIN32 st->time = mktime(&res) - timezone; #else st->time = mktime(&res) + res.tm_gmtoff; #endif xmlFree(str); } else if(!xmlStrcmp(nptr->name, (xmlChar *)"id")) { gchar *str = (gchar *)xmlNodeGetContent(nptr); st->id = atoi(str); xmlFree(str); } else if(!xmlStrcmp(nptr->name, (xmlChar *)"text")) { gchar *str = (gchar *)xmlNodeGetContent(nptr); st->text = g_strdup(str); xmlFree(str); } else if(!xmlStrcmp(nptr->name, (xmlChar *)"user")) { parse_user(nptr, st); } } } } static void free_status(status_t *st) { g_free(st->created_at); g_free(st->text); g_free(st->screen_name); g_free(st->profile_image_url); } static gboolean is_posted_message(status_t *status) { GList *pp; gboolean rv = FALSE; for(pp = postedlist; pp; pp=pp->next) { status_t *posted = (status_t *)pp->data; if(posted->id == status->id) { rv = TRUE; free_status(posted); g_free(pp->data); pp->data = NULL; } } postedlist = g_list_remove_all(postedlist, NULL); return rv; } static void get_status_with_api_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, size_t len, const gchar *error_message) { xmlDocPtr doc; xmlNode *nptr, *nptr2; static guint lastid = 0; PurpleConversation *conv; GList *stp; const gchar *start; g_return_if_fail(url_text != NULL); conv = (PurpleConversation *)user_data; if(!conv) return; /* skip to the beginning of xml */ start = strstr(url_text, "<?xml"); doc = xmlRecoverMemory(start, len - (start - url_text)); if(doc == NULL) return; for(nptr = doc->children; nptr != NULL; nptr = nptr->next) { if(nptr->type == XML_ELEMENT_NODE && !xmlStrcmp(nptr->name, (xmlChar *)"statuses")) { for(nptr2 = nptr->children; nptr2 != NULL; nptr2 = nptr2->next) { if(nptr2->type == XML_ELEMENT_NODE && !xmlStrcmp(nptr2->name, (xmlChar *)"status")) { status_t *st = g_new0(status_t, 1); statuseslist = g_list_prepend(statuseslist, st); parse_status(nptr2, st); } } } } /* process statuseslist */ for(stp = statuseslist; stp; stp=stp->next) { status_t *st = (status_t *)stp->data; if(st->id > lastid && !is_posted_message(st)) { gchar *msg = NULL; msg = g_strdup_printf("%s: %s", st->screen_name, st->text); purple_conv_im_write(conv->u.im, "twitter@twitter.com", msg, PURPLE_MESSAGE_RECV, st->time); lastid = st->id; g_free(msg); } free_status(st); g_free(stp->data); stp->data = NULL; } statuseslist = g_list_remove_all(statuseslist, NULL); } /* status fetching function. it will be called periodically. */ static gboolean get_status_with_api(gpointer data) { /* fetch friends time line */ char *request, *header; char *basic_auth, *basic_auth_encoded; twitter_debug("called\n"); PurpleConversation *conv = (PurpleConversation *)data; if(!conv) return FALSE; /* cease fetch */ const char *screen_name = purple_prefs_get_string(OPT_SCREEN_NAME_TWITTER); const char *password = purple_prefs_get_string(OPT_PASSWORD_TWITTER); if (!screen_name || !password || !screen_name[0] || !password[0]) { twitter_debug("screen_name or password is empty\n"); return TRUE; } /* auth */ 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); /* header */ header = g_strdup_printf(TWITTER_STATUS_GET, basic_auth_encoded); request = g_strconcat(header, TWITTER_STATUS_TERMINATOR, NULL); /* invoke fetch */ purple_util_fetch_url_request(TWITTER_BASE_URL, FALSE, NULL, TRUE, request, TRUE, get_status_with_api_cb, data); g_free(header); g_free(basic_auth_encoded); g_free(request); return TRUE; } /***************************/ /* API base post functions */ /***************************/ 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 = (twitter_message_t *)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); /* cache message ID that posted via API */ gchar *start = NULL; xmlDocPtr doc; xmlNode *nptr; start = strstr(url_text, "<?xml"); if(!start) goto fin; doc = xmlRecoverMemory(start, len - (start - url_text)); if(doc == NULL) return; /* enqueue posted message to postedlist */ for(nptr = doc->children; nptr != NULL; nptr = nptr->next) { if(nptr->type == XML_ELEMENT_NODE && !xmlStrcmp(nptr->name, (xmlChar *)"status")) { status_t *st = g_new0(status_t, 1); postedlist = g_list_prepend(postedlist, st); parse_status(nptr, st); } } } 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_TWITTER); const char *password = purple_prefs_get_string(OPT_PASSWORD_TWITTER); 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); } /***********************/ /* intrinsic functions */ /***********************/ static gboolean sending_im_cb(PurpleAccount *account, char *recipient, char **buffer, void *data) { int utflen, bytes; gboolean twitter_ac = FALSE, wassr_ac = FALSE, identica_ac = FALSE; twitter_debug("called\n"); twitter_ac = is_twitter_account(account, recipient); wassr_ac = is_wassr_account(account, recipient); identica_ac = is_identica_account(account, recipient); /* strip all markups */ if(twitter_ac || wassr_ac || identica_ac) strip_markup(buffer, TRUE); if(wassr_ac) { /* store sending message to address parrot problem */ g_strlcpy(wassr_post, *buffer, WASSR_POST_LEN); twitter_debug("wassr parrot pushed:%s\n", *buffer); } if(identica_ac) { /* store sending message to address parrot problem */ g_strlcpy(identica_post, *buffer, IDENTICA_POST_LEN); twitter_debug("identica parrot pushed:%s\n", *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; } g_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; } g_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; g_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); gint regp_id; data->which = which; data->service = service; regp_id = which; /* for future use --yaz */ newstr = g_regex_replace_eval(regp[regp_id], /* 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 = NULL; switch(service) { case twitter_service: screen_name = purple_prefs_get_string(OPT_SCREEN_NAME_TWITTER); break; case wassr_service: screen_name = purple_prefs_get_string(OPT_SCREEN_NAME_WASSR); break; case identica_service: screen_name = purple_prefs_get_string(OPT_SCREEN_NAME_IDENTICA); break; } if (screen_name) { m = g_strdup_printf("%s: %s", screen_name, *buffer); g_free(*buffer); *buffer = m; } } /* strip all markups */ strip_markup(buffer, TRUE); /* 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(service == twitter_service && 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: detach_from_conv(conv, NULL); break; 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) return; if(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; twitter_debug("called\n"); /* 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: attach_to_conv(conv, NULL); break; 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); twitter_debug("called\n"); g_return_if_fail(gtkconv != NULL); gint service = get_service_type(conv); /* only attach to twitter conversation window */ switch(service) { case twitter_service: /* api based retrieve */ /* xxx should configurable */ if(purple_prefs_get_bool(OPT_API_BASE_POST)) { get_status_with_api((gpointer)conv); g_timeout_add_seconds( purple_prefs_get_int(OPT_API_BASE_GET_INTERVAL), get_status_with_api, (gpointer)conv); } attach_to_conv(conv, NULL); break; 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); twitter_debug("called\n"); 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: if(purple_prefs_get_bool(OPT_API_BASE_POST)) g_source_remove_by_user_data((gpointer)conv); hash = icon_hash[twitter_service]; break; case wassr_service: hash = icon_hash[wassr_service]; break; case identica_service: hash = icon_hash[identica_service]; 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); gint service = get_service_type(conv); if(service != unknown_service) { /* 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(service == wassr_service) { 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(wassr_post && 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(service == identica_service) { if(identica_post && strlen(identica_post) && strstr(*buffer, identica_post)) { twitter_debug("identica parrot clearing: buf = %s post = %s\n", *buffer, identica_post); g_free(*sender); *sender = NULL; g_free(*buffer); *buffer = NULL; } } if(service != twitter_service) { return FALSE; } /* if we use api, discard incoming IM message. XXX too wild? */ if(purple_prefs_get_bool(OPT_API_BASE_POST)) { g_free(*sender); *sender = NULL; g_free(*buffer); *buffer = NULL; } 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_hash[twitter_service]; break; case wassr_service: hash = icon_hash[wassr_service]; break; case identica_service: hash = icon_hash[identica_service]; 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_hash[twitter_service]; break; case wassr_service: hash = icon_hash[wassr_service]; break; case identica_service: hash = icon_hash[identica_service]; 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); } /* this function will be called when profile page has been retrieved */ 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; gint regp_id = -1; if(service == twitter_service) { data = (icon_data *)g_hash_table_lookup( icon_hash[twitter_service], user_name); regp_id = IMAGE_TWITTER; } if(service == identica_service) { data = (icon_data *)g_hash_table_lookup( icon_hash[identica_service], user_name); regp_id = IMAGE_IDENTICA; } 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[regp_id], url_text, 0, &match_info); if(!g_match_info_matches(match_info)) { twitter_debug("no image url 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); gchar *slash = strrchr(url, '/'); *slash = '\0'; gchar *tmp = g_strdup_printf("%s/%s", url, purple_url_encode(slash+1)); gchar *startp = slash + 1; gchar *ext = NULL; do { ext = strrchr(startp, '.'); if(ext) { if(!strcasecmp(ext, ".jpg") || !strcasecmp(ext, ".jpeg")) { data->img_type = "jpg"; break; } else if(!strcasecmp(ext, ".png")) { data->img_type = "png"; break; } else if(!strcasecmp(ext, ".gif")) { data->img_type = "gif"; break; } startp = ext; } } while(ext); g_free(url); url = tmp; twitter_debug("requested_url=%s\n", url); /* 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); twitter_debug("request %s's icon\n", user_name); g_free(url); } } 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 gchar *ext_list[] = { "png", "gif", "jpg", NULL }; /* this function will be called when requested icon has been retrieved */ 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_hash[twitter_service]; break; case wassr_service: hash = icon_hash[wassr_service]; break; case identica_service: hash = icon_hash[identica_service]; 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; } 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; } } 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.%s", user_name, data->img_type); break; case wassr_service: filename = g_strdup_printf("%s_wassr.%s", user_name, data->img_type); break; case identica_service: filename = g_strdup_printf("%s_identica.%s", user_name, data->img_type); 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_hash[twitter_service]; suffix = "twitter"; break; case wassr_service: hash = icon_hash[wassr_service]; suffix = "wassr"; break; case identica_service: suffix = "identica"; hash = icon_hash[identica_service]; 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; gchar **extp; for(extp = ext_list; *extp; extp++) { filename = g_strdup_printf("%s_%s.%s", user_name, suffix, *extp); path = g_build_filename(purple_prefs_get_string(OPT_ICON_DIR), filename, NULL); twitter_debug("path = %s\n", path); /* make 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); data->img_type = *extp; twitter_debug("icon data has been loaded from file\n"); insert_requested_icon(user_name, service); } g_free(path); return; } } /* for */ } /* suffix */ /* Return if user's icon has been requested already. */ if(data->requested) return; else data->requested = TRUE; /* Create the URL for an user's icon. */ switch(service) { case twitter_service: url = g_strdup_printf("http://twitter.com/%s", 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 || service == twitter_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_hash[twitter_service]; break; case wassr_service: hash = icon_hash[wassr_service]; break; case identica_service: hash = icon_hash[identica_service]; break; default: twitter_debug("unknown service\n"); break; } if(hash) data = (icon_data *)g_hash_table_lookup(hash, user_name); /* proper place to allocate icon_data */ 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_hash[twitter_service]; break; case wassr_service: hash = icon_hash[wassr_service]; break; case identica_service: hash = icon_hash[identica_service]; 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 void signed_on_cb(PurpleConnection *gc) { PurpleBuddyList *list = purple_get_blist(); PurpleBlistNode *gnode, *cnode, *bnode; PurpleBuddy *b; twitter_debug("called\n"); if(!purple_prefs_get_bool(OPT_API_BASE_POST)) return; if (!list) return; twitter_debug("scan list\n"); for (gnode = list->root; gnode; gnode = gnode->next) { if(!PURPLE_BLIST_NODE_IS_GROUP(gnode)) continue; for(cnode = gnode->child; cnode; cnode = cnode->next) { if(!PURPLE_BLIST_NODE_IS_CONTACT(cnode)) continue; for(bnode = cnode->child; bnode; bnode = bnode->next) { if(!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) continue; b = (PurpleBuddy *)bnode; if(!PURPLE_BUDDY_IS_ONLINE(b)) { const char *name; PurpleAccount *account; name = purple_buddy_get_name(b); account = purple_buddy_get_account(b); if (is_twitter_account(account, name)) { PurpleConversation *gconv; gconv = purple_find_conversation_with_account( PURPLE_CONV_TYPE_IM, name, account); if (!gconv) { gconv = purple_conversation_new( PURPLE_CONV_TYPE_IM, account, name); } } } } } } } static void api_base_post_cb(const char *name, PurplePrefType type, gconstpointer value, gpointer data) { signed_on_cb(NULL); } static gboolean load_plugin(PurplePlugin *plugin) { int i; /* 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); purple_signal_connect(purple_connections_get_handle(), "signed-on", plugin, PURPLE_CALLBACK(signed_on_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); regp[IMAGE_TWITTER] = g_regex_new(P_IMAGE_TWITTER, 0, 0, NULL); for(i = twitter_service; i <= identica_service; i++) { icon_hash[i] = 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); /* allocate identica_post */ identica_post = g_new0(gchar, IDENTICA_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"); } } static void cleanup_hash_entry_func(gpointer key, gpointer value, gpointer user_data) { remove_marks_func(key, value, user_data); cancel_fetch_func(key, value, user_data); } static gboolean unload_plugin(PurplePlugin *plugin) { int i; 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(), "displaying-im-msg", plugin, PURPLE_CALLBACK(displaying_im_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)); purple_signal_disconnect(purple_connections_get_handle(), "signed-on", plugin, PURPLE_CALLBACK(signed_on_cb)); /* unreference regp */ for(i = 0; i < NUM_REGP; i++) { g_regex_unref(regp[i]); } /* remove mark list in each hash entry */ /* cancel request that has not been finished yet */ for(i = twitter_service; i <= identica_service; i++) { /* delete mark list and stop requeset for each hash table */ g_hash_table_foreach(icon_hash[i], (GHFunc)cleanup_hash_entry_func, NULL); /* destroy hash table for icon_data */ g_hash_table_destroy(icon_hash[i]); } g_hash_table_destroy(conv_hash); /* detach from twitter window */ detach_from_window(); /* free wassr_post */ g_free(wassr_post); wassr_post = NULL; /* free identica_post */ g_free(identica_post); identica_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 void invalidate_icon_data_func(gpointer key, gpointer value, gpointer user_data) { icon_data *data = (icon_data *)value; g_return_if_fail(data != NULL); g_return_if_fail(data->icon_id >0); /* img->data is managed by gdkpixbuf, so we should not free here. */ #if 0 PurpleStoredImage *img = purple_imgstore_find_by_id(data->icon_id); if(img) purple_imgstore_unref_by_id(data->icon_id); #endif data->icon_id = 0; } static void icon_size_prefs_cb(const char *name, PurplePrefType type, gconstpointer val, gpointer data) { int i; /* invalidate icon cache */ for(i = twitter_service; i <= identica_service; i++) { g_hash_table_foreach(icon_hash[i], (GHFunc)invalidate_icon_data_func, NULL); } } static void text_changed_cb(gpointer *data) { const gchar *text; gchar *pref = (gchar *)g_object_get_data(G_OBJECT(data), "pref"); text = gtk_entry_get_text(GTK_ENTRY(data)); purple_prefs_set_string(pref, text); } static void bool_toggled_cb(gpointer *data) { gchar *pref = (gchar *)g_object_get_data(G_OBJECT(data), "pref"); gboolean value = purple_prefs_get_bool(pref); purple_prefs_set_bool(pref, !value); } static void spin_changed_cb(gpointer *data) { gchar *pref = (gchar *)g_object_get_data(G_OBJECT(data), "pref"); twitter_debug("called\n"); purple_prefs_set_int( pref, gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(data))); } static void combo_changed_cb(gpointer *data) { gint position; gchar *pref = (gchar *)g_object_get_data(G_OBJECT(data), "pref"); position = gtk_combo_box_get_active(GTK_COMBO_BOX(data)); purple_prefs_set_int(pref, position); } static GtkWidget * prefs_get_frame(PurplePlugin *plugin) { GtkBuilder *builder; GError *err = NULL; gchar *filename; GtkWidget *window, *notebook, *e; const gchar *text; builder = gtk_builder_new(); #ifdef _WIN32 filename = g_build_filename(purple_user_dir(), "pidgin-twitter", "prefs.ui", NULL); #else filename = g_build_filename(DATADIR, "pidgin-twitter", "prefs.ui", NULL); #endif gtk_builder_add_from_file(builder, filename, &err); if(err) { twitter_debug("%s\n", err->message); return NULL; } g_free(filename); gtk_builder_connect_signals(builder, NULL); window = GTK_WIDGET(gtk_builder_get_object(builder, "prefswindow")); notebook = GTK_WIDGET(gtk_builder_get_object(builder, "prefsnotebook")); gtk_container_remove(GTK_CONTAINER(window), notebook); g_signal_connect(notebook, "destroy", G_CALLBACK(gtk_widget_destroyed), ¬ebook); /* connect to signals */ /* account page */ e = GTK_WIDGET(gtk_builder_get_object (builder, "account_twitter")); g_object_set_data(G_OBJECT(e), "pref", OPT_SCREEN_NAME_TWITTER); text = purple_prefs_get_string(OPT_SCREEN_NAME_TWITTER); gtk_entry_set_text(GTK_ENTRY(e), text); g_signal_connect(e, "changed", G_CALLBACK(text_changed_cb), &e); e = GTK_WIDGET(gtk_builder_get_object (builder, "account_wassr")); g_object_set_data(G_OBJECT(e), "pref", OPT_SCREEN_NAME_WASSR); text = purple_prefs_get_string(OPT_SCREEN_NAME_WASSR); gtk_entry_set_text(GTK_ENTRY(e), text); g_signal_connect(e, "changed", G_CALLBACK(text_changed_cb), &e); e = GTK_WIDGET(gtk_builder_get_object (builder, "account_identica")); g_object_set_data(G_OBJECT(e), "pref", OPT_SCREEN_NAME_IDENTICA); text = purple_prefs_get_string(OPT_SCREEN_NAME_IDENTICA); gtk_entry_set_text(GTK_ENTRY(e), text); g_signal_connect(e, "changed", G_CALLBACK(text_changed_cb), &e); e = GTK_WIDGET(gtk_builder_get_object (builder, "account_api")); g_object_set_data(G_OBJECT(e), "pref", OPT_API_BASE_POST); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), purple_prefs_get_bool(OPT_API_BASE_POST)); g_signal_connect(e, "toggled", G_CALLBACK(bool_toggled_cb), &e); purple_prefs_connect_callback(plugin, OPT_API_BASE_POST, /* xxx divide? */ api_base_post_cb, NULL); e = GTK_WIDGET(gtk_builder_get_object (builder, "account_api_password")); g_object_set_data(G_OBJECT(e), "pref", OPT_PASSWORD_TWITTER); gtk_entry_set_visibility(GTK_ENTRY(e), FALSE); if (gtk_entry_get_invisible_char(GTK_ENTRY(e)) == '*') gtk_entry_set_invisible_char(GTK_ENTRY(e), PIDGIN_INVISIBLE_CHAR); text = purple_prefs_get_string(OPT_PASSWORD_TWITTER); gtk_entry_set_text(GTK_ENTRY(e), text); g_signal_connect(e, "changed", G_CALLBACK(text_changed_cb), &e); /* translation page */ e = GTK_WIDGET(gtk_builder_get_object (builder, "translation_recipient")); g_object_set_data(G_OBJECT(e), "pref", OPT_TRANSLATE_RECIPIENT); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), purple_prefs_get_bool(OPT_TRANSLATE_RECIPIENT)); g_signal_connect(e, "toggled", G_CALLBACK(bool_toggled_cb), &e); e = GTK_WIDGET(gtk_builder_get_object (builder, "translation_sender")); g_object_set_data(G_OBJECT(e), "pref", OPT_TRANSLATE_SENDER); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), purple_prefs_get_bool(OPT_TRANSLATE_SENDER)); g_signal_connect(e, "toggled", G_CALLBACK(bool_toggled_cb), &e); e = GTK_WIDGET(gtk_builder_get_object (builder, "translation_channel")); g_object_set_data(G_OBJECT(e), "pref", OPT_TRANSLATE_CHANNEL); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), purple_prefs_get_bool(OPT_TRANSLATE_CHANNEL)); g_signal_connect(e, "toggled", G_CALLBACK(bool_toggled_cb), &e); /* utility page */ e = GTK_WIDGET(gtk_builder_get_object (builder, "utility_show_icon")); g_object_set_data(G_OBJECT(e), "pref", OPT_SHOW_ICON); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), purple_prefs_get_bool(OPT_SHOW_ICON)); g_signal_connect(e, "toggled", G_CALLBACK(bool_toggled_cb), &e); /* setup spin */ e = GTK_WIDGET(gtk_builder_get_object (builder, "utility_icon_size_spin")); g_object_set_data(G_OBJECT(e), "pref", OPT_ICON_SIZE); GtkSpinButton *spin = GTK_SPIN_BUTTON(e); int value = purple_prefs_get_int(OPT_ICON_SIZE); twitter_debug("spin value = %d\n", value); GtkObject *adjust = gtk_adjustment_new(value, 16, 128, 4, 1, 1); gtk_spin_button_set_adjustment(spin, GTK_ADJUSTMENT(adjust)); gtk_widget_set_size_request(GTK_WIDGET(spin), 50, -1); if(value == 0) { value = DEFAULT_ICON_SIZE; purple_prefs_set_int(OPT_ICON_SIZE, value); } gtk_spin_button_set_value(GTK_SPIN_BUTTON(e), (gdouble)value); g_signal_connect(e, "value-changed", G_CALLBACK(spin_changed_cb), &e); purple_prefs_connect_callback(plugin, OPT_ICON_SIZE, icon_size_prefs_cb, NULL); e = GTK_WIDGET(gtk_builder_get_object (builder, "utility_counter")); g_object_set_data(G_OBJECT(e), "pref", OPT_COUNTER); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), purple_prefs_get_bool(OPT_COUNTER)); g_signal_connect(e, "toggled", G_CALLBACK(bool_toggled_cb), &e); purple_prefs_connect_callback(plugin, OPT_COUNTER, /* xxx */ counter_prefs_cb, NULL); e = GTK_WIDGET(gtk_builder_get_object (builder, "utility_pseudo")); g_object_set_data(G_OBJECT(e), "pref", OPT_ESCAPE_PSEUDO); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), purple_prefs_get_bool(OPT_ESCAPE_PSEUDO)); g_signal_connect(e, "toggled", G_CALLBACK(bool_toggled_cb), &e); e = GTK_WIDGET(gtk_builder_get_object (builder, "utility_oops")); g_object_set_data(G_OBJECT(e), "pref", OPT_SUPPRESS_OOPS); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), purple_prefs_get_bool(OPT_SUPPRESS_OOPS)); g_signal_connect(e, "toggled", G_CALLBACK(bool_toggled_cb), &e); e = GTK_WIDGET(gtk_builder_get_object (builder, "utility_notify")); g_object_set_data(G_OBJECT(e), "pref", OPT_PREVENT_NOTIFICATION); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), purple_prefs_get_bool(OPT_PREVENT_NOTIFICATION)); g_signal_connect(e, "toggled", G_CALLBACK(bool_toggled_cb), &e); /* sound page */ e = GTK_WIDGET(gtk_builder_get_object (builder, "sound_recip_check")); g_object_set_data(G_OBJECT(e), "pref", OPT_PLAYSOUND_RECIPIENT); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), purple_prefs_get_bool(OPT_PLAYSOUND_RECIPIENT)); g_signal_connect(e, "toggled", G_CALLBACK(bool_toggled_cb), &e); e = GTK_WIDGET(gtk_builder_get_object (builder, "sound_recip_list")); g_object_set_data(G_OBJECT(e), "pref", OPT_USERLIST_RECIPIENT); text = purple_prefs_get_string(OPT_USERLIST_RECIPIENT); gtk_entry_set_text(GTK_ENTRY(e), text); g_signal_connect(e, "changed", G_CALLBACK(text_changed_cb), &e); /* setup combobox */ e = GTK_WIDGET(gtk_builder_get_object (builder, "sound_recip_combo")); gtk_combo_box_set_active(GTK_COMBO_BOX(e), purple_prefs_get_int(OPT_SOUNDID_RECIPIENT)); g_object_set_data(G_OBJECT(e), "pref", OPT_SOUNDID_RECIPIENT); g_signal_connect(e, "changed", G_CALLBACK(combo_changed_cb), &e); e = GTK_WIDGET(gtk_builder_get_object (builder, "sound_send_check")); g_object_set_data(G_OBJECT(e), "pref", OPT_PLAYSOUND_SENDER); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), purple_prefs_get_bool(OPT_PLAYSOUND_SENDER)); g_signal_connect(e, "toggled", G_CALLBACK(bool_toggled_cb), &e); e = GTK_WIDGET(gtk_builder_get_object (builder, "sound_send_list")); g_object_set_data(G_OBJECT(e), "pref", OPT_USERLIST_SENDER); text = purple_prefs_get_string(OPT_USERLIST_SENDER); gtk_entry_set_text(GTK_ENTRY(e), text); g_signal_connect(e, "changed", G_CALLBACK(text_changed_cb), &e); /* setup combobox */ e = GTK_WIDGET(gtk_builder_get_object (builder, "sound_send_combo")); gtk_combo_box_set_active(GTK_COMBO_BOX(e), purple_prefs_get_int(OPT_SOUNDID_RECIPIENT)); g_object_set_data(G_OBJECT(e), "pref", OPT_SOUNDID_SENDER); g_signal_connect(e, "changed", G_CALLBACK(combo_changed_cb), &e); gtk_widget_show_all(notebook); return notebook; } static PidginPluginUiInfo ui_info = { prefs_get_frame, 0, /* page number - reserved */ NULL, /* reserved 1 */ NULL, /* reserved 2 */ NULL, /* reserved 3 */ NULL /* reserved 4 */ }; static PurplePluginInfo info = { PURPLE_PLUGIN_MAGIC, PURPLE_MAJOR_VERSION, PURPLE_MINOR_VERSION, PURPLE_PLUGIN_STANDARD, /**< type */ PIDGIN_PLUGIN_TYPE, /**< ui_req */ 0, /**< flags */ NULL, /**< deps */ PURPLE_PRIORITY_DEFAULT, /**< priority */ PLUGIN_ID, /**< id */ "Pidgin-Twitter", /**< name */ "0.7.0", /**< 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 */ &ui_info, /**< ui_info */ NULL, /**< extra_info */ NULL, /**< 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_TWITTER, EMPTY); purple_prefs_add_string(OPT_PASSWORD_TWITTER, EMPTY); purple_prefs_add_int(OPT_ICON_SIZE, DEFAULT_ICON_SIZE); purple_prefs_add_string(OPT_SCREEN_NAME_WASSR, EMPTY); purple_prefs_add_string(OPT_SCREEN_NAME_IDENTICA, EMPTY); purple_prefs_add_int(OPT_API_BASE_GET_INTERVAL, TWITTER_DEFAULT_INTERVAL); } PURPLE_INIT_PLUGIN(pidgin_twitter, init_plugin, info)