Mercurial > pidgin-twitter
view pidgin-twitter.c @ 224:1fcd0e589b9e
revised the recipient pattern. new pattern can handle:
- recipient name surrounded by non ascii characters without any space.
- recipient name accidentally escaped with '.' such as ".@foo".
author | Yoshiki Yazawa <yaz@honeyplanet.jp> |
---|---|
date | Sat, 06 Sep 2008 23:47:17 +0900 |
parents | c3efae72f72a |
children | 8da85ae0aa2c |
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_REGPS 13 #define NUM_SERVICES 3 /* twitter, wassr, identica. */ static GRegex *regp[NUM_REGPS]; static gboolean suppress_oops = FALSE; static GHashTable *icon_hash[NUM_SERVICES]; static GHashTable *conv_hash = NULL; static GList *statuseslist = NULL; static GList *postedlist = NULL; static GList *wassr_parrot_list = NULL; static GList *identica_parrot_list = NULL; #ifdef _WIN32 static gboolean blink_state = FALSE; static gboolean blink_modified = FALSE; #endif static struct _source { guint id; PurpleConversation *conv; } source; #ifndef _WIN32 extern gchar *sanitize_utf(const gchar *msg, gsize len, gsize *newlen) __attribute__ ((weak)); #endif /*************/ /* 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 * twitter_memrchr(const gchar *s, int c, size_t n) { int nn = n; g_return_val_if_fail(s != NULL, NULL); while(nn+1) { if((int)*(s+nn) == c) return (gchar *)(s+nn); nn--; } return NULL; } 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 string 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; } end = strchr(startp, '>'); if(end) { begin = twitter_memrchr(startp, '<', end - startp); if(begin < startp) begin = NULL; if(!begin) { /* '>' found but no corresponding '<' */ tmp = g_strndup(startp, end - startp + 1); /* concat until '>' */ tmp2 = g_strconcat(str, tmp, NULL); g_free(str); str = tmp2; startp = end + 1; goto loop; } } else { /* neither '>' nor '<' were found */ tmp = g_strconcat(str, startp, NULL); /* concat the rest */ g_free(str); str = tmp; g_free(html); return str; } /* 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 { /* anything else: discard whole <>. */ startp = end + 1; goto loop; } #else /* anything else: discard whole <>. */ startp = end + 1; goto loop; #endif } /* 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 gchar *day_of_week_name[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL }; static gchar *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, guint lastid) { GList *pp = g_list_first(postedlist); gboolean rv = FALSE; while(pp) { GList *next; status_t *posted = (status_t *)pp->data; next = g_list_next(pp); if(posted->id == status->id) { rv = TRUE; } if(posted->id <= lastid) { free_status(posted); g_free(pp->data); postedlist = g_list_delete_link(postedlist, pp); } pp = next; } 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; #ifdef _WIN32 /* suppress notification of incoming messages. */ if(purple_prefs_get_bool(OPT_PREVENT_NOTIFICATION)) { if(!blink_modified) { blink_modified = TRUE; blink_state = purple_prefs_get_bool(OPT_PIDGIN_BLINK_IM); purple_prefs_set_bool(OPT_PIDGIN_BLINK_IM, FALSE); } } #endif 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); } } } } xmlFreeDoc(doc); xmlCleanupParser(); /* process statuseslist */ stp = g_list_first(statuseslist); while(stp) { GList *next; status_t *st = (status_t *)stp->data; next = g_list_next(stp); if(st->id > lastid && !is_posted_message(st, lastid)) { gchar *msg = NULL; gchar *sender = NULL; sender = g_strdup("twitter@twitter.com"); PurpleMessageFlags flag = PURPLE_MESSAGE_RECV; msg = g_strdup_printf("%s: %s", st->screen_name, st->text); /* apply filter*/ if(purple_prefs_get_bool(OPT_FILTER)) { apply_filter(&sender, &msg, &flag, twitter_service); } if(sender && msg) { purple_conv_im_write(conv->u.im, sender, msg, flag, st->time); } lastid = st->id; g_free(msg); } free_status(st); g_free(stp->data); statuseslist = g_list_delete_link(statuseslist, stp); stp = next; } } /* 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"); /* if disabled, just return */ if(!purple_prefs_get_bool(OPT_API_BASE_POST)) return TRUE; 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, "\r\n", 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 based 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); } } xmlFreeDoc(doc); xmlCleanupParser(); } 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, "\r\n", status, "\r\n", 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) { gchar *tmp, *plain; gsize dummy; tmp = strip_html_markup(*buffer); #ifndef _WIN32 if(sanitize_utf) { plain = sanitize_utf(tmp, -1, &dummy); g_free(tmp); } else #endif plain = tmp; if(wassr_ac) { /* store sending message to address parrot problem */ wassr_parrot_list = g_list_prepend(wassr_parrot_list, g_strdup(plain)); twitter_debug("wassr parrot pushed:%s\n", plain); } else if(identica_ac) { /* store sending message to address parrot problem */ identica_parrot_list = g_list_prepend(identica_parrot_list, g_strdup(plain)); twitter_debug("identica parrot pushed:%s\n", plain); } g_free(*buffer); *buffer = g_markup_escape_text(plain, -1); g_free(plain); } /* 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[SUBST_BUF_SIZE]; twitter_debug("which = %d service = %d\n", which, service); if(which == RECIPIENT) { gchar *match1 = g_match_info_fetch(match_info, 1); /* preceding \s */ gchar *match2 = g_match_info_fetch(match_info, 2); /* recipient */ 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, SUBST_BUF_SIZE, format, match1 ? match1: "", match2, match2); g_free(match1); g_free(match2); } 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, SUBST_BUF_SIZE, format, match1 ? match1: "", match2, match2); g_free(match1); g_free(match2); } else if(which == CHANNEL_WASSR && service == wassr_service) { 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, SUBST_BUF_SIZE, format, match1 ? match1: "", match2, match2); g_free(match1); g_free(match2); } else if(which == TAG_IDENTICA && service == identica_service) { gchar *match = g_match_info_fetch(match_info, 1); const gchar *format = TAG_FORMAT_IDENTICA; g_snprintf(sub, SUBST_BUF_SIZE, format, match, match); g_free(match); } 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 = NULL; gchar **candidates = NULL, **candidate = NULL; list = purple_prefs_get_string(which ? OPT_USERLIST_SENDER : OPT_USERLIST_RECIPIENT); g_return_if_fail(list != NULL); if(strstr(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 myself */ 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(purple_prefs_get_bool(OPT_TRANSLATE_RECIPIENT)) { translate(buffer, RECIPIENT, service); } if(service == wassr_service && purple_prefs_get_bool(OPT_TRANSLATE_CHANNEL)) { translate(buffer, CHANNEL_WASSR, service); } if(service == identica_service && purple_prefs_get_bool(OPT_TRANSLATE_CHANNEL)) { translate(buffer, TAG_IDENTICA, service); } /* escape pseudo command (to show the same result as 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; g_return_if_fail(gtkconv != NULL); switch(service) { case twitter_service: case identica_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 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; g_return_if_fail(gtkconv != NULL); switch(service) { case twitter_service: case identica_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 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) return; if(data->request_list) mark_list = data->request_list; /* remove the marks in its GtkTextBuffers */ current = g_list_first(mark_list); while(current) { GtkTextMark *current_mark = current->data; GtkTextBuffer *current_text_buffer = gtk_text_mark_get_buffer(current_mark); GList *next; next = g_list_next(current); 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; mark_list = g_list_delete_link(mark_list, current); } } else { gtk_text_buffer_delete_mark(current_text_buffer, current_mark); current->data = NULL; mark_list = g_list_delete_link(mark_list, current); } current = next; } 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: 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_by_account(PurpleAccount *account, const char *sender) { gint service = unknown_service; g_return_val_if_fail(account != NULL, unknown_service); g_return_val_if_fail(sender != NULL, unknown_service); if(is_twitter_account(account, sender)) service = twitter_service; else if(is_wassr_account(account, sender)) service = wassr_service; else if(is_identica_account(account, sender)) service = identica_service; return service; } 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: get_status_with_api((gpointer)conv); source.id = g_timeout_add_seconds( purple_prefs_get_int(OPT_API_BASE_GET_INTERVAL), get_status_with_api, (gpointer)conv); source.conv = 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); source.id = 0; source.conv = NULL; } 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 void apply_filter(gchar **sender, gchar **buffer, PurpleMessageFlags *flags, int service) { GMatchInfo *match_info; const gchar *list = NULL; gchar *screen_name = NULL; gchar **candidates = NULL, **candidate = NULL; g_return_if_fail(*sender != NULL); g_return_if_fail(*buffer != NULL); gchar *plain = strip_html_markup(*buffer); switch(service) { case twitter_service: default: list = purple_prefs_get_string(OPT_FILTER_TWITTER); screen_name = g_strdup_printf("@%s", purple_prefs_get_string(OPT_SCREEN_NAME_TWITTER)); break; case wassr_service: list = purple_prefs_get_string(OPT_FILTER_WASSR); screen_name = g_strdup_printf("@%s", purple_prefs_get_string(OPT_SCREEN_NAME_WASSR)); break; case identica_service: list = purple_prefs_get_string(OPT_FILTER_IDENTICA); screen_name = g_strdup_printf("@%s", purple_prefs_get_string(OPT_SCREEN_NAME_IDENTICA)); break; } g_return_if_fail(list != NULL); if(strstr(list, DEFAULT_LIST)) return; /* find @myself */ if(purple_prefs_get_bool(OPT_FILTER_EXCLUDE_REPLY) && strstr(plain, screen_name)) { g_free(plain); g_free(screen_name); return; } else g_free(screen_name); candidates = g_strsplit_set(list, " ,:;", 0); g_return_if_fail(candidates != NULL); g_regex_match(regp[SENDER], plain, 0, &match_info); while(g_match_info_matches(match_info)) { gchar *user = NULL; 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. filter %s\n", user); /* pidgin should handle this flag properly --yaz */ // *flags |= PURPLE_MESSAGE_INVISIBLE; /* temporal workaround */ g_free(*sender); *sender = NULL; g_free(*buffer); *buffer = NULL; break; } } g_free(user); g_match_info_next(match_info, NULL); } g_free(plain); g_strfreev(candidates); g_match_info_free(match_info); } 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; service = get_service_type_by_account(account, *sender); twitter_debug("service = %d\n", service); #ifdef _WIN32 /* suppress notification of incoming messages. */ if(service != unknown_service && purple_prefs_get_bool(OPT_PREVENT_NOTIFICATION)) { if(!blink_modified) { blink_modified = TRUE; blink_state = purple_prefs_get_bool(OPT_PIDGIN_BLINK_IM); purple_prefs_set_bool(OPT_PIDGIN_BLINK_IM, FALSE); } } else { if(blink_modified) { purple_prefs_set_bool(OPT_PIDGIN_BLINK_IM, blink_state); blink_modified = FALSE; } } #endif if(service == wassr_service) { gchar *stripped = strip_html_markup(*buffer); /* suppress annoying completion message from wassr */ if(strstr(*buffer, "<body>チャンネル投稿完了:")) { twitter_debug("clearing channel parrot message\n"); g_free(*sender); *sender = NULL; g_free(*buffer); *buffer = NULL; } /* discard parrot message */ else { GList *current = g_list_first(wassr_parrot_list); while(current) { GList *next = g_list_next(current); if(strstr(stripped, current->data)) { twitter_debug("parrot clearing: buf = %s post = %s\n", *buffer, (char *)current->data); g_free(*sender); *sender = NULL; g_free(*buffer); *buffer = NULL; g_free(current->data); current->data = NULL; wassr_parrot_list = g_list_delete_link(wassr_parrot_list, current); break; } current = next; } } g_free(stripped); } if(service == identica_service) { /* discard parrot message */ gchar *stripped = strip_html_markup(*buffer); GList *current = g_list_first(identica_parrot_list); while(current) { GList *next = g_list_next(current); if(strstr(stripped, current->data)) { twitter_debug("identica parrot clearing: buf = %s post = %s\n", *buffer, (char *)current->data); g_free(*sender); *sender = NULL; g_free(*buffer); *buffer = NULL; g_free(current->data); current->data = NULL; identica_parrot_list = g_list_delete_link(identica_parrot_list, current); break; } current = next; } g_free(stripped); } /* filtering */ if(purple_prefs_get_bool(OPT_FILTER)) { apply_filter(sender, buffer, flags, service); } /* return here if it is not twitter */ if(service != twitter_service) { return FALSE; } /* if we use api, discard all incoming IM messages. */ 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; 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); /* in this function, we put an icon for pending marks. we should * not invalidate the icon here, otherwise it may result in * thrashing. --yaz */ if(!data || !data->pixbuf) { return; } /* insert icon actually */ if(purple_prefs_get_bool(OPT_SHOW_ICON)) { gtk_text_buffer_insert_pixbuf(target_buffer, &insertion_point, data->pixbuf); data->use_count++; } 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 for pending requests\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; } else if(service == wassr_service) { data = (icon_data *)g_hash_table_lookup( icon_hash[wassr_service], user_name); regp_id = IMAGE_WASSR; } else 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 */ /* xxx need simplify --yaz */ g_regex_match(regp[regp_id], url_text, 0, &match_info); if(!g_match_info_matches(match_info)) { g_match_info_free(match_info); if(service == twitter_service) { twitter_debug("fall back to twitter default icon\n"); url = g_strdup(TWITTER_DEFAULT_ICON_URL); } else { twitter_debug("no image url found\n"); if(data) { data->requested = FALSE; data->fetch_data = NULL; } g_free(gotdata->user_name); g_free(gotdata); return; } } else { url = g_match_info_fetch(match_info, 1); g_match_info_free(match_info); } /* find out basename */ gchar *slash = strrchr(url, '/'); *slash = '\0'; gchar *lower = g_ascii_strdown(slash+1, -1); if(strstr(lower, ".png")) data->img_type = "png"; else if(strstr(lower, ".gif")) data->img_type = "gif"; else if(strstr(lower, ".jpg") || strstr(lower, ".jpeg")) data->img_type = "jpg"; g_free(lower); gchar *tmp; /* url encode basename. twitter needs this. */ if(service == twitter_service) tmp = g_strdup_printf("%s/%s", url, purple_url_encode(slash+1)); else if(service == wassr_service) { gchar *tmp0 = NULL; tmp0 = g_regex_replace(regp[SIZE_128_WASSR], slash+1, -1, 0, ".64.", 0, NULL); tmp = g_strdup_printf("http://wassr.jp%s/%s", url, tmp0 ? tmp0 : slash+1); g_free(tmp0); } else { tmp = g_strdup_printf("%s/%s", url, slash+1); } g_free(url); url = tmp; /* if requesting icon url is the same as old, return. */ if(url && data->icon_url && !strcmp(data->icon_url, url)) { twitter_debug("old url = %s new url = %s\n", data->icon_url, url); data->requested = FALSE; data->fetch_data = NULL; g_free(url); return; } if(data && data->pixbuf) { gdk_pixbuf_unref(data->pixbuf); data->pixbuf = NULL; } g_free(data->icon_url); data->icon_url = g_strdup(url); data->use_count = 0; data->mtime = time(NULL); /* xxx is there a better way? */ 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 = DEFAULT_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; 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->pixbuf) { 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; if(!data) { twitter_debug("allocate icon_data (shouldn't be called)\n"); data = g_new0(icon_data, 1); } data->pixbuf = pixbuf; twitter_debug("new icon pixbuf = %p size = %d\n", pixbuf, gdk_pixbuf_get_rowstride(pixbuf) * gdk_pixbuf_get_height(pixbuf)); 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; const gchar *suffix = NULL; gchar **extp; switch(service) { case twitter_service: suffix = "twitter"; break; case wassr_service: suffix = "wassr"; break; case identica_service: suffix = "identica"; break; default: twitter_debug("unknown service\n"); break; } /* remove old file first */ for(extp = ext_list; *extp; extp++) { filename = g_strdup_printf("%s_%s.%s", user_name, suffix, *extp); path = g_build_filename(dirname, filename, NULL); g_remove(path); g_free(filename); g_free(path); } /* setup path */ filename = g_strdup_printf("%s_%s.%s", user_name, suffix, data->img_type); path = g_build_filename(dirname, filename, NULL); g_free(filename); filename = NULL; g_file_set_contents(path, url_text, len, NULL); g_free(path); path = NULL; data->mtime = time(NULL); } twitter_debug("Downloading %s's icon has been complete.\n", user_name); /* 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, gboolean renew) { 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); /* if img has been registerd, just return */ if(data && data->pixbuf && !renew) return; /* check if saved file exists */ if(suffix && !renew) { 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); /* 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; struct stat buf; if (!g_file_get_contents(path, &imgdata, &len, &err)) { twitter_debug("Error reading %s: %s\n", path, err->message); g_error_free(err); } if(stat(path, &buf)) data->mtime = buf.st_mtime; pixbuf = make_scaled_pixbuf(imgdata, len); g_free(imgdata); if(pixbuf) { data->pixbuf = pixbuf; twitter_debug("new icon pixbuf = %p size = %d\n", pixbuf, gdk_pixbuf_get_rowstride(pixbuf) * gdk_pixbuf_get_height(pixbuf)); 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", 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 == twitter_service || service == wassr_service || 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_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_prepend(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 nor 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 service = get_service_type(conv); icon_data *data = NULL; gint linenumber; GHashTable *hash = NULL; gboolean renew = FALSE; 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) { /* check validity of icon */ int count_thres = purple_prefs_get_int(OPT_ICON_MAX_COUNT); int days_thres = DAYS_TO_SECONDS( purple_prefs_get_int(OPT_ICON_MAX_DAYS)); if(data->use_count > count_thres || (data->mtime && ((time(NULL) - data->mtime)) > days_thres)) { twitter_debug("count=%d mtime=%d\n", data->use_count, (int)(data->mtime)); renew = TRUE; request_icon(user_name, service, renew); } } /* if we don't have the icon for this user, put a mark instead and * request the icon */ if(!data || !data->pixbuf) { 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, renew); 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)) { gtk_text_buffer_insert_pixbuf(text_buffer, &insertion_point, data->pixbuf); data->use_count++; } 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); get_status_with_api((gpointer)(source.conv)); } 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_WASSR] = g_regex_new(P_CHANNEL, 0, 0, NULL); regp[IMAGE_TWITTER] = g_regex_new(P_IMAGE_TWITTER, 0, 0, NULL); regp[IMAGE_WASSR] = g_regex_new(P_IMAGE_WASSR, 0, 0, NULL); regp[IMAGE_IDENTICA] = g_regex_new(P_IMAGE_IDENTICA, 0, 0, NULL); regp[TAG_IDENTICA] = g_regex_new(P_TAG_IDENTICA, 0, 0, NULL); regp[SIZE_128_WASSR] = g_regex_new(P_SIZE_128_WASSR, 0, 0, NULL); for(i = twitter_service; i < NUM_SERVICES; 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(); } 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; GList *current; 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_REGPS; 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 < NUM_SERVICES; 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_parrot_list */ current = g_list_first(wassr_parrot_list); while(current) { GList *next; next = g_list_next(current); g_free(current->data); wassr_parrot_list = g_list_delete_link(wassr_parrot_list, current); current = next; } g_list_free(wassr_parrot_list); wassr_parrot_list = NULL; /* free identica_parot_list */ current = g_list_first(identica_parrot_list); while(current) { GList *next; next = g_list_next(current); g_free(current->data); identica_parrot_list = g_list_delete_link(identica_parrot_list, current); current = next; } g_list_free(identica_parrot_list); identica_parrot_list = 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_object_unref(data->pixbuf); data->pixbuf = NULL; } 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 < NUM_SERVICES; i++) { g_hash_table_foreach(icon_hash[i], (GHFunc)invalidate_icon_data_func, NULL); } } static void interval_prefs_cb(const char *name, PurplePrefType type, gconstpointer val, gpointer data) { /* remove idle func */ g_source_remove_by_user_data((gpointer)(source.conv)); /* add idle func */ if(purple_prefs_get_bool(OPT_API_BASE_POST)) { source.id = g_timeout_add_seconds( purple_prefs_get_int(OPT_API_BASE_GET_INTERVAL), get_status_with_api, (gpointer)(source.conv)); } } 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 void disconnect_prefs_cb(GtkObject *object, gpointer data) { PurplePlugin *plugin = (PurplePlugin *)data; purple_prefs_disconnect_by_handle(plugin); } static GtkWidget * prefs_get_frame(PurplePlugin *plugin) { GtkBuilder *builder; GError *err = NULL; gchar *filename; GtkWidget *window, *notebook, *e; const gchar *text; GtkSpinButton *spin; GtkObject *adjust; gint value; 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); g_free(filename); 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(disconnect_prefs_cb), plugin); /**********************/ /* 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); /* interval spin */ e = GTK_WIDGET(gtk_builder_get_object (builder, "account_api_get_interval_spin")); g_object_set_data(G_OBJECT(e), "pref", OPT_API_BASE_GET_INTERVAL); spin = GTK_SPIN_BUTTON(e); value = purple_prefs_get_int(OPT_API_BASE_GET_INTERVAL); twitter_debug("spin value = %d\n", value); adjust = gtk_adjustment_new(value, 40, 3600, 10, 100, 100); gtk_spin_button_set_adjustment(spin, GTK_ADJUSTMENT(adjust)); gtk_widget_set_size_request(GTK_WIDGET(spin), 50, -1); if(value == 0) { value = TWITTER_DEFAULT_INTERVAL; purple_prefs_set_int(OPT_API_BASE_GET_INTERVAL, 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_API_BASE_GET_INTERVAL, interval_prefs_cb, NULL); /********************/ /* 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); /***************/ /* filter page */ /***************/ e = GTK_WIDGET(gtk_builder_get_object (builder, "filter_filter_check")); g_object_set_data(G_OBJECT(e), "pref", OPT_FILTER); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), purple_prefs_get_bool(OPT_FILTER)); g_signal_connect(e, "toggled", G_CALLBACK(bool_toggled_cb), &e); e = GTK_WIDGET(gtk_builder_get_object (builder, "filter_exclude_reply_check")); g_object_set_data(G_OBJECT(e), "pref", OPT_FILTER_EXCLUDE_REPLY); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), purple_prefs_get_bool(OPT_FILTER_EXCLUDE_REPLY)); g_signal_connect(e, "toggled", G_CALLBACK(bool_toggled_cb), &e); e = GTK_WIDGET(gtk_builder_get_object (builder, "filter_twitter")); g_object_set_data(G_OBJECT(e), "pref", OPT_FILTER_TWITTER); text = purple_prefs_get_string(OPT_FILTER_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, "filter_wassr")); g_object_set_data(G_OBJECT(e), "pref", OPT_FILTER_WASSR); text = purple_prefs_get_string(OPT_FILTER_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, "filter_identica")); g_object_set_data(G_OBJECT(e), "pref", OPT_FILTER_IDENTICA); text = purple_prefs_get_string(OPT_FILTER_IDENTICA); gtk_entry_set_text(GTK_ENTRY(e), text); g_signal_connect(e, "changed", G_CALLBACK(text_changed_cb), &e); /*************/ /* icon page */ /*************/ e = GTK_WIDGET(gtk_builder_get_object (builder, "icon_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); /* icon size spin */ e = GTK_WIDGET(gtk_builder_get_object (builder, "icon_icon_size_spin")); g_object_set_data(G_OBJECT(e), "pref", OPT_ICON_SIZE); spin = GTK_SPIN_BUTTON(e); value = purple_prefs_get_int(OPT_ICON_SIZE); twitter_debug("spin value = %d\n", value); adjust = gtk_adjustment_new(value, 16, 128, 4, 4, 4); 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); /* enable update */ e = GTK_WIDGET(gtk_builder_get_object (builder, "icon_enable_update")); g_object_set_data(G_OBJECT(e), "pref", OPT_UPDATE_ICON); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), purple_prefs_get_bool(OPT_UPDATE_ICON)); g_signal_connect(e, "toggled", G_CALLBACK(bool_toggled_cb), &e); /* max count spin */ e = GTK_WIDGET(gtk_builder_get_object (builder, "icon_max_count_spin")); g_object_set_data(G_OBJECT(e), "pref", OPT_ICON_MAX_COUNT); spin = GTK_SPIN_BUTTON(e); value = purple_prefs_get_int(OPT_ICON_MAX_COUNT); twitter_debug("spin value = %d\n", value); adjust = gtk_adjustment_new(value, 2, 10000, 1, 10, 10); 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_MAX_COUNT; purple_prefs_set_int(OPT_ICON_MAX_COUNT, value); } gtk_spin_button_set_value(GTK_SPIN_BUTTON(e), (gdouble)value); g_signal_connect(e, "value-changed", G_CALLBACK(spin_changed_cb), &e); /* max days spin */ e = GTK_WIDGET(gtk_builder_get_object (builder, "icon_max_days_spin")); g_object_set_data(G_OBJECT(e), "pref", OPT_ICON_MAX_DAYS); spin = GTK_SPIN_BUTTON(e); value = purple_prefs_get_int(OPT_ICON_MAX_DAYS); twitter_debug("spin value = %d\n", value); adjust = gtk_adjustment_new(value, 1, 180, 1, 10, 10); 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_MAX_DAYS; purple_prefs_set_int(OPT_ICON_MAX_DAYS, value); } gtk_spin_button_set_value(GTK_SPIN_BUTTON(e), (gdouble)value); g_signal_connect(e, "value-changed", G_CALLBACK(spin_changed_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); /* recipient 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); /* sender 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); /****************/ /* utility page */ /****************/ 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, 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); e = GTK_WIDGET(gtk_builder_get_object (builder, "utility_log_output")); g_object_set_data(G_OBJECT(e), "pref", OPT_LOG_OUTPUT); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e), purple_prefs_get_bool(OPT_LOG_OUTPUT)); g_signal_connect(e, "toggled", G_CALLBACK(bool_toggled_cb), &e); /* all done */ 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.8.0d1", /**< version */ "provides useful features for twitter", /** summary */ "provides useful features for twitter", /** desc */ "Yoshiki Yazawa, mikanbako, \nKonosuke Watanabe, IWATA Ray, \nmojin, umq, \nthe pidging-twitter team", /**< author */ "http://www.honeyplanet.jp/pidgin-twitter/", /**< 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_API_BASE_POST, FALSE); purple_prefs_add_int(OPT_API_BASE_GET_INTERVAL, TWITTER_DEFAULT_INTERVAL); purple_prefs_add_string(OPT_SCREEN_NAME_TWITTER, EMPTY); purple_prefs_add_string(OPT_PASSWORD_TWITTER, EMPTY); purple_prefs_add_string(OPT_SCREEN_NAME_WASSR, EMPTY); purple_prefs_add_string(OPT_SCREEN_NAME_IDENTICA, EMPTY); purple_prefs_add_bool(OPT_SHOW_ICON, TRUE); purple_prefs_add_int(OPT_ICON_SIZE, DEFAULT_ICON_SIZE); purple_prefs_add_bool(OPT_UPDATE_ICON, TRUE); purple_prefs_add_int(OPT_ICON_MAX_COUNT, DEFAULT_ICON_MAX_COUNT); purple_prefs_add_int(OPT_ICON_MAX_DAYS, DEFAULT_ICON_MAX_DAYS); purple_prefs_add_bool(OPT_LOG_OUTPUT, FALSE); purple_prefs_add_bool(OPT_FILTER, TRUE); purple_prefs_add_bool(OPT_FILTER_EXCLUDE_REPLY, TRUE); purple_prefs_add_string(OPT_FILTER_TWITTER, DEFAULT_LIST); purple_prefs_add_string(OPT_FILTER_WASSR, DEFAULT_LIST); purple_prefs_add_string(OPT_FILTER_IDENTICA, DEFAULT_LIST); } PURPLE_INIT_PLUGIN(pidgin_twitter, init_plugin, info)