# HG changeset patch # User Yoshiki Yazawa # Date 1216203550 -32400 # Node ID 7d0dd0e1dbd0f1dae01856ca8612aebdd856b12a # Parent 48bfe86ff99037775394964fa2af16af490d36ec very preliminary twitter API get status feature. diff -r 48bfe86ff990 -r 7d0dd0e1dbd0 Makefile.in --- 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 diff -r 48bfe86ff990 -r 7d0dd0e1dbd0 configure.in --- 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]) diff -r 48bfe86ff990 -r 7d0dd0e1dbd0 pidgin-twitter.c --- 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 +#include +#include +#include +#include +#include + +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, "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)