changeset 254:c2620a99622b

- divided the source file into several parts.
author Yoshiki Yazawa <yaz@honeyplanet.jp>
date Sat, 22 Nov 2008 18:01:18 +0900
parents a37ae6c8fa66
children dcf53bc05627
files Makefile.in Makefile.mingw autogen.sh configure.ac configure.in icon.c icon.h main.c pidgin-twitter.c pidgin-twitter.h prefs.c prefs.h twitter_api.c twitter_api.h util.c util.h
diffstat 16 files changed, 3640 insertions(+), 3578 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile.in	Fri Nov 21 22:22:33 2008 +0900
+++ b/Makefile.in	Sat Nov 22 18:01:18 2008 +0900
@@ -1,6 +1,7 @@
-OBJECTIVE = pidgin-twitter.so
-SRC = pidgin-twitter.c
-HDR = ${SRC:.c=.h}
+TARGET = pidgin-twitter.so
+SRCS = main.c util.c prefs.c icon.c twitter_api.c
+OBJS = ${SRCS:.c=.o}
+DEPEND = .deps
 
 PIDGIN_PLUGIN_DIR = @PIDGIN_PREFIX@/lib/pidgin
 PIDGIN_DATA_DIR = @PIDGIN_PREFIX@/share
@@ -12,23 +13,27 @@
 
 GLIB_LIBS = @GLIB_LIBS@
 XML_LIBS = @XML_LIBS@
-LDFLAGS = $(GLIB_LIBS) $(XML_LIBS)
+LDFLAGS = -fPIC -shared -Wall $(GLIB_LIBS) $(XML_LIBS)
 
-all: $(OBJECTIVE)
+all: $(TARGET)
 
 
-$(OBJECTIVE): $(SRC) $(HDR)
-	gcc -o $@ $(SRC) $(CFLAGS) $(LDFLAGS) -g
+$(TARGET): $(OBJS)
+	$(CC) -o $@ $^ $(LDFLAGS)
 
 
-install: $(OBJECTIVE)
-	install -m 755 $(OBJECTIVE) $(PIDGIN_PLUGIN_DIR)
+.c.o:
+	$(CC) -o $@ $< -c $(CFLAGS)
+
+
+install: $(TARGET)
+	install -m 755 $(TARGET) $(PIDGIN_PLUGIN_DIR)
 	install -d -m 755 $(PIDGIN_DATA_DIR)/pidgin-twitter
 	install -m 644 prefs.ui $(PIDGIN_DATA_DIR)/pidgin-twitter
 
 
 clean:
-	rm -f $(OBJECTIVE)
+	rm -f $(TARGET) $(OBJS) $(DEPEND)
 
 
 distclean: clean
@@ -42,3 +47,9 @@
 release: maintainer-clean
 	rm -f .hgtags
 	rm -rf .hg
+
+
+$(DEPEND): $(SRCS)
+	$(CC) -MM $(SRCS) > $@
+
+include $(DEPEND)
--- a/Makefile.mingw	Fri Nov 21 22:22:33 2008 +0900
+++ b/Makefile.mingw	Sat Nov 22 18:01:18 2008 +0900
@@ -13,7 +13,7 @@
 STRIP = strip.exe
 
 TARGET = pidgin-twitter.dll
-SRCS = pidgin-twitter.c
+SRCS = main.c util.c prefs.c icon.c twitter_api.c
 
 OBJS = $(SRCS:%.c=%.o) prefs_ui.o
 
@@ -55,7 +55,7 @@
 strip: $(TARGET)
 	$(STRIP) $^
 
-$(TARGET): $(OBJS) 
+$(TARGET): $(OBJS)
 	$(CC) -shared $^ $(LDFLAGS) -o $@
 
 .c.o:
--- a/autogen.sh	Fri Nov 21 22:22:33 2008 +0900
+++ b/autogen.sh	Sat Nov 22 18:01:18 2008 +0900
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-[ -f configure.in ] || {
+[ -f configure.ac ] || {
   echo "autogen.sh: run this command only at the top of a pidgin-twitter source tree."
   exit 1
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/configure.ac	Sat Nov 22 18:01:18 2008 +0900
@@ -0,0 +1,74 @@
+#                                               -*- Autoconf -*-
+# Process this file with autoconf to produce a configure script.
+
+AC_PREREQ(2.59)
+AC_INIT(pidgin-twitter, 0.9.0, yaz@honeyplanet.jp)
+AC_CONFIG_SRCDIR([main.c])
+
+# Checks for programs.
+AC_PROG_CC
+
+# Checks for libraries.
+PKG_CHECK_MODULES(GTK, [gtk+-2.0 >= 2.12.0], , [
+        AC_MSG_RESULT(no)
+        AC_MSG_ERROR([
+
+You must have gtk+-2.0 >= 2.12.0 development headers installed to build.
+])])
+
+PKG_CHECK_MODULES(GLIB, [glib-2.0 >= 2.14.0], , [
+        AC_MSG_RESULT(no)
+        AC_MSG_ERROR([
+
+You must have glib >= 2.14.0 development headers installed to build.
+])])
+
+GLIB_CFLAGS=`pkg-config --cflags glib-2.0 2> /dev/null`
+GLIB_LIBS=`pkg-config --libs glib-2.0 2> /dev/null`
+GLIB_LIB_DIR=`pkg-config --variable=lib_dir glib-2.0 2> /dev/null`
+AC_SUBST(GLIB_CFLAGS)
+AC_SUBST(GLIB_LIBS)
+AC_SUBST(GLIB_LIB_DIR)
+
+PKG_CHECK_MODULES(PIDGIN, [pidgin >= 2.4.0], , [
+        AC_MSG_RESULT(no)
+        AC_MSG_ERROR([
+
+You must have pidgin >= 2.4.0 development headers installed to build.
+])])
+PIDGIN_CFLAGS=`pkg-config --cflags pidgin 2> /dev/null`
+PIDGIN_LIBS=`pkg-config --libs pidgin 2> /dev/null`
+if test x"$prefix" = x"NONE" ; then
+	PIDGIN_PREFIX=`pkg-config --variable=prefix pidgin 2> /dev/null`
+else
+	PIDGIN_PREFIX=$prefix
+fi
+AC_SUBST(PIDGIN_CFLAGS)
+AC_SUBST(PIDGIN_LIBS)
+AC_SUBST(PIDGIN_PREFIX)
+
+PKG_CHECK_MODULES(XML, [libxml-2.0 >= 2.6.27], , [
+        AC_MSG_RESULT(no)
+        AC_MSG_ERROR([
+
+You must have libxml2 >= 2.6.27 installed to build.
+])])
+
+XML_CFLAGS=`pkg-config --cflags libxml-2.0 2> /dev/null`
+XML_LIBS=`pkg-config --libs libxml-2.0 2> /dev/null`
+XML_LIB_DIR=`pkg-config --variable=lib_dir libxml-2.0 2> /dev/null`
+AC_SUBST(XML_CFLAGS)
+AC_SUBST(XML_LIBS)
+AC_SUBST(XML_LIB_DIR)
+
+# Checks for header files.
+AC_HEADER_STDC
+AC_CHECK_HEADERS([stdlib.h string.h])
+
+# Checks for typedefs, structures, and compiler characteristics.
+AC_C_CONST
+
+# Checks for library functions.
+AC_CHECK_FUNCS([strstr])
+
+AC_OUTPUT(Makefile)
--- a/configure.in	Fri Nov 21 22:22:33 2008 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,74 +0,0 @@
-#                                               -*- Autoconf -*-
-# Process this file with autoconf to produce a configure script.
-
-AC_PREREQ(2.59)
-AC_INIT(pidgin-twitter, 0.8.0, yaz@honeyplanet.jp)
-AC_CONFIG_SRCDIR([pidgin-twitter.c])
-
-# Checks for programs.
-AC_PROG_CC
-
-# Checks for libraries.
-PKG_CHECK_MODULES(GTK, [gtk+-2.0 >= 2.12.0], , [
-        AC_MSG_RESULT(no)
-        AC_MSG_ERROR([
-
-You must have gtk+-2.0 >= 2.12.0 development headers installed to build.
-])])
-
-PKG_CHECK_MODULES(GLIB, [glib-2.0 >= 2.14.0], , [
-        AC_MSG_RESULT(no)
-        AC_MSG_ERROR([
-
-You must have glib >= 2.14.0 development headers installed to build.
-])])
-
-GLIB_CFLAGS=`pkg-config --cflags glib-2.0 2> /dev/null`
-GLIB_LIBS=`pkg-config --libs glib-2.0 2> /dev/null`
-GLIB_LIB_DIR=`pkg-config --variable=lib_dir glib-2.0 2> /dev/null`
-AC_SUBST(GLIB_CFLAGS)
-AC_SUBST(GLIB_LIBS)
-AC_SUBST(GLIB_LIB_DIR)
-
-PKG_CHECK_MODULES(PIDGIN, [pidgin >= 2.4.0], , [
-        AC_MSG_RESULT(no)
-        AC_MSG_ERROR([
-
-You must have pidgin >= 2.4.0 development headers installed to build.
-])])
-PIDGIN_CFLAGS=`pkg-config --cflags pidgin 2> /dev/null`
-PIDGIN_LIBS=`pkg-config --libs pidgin 2> /dev/null`
-if test x"$prefix" = x"NONE" ; then
-	PIDGIN_PREFIX=`pkg-config --variable=prefix pidgin 2> /dev/null`
-else
-	PIDGIN_PREFIX=$prefix
-fi
-AC_SUBST(PIDGIN_CFLAGS)
-AC_SUBST(PIDGIN_LIBS)
-AC_SUBST(PIDGIN_PREFIX)
-
-PKG_CHECK_MODULES(XML, [libxml-2.0 >= 2.6.27], , [
-        AC_MSG_RESULT(no)
-        AC_MSG_ERROR([
-
-You must have libxml2 >= 2.6.27 installed to build.
-])])
-
-XML_CFLAGS=`pkg-config --cflags libxml-2.0 2> /dev/null`
-XML_LIBS=`pkg-config --libs libxml-2.0 2> /dev/null`
-XML_LIB_DIR=`pkg-config --variable=lib_dir libxml-2.0 2> /dev/null`
-AC_SUBST(XML_CFLAGS)
-AC_SUBST(XML_LIBS)
-AC_SUBST(XML_LIB_DIR)
-
-# Checks for header files.
-AC_HEADER_STDC
-AC_CHECK_HEADERS([stdlib.h string.h])
-
-# Checks for typedefs, structures, and compiler characteristics.
-AC_C_CONST
-
-# Checks for library functions.
-AC_CHECK_FUNCS([strstr])
-
-AC_OUTPUT(Makefile)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/icon.c	Sat Nov 22 18:01:18 2008 +0900
@@ -0,0 +1,677 @@
+#include "pidgin-twitter.h"
+#include "icon.h"
+
+extern GHashTable *icon_hash[];
+extern GRegex *regp[];
+
+/* prototypes */
+static void insert_icon_at_mark(GtkTextMark *requested_mark, gpointer user_data);
+static void insert_requested_icon(const gchar *user_name, gint service);
+static void got_icon_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message);
+static GdkPixbuf *make_scaled_pixbuf(const gchar *url_text, gsize len);
+static void got_page_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message);
+
+/* functions */
+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;
+    case jisko_service:
+        hash = icon_hash[jisko_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;
+    case jisko_service:
+        hash = icon_hash[jisko_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;
+    }
+    else if(service == jisko_service) {
+        data = (icon_data *)g_hash_table_lookup(
+            icon_hash[jisko_service], user_name);
+        regp_id = IMAGE_JISKO;
+    }
+
+    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 if(service == jisko_service) {
+            twitter_debug("fall back to jisko default icon\n");
+            url = g_strdup(JISKO_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;
+    case jisko_service:
+        hash = icon_hash[jisko_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;
+        case jisko_service:
+            suffix = "jisko";
+            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);
+}
+
+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;
+    case jisko_service:
+        suffix = "jisko";
+        hash = icon_hash[jisko_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);
+            g_free(filename);
+
+            twitter_debug("path = %s\n", path);
+
+            /* build image from file, if file exists */
+            if(g_file_test(path, G_FILE_TEST_EXISTS)) {
+                gchar *imgdata = NULL;
+                size_t len;
+                GError *err = NULL;
+                GdkPixbuf *pixbuf = NULL;
+                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;
+            }
+
+            g_free(path);
+
+        } /* 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;
+    case jisko_service:
+        url = g_strdup_printf("http://jisko.net/%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 ||
+           service == jisko_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);
+    }
+}
+
+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;
+    case jisko_service:
+        hash = icon_hash[jisko_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);
+}
+
+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;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/icon.h	Sat Nov 22 18:01:18 2008 +0900
@@ -0,0 +1,8 @@
+#ifndef _PIDGIN_TWITTER_ICON_H_
+#define _PIDGIN_TWITTER_ICON_H_
+
+void invalidate_icon_data_func(gpointer key, gpointer value, gpointer user_data);
+void request_icon(const char *user_name, gint service, gboolean renew);
+void mark_icon_for_user(GtkTextMark *mark, const gchar *user_name, gint service);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.c	Sat Nov 22 18:01:18 2008 +0900
@@ -0,0 +1,1489 @@
+/*
+ * Pidgin-Twitter plugin.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+#define PURPLE_PLUGINS 1
+
+#include "pidgin-twitter.h"
+#include "main.h"
+
+/***********/
+/* globals */
+/***********/
+GRegex *regp[NUM_REGPS];
+static gboolean suppress_oops = FALSE;
+GHashTable *icon_hash[NUM_SERVICES];
+static GHashTable *conv_hash = 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
+
+source_t source;
+
+#ifndef _WIN32
+extern gchar *sanitize_utf(const gchar *msg, gsize len, gsize *newlen) __attribute__ ((weak));
+#endif
+
+/* prototypes */
+static gboolean is_twitter_conv(PurpleConversation *conv);
+static gboolean is_wassr_account(PurpleAccount *account, const char *name);
+static gboolean is_wassr_conv(PurpleConversation *conv);
+static gboolean is_identica_account(PurpleAccount *account, const char *name);
+static gboolean is_identica_conv(PurpleConversation *conv);
+static gboolean is_jisko_account(PurpleAccount *account, const char *name);
+static gboolean is_jisko_conv(PurpleConversation *conv);
+static void cleanup_hash_entry_func(gpointer key, gpointer value, gpointer user_data);
+
+static void init_plugin(PurplePlugin *plugin);
+static gboolean load_plugin(PurplePlugin *plugin);
+static gboolean unload_plugin(PurplePlugin *plugin);
+static gboolean sending_im_cb(PurpleAccount *account, char *recipient, char **buffer, void *data);
+static gboolean eval(const GMatchInfo *match_info, GString *result, gpointer user_data);
+static void translate(gchar **str, gint which, gint service);
+static void playsound(gchar **str, gint which);
+static gboolean writing_im_cb(PurpleAccount *account, char *sender, char **buffer, PurpleConversation *conv, int flags, void *data);
+static void insert_text_cb(GtkTextBuffer *textbuffer, GtkTextIter *position, gchar *new_text, gint new_text_length, gpointer user_data);
+static void delete_text_cb(GtkTextBuffer *textbuffer, GtkTextIter *start_pos, GtkTextIter *end_pos, gpointer user_data);
+static void detach_from_conv(PurpleConversation *conv, gpointer null);
+static void delete_requested_icon_marks(PidginConversation *gtkconv, GHashTable *table);
+static void attach_to_conv(PurpleConversation *conv, gpointer null);
+
+
+static void conv_created_cb(PurpleConversation *conv, gpointer null);
+static void deleting_conv_cb(PurpleConversation *conv);
+static gboolean receiving_im_cb(PurpleAccount *account, char **sender, char **buffer, PurpleConversation *conv, PurpleMessageFlags *flags, void *data);
+
+static void remove_marks_func(gpointer key, gpointer value, gpointer user_data);
+static void cancel_fetch_func(gpointer key, gpointer value, gpointer user_data);
+
+static gboolean displaying_im_cb(PurpleAccount *account, const char *who, char **message, PurpleConversation *conv, PurpleMessageFlags flags, void *data);
+static void displayed_im_cb(PurpleAccount *account, const char *who, char *message, PurpleConversation *conv, PurpleMessageFlags flags);
+
+
+
+/*************/
+/* functions */
+/*************/
+
+
+/***********************/
+/* 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;
+        case jisko_service:
+            format = RECIPIENT_FORMAT_JISKO;
+            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;
+        case jisko_service:
+            format = SENDER_FORMAT_JISKO;
+            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);
+        gchar *link = g_ascii_strdown(match, -1);
+        purple_str_strip_char(link, '-');
+        purple_str_strip_char(link, '_');
+        const gchar *format = TAG_FORMAT_IDENTICA;
+        g_snprintf(sub, SUBST_BUF_SIZE, format, link, match);
+        g_free(match);
+        g_free(link);
+    }
+    else if(which == EXCESS_LF) {
+        g_snprintf(sub, SUBST_BUF_SIZE, "%s", "\n");
+    }
+
+    g_string_append(result, sub);
+    twitter_debug("sub = %s\n", sub);
+
+    return FALSE;
+}
+
+static void
+translate(gchar **str, gint regp_id, gint service)
+{
+    gchar *newstr;
+    eval_data *data = g_new0(eval_data, 1);
+
+    data->which = regp_id;
+    data->service = service;
+
+    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", regp_id, *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, 2);
+        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;
+        case jisko_service:
+            screen_name = purple_prefs_get_string(OPT_SCREEN_NAME_JISKO);
+            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);
+    }
+
+    if(purple_prefs_get_bool(OPT_STRIP_EXCESS_LF)) {
+        translate(buffer, EXCESS_LF, service);
+    }
+
+    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:
+    case jisko_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:
+    case jisko_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);
+}
+
+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:
+        case jisko_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);
+}
+
+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:
+        case jisko_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);
+}
+
+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 gboolean
+is_jisko_account(PurpleAccount *account, const char *name)
+{
+    const gchar *proto = purple_account_get_protocol_id(account);
+
+    if(g_strstr_len(name,  16, "bot@jisko.net") &&
+       g_strstr_len(proto, 11, "prpl-jabber")) {
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+static gboolean
+is_jisko_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_jisko_account(account, name);
+}
+
+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;
+    else if(is_jisko_account(account, sender))
+        service = jisko_service;
+
+    return service;
+}
+
+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;
+    else if(is_jisko_conv(conv))
+        service = jisko_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:
+    case jisko_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;
+    case jisko_service:
+        hash = icon_hash[jisko_service];
+        break;
+    default:
+        twitter_debug("unknown service\n");
+        break;
+    }
+
+    if(hash)
+        delete_requested_icon_marks(gtkconv, hash);
+}
+
+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;
+    case jisko_service:
+        list = purple_prefs_get_string(OPT_FILTER_JISKO);
+        screen_name = g_strdup_printf("@%s", purple_prefs_get_string(OPT_SCREEN_NAME_JISKO));
+        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 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], 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;
+    case jisko_service:
+        hash = icon_hash[jisko_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 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[CHANNEL_WASSR]  = g_regex_new(P_CHANNEL, 0, 0, NULL);
+    regp[TAG_IDENTICA]   = g_regex_new(P_TAG_IDENTICA, 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[IMAGE_JISKO]    = g_regex_new(P_IMAGE_JISKO, 0, 0, NULL);
+    regp[SIZE_128_WASSR] = g_regex_new(P_SIZE_128_WASSR, 0, 0, NULL);
+    regp[EXCESS_LF] = g_regex_new(P_EXCESS_LF, 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->requested) {
+        purple_util_fetch_url_cancel(data->fetch_data);
+        data->fetch_data = NULL;
+        data->requested = FALSE;
+    }
+
+    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 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.9.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_STRIP_EXCESS_LF, 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_string(OPT_SCREEN_NAME_JISKO, 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_prefs_add_string(OPT_FILTER_JISKO, DEFAULT_LIST);
+}
+
+PURPLE_INIT_PLUGIN(pidgin_twitter, init_plugin, info)
--- a/pidgin-twitter.c	Fri Nov 21 22:22:33 2008 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3413 +0,0 @@
-/*
- * 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 4          /* twitter, wassr, identica, jisko. */
-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);
-            g_free(tmp);
-            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(sender);
-             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;
-        case jisko_service:
-            format = RECIPIENT_FORMAT_JISKO;
-            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;
-        case jisko_service:
-            format = SENDER_FORMAT_JISKO;
-            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);
-        gchar *link = g_ascii_strdown(match, -1);
-        purple_str_strip_char(link, '-');
-        purple_str_strip_char(link, '_');
-        const gchar *format = TAG_FORMAT_IDENTICA;
-        g_snprintf(sub, SUBST_BUF_SIZE, format, link, match);
-        g_free(match);
-        g_free(link);
-    }
-    else if(which == EXCESS_LF) {
-        g_snprintf(sub, SUBST_BUF_SIZE, "%s", "\n");
-    }
-
-    g_string_append(result, sub);
-    twitter_debug("sub = %s\n", sub);
-
-    return FALSE;
-}
-
-static void
-translate(gchar **str, gint regp_id, gint service)
-{
-    gchar *newstr;
-    eval_data *data = g_new0(eval_data, 1);
-
-    data->which = regp_id;
-    data->service = service;
-
-    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", regp_id, *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, 2);
-        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;
-        case jisko_service:
-            screen_name = purple_prefs_get_string(OPT_SCREEN_NAME_JISKO);
-            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);
-    }
-
-    if(purple_prefs_get_bool(OPT_STRIP_EXCESS_LF)) {
-        translate(buffer, EXCESS_LF, service);
-    }
-
-    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:
-    case jisko_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:
-    case jisko_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:
-        case jisko_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:
-        case jisko_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 gboolean
-is_jisko_account(PurpleAccount *account, const char *name)
-{
-    const gchar *proto = purple_account_get_protocol_id(account);
-
-    if(g_strstr_len(name,  16, "bot@jisko.net") &&
-       g_strstr_len(proto, 11, "prpl-jabber")) {
-        return TRUE;
-    }
-
-    return FALSE;
-}
-
-static gboolean
-is_jisko_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_jisko_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;
-    else if(is_jisko_account(account, sender))
-        service = jisko_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;
-    else if(is_jisko_conv(conv))
-        service = jisko_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:
-    case jisko_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;
-    case jisko_service:
-        hash = icon_hash[jisko_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;
-    case jisko_service:
-        list = purple_prefs_get_string(OPT_FILTER_JISKO);
-        screen_name = g_strdup_printf("@%s", purple_prefs_get_string(OPT_SCREEN_NAME_JISKO));
-        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;
-    case jisko_service:
-        hash = icon_hash[jisko_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;
-    case jisko_service:
-        hash = icon_hash[jisko_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;
-    }
-    else if(service == jisko_service) {
-        data = (icon_data *)g_hash_table_lookup(
-            icon_hash[jisko_service], user_name);
-        regp_id = IMAGE_JISKO;
-    }
-
-    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 if(service == jisko_service) {
-            twitter_debug("fall back to jisko default icon\n");
-            url = g_strdup(JISKO_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;
-    case jisko_service:
-        hash = icon_hash[jisko_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;
-        case jisko_service:
-            suffix = "jisko";
-            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;
-    case jisko_service:
-        suffix = "jisko";
-        hash = icon_hash[jisko_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);
-            g_free(filename);
-
-            twitter_debug("path = %s\n", path);
-
-            /* build image from file, if file exists */
-            if(g_file_test(path, G_FILE_TEST_EXISTS)) {
-                gchar *imgdata = NULL;
-                size_t len;
-                GError *err = NULL;
-                GdkPixbuf *pixbuf = NULL;
-                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;
-            }
-
-            g_free(path);
-
-        } /* 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;
-    case jisko_service:
-        url = g_strdup_printf("http://jisko.net/%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 ||
-           service == jisko_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;
-    case jisko_service:
-        hash = icon_hash[jisko_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], 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;
-    case jisko_service:
-        hash = icon_hash[jisko_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[CHANNEL_WASSR]  = g_regex_new(P_CHANNEL, 0, 0, NULL);
-    regp[TAG_IDENTICA]   = g_regex_new(P_TAG_IDENTICA, 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[IMAGE_JISKO]    = g_regex_new(P_IMAGE_JISKO, 0, 0, NULL);
-    regp[SIZE_128_WASSR] = g_regex_new(P_SIZE_128_WASSR, 0, 0, NULL);
-    regp[EXCESS_LF] = g_regex_new(P_EXCESS_LF, 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->requested) {
-        purple_util_fetch_url_cancel(data->fetch_data);
-        data->fetch_data = NULL;
-        data->requested = FALSE;
-    }
-
-    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;
-#ifdef _WIN32
-    extern char binary_prefs_ui_start[];
-    extern char binary_prefs_ui_size[];
-#endif
-
-    builder = gtk_builder_new();
-
-#ifdef _WIN32
-    gtk_builder_add_from_string(builder, binary_prefs_ui_start,
-                                (int)binary_prefs_ui_size, NULL);
-#else
-    filename = g_build_filename(DATADIR,
-                                "pidgin-twitter", "prefs.ui", NULL);
-    gtk_builder_add_from_file(builder, filename, &err);
-#endif
-
-    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_jisko"));
-    g_object_set_data(G_OBJECT(e), "pref", OPT_SCREEN_NAME_JISKO);
-    text = purple_prefs_get_string(OPT_SCREEN_NAME_JISKO);
-    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);
-
-    e = GTK_WIDGET(gtk_builder_get_object (builder, "filter_jisko"));
-    g_object_set_data(G_OBJECT(e), "pref", OPT_FILTER_JISKO);
-    text = purple_prefs_get_string(OPT_FILTER_JISKO);
-    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_strip_excess_lf"));
-    g_object_set_data(G_OBJECT(e), "pref", OPT_STRIP_EXCESS_LF);
-    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e),
-                                 purple_prefs_get_bool(OPT_STRIP_EXCESS_LF));
-    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.0",                    /**< 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_STRIP_EXCESS_LF, 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_string(OPT_SCREEN_NAME_JISKO, 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_prefs_add_string(OPT_FILTER_JISKO, DEFAULT_LIST);
-}
-
-PURPLE_INIT_PLUGIN(pidgin_twitter, init_plugin, info)
--- a/pidgin-twitter.h	Fri Nov 21 22:22:33 2008 +0900
+++ b/pidgin-twitter.h	Sat Nov 22 18:01:18 2008 +0900
@@ -15,14 +15,20 @@
 #include <gdk-pixbuf/gdk-pixbuf.h>
 #include <libxml/xmlreader.h>
 
-#include "gtkplugin.h"
+#include <gtkplugin.h>
+#include <util.h>
+#include <debug.h>
+#include <connection.h>
+#include <version.h>
+#include <sound.h>
+#include <gtkconv.h>
+#include <gtkimhtml.h>
+
 #include "util.h"
-#include "debug.h"
-#include "connection.h"
-#include "version.h"
-#include "sound.h"
-#include "gtkconv.h"
-#include "gtkimhtml.h"
+#include "prefs.h"
+#include "twitter_api.h"
+#include "icon.h"
+#include "main.h"
 
 /* regp id */
 enum {
@@ -84,6 +90,11 @@
     guint id;
 } status_t;
 
+typedef struct _source {
+    guint id;
+    PurpleConversation *conv;
+} source_t;
+
 /* container for api based post */
 typedef struct twitter_message {
     PurpleAccount *account;
@@ -197,80 +208,11 @@
 #define DEFAULT_ICON_MAX_COUNT (50)
 #define DEFAULT_ICON_MAX_DAYS (7)
 #define DAYS_TO_SECONDS(d) ((d) * 86400)
+#define NUM_REGPS (13)
+#define NUM_SERVICES (4)          /* twitter, wassr, identica, jisko. */
 
 /* debug macros */
 #define twitter_debug(fmt, ...)	do { if(purple_prefs_get_bool(OPT_LOG_OUTPUT)) purple_debug(PURPLE_DEBUG_INFO, PLUGIN_NAME, "%s():%4d:  " fmt, __FUNCTION__, (int)__LINE__, ## __VA_ARGS__); } while(0);
 #define twitter_error(fmt, ...)	do { if(purple_prefs_get_bool(OPT_LOG_OUTPUT)) purple_debug(PURPLE_DEBUG_ERROR, PLUGIN_NAME, "%s():%4d:  " fmt, __FUNCTION__, (int)__LINE__, ## __VA_ARGS__); } while(0);
 
-/* prototypes */
-static void escape(gchar **str);
-static gboolean sending_im_cb(PurpleAccount *account, char *recipient, char **buffer, void *data);
-static gboolean eval(const GMatchInfo *match_info, GString *result, gpointer user_data);
-static void translate(gchar **str, gint which, gint service);
-static void playsound(gchar **str, gint which);
-static gboolean writing_im_cb(PurpleAccount *account, char *sender, char **buffer, PurpleConversation *conv, int flags, void *data);
-static void insert_text_cb(GtkTextBuffer *textbuffer, GtkTextIter *position, gchar *new_text, gint new_text_length, gpointer user_data);
-static void delete_text_cb(GtkTextBuffer *textbuffer, GtkTextIter *start_pos, GtkTextIter *end_pos, gpointer user_data);
-static void detach_from_window(void);
-static void detach_from_conv(PurpleConversation *conv, gpointer null);
-static void delete_requested_icon_marks(PidginConversation *gtkconv, GHashTable *table);
-static void attach_to_window(void);
-static void attach_to_conv(PurpleConversation *conv, gpointer null);
-static gboolean is_twitter_account(PurpleAccount *account, const char *name);
-static gboolean is_twitter_conv(PurpleConversation *conv);
-static gboolean is_wassr_account(PurpleAccount *account, const char *name);
-static gboolean is_wassr_conv(PurpleConversation *conv);
-static gboolean is_identica_account(PurpleAccount *account, const char *name);
-static gboolean is_identica_conv(PurpleConversation *conv);
-static gboolean is_jisko_account(PurpleAccount *account, const char *name);
-static gboolean is_jisko_conv(PurpleConversation *conv);
-static void conv_created_cb(PurpleConversation *conv, gpointer null);
-static void deleting_conv_cb(PurpleConversation *conv);
-static gboolean receiving_im_cb(PurpleAccount *account, char **sender, char **buffer, PurpleConversation *conv, PurpleMessageFlags *flags, void *data);
-static void insert_icon_at_mark(GtkTextMark *requested_mark, gpointer user_data);
-static void insert_requested_icon(const gchar *user_name, gint service);
-static void got_icon_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message);
-static void request_icon(const char *user_name, gint service, gboolean renew);
-static void mark_icon_for_user(GtkTextMark *mark, const gchar *user_name, gint service);
-static gboolean displaying_im_cb(PurpleAccount *account, const char *who, char **message, PurpleConversation *conv, PurpleMessageFlags flags, void *data);
-static void displayed_im_cb(PurpleAccount *account, const char *who, char *message, PurpleConversation *conv, PurpleMessageFlags flags);
-static gboolean load_plugin(PurplePlugin *plugin);
-static gboolean unload_plugin(PurplePlugin *plugin);
-static void counter_prefs_cb(const char *name, PurplePrefType type, gconstpointer val, gpointer data);
-
-static void init_plugin(PurplePlugin *plugin);
-static void remove_marks_func(gpointer key, gpointer value, gpointer user_data);
-static void cancel_fetch_func(gpointer key, gpointer value, gpointer user_data);
-static gint get_service_type(PurpleConversation *conv);
-static GdkPixbuf *make_scaled_pixbuf(const gchar *url_text, gsize len);
-
-static void parse_user(xmlNode *user, status_t *st);
-static void parse_status(xmlNode *status, status_t *st);
-static void get_status_with_api_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, size_t len, const gchar *error_message);
-static gboolean get_status_with_api(gpointer data);
-static void read_timestamp(const char *str, struct tm *res);
-
-static void strip_markup(gchar **str, gboolean escape);
-static gchar *strip_html_markup(const gchar *src);
-static GtkWidget *prefs_get_frame(PurplePlugin *plugin);
-
-/* more prototypes */
-static gboolean is_posted_message(status_t *status, guint lastid);
-static void free_status(status_t *st);
-static gboolean ensure_path_exists(const char *dir);
-static gchar *twitter_memrchr(const gchar *s, int c, size_t n);
-static void got_page_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message);
-static void cleanup_hash_entry_func(gpointer key, gpointer value, gpointer user_data);
-static void invalidate_icon_data_func(gpointer key, gpointer value, gpointer user_data);
-static void icon_size_prefs_cb(const char *name, PurplePrefType type, gconstpointer val, gpointer data);
-static void interval_prefs_cb(const char *name, PurplePrefType type, gconstpointer val, gpointer data);
-static void text_changed_cb(gpointer *data);
-static void bool_toggled_cb(gpointer *data);
-static void spin_changed_cb(gpointer *data);
-static void combo_changed_cb(gpointer *data);
-static void disconnect_prefs_cb(GtkObject *object, gpointer data);
-static GtkWidget *prefs_get_frame(PurplePlugin *plugin);
-static void apply_filter(gchar **sender, gchar **buffer, PurpleMessageFlags *flags, int service);
-static gint get_service_type_by_account(PurpleAccount *account, const char *sender);
-
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/prefs.c	Sat Nov 22 18:01:18 2008 +0900
@@ -0,0 +1,502 @@
+#include "pidgin-twitter.h"
+
+extern GHashTable *icon_hash[];
+extern source_t source;
+
+
+/* prototypes */
+static void icon_size_prefs_cb(const char *name, PurplePrefType type, gconstpointer val, gpointer data);
+static void interval_prefs_cb(const char *name, PurplePrefType type, gconstpointer val, gpointer data);
+static void text_changed_cb(gpointer *data);
+static void bool_toggled_cb(gpointer *data);
+static void spin_changed_cb(gpointer *data);
+static void combo_changed_cb(gpointer *data);
+static void disconnect_prefs_cb(GtkObject *object, gpointer data);
+static void counter_prefs_cb(const char *name, PurplePrefType type, gconstpointer val, gpointer data);
+
+
+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
+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 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));
+}
+
+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;
+#ifdef _WIN32
+    extern char binary_prefs_ui_start[];
+    extern char binary_prefs_ui_size[];
+#endif
+
+    builder = gtk_builder_new();
+
+#ifdef _WIN32
+    gtk_builder_add_from_string(builder, binary_prefs_ui_start,
+                                (int)binary_prefs_ui_size, NULL);
+#else
+    filename = g_build_filename(DATADIR,
+                                "pidgin-twitter", "prefs.ui", NULL);
+    gtk_builder_add_from_file(builder, filename, &err);
+#endif
+
+    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_jisko"));
+    g_object_set_data(G_OBJECT(e), "pref", OPT_SCREEN_NAME_JISKO);
+    text = purple_prefs_get_string(OPT_SCREEN_NAME_JISKO);
+    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);
+
+    e = GTK_WIDGET(gtk_builder_get_object (builder, "filter_jisko"));
+    g_object_set_data(G_OBJECT(e), "pref", OPT_FILTER_JISKO);
+    text = purple_prefs_get_string(OPT_FILTER_JISKO);
+    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_strip_excess_lf"));
+    g_object_set_data(G_OBJECT(e), "pref", OPT_STRIP_EXCESS_LF);
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e),
+                                 purple_prefs_get_bool(OPT_STRIP_EXCESS_LF));
+    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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/prefs.h	Sat Nov 22 18:01:18 2008 +0900
@@ -0,0 +1,6 @@
+#ifndef _PIDGIN_TWITTER_PREFS_H_
+#define _PIDGIN_TWITTER_PREFS_H_
+
+GtkWidget *prefs_get_frame(PurplePlugin *plugin);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/twitter_api.c	Sat Nov 22 18:01:18 2008 +0900
@@ -0,0 +1,564 @@
+#include "pidgin-twitter.h"
+
+static GList *postedlist = NULL;
+static GList *statuseslist = NULL;
+
+/* prototypes */
+static void parse_user(xmlNode *user, status_t *st);
+static void read_timestamp(const char *str, struct tm *res);
+static void parse_status(xmlNode *status, status_t *st);
+
+static void free_status(status_t *st);
+static gboolean is_posted_message(status_t *status, guint lastid);
+static void get_status_with_api_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, size_t len, const gchar *error_message);
+static void post_status_with_api_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, size_t len, const gchar *error_message);
+
+
+/**************************/
+/* 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(sender);
+             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. */
+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);
+    }
+
+}
+
+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);
+
+}
+
+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);
+                        }
+                    }
+                }
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/twitter_api.h	Sat Nov 22 18:01:18 2008 +0900
@@ -0,0 +1,8 @@
+#ifndef _PIDGIN_TWITTER_TWITTER_API_H_
+#define _PIDGIN_TWITTER_TWITTER_API_H_
+
+void post_status_with_api(PurpleAccount *account, char **buffer);
+gboolean get_status_with_api(gpointer data);
+void signed_on_cb(PurpleConnection *gc);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util.c	Sat Nov 22 18:01:18 2008 +0900
@@ -0,0 +1,259 @@
+#include "pidgin-twitter.h"
+
+extern GRegex *regp[];
+
+/* prototypes */
+static gchar *twitter_memrchr(const gchar *s, int c, size_t n);
+
+
+
+/* this function has been taken from autoaccept plugin */
+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;
+}
+
+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
+};
+
+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);
+            g_free(tmp);
+            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 */
+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;
+    }
+}
+
+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);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util.h	Sat Nov 22 18:01:18 2008 +0900
@@ -0,0 +1,9 @@
+#ifndef _PIDGIN_TWITTER_UTIL_H_
+#define _PIDGIN_TWITTER_UTIL_H_
+
+void escape(gchar **str);
+void strip_markup(gchar **str, gboolean escape);
+gchar *strip_html_markup(const gchar *src);
+gboolean ensure_path_exists(const char *dir);
+
+#endif