changeset 119:0c4a83f734cd

- duplication avoidance for the posted messages has been implemented. - time stamps in a log will be printed correctly. - some functions have been relocated in the source file. - some data definitions and macros have been moved to the header file. - minor modification to Makefile.in.
author Yoshiki Yazawa <yaz@honeyplanet.jp>
date Fri, 18 Jul 2008 01:00:18 +0900
parents 8b097fcb9243
children b47bc7b89de6
files Makefile.in pidgin-twitter.c pidgin-twitter.h
diffstat 3 files changed, 380 insertions(+), 268 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile.in	Thu Jul 17 23:53:15 2008 +0900
+++ b/Makefile.in	Fri Jul 18 01:00:18 2008 +0900
@@ -1,5 +1,6 @@
 OBJECTIVE = pidgin-twitter.so
 SRC = pidgin-twitter.c
+HDR = ${SRC:.c=.h}
 
 PIDGIN_CFLAGS = @PIDGIN_CFLAGS@
 GLIB_CFLAGS = @GLIB_CFLAGS@
@@ -15,7 +16,7 @@
 all: $(OBJECTIVE)
 
 
-$(OBJECTIVE): $(SRC)
+$(OBJECTIVE): $(SRC) $(HDR)
 	gcc -o $@ $(SRC) $(CFLAGS) $(LDFLAGS) -g 
 
 
--- a/pidgin-twitter.c	Thu Jul 17 23:53:15 2008 +0900
+++ b/pidgin-twitter.c	Fri Jul 18 01:00:18 2008 +0900
@@ -19,260 +19,24 @@
 #define PURPLE_PLUGINS 1
 
 #include "pidgin-twitter.h"
-#include <gdk-pixbuf/gdk-pixbuf.h>
-
+
+/***********/
 /* globals */
+/***********/
 static GRegex *regp[9];
 static gboolean suppress_oops = FALSE;
 static GHashTable *icon_data_by_user  = NULL; // twitter
 static GHashTable *icon_data_by_user2 = NULL; // wassr
 static GHashTable *icon_data_by_user3 = NULL; // identi.ca
 static GHashTable *conv_hash = NULL;
-
-#define WASSR_POST_LEN (255 * 4)
+static GList *statuseslist = NULL;
+static GList *postedlist = NULL;
 static gchar *wassr_post = NULL;
 
-typedef struct _icon_data {
-    gint icon_id;        // image id
-    gboolean requested;  // TRUE if download icon has been requested
-    GList *request_list; // marker list
-    PurpleUtilFetchUrlData *fetch_data;          // icon fetch data
-} icon_data;
-
-enum {
-    unknown_service = 0,
-    twitter_service,
-    wassr_service,
-    identica_service
-};
-
-typedef struct _eval_data {
-    gint which;
-    gint service;
-} eval_data;
-
-typedef struct _got_icon_data {
-    gchar *user_name;
-    gint service;
-} got_icon_data;
-
-#define TWITTER_STATUS_POST "POST /statuses/update.xml HTTP/1.0\r\n" \
-    "Host: twitter.com\r\n"                                          \
-    "User-Agent: Pidgin-Twitter\r\n"                                 \
-    "Authorization: Basic %s\r\n"                                    \
-    "Content-Length: %d\r\n\r\n"
-
-#define TWITTER_STATUS_FORMAT "status=%s"
-#define TWITTER_STATUS_TERMINATOR "\r\n\r\n"
-
-#define TWITTER_BASE_URL "http://twitter.com"
-
-#define TWITTER_STATUS_GET "GET /statuses/friends_timeline.xml HTTP/1.0\r\n" \
-    "Host: twitter.com\r\n"                                          \
-    "User-Agent: Pidgin-Twitter\r\n"                                 \
-    "Authorization: Basic %s\r\n"
-
-
-
-
-
-
-
-/* xml parser*/
-#include <libxml/tree.h>
-#include <libxml/parser.h>
-#include <libxml/xmlreader.h>
-#include <libxml/xpath.h>
-#include <libxml/xpathInternals.h>
-#include <libxml/uri.h>
-
-typedef struct _status {
-    time_t time;
-    guint id;
-    gchar *created_at;
-    gchar *text;
-    gchar *screen_name;
-    gchar *profile_image_url;
-} status_t;
-
-GList *stlist = NULL;
-
-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 void
-parse_status(xmlNode *status)
-{
-    xmlNode *nptr;
-
-    status_t *st = g_new0(status_t, 1);
-
-    stlist = g_list_prepend(stlist, st);
-
-    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);
-                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
-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;
-    g_return_if_fail(url_text != NULL);
-
-    PurpleConversation *conv = (PurpleConversation *)user_data;
-
-    if(!conv)
-        return;
-
-    const gchar *start = strstr(url_text, "<?xml");
-
-    doc = xmlRecoverMemory(start, len);
-    if(doc == NULL)
-        return;
-
-     for(nptr = doc->children; nptr != NULL; nptr = nptr->next) {
-        if(nptr->type == XML_ELEMENT_NODE &&
-           !xmlStrcmp(nptr->name, (xmlChar *)"statuses")) {
-
-            for(nptr2 = nptr->children; nptr2 != NULL; nptr2 = nptr2->next) {
-                if(nptr2->type == XML_ELEMENT_NODE &&
-                    !xmlStrcmp(nptr2->name, (xmlChar *)"status")) {
-                    parse_status(nptr2);
-                }
-            }
-        }
-     } /* for */
-
-     /* process stlist */
-     GList *stp;
-
-     for(stp = stlist; stp; stp=stp->next) {
-         status_t *st = (status_t *)stp->data;
-         if(st->id > lastid &&
-            strcmp(st->screen_name,
-                   purple_prefs_get_string(OPT_SCREEN_NAME_TWITTER))) {
-
-             gchar *msg = NULL;
-
-             msg = g_strdup_printf("%s: %s\n", st->screen_name, st->text);
-             purple_conv_im_write(conv->u.im,
-                                  "twitter@twitter.com",
-                                  msg,
-                                  PURPLE_MESSAGE_RECV,
-                                  st->time);
-             lastid = st->id;
-
-             g_free(msg);
-         }
-
-         g_free(st->created_at);
-         g_free(st->text);
-         g_free(st->screen_name);
-         g_free(st->profile_image_url);
-         g_free(stp->data);
-         stp->data = NULL;
-     }
-
-     stlist = g_list_remove_all(stlist, NULL);
-}
-
-
-/* api based get */
-static gboolean
-get_status_with_api(gpointer data)
-{
-    /* fetch friends time line */
-    char *request, *header;
-    char *basic_auth, *basic_auth_encoded;
-
-    PurpleConversation *conv = (PurpleConversation *)data;
-    if(!conv)
-        return FALSE; //cease fetch
-
-    const char *screen_name =
-        purple_prefs_get_string(OPT_SCREEN_NAME_TWITTER);
-    const char *password =
-        purple_prefs_get_string(OPT_PASSWORD_TWITTER);
-
-    if (!screen_name || !password || !screen_name[0] || !password[0]) {
-        twitter_debug("screen_name or password is empty\n");
-        return TRUE;
-    }
-
-    /* auth */
-    basic_auth = g_strdup_printf("%s:%s", screen_name, password);
-    basic_auth_encoded = purple_base64_encode((unsigned char *)basic_auth,
-                                              strlen(basic_auth));
-    g_free(basic_auth);
-
-    /* header */
-    header = g_strdup_printf(TWITTER_STATUS_GET, basic_auth_encoded);
-    request = g_strconcat(header, TWITTER_STATUS_TERMINATOR, NULL);
-
-    /* invoke fetch */
-    purple_util_fetch_url_request(TWITTER_BASE_URL, FALSE,
-                                  NULL, TRUE, request, TRUE,
-                                  get_status_with_api_cb, data);
-
-    g_free(header);
-    g_free(basic_auth_encoded);
-    g_free(request);
-
-    return TRUE;
-}
-
-
-
-
-
-
-
-
+
+/*************/
+/* functions */
+/*************/
 
 /* this function is a modified clone of purple_markup_strip_html() */
 static char *
@@ -447,6 +211,8 @@
 /**********************/
 /* our implementation */
 /**********************/
+
+/* string utilities */
 static void
 escape(gchar **str)
 {
@@ -502,18 +268,238 @@
 }
 
 
-typedef struct twitter_message {
-    PurpleAccount *account;
-    char *status;
-    time_t time;
-} twitter_message_t;
-
+/**************************/
+/* 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 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);
+
+                /* set locale to C */
+                gchar *lc_time = setlocale(LC_TIME, NULL);
+                setlocale(LC_TIME, "C");
+
+                /* set timezone to UTC */
+                gchar *timezone = g_strdup(getenv("TZ"));
+                setenv("TZ", "UTC", TRUE);
+                tzset();
+
+                /* read time stamp */
+                struct tm res;
+                strptime(str, "%a %b %d %T %z %Y", &res);
+                st->time = mktime(&res);
+
+                /* restore timezone */
+                if(timezone)
+                    setenv("TZ", timezone, TRUE);
+                else
+                    unsetenv("TZ");
+
+                g_free(timezone);
+
+                /* restore locale */
+                setlocale(LC_TIME, lc_time);
+
+                xmlFree(str);
+            }
+            else if(!xmlStrcmp(nptr->name, (xmlChar *)"id")) {
+                gchar *str = (gchar *)xmlNodeGetContent(nptr);
+                st->id = atoi(str);
+                xmlFree(str);
+            }
+            else if(!xmlStrcmp(nptr->name, (xmlChar *)"text")) {
+                gchar *str = (gchar *)xmlNodeGetContent(nptr);
+                st->text = g_strdup(str);
+                xmlFree(str);
+            }
+            else if(!xmlStrcmp(nptr->name, (xmlChar *)"user")) {
+                parse_user(nptr, st);
+            }
+        }
+    }
+}
+
+static void
+free_status(status_t *st)
+{
+    g_free(st->created_at);
+    g_free(st->text);
+    g_free(st->screen_name);
+    g_free(st->profile_image_url);
+}
+
+static gboolean
+is_posted_message(status_t *status)
+{
+    GList *pp;
+    gboolean rv = FALSE;
+
+    for(pp = postedlist; pp; pp=pp->next) {
+        status_t *posted = (status_t *)pp->data;
+        if(posted->id == status->id) {
+            rv = TRUE;
+            free_status(posted);
+            g_free(pp->data);
+            pp->data = NULL;
+        }
+    }
+
+    postedlist = g_list_remove_all(postedlist, NULL);
+
+    return rv;
+}
+
+static void
+get_status_with_api_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
+                        const gchar *url_text, size_t len,
+                        const gchar *error_message)
+
+{
+    xmlDocPtr doc;
+    xmlNode *nptr, *nptr2;
+    static guint lastid = 0;
+    PurpleConversation *conv;
+    GList *stp;
+    const gchar *start;
+
+    g_return_if_fail(url_text != NULL);
+
+    conv = (PurpleConversation *)user_data;
+    if(!conv)
+        return;
+
+    /* skip to the beginning of xml */
+    start = strstr(url_text, "<?xml");
+
+    doc = xmlRecoverMemory(start, len - (start - url_text));
+    if(doc == NULL)
+        return;
+
+     for(nptr = doc->children; nptr != NULL; nptr = nptr->next) {
+        if(nptr->type == XML_ELEMENT_NODE &&
+           !xmlStrcmp(nptr->name, (xmlChar *)"statuses")) {
+
+            for(nptr2 = nptr->children; nptr2 != NULL; nptr2 = nptr2->next) {
+                if(nptr2->type == XML_ELEMENT_NODE &&
+                    !xmlStrcmp(nptr2->name, (xmlChar *)"status")) {
+                    status_t *st = g_new0(status_t, 1);
+                    statuseslist = g_list_prepend(statuseslist, st);
+                    parse_status(nptr2, st);
+                }
+            }
+        }
+     }
+
+     /* process statuseslist */
+     for(stp = statuseslist; stp; stp=stp->next) {
+         status_t *st = (status_t *)stp->data;
+
+         if(st->id > lastid && !is_posted_message(st)) {
+             gchar *msg = NULL;
+
+             msg = g_strdup_printf("%s: %s\n", st->screen_name, st->text);
+             purple_conv_im_write(conv->u.im,
+                                  "twitter@twitter.com",
+                                  msg,
+                                  PURPLE_MESSAGE_RECV,
+                                  st->time);
+             lastid = st->id;
+
+             g_free(msg);
+         }
+
+         free_status(st);
+         g_free(stp->data);
+         stp->data = NULL;
+     }
+
+     statuseslist = g_list_remove_all(statuseslist, NULL);
+}
+
+/* status fetching function. it will be called periodically. */ 
+static gboolean
+get_status_with_api(gpointer data)
+{
+    /* fetch friends time line */
+    char *request, *header;
+    char *basic_auth, *basic_auth_encoded;
+
+    twitter_debug("called\n");
+
+    PurpleConversation *conv = (PurpleConversation *)data;
+    if(!conv)
+        return FALSE; //cease fetch
+
+    const char *screen_name =
+        purple_prefs_get_string(OPT_SCREEN_NAME_TWITTER);
+    const char *password =
+        purple_prefs_get_string(OPT_PASSWORD_TWITTER);
+
+    if (!screen_name || !password || !screen_name[0] || !password[0]) {
+        twitter_debug("screen_name or password is empty\n");
+        return TRUE;
+    }
+
+    /* auth */
+    basic_auth = g_strdup_printf("%s:%s", screen_name, password);
+    basic_auth_encoded = purple_base64_encode((unsigned char *)basic_auth,
+                                              strlen(basic_auth));
+    g_free(basic_auth);
+
+    /* header */
+    header = g_strdup_printf(TWITTER_STATUS_GET, basic_auth_encoded);
+    request = g_strconcat(header, TWITTER_STATUS_TERMINATOR, NULL);
+
+    /* invoke fetch */
+    purple_util_fetch_url_request(TWITTER_BASE_URL, FALSE,
+                                  NULL, TRUE, request, TRUE,
+                                  get_status_with_api_cb, data);
+
+    g_free(header);
+    g_free(basic_auth_encoded);
+    g_free(request);
+
+    return TRUE;
+}
+
+/***************************/
+/* API base post functions */
+/***************************/
 static void
 post_status_with_api_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
                         const gchar *url_text, size_t len,
                         const gchar *error_message)
 {
-    twitter_message_t *tm = (struct twitter_message *)user_data;
+    twitter_message_t *tm = (twitter_message_t *)user_data;
     gchar *msg = NULL;
     char *p1 = NULL, *p2 = NULL;
     int error = 1;
@@ -589,6 +575,31 @@
         purple_conv_im_write(conv->u.im,
                              purple_account_get_username(tm->account),
                              tm->status, PURPLE_MESSAGE_SEND, tm->time);
+
+        /* cache message ID that posted via API */
+        gchar *start = NULL;
+        xmlDocPtr doc;
+        xmlNode *nptr;
+
+        start = strstr(url_text, "<?xml");
+
+        if(!start)
+            goto fin;
+
+        doc = xmlRecoverMemory(start, len - (start - url_text));
+        if(doc == NULL)
+            return;
+
+        /* enqueue posted message to postedlist */
+        for(nptr = doc->children; nptr != NULL; nptr = nptr->next) {
+            if(nptr->type == XML_ELEMENT_NODE &&
+               !xmlStrcmp(nptr->name, (xmlChar *)"status")) {
+                status_t *st = g_new0(status_t, 1);
+                postedlist = g_list_prepend(postedlist, st);
+                parse_status(nptr, st);
+            }
+        }
+
     } else {
         gchar *m;
         m = g_strdup_printf("%s<BR>%s",
@@ -661,6 +672,9 @@
 
 }
 
+/***********************/
+/* intrinsic functions */
+/***********************/
 static gboolean
 sending_im_cb(PurpleAccount *account, char *recipient, char **buffer,
               void *data)
@@ -738,7 +752,7 @@
             twitter_debug("unknown service\n");
             break;
         }
-        snprintf(sub, 128, format, match, match);
+        g_snprintf(sub, 128, format, match, match);
         g_free(match);
     }
     else if(which == SENDER) {
@@ -761,7 +775,7 @@
             break;
         }
 
-        snprintf(sub, 128, format, match1 ? match1: "", match2, match2);
+        g_snprintf(sub, 128, format, match1 ? match1: "", match2, match2);
 
         g_free(match1);
         g_free(match2);
@@ -771,7 +785,7 @@
         gchar *match2 = g_match_info_fetch(match_info, 2); //channel
         const gchar *format = CHANNEL_FORMAT_WASSR;
 
-        snprintf(sub, 128, format, match1 ? match1: "", match2, match2);
+        g_snprintf(sub, 128, format, match1 ? match1: "", match2, match2);
 
         g_free(match1);
         g_free(match2);
@@ -1050,7 +1064,10 @@
         gint service = get_service_type(conv);
         switch(service) {
         case twitter_service:
-            g_source_remove_by_user_data((gpointer)conv);
+#if 0
+            if(purple_prefs_get_bool(OPT_API_BASE_POST))
+                g_source_remove_by_user_data((gpointer)conv);
+#endif
             detach_from_conv(conv, NULL);
             break;
         case wassr_service:
@@ -1153,6 +1170,8 @@
 {
     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;
@@ -1162,9 +1181,13 @@
         /* only attach to twitter conversation window */
         switch(service) {
         case twitter_service:
+#if 0
             /* api based retrieve */ //xxx should configurable
-            get_status_with_api((gpointer)conv);
-            g_timeout_add_seconds(60, get_status_with_api, (gpointer)conv);
+            if(purple_prefs_get_bool(OPT_API_BASE_POST)) {
+                get_status_with_api((gpointer)conv);
+                g_timeout_add_seconds(60, get_status_with_api, (gpointer)conv);
+            }
+#endif
             attach_to_conv(conv, NULL);
             break;
         case wassr_service:
@@ -1331,6 +1354,9 @@
 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);
@@ -1338,8 +1364,12 @@
     switch(service) {
     case twitter_service:
         /* api based retrieve */ //xxx should configurable
-        get_status_with_api((gpointer)conv);
-        g_timeout_add_seconds(60, get_status_with_api, (gpointer)conv);
+#if 1
+        if(purple_prefs_get_bool(OPT_API_BASE_POST)) {
+            get_status_with_api((gpointer)conv);
+            g_timeout_add_seconds(60, get_status_with_api, (gpointer)conv);
+        }
+#endif
         attach_to_conv(conv, NULL);
         break;
     case wassr_service:
@@ -1357,6 +1387,8 @@
 {
     PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
 
+    twitter_debug("called\n");
+
     g_return_if_fail(gtkconv != NULL);
 
     gint service = get_service_type(conv);
@@ -1365,6 +1397,8 @@
     /* only attach to twitter conversation window */
     switch(service) {
     case twitter_service:
+        if(purple_prefs_get_bool(OPT_API_BASE_POST))
+            g_source_remove_by_user_data((gpointer)conv);
         hash = icon_data_by_user;
         break;
     case wassr_service:
@@ -1415,10 +1449,10 @@
         return FALSE;
     }
 
-    /* if we use api, discard incoming IM message. XXX need fix */
+    /* if we use api, discard incoming IM message. XXX too wild? */
     if(purple_prefs_get_bool(OPT_API_BASE_POST)) {
-            g_free(*sender); *sender = NULL;
-            g_free(*buffer); *buffer = NULL;
+        g_free(*sender); *sender = NULL;
+        g_free(*buffer); *buffer = NULL;
     }
 
     if(!suppress_oops || !purple_prefs_get_bool(OPT_SUPPRESS_OOPS))
@@ -1482,7 +1516,6 @@
     }
 
     /* insert icon to the mark */
-
     gtk_text_buffer_get_iter_at_mark(target_buffer,
                                      &insertion_point, requested_mark);
 
@@ -1573,6 +1606,7 @@
     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)
@@ -1654,7 +1688,7 @@
     return dest;
 }
 
-
+/* 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)
@@ -2474,12 +2508,12 @@
     /****************/
     /* API heading */
     /****************/
-    pref = purple_plugin_pref_new_with_label("API Based Post");
+    pref = purple_plugin_pref_new_with_label("API Based Twitter Access");
     purple_plugin_pref_frame_add(frame, pref);
 
     /* post configuration */
     pref = purple_plugin_pref_new_with_name_and_label(OPT_API_BASE_POST,
-                                             "Post Status to Twitter via API");
+                                             "Post/get statuses via API");
     purple_plugin_pref_frame_add(frame, pref);
 
     purple_prefs_connect_callback(plugin, OPT_API_BASE_POST, 
--- a/pidgin-twitter.h	Thu Jul 17 23:53:15 2008 +0900
+++ b/pidgin-twitter.h	Fri Jul 18 01:00:18 2008 +0900
@@ -1,11 +1,17 @@
 #ifndef _PIDGIN_TWITTER_H_
 #define _PIDGIN_TWITTER_H_
 
+#define _XOPEN_SOURCE 600
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <glib.h>
 #include <sys/stat.h>
+#include <time.h>
+#include <locale.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <libxml/xmlreader.h>
 
 #include "gtkplugin.h"
 #include "util.h"
@@ -29,6 +35,52 @@
     IMAGE_IDENTICA
 };
 
+/* service id */
+enum {
+    unknown_service = 0,
+    twitter_service,
+    wassr_service,
+    identica_service
+};
+
+/* container to hold icon data */
+typedef struct _icon_data {
+    gint icon_id;        // image id
+    gboolean requested;  // TRUE if download icon has been requested
+    GList *request_list; // marker list
+    PurpleUtilFetchUrlData *fetch_data;          // icon fetch data
+} icon_data;
+
+/* used by got_icon_cb */
+typedef struct _got_icon_data {
+    gchar *user_name;
+    gint service;
+} got_icon_data;
+
+/* used by eval */
+typedef struct _eval_data {
+    gint which;
+    gint service;
+} eval_data;
+
+/* container for api based retrieve */
+typedef struct _status {
+    gchar *created_at;
+    gchar *text;
+    gchar *screen_name;
+    gchar *profile_image_url;
+    time_t time;
+    guint id;
+} status_t;
+
+/* container for api based post */
+typedef struct twitter_message {
+    PurpleAccount *account;
+    char *status;
+    time_t time;
+} twitter_message_t;
+
+
 #define PLUGIN_ID	            "gtk-honeyplanet-pidgin_twitter"
 #define PLUGIN_NAME	            "pidgin-twitter"
 
@@ -79,6 +131,26 @@
 #define P_CHANNEL           "^(.*?<a .+?>[A-Za-z0-9_]+</a>: \\r?\\n?#)([A-Za-z0-9_]+) "
 #define P_IMAGE_IDENTICA    "<img src=\"(http://avatar.identi.ca/[A-Za-z0-9-.]+)\" class=\"avatar profile\" width=\"96\" height=\"96\" alt=\"[A-Za-z0-0_]+\"/>"
 
+/* twitter API specific macros */
+#define TWITTER_STATUS_POST "POST /statuses/update.xml HTTP/1.0\r\n" \
+    "Host: twitter.com\r\n"                                          \
+    "User-Agent: Pidgin-Twitter\r\n"                                 \
+    "Authorization: Basic %s\r\n"                                    \
+    "Content-Length: %d\r\n\r\n"
+
+#define TWITTER_STATUS_FORMAT "status=%s"
+#define TWITTER_STATUS_TERMINATOR "\r\n\r\n"
+
+#define TWITTER_BASE_URL "http://twitter.com"
+
+#define TWITTER_STATUS_GET "GET /statuses/friends_timeline.xml HTTP/1.0\r\n" \
+    "Host: twitter.com\r\n"                                          \
+    "User-Agent: Pidgin-Twitter\r\n"                                 \
+    "Authorization: Basic %s\r\n"
+
+/* wassr specific macros */
+#define WASSR_POST_LEN (255 * 4)
+
 /* debug macros */
 #define twitter_debug(fmt, ...)	purple_debug(PURPLE_DEBUG_INFO, PLUGIN_NAME, "%s():%4d:  " fmt, __FUNCTION__, (int)__LINE__, ## __VA_ARGS__);
 #define twitter_error(fmt, ...)	purple_debug(PURPLE_DEBUG_ERROR, PLUGIN_NAME, "%s():%4d:  " fmt, __FUNCTION__, (int)__LINE__, ## __VA_ARGS__);
@@ -123,4 +195,9 @@
 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);
+
 #endif