changeset 115:7d0dd0e1dbd0

very preliminary twitter API get status feature.
author Yoshiki Yazawa <yaz@honeyplanet.jp>
date Wed, 16 Jul 2008 19:19:10 +0900
parents 48bfe86ff990
children dd27aeead474
files Makefile.in configure.in pidgin-twitter.c
diffstat 3 files changed, 252 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile.in	Wed Jul 16 01:30:28 2008 +0900
+++ b/Makefile.in	Wed Jul 16 19:19:10 2008 +0900
@@ -3,10 +3,11 @@
 
 PIDGIN_CFLAGS = @PIDGIN_CFLAGS@
 GLIB_CFLAGS = @GLIB_CFLAGS@
-CFLAGS = -fPIC -shared -Wall $(PIDGIN_CFLAGS) $(GLIB_CFLAGS)
+XML_CFLAGS = @XML_CFLAGS@
+CFLAGS = -fPIC -shared -Wall $(PIDGIN_CFLAGS) $(GLIB_CFLAGS) $(XML_CFLAGS)
 
 GLIB_LIBS = @GLIB_LIBS@
-LDFLAGS = $(GLIB_LIBS)
+LDFLAGS = $(GLIB_LIBS) $(XML_LIBS)
 
 PIDGIN_PLUGIN_DIR = @PIDGIN_PREFIX@/lib/pidgin
 
--- a/configure.in	Wed Jul 16 01:30:28 2008 +0900
+++ b/configure.in	Wed Jul 16 19:19:10 2008 +0900
@@ -40,6 +40,20 @@
 AC_SUBST(PIDGIN_LIBS)
 AC_SUBST(PIDGIN_PREFIX)
 
+PKG_CHECK_MODULES(GLIB, [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])
--- a/pidgin-twitter.c	Wed Jul 16 01:30:28 2008 +0900
+++ b/pidgin-twitter.c	Wed Jul 16 19:19:10 2008 +0900
@@ -56,6 +56,221 @@
     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) {
+             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 TRUE; //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 FALSE;
+    }
+
+    /* 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 FALSE;
+}
+
+
+
+
+
+
+
+
+
 /* this function is a modified clone of purple_markup_strip_html() */
 static char *
 strip_html_markup(const char *str)
@@ -283,16 +498,6 @@
     *str = plain;
 }
 
-#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"
 
 typedef struct twitter_message {
     PurpleAccount *account;
@@ -842,6 +1047,9 @@
         gint service = get_service_type(conv);
         switch(service) {
         case twitter_service:
+            g_source_remove_by_user_data((gpointer)conv);
+            detach_from_conv(conv, NULL);
+            break;
         case wassr_service:
         case identica_service:
             detach_from_conv(conv, NULL);
@@ -951,6 +1159,11 @@
         /* only attach to twitter conversation window */
         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);
+            attach_to_conv(conv, NULL);
+            break;
         case wassr_service:
         case identica_service:
             attach_to_conv(conv, NULL);
@@ -1121,6 +1334,11 @@
     /* only attach to twitter conversation window */
     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);
+        attach_to_conv(conv, NULL);
+        break;
     case wassr_service:
     case identica_service:
         attach_to_conv(conv, NULL);
@@ -1194,6 +1412,12 @@
         return FALSE;
     }
 
+    /* if we use api, discard incoming IM message. XXX need fix */
+    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;
 
@@ -2261,6 +2485,7 @@
     purple_prefs_add_int(OPT_ICON_SIZE, 48);
     purple_prefs_add_string(OPT_SCREEN_NAME_WASSR, EMPTY);
     purple_prefs_add_string(OPT_SCREEN_NAME_IDENTICA, EMPTY);
+
 }
 
 PURPLE_INIT_PLUGIN(pidgin_twitter, init_plugin, info)