diff main.c @ 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 pidgin-twitter.c@a37ae6c8fa66
children 9fb8f597adf3
line wrap: on
line diff
--- /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)