view twitter_api.c @ 355:0fe895195132

implement OAuth authentication.
author Yoshiki Yazawa <yaz@honeyplanet.jp>
date Thu, 26 Aug 2010 15:05:33 +0900
parents ff078879e68e
children 732de90812d8
line wrap: on
line source

#include "pidgin-twitter.h"

static GList *postedlist = NULL;
static GList *statuseslist = NULL;

/* prototypes */
static void parse_user(xmlNode *user, status_t *st);
static void read_timestamp(const char *str, struct tm *res);
static void parse_status(xmlNode *status, status_t *st);

static void free_status(status_t *st);
static gboolean is_posted_message(status_t *status, guint64 lastid);
static void get_status_with_api_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, size_t len, const gchar *error_message);
static void post_status_with_api_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, size_t len, const gchar *error_message);

/* oauth */
#define TYPE_GET  0
#define TYPE_POST 1

typedef struct oauth_request {
    char *url;
    char *c_key;
    char *c_sec;
    char *a_key;
    char *a_sec;
    char *verifier;
    char *status;
    PurpleConversation *conv;
    guint64 msgid;
    int count;
    int type;
    gboolean notoken;
} oauth_request_t;

char *make_oauth_get(oauth_request_t *oauth_req);
char *make_oauth_post(oauth_request_t *auth_req);
static void oauth_setup_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, size_t len, const gchar *error_message);


#ifdef _WIN32
extern gboolean blink_state;
extern gboolean blink_modified;
#endif

extern guint64 reply_to_msgid;
extern PurpleAccount *account_for_twitter;

char *request_token_url = "http://twitter.com/oauth/request_token";
char *access_token_url  = "http://twitter.com/oauth/access_token";
char *authorize_url     = "http://twitter.com/oauth/authorize";

char *c_key = "wrD3WGIh2P31d3fIjRkfcw";
char *c_sec = "ZEhViGY8P5IPjwgV8EVOkdjHhShRAZ9yhlYw0ZDXU";
char *SAMPLE_NONCE = "0123456789abcdefghijk";

void
oauth_access_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
                        const gchar *url_text, size_t len,
                        const gchar *error_message)
{
    char *f = NULL, *e = NULL;
    oauth_request_t *oauth_req = (oauth_request_t *)user_data;

    /* separate key and secret */
    f = strstr(url_text, "oauth_token=");
    if(!f)
        return;
    e = strstr(f, "&");
    if(!e)
        return;
    g_free(oauth_req->a_key);
    oauth_req->a_key = g_strndup(f+12, e-f-12);

    f = strstr(e+1, "oauth_token_secret=");
    if(!f)
        return;
    e = strstr(f, "&");
    if(!e)
        return;
    g_free(oauth_req->a_sec);
    oauth_req->a_sec = g_strndup(f+19, e-f-19);

    /* write a_key and a_sec to prefs */
    purple_prefs_set_string(OPT_AKEY_TWITTER, oauth_req->a_key);
    purple_prefs_set_string(OPT_ASEC_TWITTER, oauth_req->a_sec);

    /* invoke fetch xxx */
    g_usleep(3*1000000); /* wait for server configuration */
    get_status_with_api((gpointer)oauth_req->conv);

    /* all done */
    g_free(oauth_req->url);
    g_free(oauth_req->c_key);
    g_free(oauth_req->c_sec);
    g_free(oauth_req->a_key);
    g_free(oauth_req->a_sec);
    g_free(oauth_req);
}


void
pin_dialog_ok_cb(gpointer data, char *pin)
{
    char *oauth = NULL;
    char *request = NULL;
    oauth_request_t *oauth_req = (oauth_request_t *)data;
/*
request URL:http://twitter.com/oauth/access_token?
    oauth_consumer_key=wrD3WGIh2P31d3fIjRkfcw&
    oauth_nonce=QUzhkt0AO3tjNrR&
    oauth_signature_method=HMAC-SHA1&
    oauth_timestamp=1282450223&

    oauth_token=VL126k8KRNXid7Q7ZHYHh05PuASunVaPzyzrozf14&
    oauth_verifier=9772286&
    oauth_version=1.0&
    oauth_signature=OyBsxqWdsvLkV4LBnN8tMBnImVY%3D
*/

    /* access token*/
    g_free(oauth_req->url);
    oauth_req->url = g_strdup(access_token_url);
    oauth_req->verifier = g_strdup(pin);
    oauth_req->type = TYPE_GET;
    oauth_req->notoken = FALSE;

    twitter_debug("a_key=%s\n", oauth_req->a_key);

    oauth = make_oauth_get(oauth_req);

    request = g_strdup_printf("%s?%s", access_token_url, oauth);
    twitter_debug("request=%s\n", request);

    purple_util_fetch_url_request(request, TRUE,
                                  NULL, TRUE, NULL, TRUE,
                                  oauth_access_cb, data);
    g_free(oauth);
    g_free(request);
}


static void
oauth_setup_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
                        const gchar *url_text, size_t len,
                        const gchar *error_message)
{
    char *f = NULL, *e = NULL;
    oauth_request_t *oauth_req = (oauth_request_t *)user_data;
    PurpleConversation *conv = oauth_req->conv;
    PurpleAccount *account = purple_conversation_get_account(conv);
    PurpleConnection *conn = purple_conversation_get_gc(conv);

    g_return_if_fail(url_text != NULL);
    twitter_debug("len=%d\n", (int)len);
    twitter_debug("url_text=%s\n", url_text);

/*
    HTTP-reply:
    oauth_token=zKILrNcVPGRtUE6Rlh1KU6upYNJWW51mzt7btZx5Ac&
    oauth_token_secret=EMD3u1piAKPsQnq44Its9f8WmIReYnUFcJIgd3niu4&
    oauth_callback_confirmed=true
*/
    /* separate key and secret */
    f = strstr(url_text, "oauth_token=");
    if(!f)
        return;
    e = strstr(f, "&");
    if(!e)
        return;
    g_free(oauth_req->a_key);
    oauth_req->a_key = g_strndup(f+12, e-f-12);

    f = strstr(e+1, "oauth_token_secret=");
    if(!f)
        return;
    e = strstr(f, "&");
    if(!e)
        return;
    g_free(oauth_req->a_sec);
    oauth_req->a_sec = g_strndup(f+19, e-f-19);

    /* redirect twitter's authorization url */
    char *uri = g_strdup_printf("%s?oauth_token=%s", authorize_url, oauth_req->a_key);
    twitter_debug("auth uri=%s\n", uri);
    purple_notify_uri(conn, uri);

    /* show dialog to wait PIN number*/
    purple_request_input(conn,
                         "PIN",
                         "Enter PIN",
                         "Press allow button in the browser, then enter the PIN  to complete process.",
                         "",
                         FALSE,
                         FALSE,
                         NULL,
                         "OK", G_CALLBACK(pin_dialog_ok_cb),
                         "Cancel", NULL,
                         account,
                         NULL,
                         NULL,
                         user_data);

    g_free(uri);
}

void
oauth_setup(gpointer data)
{
    char *oauth = NULL;
    char *request = NULL;
    oauth_request_t *oauth_req = g_new0(oauth_request_t, 1);
/*
http://twitter.com/oauth/request_token?
    oauth_consumer_key=wrD3WGIh2P31d3fIjRkfcw&
    oauth_nonce=HCUxu1D3qN4Nklr9QVAymve40PtJyU&
    oauth_signature_method=HMAC-SHA1&
    oauth_timestamp=1282446611&oauth_version=1.0&
    oauth_signature=A%2BZIiUVsQv5ZR8u%2F2oLmUFX1eHE%3D
*/

    oauth_req->url = strdup(request_token_url);
    oauth_req->c_key = strdup(c_key);
    oauth_req->c_sec = strdup(c_sec);
    oauth_req->a_key = NULL;
    oauth_req->a_sec = NULL;
    oauth_req->type = TYPE_GET;
    oauth_req->notoken = TRUE;
    oauth_req->conv = (PurpleConversation *)data;

    /* request token*/
    oauth = make_oauth_get(oauth_req);
    request = g_strdup_printf("%s?%s", request_token_url, oauth);

    twitter_debug("request=%s\n", request);

    purple_util_fetch_url_request(request, TRUE,
                                  NULL, TRUE, NULL, TRUE,
                                  oauth_setup_cb, oauth_req);
}


char *
hmac_sha1(char *text, char *key) {
	PurpleCipherContext *context = NULL;
	size_t len;
	guchar digest[255];
	char *signature = NULL;

    twitter_debug("text=%s\n", text);
    twitter_debug("key=%s\n", key);

    context = purple_cipher_context_new_by_name("hmac", NULL);
    if(!context)
        return NULL;

	purple_cipher_context_set_option(context, "hash", "sha1");
	purple_cipher_context_set_key(context, (guchar *)key);
	purple_cipher_context_append(context, (guchar *)text, strlen(text));

	if(purple_cipher_context_digest(context, sizeof(digest), digest, &len)) {
		signature = purple_base64_encode(digest, len);
        twitter_debug("hmac1 signature=%s\n", signature);
	}
    else {
		twitter_debug("digest signature failed\n");
	}

	purple_cipher_context_destroy(context);

	return signature;
}

char *
make_oauth_get(oauth_request_t *req)
{
    gchar *tmp = NULL;
    char *signature = NULL;
    time_t current_time = time(NULL);
    char *params = NULL;
    char *oauth;
    char *count_str = NULL;
    char *token_str = NULL;
    char *verifier_str = NULL;

    if(req->count)
        count_str = g_strdup_printf("count=%d&", req->count);
    else
        count_str = g_strdup("");

    if(req->notoken) {
        twitter_debug("notoken\n");
        token_str = g_strdup("");
    }
    else
        token_str = g_strdup_printf("oauth_token=%s&", req->a_key?req->a_key:req->c_key);

    if(req->verifier)
        verifier_str = g_strdup_printf("oauth_verifier=%s&", req->verifier);
    else
        verifier_str = g_strdup("");

    params = g_strdup_printf("%soauth_consumer_key=%s&oauth_nonce=%s&oauth_signature_method=HMAC-SHA1&oauth_timestamp=%d&%s%soauth_version=1.0",
                             count_str,
                             req->c_key,
                             SAMPLE_NONCE,
                             (int)current_time,
                             token_str,
                             verifier_str);

    g_free(count_str);
    g_free(token_str);
    g_free(verifier_str);

    const char *url_encoded = g_uri_escape_string(req->url, "", FALSE);
    const char *params_encoded = g_uri_escape_string(params, "", FALSE);

    tmp = g_strdup_printf("GET&%s&%s", url_encoded, params_encoded);
    char *key = g_strdup_printf("%s&%s", req->c_sec, req->a_sec?req->a_sec:"");
    signature = hmac_sha1(tmp, key);
    g_free(key);

    const char *signature_encoded = g_uri_escape_string(signature, "", FALSE);

    oauth = g_strdup_printf("%s&oauth_signature=%s", params, signature_encoded);

    g_free(tmp);
    g_free(signature);
    g_free(params);

    twitter_debug("oauth_block=%s\n", oauth);
    return oauth;
}

char *
make_oauth_post(oauth_request_t *req)
{
    gchar *tmp = NULL;
    char *signature = NULL;
    time_t current_time = time(NULL);
    char *params = NULL;
    char *oauth;
    char *status_str = NULL;
    char *msgid_str = NULL;

    if(req->status)
        status_str = g_strdup_printf("&status=%s", req->status);
    else
        status_str = g_strdup("");

    if(req->msgid)
        msgid_str = g_strdup_printf("in_reply_to_status_id=%llu&",
                                    (long long unsigned int)req->msgid);
    else
        msgid_str = g_strdup("");

    params = g_strdup_printf("%soauth_consumer_key=%s&oauth_nonce=%s&oauth_signature_method=HMAC-SHA1&oauth_timestamp=%d&oauth_token=%s&oauth_version=1.0%s",
                             msgid_str,
                             req->c_key,
                             SAMPLE_NONCE,
                             (int)current_time,
                             req->a_key,
                             status_str);

    g_free(status_str);
    g_free(msgid_str);

    const char *url_encoded = g_uri_escape_string(req->url, "", FALSE);
    const char *params_encoded = g_uri_escape_string(params, "", FALSE);

    tmp = g_strdup_printf("POST&%s&%s", url_encoded, params_encoded);
    char *key = g_strdup_printf("%s&%s", req->c_sec, req->a_sec);
    signature = hmac_sha1(tmp, key);
    g_free(key);

    const char *signature_encoded = g_uri_escape_string(signature, "", FALSE);

    oauth = g_strdup_printf("%s&oauth_signature=%s", params, signature_encoded);

    g_free(tmp);
    g_free(signature);
    g_free(params);

    twitter_debug("oauth_block=%s\n", oauth);
    return oauth;
}

/**************************/
/* 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);
                twitter_debug("screen_name=%s\n", st->screen_name);
                xmlFree(str);
            }
            else if(!xmlStrcmp(nptr->name, (xmlChar *)"profile_image_url")) {
                gchar *str = (gchar *)xmlNodeGetContent(nptr);
                st->profile_image_url = g_strdup(str);
                xmlFree(str);
            }
        }
    }
}

static gchar *day_of_week_name[] = {
    "Sun",
    "Mon",
    "Tue",
    "Wed",
    "Thu",
    "Fri",
    "Sat",
    NULL
};

static gchar *month_name[] = {
    "Jan",
    "Feb",
    "Mar",
    "Apr",
    "May",
    "Jun",
    "Jul",
    "Aug",
    "Sep",
    "Oct",
    "Nov",
    "Dec",
    NULL
};

static void
read_timestamp(const char *str, struct tm *res)
{
    char day_of_week[4];
    char month[4];
    char time_offset[6];
    int day, hour, minute, second, year;
    int i;

    if(str == NULL || res == NULL)
        return;

    sscanf(str, "%s %s %d %d:%d:%d %s %d",
           day_of_week, month, &day,
           &hour, &minute, &second,
           time_offset, &year);

    for(i=0; i<7; i++) {
        if(!strcmp(day_of_week_name[i], day_of_week)) {
            res->tm_wday = i;
        }
    }
    for(i=0; i<12; i++) {
        if(!strcmp(month_name[i], month)) {
            res->tm_mon = i;
        }
    }

    res->tm_mday = day;
    res->tm_hour = hour;
    res->tm_min  = minute;
    res->tm_sec  = second;
    res->tm_year = year - 1900;
#ifndef _WIN32
    int offset   = atoi(time_offset);
    res->tm_gmtoff = -1 * (60 * 60 * offset / 100);
#endif

}

static void
parse_status(xmlNode *status, status_t *st)
{
    xmlNode *nptr;

    for(nptr = status->children; nptr != NULL; nptr = nptr->next) {
        if(nptr->type == XML_ELEMENT_NODE) {
            if(!xmlStrcmp(nptr->name, (xmlChar *)"created_at")) {
                gchar *str = (gchar *)xmlNodeGetContent(nptr);
                st->created_at = g_strdup(str);

                /* read time stamp */
                struct tm res;
                memset(&res, 0x00, sizeof(struct tm));
                read_timestamp(str, &res);
                tzset();
#ifdef _WIN32
                st->time = mktime(&res) - timezone;
#else
                st->time = mktime(&res) + res.tm_gmtoff;
#endif

                xmlFree(str);
            }
            else if(!xmlStrcmp(nptr->name, (xmlChar *)"id")) {
                gchar *str = (gchar *)xmlNodeGetContent(nptr);
                st->id = atoll(str);
                twitter_debug("id=%llu\n", (long long unsigned int)st->id);
                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);
            }
            else if(!xmlStrcmp(nptr->name, (xmlChar *)"in_reply_to_status_id")) {
                gchar *str = (gchar *)xmlNodeGetContent(nptr);
                st->in_reply_to_status_id = atoll(str);
                twitter_debug("in_reply_to_status_id=%llu\n", (long long unsigned int)st->in_reply_to_status_id);
                xmlFree(str);
            }
            else if(!xmlStrcmp(nptr->name, (xmlChar *)"in_reply_to_screen_name")) {
                gchar *str = (gchar *)xmlNodeGetContent(nptr);
                st->in_reply_to_screen_name = g_strdup(str);
                twitter_debug("in_reply_to_screen_name=%s\n", st->in_reply_to_screen_name);
                xmlFree(str);
            }
        }
    }
}

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);
    g_free(st->in_reply_to_screen_name);
}

static gboolean
is_posted_message(status_t *status, guint64 lastid)
{
    GList *pp = g_list_first(postedlist);
    gboolean rv = FALSE;

    while(pp) {
        GList *next;
        status_t *posted = (status_t *)pp->data;

        next = g_list_next(pp);

        if(posted->id == status->id) {
            rv = TRUE;
        }

        if(posted->id <= lastid) {
            free_status(posted);
            g_free(pp->data);
            postedlist = g_list_delete_link(postedlist, pp);
        }

        pp = next;
    }

    return rv;
}

static void
get_status_with_api_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
                        const gchar *url_text, size_t len,
                        const gchar *error_message)

{
    xmlDocPtr doc;
    xmlNode *nptr, *nptr2;
    static guint64 lastid = 0;
    PurpleConversation *conv;
    GList *stp;
    const gchar *start;

    g_return_if_fail(url_text != NULL);

    conv = (PurpleConversation *)user_data;
    if(!conv)
        return;

    /* skip to the beginning of xml */
    start = strstr(url_text, "<?xml");

    doc = xmlRecoverMemory(start, len - (start - url_text));
    if(doc == NULL)
        return;

#ifdef _WIN32
    /* suppress notification of incoming messages. */
    if(purple_prefs_get_bool(OPT_PREVENT_NOTIFICATION)) {
        if(!blink_modified) {
            blink_modified = TRUE;
            blink_state = purple_prefs_get_bool(OPT_PIDGIN_BLINK_IM);
            purple_prefs_set_bool(OPT_PIDGIN_BLINK_IM, FALSE);
        }
    }
#endif

     for(nptr = doc->children; nptr != NULL; nptr = nptr->next) {
        if(nptr->type == XML_ELEMENT_NODE &&
           !xmlStrcmp(nptr->name, (xmlChar *)"statuses")) {

            for(nptr2 = nptr->children; nptr2 != NULL; nptr2 = nptr2->next) {
                if(nptr2->type == XML_ELEMENT_NODE &&
                    !xmlStrcmp(nptr2->name, (xmlChar *)"status")) {
                    status_t *st = g_new0(status_t, 1);
                    statuseslist = g_list_prepend(statuseslist, st);
                    parse_status(nptr2, st);
                }
            }
        }
     }

     xmlFreeDoc(doc);
     xmlCleanupParser();

     /* process statuseslist */
     stp = g_list_first(statuseslist);
     while(stp) {
         GList *next;
         status_t *st = (status_t *)stp->data;

         next = g_list_next(stp);

         if(st->id > lastid && !is_posted_message(st, lastid)) {
             gchar *msg = NULL;
             gchar *sender = NULL;

             sender = g_strdup("twitter@twitter.com");

             PurpleMessageFlags flag = PURPLE_MESSAGE_RECV;

             msg = g_strdup_printf("%s: %s pttag=%llu:%llu:%s",
                                   st->screen_name,
                                   st->text,
                                   (long long unsigned int)st->id,
                                   (long long unsigned int)st->in_reply_to_status_id,
                                   st->in_reply_to_screen_name ? st->in_reply_to_screen_name : "");

             /* apply filter */
             if(purple_prefs_get_bool(OPT_FILTER)) {
                 apply_filter(&sender, &msg, &flag, twitter_service);
             }
             if(sender && msg) {
                 purple_conv_im_write(conv->u.im,
                                      sender,
                                      msg,
                                      flag,
                                      st->time);
             }
             lastid = st->id;

             g_free(sender);
             g_free(msg);
         }

         free_status(st);
         g_free(stp->data);
         statuseslist = g_list_delete_link(statuseslist, stp);

         stp = next;
     }
}

/* status fetching function. it will be called periodically. */
gboolean
get_status_with_api(gpointer data)
{
    twitter_debug("called\n");

    /* fetch friends time line */
    char *request, *header;
    gint count = purple_prefs_get_int(OPT_RETRIEVE_COUNT);
    char *oauth;
    const char *a_key = NULL;
    const char *a_sec = NULL;
    static gboolean setup = FALSE;
    oauth_request_t oauth_req;

    a_key = purple_prefs_get_string(OPT_AKEY_TWITTER);
    a_sec = purple_prefs_get_string(OPT_ASEC_TWITTER);

    if(!a_key || !a_sec) {
        if(!setup) {
            oauth_setup(data);
            setup = TRUE;
        }
        return TRUE;
    }

    if(count < TWITTER_DEFAULT_RETRIEVE_COUNT)
        count = TWITTER_DEFAULT_RETRIEVE_COUNT;

    /* if disabled, just return */
    if(!purple_prefs_get_bool(OPT_API_BASE_POST))
        return TRUE;

    /* oauth */
    char *url0 = g_strdup_printf(TWITTER_API_BASE_URL "/1/statuses/home_timeline.xml");
    oauth_req.url = url0;
    oauth_req.c_key = c_key;
    oauth_req.c_sec = c_sec;
    oauth_req.a_key = (char *)a_key;
    oauth_req.a_sec = (char *)a_sec;
    oauth_req.verifier = NULL;
    oauth_req.status = NULL;
    oauth_req.type = TYPE_GET;
    oauth_req.count = count;
    oauth_req.msgid = 0;
    oauth_req.notoken = FALSE;

    oauth = make_oauth_get(&oauth_req);

    /* header */
    header = g_strdup_printf(TWITTER_STATUS_GET, oauth);
    request = g_strconcat(header, "\r\n", NULL);

    twitter_debug("request=%s\n", request);

    /* invoke fetch */
    purple_util_fetch_url_request(TWITTER_API_BASE_URL, FALSE,
                                  NULL, TRUE, request, TRUE,
                                  get_status_with_api_cb, data);

    g_free(header);
    g_free(request);
    g_free(oauth);
    g_free(url0);
    return TRUE;
}

/****************************/
/* API based post functions */
/****************************/
static void
post_status_with_api_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
                        const gchar *url_text, size_t len,
                        const gchar *error_message)
{
    twitter_message_t *tm = (twitter_message_t *)user_data;
    gchar *msg = NULL;
    char *p1 = NULL, *p2 = NULL;
    int error = 1;
    PurpleConversation *conv;

    conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY,
                                                 "twitter@twitter.com",
                                                 tm->account);
    if (!conv) {
        twitter_debug("failed to get conversation\n");
        goto fin;
    }

    if (error_message) {
        /* connection failed or something */
        msg = g_strdup_printf("Local error: %s", error_message);
    }
    else {
        int code = -1;

        if ((strncmp(url_text, "HTTP/1.0", strlen("HTTP/1.0")) == 0
             || strncmp(url_text, "HTTP/1.1", strlen("HTTP/1.1")) == 0)) {

            p1 = strchr(url_text, ' ');

            if (p1) {
                p1++;
                p2 = strchr(p1, ' ');
                if (p2)
                    p2++;
                else
                    p2 = NULL;
            }
        }

        code = atoi(p1);

        if (code == 200) {
            error = 0;
        }
        else {
            switch (code) {
            case 400:
                msg = g_strdup("Invalid request. Too many updates?");
                break;
            case 401:
                msg = g_strdup("Authorization failed.");
                break;
            case 403:
                msg = g_strdup("Your update has been refused by Twitter server "
                               "for some reason.");
                break;
            case 404:
                msg = g_strdup("Requested URI is not found.");
                break;
            case 500:
                msg = g_strdup("Server error.");
                break;
            case 502:
                msg = g_strdup("Twitter is down or under maintenance.");
                break;
            case 503:
                msg = g_strdup("Twitter is extremely crowded. "
                               "Try again later.");
                break;
            default:
                msg = g_strdup_printf("Unknown error. (%d %s)",
                                      code, p2 ? p2 : "");
                break;
            }
        }
    }

    if (!error) {
        /* cache message ID that posted via API */
        gchar *start = NULL;
        xmlDocPtr doc;
        xmlNode *nptr;
        status_t *st;
        gchar *m;

        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")) {
                st = g_new0(status_t, 1);
                postedlist = g_list_prepend(postedlist, st);
                parse_status(nptr, st);
            }
        }

        xmlFreeDoc(doc);
        xmlCleanupParser();

        m = g_strdup_printf("%s pttag=%llu:%llu:%s",
                            tm->status,
                            (long long unsigned int)st->id,
                            (long long unsigned int)st->in_reply_to_status_id,
                            st->in_reply_to_screen_name ? st->in_reply_to_screen_name : "");

        purple_conv_im_write(conv->u.im,
                             purple_account_get_username(tm->account),
                             m,
                             PURPLE_MESSAGE_SEND,
                             tm->time);
        g_free(m);

    }
    else {
        gchar *m;
        m = g_strdup_printf("%s<BR>%s",
                            msg, tm->status);
        /* FIXME: too strong. it should be more smart */
        purple_conv_im_write(conv->u.im,
                             purple_account_get_username(tm->account),
                             m,
                             PURPLE_MESSAGE_ERROR,
                             time(NULL));
        g_free(m);
    }

 fin:
    if (msg)
        g_free(msg);

    if (tm) {
        if (tm->status)
            g_free(tm->status);
        g_free(tm);
    }

}

void
post_status_with_api(PurpleAccount *account, char **buffer)
{
    char *request, *header;
    const char *url_encoded = g_uri_escape_string(*buffer, "", FALSE);
    PurpleConversation *conv;
    char *oauth;
    twitter_message_t *tm;
    const char *a_key = NULL;
    const char *a_sec = NULL;
    oauth_request_t oauth_req;

    conv = purple_find_conversation_with_account(
        PURPLE_CONV_TYPE_ANY, "twitter@twitter.com",
        account_for_twitter); /* xxx */

    a_key = purple_prefs_get_string(OPT_AKEY_TWITTER);
    a_sec = purple_prefs_get_string(OPT_ASEC_TWITTER);

    if(!a_key || !a_sec) {
        return;
    }

    tm = g_new(twitter_message_t, 1);
    tm->account = account;
    tm->status = g_strdup(*buffer);
    tm->time = time(NULL);

    /* oauth */
    char *url0 = g_strdup_printf(TWITTER_API_BASE_URL "/1/statuses/update.xml");

    oauth_req.url = url0;
    oauth_req.c_key = c_key;
    oauth_req.c_sec = c_sec;
    oauth_req.a_key = (char *)purple_prefs_get_string(OPT_AKEY_TWITTER);
    oauth_req.a_sec = (char *)purple_prefs_get_string(OPT_ASEC_TWITTER);
    oauth_req.verifier = NULL;
    oauth_req.status = (char *)url_encoded;
    oauth_req.type = TYPE_POST;
    oauth_req.count = 0;
    oauth_req.msgid = reply_to_msgid;
    oauth_req.notoken = FALSE;

    oauth = make_oauth_post(&oauth_req);

    reply_to_msgid = 0;


    header = g_strdup_printf(TWITTER_STATUS_POST,
                             (int)strlen(oauth));

    request = g_strconcat(header, "\r\n", oauth, "\r\n", NULL);
    twitter_debug("request=%s\n", request);
    purple_util_fetch_url_request(TWITTER_BASE_URL, FALSE,
                                  NULL, TRUE, request, TRUE,
                                  post_status_with_api_cb, tm);

    g_free(header);
    g_free(oauth);
    g_free(request);

}

static void
fav_with_api_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
                        const gchar *url_text, size_t len,
                        const gchar *error_message)
{
    /* dummy */
}

void
fav_with_api(guint64 id)
{
    char *header, *request;
    char *oauth;
    const char *a_key = NULL;
    const char *a_sec = NULL;
    PurpleConversation *conv;
    oauth_request_t oauth_req;

    conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY,
                                                 "twitter@twitter.com",
                                                 account_for_twitter); /* xxx */


    a_key = purple_prefs_get_string(OPT_AKEY_TWITTER);
    a_sec = purple_prefs_get_string(OPT_ASEC_TWITTER);

    if(!a_key || !a_sec) {
        return;
    }

    /* oauth */
    char *url0 = g_strdup_printf(TWITTER_API_BASE_URL "/1/favorites/create/%llu.xml", (long long unsigned int)id);

    oauth_req.url = url0;
    oauth_req.c_key = c_key;
    oauth_req.c_sec = c_sec;
    oauth_req.a_key = (char *)a_key;
    oauth_req.a_sec = (char *)a_sec;
    oauth_req.verifier = NULL;
    oauth_req.status = NULL;
    oauth_req.type = TYPE_POST;
    oauth_req.count = 0;
    oauth_req.msgid = 0;
    oauth_req.notoken = FALSE;

    oauth = make_oauth_post(&oauth_req);

    header = g_strdup_printf(TWITTER_FAV_POST,
                             (long long unsigned int)id,
                             (int)strlen(oauth));

    request = g_strconcat(header, "\r\n", oauth, NULL);
    twitter_debug("request=%s\n", request);

    purple_util_fetch_url_request(TWITTER_BASE_URL, FALSE,
                                  NULL, TRUE, request, TRUE,
                                  fav_with_api_cb, NULL);

    g_free(header);
    g_free(oauth);
    g_free(request);
}

static void
retweet_with_api_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
                        const gchar *url_text, size_t len,
                        const gchar *error_message)
{
    /* dummy */
}

void
retweet_with_api(guint64 id)
{
    char *header, *request;
    char *oauth;
    const char *a_key = NULL;
    const char *a_sec = NULL;
    PurpleConversation *conv;
    oauth_request_t oauth_req;

    conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY,
                                                 "twitter@twitter.com",
                                                 account_for_twitter); /* xxx */

    a_key = purple_prefs_get_string(OPT_AKEY_TWITTER);
    a_sec = purple_prefs_get_string(OPT_ASEC_TWITTER);

    if(!a_key || !a_sec) {
        return;
    }

    if(id == 0) {
        twitter_debug("invalid message id\n");
        return;
    }

    /* oauth */
    char *url0 = g_strdup_printf(TWITTER_API_BASE_URL "/1/statuses/retweet%llu.xml", (long long unsigned int)id);

    oauth_req.url = url0;
    oauth_req.c_key = c_key;
    oauth_req.c_sec = c_sec;
    oauth_req.a_key = (char *)a_key;
    oauth_req.a_sec = (char *)a_sec;
    oauth_req.verifier = NULL;
    oauth_req.status = NULL;
    oauth_req.type = TYPE_POST;
    oauth_req.count = 0;
    oauth_req.msgid = 0;
    oauth_req.notoken = FALSE;

    oauth = make_oauth_post(&oauth_req);

    header = g_strdup_printf(TWITTER_RETWEET_POST,
                             (long long unsigned int)id,
                             (int)strlen(oauth));

    request = g_strconcat(header, "\r\n", oauth, NULL);
    twitter_debug("request=%s\n", request);

    purple_util_fetch_url_request(TWITTER_API_BASE_URL, FALSE,
                                  NULL, TRUE, request, TRUE,
                                  retweet_with_api_cb, NULL);

    g_free(header);
    g_free(oauth);
    g_free(request);
}

void
signed_on_cb(PurpleConnection *gc)
{
    PurpleAccount *account;
    PurpleBuddy *buddy;
    PurpleConversation *gconv;
    const char name[] = "twitter@twitter.com";

    if(!purple_prefs_get_bool(OPT_API_BASE_POST)) {
        twitter_debug("per prefs\n");
        return;
    }

    account = purple_connection_get_account(gc);
    if(!account) {
        twitter_debug("no account\n");
        return;
    }

    buddy = purple_find_buddy(account, name);
    if(!buddy) {
        twitter_debug("no buddy\n");
        return;
    }

    account_for_twitter = account;

    gconv = purple_find_conversation_with_account(
        PURPLE_CONV_TYPE_IM, name, account);
    if(!gconv) {
        gconv = purple_conversation_new(
            PURPLE_CONV_TYPE_IM, account, name);
        twitter_debug("new conv\n");
    }
}