Mercurial > pidgin-twitter
diff twitter_api.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 | |
children | d973f9debe86 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/twitter_api.c Sat Nov 22 18:01:18 2008 +0900 @@ -0,0 +1,564 @@ +#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, guint 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); + + +/**************************/ +/* 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 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 = 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, guint 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 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; + +#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", st->screen_name, st->text); + + /* 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) +{ + /* fetch friends time line */ + char *request, *header; + char *basic_auth, *basic_auth_encoded; + + twitter_debug("called\n"); + + /* if disabled, just return */ + if(!purple_prefs_get_bool(OPT_API_BASE_POST)) + return TRUE; + + 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, "\r\n", 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 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) { + 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); + } + } + + xmlFreeDoc(doc); + xmlCleanupParser(); + + } 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, *status, *header; + const char *url_encoded = purple_url_encode(*buffer); + char *basic_auth, *basic_auth_encoded; + + twitter_message_t *tm; + + const char *screen_name = + purple_prefs_get_string(OPT_SCREEN_NAME_TWITTER); + const char *password = purple_prefs_get_string(OPT_PASSWORD_TWITTER); + + twitter_debug("tm.account: %s\n", + purple_account_get_username(account)); + + if (!screen_name || !password || !screen_name[0] || !password[0]) { + twitter_debug("screen_name or password is empty\n"); + return; + } + + tm = g_new(twitter_message_t, 1); + tm->account = account; + tm->status = g_strdup(*buffer); + tm->time = time(NULL); + + 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); + + status = g_strdup_printf(TWITTER_STATUS_FORMAT, url_encoded); + header = g_strdup_printf(TWITTER_STATUS_POST, basic_auth_encoded, + (int)strlen(status)); + + request = g_strconcat(header, "\r\n", status, "\r\n", NULL); + + purple_util_fetch_url_request(TWITTER_BASE_URL, FALSE, + NULL, TRUE, request, TRUE, + post_status_with_api_cb, tm); + + g_free(header); + g_free(basic_auth_encoded); + g_free(status); + g_free(request); + +} + +void +signed_on_cb(PurpleConnection *gc) +{ + PurpleBuddyList *list = purple_get_blist(); + PurpleBlistNode *gnode, *cnode, *bnode; + PurpleBuddy *b; + + twitter_debug("called\n"); + + if(!purple_prefs_get_bool(OPT_API_BASE_POST)) + return; + + if (!list) + return; + + twitter_debug("scan list\n"); + + for (gnode = list->root; gnode; gnode = gnode->next) { + if(!PURPLE_BLIST_NODE_IS_GROUP(gnode)) + continue; + + for(cnode = gnode->child; cnode; cnode = cnode->next) { + + if(!PURPLE_BLIST_NODE_IS_CONTACT(cnode)) + continue; + + for(bnode = cnode->child; bnode; bnode = bnode->next) { + if(!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) + continue; + + b = (PurpleBuddy *)bnode; + + if(!PURPLE_BUDDY_IS_ONLINE(b)) { + const char *name; + PurpleAccount *account; + name = purple_buddy_get_name(b); + account = purple_buddy_get_account(b); + if (is_twitter_account(account, name)) { + PurpleConversation *gconv; + gconv = purple_find_conversation_with_account( + PURPLE_CONV_TYPE_IM, name, account); + if (!gconv) { + gconv = purple_conversation_new( + PURPLE_CONV_TYPE_IM, account, name); + } + } + } + } + } + } +}